This may seem obvious, but I am biased to avoid saying things that are obvious to me, and then things don't get said that are worth saying.

A nice thing about statically typed languages, like C++, is how you can use the compiler to help refactor.

Code often needs to be refactored. If it is not, it gets harder to fix issues and implement new features. When this happens, the temptation arises to throw the code away and write new code, but this is almost always a mistake. The correct process is to refactor code that works, to disentangle anything that needs disentangling, so that improvement becomes easier.

When there is existing code you can use, you should only ever write new code if you're willing to go through the entire years-long process of how it needs to work, and why it needs to work that way. Otherwise, use existing code, and keep improving it.

Refactoring is scary. It is scary because you have this hairy code, and you're concerned that any change you make to it will break it.

Behold, the beauty of a statically typed language. By leaning on the compiler, you can refactor in such a way that code will not compile until it is fully updated – and ideally, correct:
  1. Start with code that compiles and passes any unit tests you might have. Write unit tests if there aren't any. Focus on functionality you're worried about, and want to fix and/or preserve.
  2. For each modification you want to make:
    1. Find a way to make this modification so that the code will not compile unless all parts that need to take this modification into account are updated.
    2. Repeat the build as many times as necessary to identify all parts you need to update.
  3. When the code builds, if you used the compiler to aid you correctly, you should have found all the places that required updates. Unit tests should not fail because of missing an update. They should only fail if an update you made was incorrect.
For example:
  • If you change parameters accepted by a function, make sure no possible call using the previous signature will compile with the new signature.
  • If this cannot be achieved, rename the function, so that references to the old name will not build.
  • If you change the behavior of a function in a way that requires callers to update, change its signature in a breaking way, or change its name.
  • If you change the rules regarding a widely used data member, change its name.
  • If you change the rules regarding a widely used type, change its name.
Aspire to perform all changes in such a way that, if you're not 100% done, the code will not even compile. Don't make changes that allow the code to compile if you're only 1%, or 40%, or 99% done.