I want to refactor — Chicken and the egg theory

Cosmin Vladutu
4 min readFeb 7, 2022

--

You work on a legacy code. You want to refactor. You invest a lot of energy and time to get everyone on board and get the budget and you get ready to start. You start by creating a plan, and writing it down on a board: In the first step, to be sure we don’t break our current functionality, we need a set of tests that will ensure that our refactoring will go smoothly. While writing you realize that 75% of your code is tightly coupled and 25% is not testable. To get unit tests you’ll need to refactor (Chicken and the egg). You stop writing and start thinking more about what you’re getting yourself into. Maybe you also think that maybe it wasn’t a really good idea to start with this refactoring, but now it’s too late since everyone is on board and if you lose this opportunity most probably you won’t get one. You can’t refactor because you don’t have tests to be sure you’re not breaking anything and you can’t write tests on your code without refactoring.

What to do, what to do?

Step 1 should be to start refactoring based on the “forces” that you or your company already paid for. Extract code into other classes to decouple, extract methods to make the code lighter and shorter (easy to understand what’s in there), move members to base classes, extract base classes, basically use whatever you are provided to make your life a little bit easier and it can be done automatically only by clicking, without pressing buttons.

Step 2 should be to write some integration, more exactly some acceptance tests, that should test only the input and the output

Step 3 start writing unit tests that bring you value, and doesn’t eat your all your time only the mocking part.

Step 4 start refactoring.

I know, it’s counterintuitive, maybe a lot of you won’t agree with me, but I don’t think anyone should start refactoring “Tarzan style” (as Victor Rentea named it in one of his presentations), because even if you accept it or not, you will break something for sure. Hell…you’ll break something even having the tests, but based on them hopefully, you’ll get smaller bugs! You also shouldn’t focus a lot on unit tests. I know, they provide the fastest way to get feedback, they make sure your system is decoupled and so on, but, your system is not! You want to decouple it, you want to refactor it and make it better, you shouldn’t lose more time in tests than in the reactor itself. You should focus more on the high-level tests, that yeah, it will take longer to run, but it will test your system as a black-box. If you don’t trust the acceptance tests, you can write e2e tests, that would be even better, but most probably more time-consuming. This is what you need. After the refactor will be done, you can push the tests down on the testing pyramid, as much as you can, but for now, you need only confidence that what you type is not introducing bugs.

Now let’s add a little bit more clarity about what I mean with these acceptance tests. Most of you heard about the pyramid of testing having 3 layers: on the bottom the unit tests, in the middle the integration tests and on the top the e2e tests. For me, that middle layer should be split up into more layers: the integration part is only formed by tests that ensure the integrity of the system. Integration tests test that your modules work together, and for example, in case of an API can test that based on a specific type of input you’ll get an expected type of output and expected HTTP status code, but that’s all. Martin Fowler has a nice article on the subject that can be found here. The next layers are the contract testing (and here if you are java lovers, you can take a look at Pact), which test the input and the output, concurrency characteristics and the last functional tests, which test the side effects that are expected (for a specific input with specific values your are expecting an output with specific values). For me, the acceptance tests are the types combined. Most of the people test everything in one place (if I send object A with 1 for property top, I expect 200 ok HTTP status code with the first item from my hardcoded list), but I think it will make your tests bigger and will do more than one thing, which is not ok.

Every (any) code can be refactored. Be very sure before starting that you have all your tools around you and my biggest advice is not to be afraid of ctrl+z. Make small commits and if something is going in a direction that you don’t like, just undo that part and start again. Refactoring should be fun and happy that the new code will be nicer to dive into…not create headaches.

--

--

Cosmin Vladutu
Cosmin Vladutu

Written by Cosmin Vladutu

Software Engineer | Azure & .NET Full Stack Developer | Leader

No responses yet