The other day I had a discussion with one of our team members about how to keep classes readable and don’t end up with huge files containing thousands of lines and hundreds of methods. It turns out that this question deals with the very basics of Object Oriented programming and I decided to give my view on some of the OOP principles that help me keep a code growth maintainable and get my code to comply with good design principles. One of the first principles that comes to my mind is Single responsibility principle (SRP) that states “Each software module should have one and only one reason to change”. Frankly for a long time I had a hard time to apply this description to daily code and therefore I came up with my own steps derived from other OOP principles that help me to follow this principle.
Starting Point
Since lately I was developing either webservices, websites or mobile applications then my starting point is one of the widely acknowledge architectural patterns It gives me a good starting point with project structure and files, where I can start to add a code. For this moment these patterns are well established and I really suggest to go with one of these design patterns unless you are developing a very simple application and don’t have plans to evolve the project. And if you know the project will be something more than delegating CRUD requests to the database, then it is worth to start already with layered project structure where Model in MVC, MVP or MVVM is organized in Business Models, Services and Repositories. And maybe even consider the whole Domain Driven Development approach. But how to estimate the starting architecture for the application is a topic for another article.
Single responsibility principle for methods
Then next I start to fill provided methods (actions in controllers in case of web-services or activity’s lifecycle methods in case of Android application) and observe how my starting methods evolve. These methods are placed where I start to apply the Single responsibility principle for methods. Once I have methods that have only one reason to change I switch my focus to classes. And here are some rules I follow to achieve SRP in my methods:
- DRY (don’t repeat yourself). If I discover that several methods share a common piece of code I extract the common code in a separate method and make it reusable for other methods. I think this is one of the first principles of clean code most of the developers learn. And since this principle is so fundamental then many IDEs included the method extraction as part of their refactoring tool set.
- I check whether there are common variables more than one method is operating on. If there are such variables I make them class level private fields. I repeat this step every time when a new method is extracted. And if several classes have common fields or extracted methods then it is time for new base class.
- I make sure the methods I extract are doing what its name suggests. If a method’s name contains one verb then I make sure the method is either command that changes the state of the object or it returns data. And if the method name contains more than one verb it is obvious that the method is doing more than one thing. Sometimes it is acceptable. For example in cases when I write logs along the method’s basic logic. If tools or frameworks allow handling such method side behavior I extract it as an aspect.
- I respect the levels of abstraction and try to keep method statements on the same abstraction level. One sign of that I might be violating this rule are a long loop and if bodies. The statements in long bodies most likely belong to lower level abstraction than the statements outside the loop or if. But sometimes on high-level method I have to call a single line of lower abstraction code, then I leave it.
- I keep the number of method arguments short. If I need more than 2 arguments, then maybe it is time for new class, and instead of several primitive types, I should be passing a class as a method argument.
- And finally, I use cohesion level description to match my methods against different cohesion types and see that I am avoiding Procedural, Logical, and Coincidental Cohesion.
Single responsibility principle for classes
As code evolves I start to have more and more methods in classes provided by initial architecture and fields in those classes. Now I check if it is not the time to split my classes. For a long time, I had a hard time to choose the right class for a method. And one of the reason were examples I was reading about in books and articles on the Internet. Those examples were focusing mainly on the names of methods and classes and how by names estimate the relationships between methods. But soon I discovered that using only names can lead to subjective decisions. Although many OOP principles are subjective from my point of view anyway (in case of SRP someone could argue that keeping all methods in one class is more convenient than creating a hierarchy of classes, and one should use an IDE for navigation between the methods instead of file system navigator) I wanted to use something measurable that would allow me to estimate how tight the relationship between a method and its containing class is. And one of such metrics I found is cohesion. Low cohesion means methods inside the class are independent of each other. On the other hand, high cohesion means methods in the class are strongly related. But how can we express this relationship in numbers? It turns out there are several cohesion metrics that give a developer insight into relationships between methods. And sometimes distributing methods to classes according to the cohesion level among them, one can discover new classes, he didn’t even think about before. And here are the description of some of the cohesion metrics. The metric I am using the most is “Lack of Cohesion of Methods” (LCOM) metric, that for each field in a class counts the number of methods that reference it. Then it sums up the number of methods and divides the result with a count of methods times the count of fields and subtract the result from one, like this: 1 – (NumberOfMethodsReferencingFields /(NumberOfMethods * NumberOfFields)). The metric ranges from 0 to 1, where 0 means high cohesion and 1 low cohesion.
Following these rules, I was able to achieve the Single Responsibility principle in my modules.
P.S. Visit our website and join the mailing list! Our app is coming soon: http://fitradar.me/