> A simple way to begin to control decline is to cordon off the blighted areas, and put an attractive façade around them. We call this strategy SWEEPING IT UNDER THE RUG. In more advanced cases, there may be no alternative but to tear everything down and start over.
I've found the most successful way of dealing with these code bases is definitely not this. It is to surround them with a body end-to-end functional tests and then slowly refactor what is underneath, which mostly just involves doing this:
* De-duplicate wherever there is code duplication.
* Pull the different modules apart so that they are minimally dependent upon one another instead of tightly coupled.
* Replacing re-invented wheels once they are de-coupled with higher quality modules.
An unappreciated facet of dealing with big balls of mud is also that unit tests usually range from unhelpful to downright harmful. If you don't have self-contained, loosely coupled algorithmic modules to test, unit tests aren't helpful. They will almost inevitably mean tightly coupling your tests to architecture you know is bad, a mess of unnecessary mock objects and an inability to write tests for real bug scenarios that users report.
I totally agree with this!! I've been learning this the hard way and came to similar conslusions about the need to wrap code with functional testing first and then refactor. It solves the chicken/egg problem with unit test/refactor. But it's not very popular -- maybe because of a percieved two-pass testing. Lots of work.
I think that's precisely why this testing insight isn't more commonly used. In reality, it's not about unit vs functional, it seems to be about scope. E.g. If you have behavior that can't be tested without doing a full manual integration test, then that's where you need to start. Automate it and start to push back the layers until you get to functional, until you get to unit.
I've been experimenting with applying code coverage to integration test since it gives testers insight into whether they are testing the code at hand vs internals of other integrated systems (this difficulty is likely what keeps many from trying such testing).
But yes, these methods take a lot of time that we don't have.
The other approach I've been leaning towards is static code-flow analysis, which can also be difficult (esp. in distributed web applications).
> I've found the most successful way of dealing with these code bases is definitely not this. It is to surround them with a body end-to-end functional tests and then slowly refactor what is underneath, which mostly just involves doing this:
Keep in mind that this was written in 1999. The current philosophy of testing+refactoring old code was mostly developed during 2000-201x.
Testing + refactoring is absolutely not new. Even the Wikipedia article on Extreme Programming (https://en.wikipedia.org/wiki/Extreme_programming) acknowledes that testing-first was used extensively by NASA durin 1960's, at least for computing.
I work at an organization that has a large number of big balls of mud. Key performance indicators and budgets last for 6 months, so no one has any incentive to invest in cleaning up the mess. So I am resigned to the fact that I have to work with it for as long as I remain here. But I am paid astoundingly well, and my job is very secure.
While I'm glad that you have financial security, dear stranger on the Internet, please make sure that you keep improving your architectural skills despite working on your BBMs. For the sake of both you and any future poor souls that will inherit your code.
I've found the most successful way of dealing with these code bases is definitely not this. It is to surround them with a body end-to-end functional tests and then slowly refactor what is underneath, which mostly just involves doing this:
* De-duplicate wherever there is code duplication.
* Pull the different modules apart so that they are minimally dependent upon one another instead of tightly coupled.
* Replacing re-invented wheels once they are de-coupled with higher quality modules.
An unappreciated facet of dealing with big balls of mud is also that unit tests usually range from unhelpful to downright harmful. If you don't have self-contained, loosely coupled algorithmic modules to test, unit tests aren't helpful. They will almost inevitably mean tightly coupling your tests to architecture you know is bad, a mess of unnecessary mock objects and an inability to write tests for real bug scenarios that users report.