lundi 6 janvier 2020

Stop feeding the big classes - Class division

I typically ask people to show me the one or the a few classes that hinder them from writing unit tests. Almost consistently (!) this is a class of 1000 lines or even a few thousand lines of code and this is where most of the development work takes place. Of course this is very difficult to test and even though sometimes these classes are tested with unit tests, they are very very hard to maintain. 

I wouldn't even try

I do admit that it is very hard to write unit tests for these classes. I would find it very difficult to write good unit tests for them too. In fact I wouldn't even try to test this with unit tests. Now what I would do is I would extract the behaviour that I want to modify into other classes or more likely to a completely new class. If I move out a method of say 10 to 50 lines of code then that's very easy to test and things that are easy to test are easy to change by adding behaviour to it. In doing this, besides making things testable, we improve things by splitting things into smaller pieces. Smaller pieces are more easy to understand to test and to assemble into new behaviour i.e. reuse these classes elsewhere

flickr.com/photos/102642344@N02/10203178726/


Given that we do split the big classes into pieces as we do features then it doesn't take many stories to make our codebase a lot more maintainable and all the way we been able to work with lower level tests. All we needed to ensure is that the things we split apart are meaningful pieces that are functionally coherent. This is probably the most difficult part - extracting something meaningful. It requires being able to design code and if we don't have much practice doing it won't be very good. On the other hand if we don't start practicing this then all code we  create will just be a bigger and bigger mess. I mean there's a reason (almost) all living things grow by cell division, not by cell growth.

The actual act of extracting something is really simple. Very often it is as simple as

  1. Extract Method (automated refactoring)
  2. Create class and add it as a member of the existing class
  3. Move Method (automated refactoring)


The bottom line

The next time you touch a big class then split something out, find something meaningful you can move out and test and then change that piece. Done! The modification is safe and it's done rather quickly. We started out with a testability issue, but really it's a design issue regardless of the need to test.

2 commentaires:

  1. As you said, the most difficult part is to extract functionally coherent parts.
    Very often, I've seen team undertake large refactoring to build some kind of 'Anti Corruption Layer' around what they are doing. Unfortunately, a team's area of responsibility is often historic and not all functionally coherent! This can end up in an incoherent patchwork of components. Having a target functional architecture in mind can really help there. I've written about how to do it with Event Storming (https://philippe.bourgau.net/drafting-a-functional-architecture-vision-with-ddd-event-storming-part-1/), but I guess it's just one way out of many to do it.
    Thanks for the post

    RépondreSupprimer
    Réponses
    1. Indeed finding Functional Areas through EventStorming is a very good tool. Good short introductory post. Hadn't seen the use of a wool string to draw boundaries! I also perfectly agree that we always benefit from rethinking the solution from the basic need: What are the most rudimentary inputs and outputs?

      I was mostly referring to micro-functional areas, the size of a method or a class. At this level mistakes are cheap and the best way of learning is by doing, although having a mentor is valuable.

      Supprimer