When software engineers look at a piece of code, the first question they ask themselves is “what is it doing?” The next question is “why is it doing it?” In fact, there is a deep connection between these two – why becomes what at the next level of abstraction. That is, what and why are mirrors of each other across an abstraction boundary. Understanding this can help engineers write more maintainable, readable software.
An example will help illustrate the principle above. Suppose we’re working on a django webapp, and we want to remind users that haven’t logged in in a while how awesome our webapp is. Suppose we have the following code:
def remind_users_we_are_awesome(): users = User.objects.filter( last_login__lte=timezone.now() - timedelta(days=7), last_login__gte=timezone.now() - timedelta(days=14) ) for u in users: message = EmailMessage( subject=’This website is awesome’, message=’You\’re totally missing out’, from=’email@example.com’, to=user.email, ) message.send()
What is this code doing? Well, first it runs a database query. Why? Uh, it looks like it iterates over the results, and constructs some email message. It’s not clear why.
We can tell that this code mixed up abstraction levels because there is no clear separation between what and why, no clear point at which a why became a what. If you instead look at this code:
class UserQuerySet(models.QuerySet): def idle(): return self.filter( last_login__lte=timezone.now() - timedelta(days=7), last_login__gte=timezone.now() - timedelta(days=14) )
What is this code doing? Well, it’s running a database query. Why? The method name tells us: in order to get idle users. Now look at the next level up:
def remind_users_we_are_awesome(): for u in User.objects.idle(): send_reminder_email(u)
What is that code doing? It’s listing the idle users. Why? In order to send them reminder emails. Notice how the why answer from before became the what now – at the previous level we saw database queries. At this level we see idle users. That’s how we can tell we’ve crossed an abstraction boundary – what and why switch places. There’s a nice symmetry in these concepts.
This code is also more reusable. We’ve carved out a concept, idle, that can be used whenever we’re looking at idle users.
When we use this what/why framework to write methods, each method name will tell us why the method is doing the things in its body. But notice that each method is composed of other methods; if those methods are following this rule we’ll end up with a set of statements that encapsulate the goals of those lower down. This will lead to a self-explanatory codebase; developers won’t have to hold a mental model of computer actions in order to read through code. They can use some meaningful mental symbols, like the idle concept above. That’s less cognitive load, which means development effort to change code and write new features.
This separation of methods would follow from Sandi Metz’s 5-line method rule, in which we limit ourselves to methods that are 5 lines or less. Using methods that short forces developers to encapsulate each action and compose them; the observations here about why and what should hopefully provide some animating theory for why such short methods are useful.
This article was written by Alex Kudlick.