Progressive refactoring
Lately, I watched this Jim Weirich's refactoring video again.
I could have watched it more carefully the first time because I'm missing a point when I refactor web applications to decouple business code from framework.
Usually I:
- Look at the controller that calls to the feature I want to extract
- Complete the test harness if needed
- Create an isolated business class that perform the feature, driven by pure unit tests
- Create a repository service to store and fetch the data from the database
- Integrate the class when I'm done in the controller
This has some downsides:
- It's not progressive enough in terms of integration. The new class is wired or it is not.
- It makes me think about a lot of implementation details upfront
- It makes me go backward in case of error, if I miss a thing from initial feature
Look at his strategy
- he starts with the controller and extract stuff to decouple business logic from framework logic
- he refactors while tests keep passing all the time
- thh process is progressive and changes can be deployed anytime. The system remains in a hybrid state though, but as the features are still working, this is just a developer concern.
This can prevent the fear of refactoring.
I like his usage of test doubles (mocks) to remain in the flow while writing tests. I'm not a fan of mocking frameworks because I had experiences where they were hard to configure and their behaviour were not clear to me. But here (maybe thanks to rspec), mocking actually seems to facilitate test writing process.
The idea here is to mock at boundaries to avoid lying (fantasy) tests. Remember not to mock things you don't own.
This approach remains controversial in 2020. The objective can be misunderstood. Okay, the business logic is extracted but does it worth the introduced complexity and the risk of regression? Web frameworks usually come with tooling to ease integration testing against a database, so what is the point?
To me, it brings speed and allows application to evolve beyond frameworks. Speed because pure unit tests run at the speed of light. You can write more of them and run them more often while staying in the flow, which is the goal of Test Driven Development. Patterns like repository allow us to put all the calls to the storage infrastructure in one place, making it easier to tune and change.