Ways to make a Readable Code
For many years I was a big fan of beautiful code, back to ruby I was amazed by what I was able to do in one line. With Java, we can stream, map and then collect!
But as time passed, I read many impressive optimizations, small tricks, and clever algorithms and I got less and less excited about these discoveries. That’s why I was chasing baubles and they almost always were some very elegant expressiveness in a new language (Ruby converts from Java can testify this) or a technique that I’m not likely to use.
When I joined the Ruby community I always heard about code clarity and code readable by the rush of agile methodology throwing away almost all projects documentation. So my esthetic sense turned to code clarity.
But why code readability is so important?
Developers must know that they’re not writing code for themselves, but writing code for others, and with low documentation when a new developer comes he needs to read and understand the code.
When a developer initially writes the code everything is fresh in mind, their knowledge of the system is so much detailed because they have read requirements and technical specs and have worked on that for some time.
But a year later no one knows the system that well again, so they’ll need to read the code again and learn from it. And if a newcomer joins the team he will learn most from the code.
If you struggle to read the code, how the hell are you meant to fix it? —Ben Hosk
So how do we improve our readability?
Naming, dear God naming!
Don’t write complex beautiful codes, more it simple then less bugs it may have and less time needed to debug them. Imagine how hard it will be to debug a data process inside a stream map?
Code should do only what It needs without tons of abstractions and comments. Naming is hard, but it’s important because a method data updates some data for an object should scream that it updates the object’s data and only do that! — but this is not SRP and I’ll explain that in another time.
Names of variables and functions should be distinct and provide a general idea of what they do. The important thing about naming is that it should describe what it does to your team, so it should conform to the conventions chosen in the project. Even if you don’t agree with them. If every request for a record in the database starts with “find” word, like “findUser”, then your team might get confused if you come to the project and name your database function “getUserProfile” because this is what you are used to. Try to group naming when possible, for example, if you have many classes for input validation, putting “Validator” as the suffix for the name may quickly provide information what the purpose of the class is.
If a variable or constant might be seen or used in multiple places in a body of code, it is imperative to give it a search-friendly name and avoid comments showing a bad code.
int d; // elapsed time in days
int elapsedTimeInDays;
Good code should be understandable without a line of comments and is a self-documentation.
Commented code is confusing. Did someone remove it temporarily? Is it important? When was it commented? It’s dead, take it out of its misery. Just remove it.
Patterns my friend, patterns!
Back to 2013 Sand Metz create some rules for developers.
_There are four rules._
1. Classes can be no longer than one hundred lines of code.
2. Methods can be no longer than five lines of code.
3. Pass no more than four parameters into a method. Hash options are parameters.
4. Controllers can instantiate only one object. Therefore, views can only know about one instance variable and views should only send messages to that object (
@object.collaborator.value
is not allowed).
And, for me, the most interesting rule is the five lines per method rule.
We agreed if
, else
, and end
are all lines. In an if
block with two branches, each branch could only be one line.
public void validateActor() {
if (actor_type == 'Group') {
userMustBelongToGroup()
} else if (actor_type == 'User') {
userMustBeTheSameAsActor()
}
}
Five lines ensured that we never use else
with else if
.
Having only one line per branch urged us to use well-named private methods to get work done. Private methods are great documentation. They need very clear names, which forced us to think about the content of the code we were extracting.
You should break these rules only if you have a good reason or your pair lets you — Sandi Metz
Guard clauses may be all you need!
There is as a great discussion on how the if-statements should be used for better code readability. Usually, it boils down to an opinion that is completely subjective and aesthetic. But is certain that the most documented case is the guard clause, also known as assert or precondition. And the idea of it is when you have something to assert in de beginning of a method do this using the fast return.
Let’s start with a snippet of code:
public void updateData(Data data) {
if (data == null) {
return;
} else if (data.isPresent()) {
// do stuff here
} else {
trow new Exception(msg);
}
}
What about the following one?
public void updateData(Data data) {
if (data == null) return;
if (!data.isPresent()) trow new Exception(msg);
// do stuff here
}
It’s pretty common to find large if-blocks in a codebase because it’s due to the fact that our brains function differently: we tend to think “do stuff if it’s enabled”, not “do stuff if it’s enabled, but if it’s not enabled do nothing”.
So why these guidelines?
This is just a few examples, but most important, of what can be done, always write code that simple to read and which will be understandable for other developers, because time and resources that will be spent on the hard readable code will be much higher than what you get from optimizations. I believe that this is the way to make things better, not for us, but for the ones that will read our code later.
You also need to learn when to not use them, each one solves a specific problem/issue in the applications, their usefulness can be affected by many different factors like the size of the project, the number of people working on it, time/cost constraints or required complexity for the solution. Some patterns have been named antipatterns, like the Singleton pattern because even though they provide some solutions they also introduce some issues in certain cases.
There are many ways to be right. If you are convinced beyond doubt that you should always use DRY in your applications or is better always keep using only a well-known design pattern, it's okay.
You may choose the path that is right for you and your team, and there are no indisputable truths, but always think how other developers will read your code.
if (readableCode()) {
beHappy();
} else {
refactor();
}