Writing code, as in many other forms of communication, comes with a challenge of clearly expressing thoughts and ideas into a limited set of words. Quite often we envision the end-goal but the process of translating that vision into words does not always lend itself nicely. Moreover, it gets more complicated when consideration is given to not only what was communicated but how it was conveyed as well. In this essay, we explore ways object oriented languages have addressed this challenge and how developers can leverage these building blocks to increase readability of their code.
Before we get started, note that we specifically referred to programming as a communication form. Take a minute to let this sink in, because it’s going to be very important moving forward. Back…? Good. Now, this communication takes place among three parties; there are the original programmer, the machine, and the future programmer. As you can probably figure already, most complications will arise from the “future programmer aspect of this communication triangle and more often this is ignored completely. Just in case you are not totally onboard with this third party, suffice to note that even the “future you can be regarded as a third party!
Now that we have the basics covered, it is time to briefly look at what constitutes an effective communicator. This is a broad subject itself so we only offer a small glimpse but you are welcome to read more on your own. To quote the master of rhetoric, Aristotle, to be an effective communicator you have to “Tell them what you are going to tell them, tell them and tell them what you just told them”.
If you are starting to wonder how this all relates to programming, ask yourself this; what do we or should we be communicating in programming? We’ve already established that it’s a communication form. As ridiculous or simple it may sound, it is imperative to understand this if we are going to be effective at it. As an example, suppose you encounter the following definition of function foo:
Public int foo(int a, int b)
What have I communicated to you? Ok, you may say, function foo takes two integers and returns an integer. If that were all that was given to you, would you consider it effective? To a machine this is sufficient but what about the future programmer? Hint; recall that we are in an object-oriented language here, therefore, there is a thing called "state". So the short answer is no. The above snippet fails to clearly communicate its intent. More important than the details of how a specific task is accomplished, the intent is the thing we should strive to communicate at all levels.
So then, where and how should intent be communicated? The obvious location is in the comment section, as most of you would agree. But what should we put there? Contrary to popular practice, for those who comment their code, I argue that less is more. What do I mean? It is often advised to put as much information as you can think of in the comment section with the idea that over-communicating is better than under-communicating. While this is sometimes true, it also highlights a deficiency. We don't know what to say so we say everything and you sort it out as you see fit. The trick here is to go for less. If more is needed, perhaps the functionality is too broad or we are getting into the how. Recall the quote from Aristotle, so basically state what a particular piece of code is intended to do, any pre-conditions expected and what effects such functionality has on the object’s state – i.e post-condition.
Ex:
///Intent:
///Pre-condition:
///Post-condition:
public int foo(int a, int b);
The idea of communicating intent is that one should be able to follow your code, without ever stepping into definitions. This applies at the module, class and function level.
The second location for communicating intent is in naming. This is without a bought one of the hardest problems in programming and in Computer Science in general. Therefore all we can say is try your best to keep it meaningful! This serves to highlight the importance of a comment section as described above. Naming itself is not reliable enough to communicate our intent no matter how clever the name seems.
The last and most unappreciated location to place our intent is in unit tests. Sure, a unit test is meant to confirm that a piece of code accomplishes what it was set out to do, you may say. This is an important reassurance to communicate to your “future programmer and it serves to keep the integrity of the original intent. Additionally, what makes it absolutely indispensable is that it is executable. Therefore we are able to change how a particular functionality is achieved and be confident that the intent is still intact. Surprisingly, it is also the case that unit testing strengths naming and commenting.
So what constitutes a readable code? We discussed the importance of looking at programming as a communication form among three parties where "intent" is the most important piece of information to be communicated. There are three places where this intent can be stated; namely in the comment section, naming, and unit testing. When it comes to commenting, we also urged the programmer to strive for less as opposed to over-communicating. A readable code communicates its intent over approach. In the case of object-oriented languages, this is achieved with the proper use of classes.
Top comments (13)
I disagree with "Naming itself is not reliable enough to communicate our intent no matter how clever the name seems".
Combined with parameter/return types I believe the signature of a function can fully convey the intent of the function, at least to the point of it being used correctly.
At times I'd argue the name itself is sufficient. Consider a simple function like
radiansToDegrees
, it's hard to mistake the intent of this function.One thing I've noticed over time is that 'simple' is extremely context dependent.
To take your example, if I didn't happen to know what 'radians' meant, I could interpret your function as having something to do with temperature.
Beyond that... I think "communicating our intent" is not the same as "communicating what the code does". E.g.
Captures intent (I need degrees for the ui) whereas the un-commented version just explains the 'function'.
No, interpreting radians that way is just wrong. We have to assume that the people working on our project have at least taken a passing interest in the domain and would understand the commonly used, and perhaps uncommonly used terms. Trying to communicate to somebody that knows nothing about what's going on is a waste of time.
Your comment also causes confusion. Does this mean I can't use this function where it doesn't involve UI? Is this conversion somehow special to the values that a user might input. You've done the opposite of what a comment should do: you've added unclarity where before there was none.
"Assuming a passing interest in the domain" is good, assuming everyone in your team has a common set of vocabulary is more problematic. For the specific example you gave, I admit my interpretation as temperature was contrived.
I frequently work with people for whom english is not their first language, and I have found that describing even "obvious" function names has been very helpful.
Also agreed that the one liner I gave was not helpful by itself; to match the form of the article, my comment would have fit in the "Intent" line.
In reflecting on your feedback, I realized that I mostly use "why" comments inside function bodies whenever I'm doing something weird. So I guess unless your function is doing something weird, "why" comments matter less.
There is certainly a reason why "Naming" remains one of the hardest problems in Computer Science. Absolutely better function signature are very helpful but not enough. Also, depending on the type of application you are developing, "sufficient" may not be the level of clarity to strive for.
Saying something is "not enough" is unfortunately not a strong enough justification for adding comments to all functions. Though many functions do need comments, a large percentage (maybe not more than half), of the functions I've seen on projedts simply don't need to be commented.
Always keep in mind that comments interfere with refactoring. It's easy to break up and splity apart the code of a function, but it's time consuming and error prone to do the same for comments.
You definitely don't want to have too much verbosity regardless of intent here. There's a reason people read Twitter more willingly than books. We're all human and might glance past a wall of text. When possible, be concise...
I would argue this depends what we speak of.
If we speak of some client code used only once, I would expect enough comments to guide me through the code structure, to explain the overall intent and explain the critical/strange code you may have to actually have.
Now would this code made reusable as a shared API I would expect a lot more. I would expect a clear facade as a design, witch each method, parameter etc fully explained. I would expect a guide on how to use the API, the common pitfalls and a few tutorials to show how to use it in practice... At least.
Comments are expensive to write and many people don't even read comments. But that's actually a problem, not a good thing. When I use a public API, same JDK or spring or whatever, I usually read what the API is saying. What are the exception cases, what are required on my parameters and also get lot of information on if it is thread safe, the performance, alternate methods in the API and so on.
I have seen that I can often be done with a non trivial problems very very fast this way because the code I read end up being both robust (being design to fit how the API is really working) and concise because I know when I need to test a null or not, when it make sense to catch an exception and when it is unecesary... And usually when other have to read it, it is quite clear for them.
Sometime they complain of the style used, I should use more inheritences, more class or less of something... But they find the code clear, understandable and maintenable. And that what we want, isn't it?
Fully commenting a code, including robust unit tests, including a few clear examples, relate to the overall design with other part of the code, refer to key concepts, key steps and maybe also explaining why it was done this way and what are the obvious extension points allows even a newcomer to understand what is happening much faster.
If we apply information theory on code, boilerplate, design pattern and so own are all noise and what we call incidental complexity. It may be obvious, may not require comments but the problem is also useless. A code should be meaningfull a so it would be expected to not have just noise. What is actually doing something would likely need some explanation and because the code is dry, the comment ratio would appear to be high.
But like we need to remove uncessary comments, we also really need to remove unecessary code.
As always, thanks for the feedback. Will definitely keep that in mind😊
🙌
Great article Loicniragire!
I have one suggestion, if I may.
I believe this article will be much better if you include more examples.
Probably not an example per statement, but an example per important idea,
since it helps a lot while learning.
Thanks again.
Thanks for your suggestion - I think it would've been helpful in this case.
Nice post! Thank you, Loicniragire!