I think that sometimes Hungarian notation and strict coding guidelines can help, but more and more I think that there's no way to effectively enforce those things unless you automate them from the beginning. I've worked on very effective teams, where nothing ever got checked into master without two sets of eyes on it, strict rules were followed, etc., and there are always holes from lack of automation and legacy code.
Automation helps: linters, static checkers, runtime memory checkers, automated tests, all should be run on every check in. On a C# project I even created a tool that correlated diffs with a code coverage tool and rejected diffs that weren't covered by unit tests (it misses cases where code paths were executed by preexisting unit tests, so it didn't necessarily require a new test for every diff--I consider this a bug). But you always end up making compromises because there's Xthousand lines of code written at the beginning of the project that you don't have time to go back and write tests for.
One of the reasons I'm really excited about Rust right now is that it makes it easier to set these things up at the beginning. A lot of memory checking you have to do with separate tools in C comes free with Rust's type system, while Cargo makes it very easy to get unit tests up and running. My hope is that if Rust finds wider usage, the projects I come into will be more likely to have been set up properly from square one, and it will be easier to work on larger code bases.
I dunno, I'm somewhat new at technical leadership, so I'm still working out some of this stuff.
Automation helps: linters, static checkers, runtime memory checkers, automated tests, all should be run on every check in. On a C# project I even created a tool that correlated diffs with a code coverage tool and rejected diffs that weren't covered by unit tests (it misses cases where code paths were executed by preexisting unit tests, so it didn't necessarily require a new test for every diff--I consider this a bug). But you always end up making compromises because there's Xthousand lines of code written at the beginning of the project that you don't have time to go back and write tests for.
One of the reasons I'm really excited about Rust right now is that it makes it easier to set these things up at the beginning. A lot of memory checking you have to do with separate tools in C comes free with Rust's type system, while Cargo makes it very easy to get unit tests up and running. My hope is that if Rust finds wider usage, the projects I come into will be more likely to have been set up properly from square one, and it will be easier to work on larger code bases.
I dunno, I'm somewhat new at technical leadership, so I'm still working out some of this stuff.