It’s virtuous to separate out different perspectives in code because it helps make code more testable, extendable, and understandable. We want to use entities at the same level of perspective so that code is easier to read and understand code.
The same thing is true within entities where we want to do tasks at the same level of abstraction.
As human beings, we operate on many levels of abstraction. We think about the world in a series of nested abstractions that help us organize and understand the world around us.
We do the same thing in code. Well-written code is organized around abstractions that represent the main concepts of the domain. However, in software we usually focus on just one level of abstraction because that’s most useful and relevant when building a program, whereas when we think about things in the physical world we generally have many levels of abstraction that we could potentially consider.
One of the main shifts when going from journeyman programmer to a master is the move from thinking about creating programming constructs to creating behaviors in the system. We still understand and use programming constructs but we use them like a painter using paint and canvass as a means of conveying something more important. It’s important for code to focus on creating testable behaviors and hiding as much implementation detail as possible. This is how we make software more testable, more maintainable, and more dependable.
When we mix perspectives in code, when parts of the method delegate behavior and other parts of the method dive into implementation. It makes it hard to read and understand. If it’s done just once or twice, it’s a small thing and easy to overcome. But doing it over and over in code increases the viscosity of the software and makes it harder to make connections about what’s going on in the code.
When I write an API, some publicly available service, it only delegates. The only thing in that method is calls to other methods. It may have some high level control flow as well but I paid particular attention to keeping out any implementation details. If any of those methods are used only by this API then I’ll make those methods privates. Breaking these behaviors out into private methods gives me the opportunity to name them, and I try to give them a good intention revealing name that clearly describes exactly what the method does. I find this far more effective than writing comments in code because the code itself becomes expressive. This connects what the code does to the code itself rather than providing a separate narrative in comments.
Time and time again it has been shown that highly commented code is often a liability rather than an asset. Of course, there are many reasons for commenting code, and some of those reasons are quite valid. But if the code itself can express what it’s doing then that’s far more effective than a comment.
Keeping code within a method at the same level of abstraction helps ensure that there are no surprises when reading code. It helps us organize our code in ways that make sense and support the clear representation of the domain model. Following the single level of abstraction principle helps keep code focused around behaviors and easier to read and understand.