1. Introduction
Whether we are professional developers or just starting to write our first lines of code, we’ve most likely already faced different formatting styles for variables and functions such as camelCase, PascalCase, snake_case, and kebab-case.
This is only one of the styling aspects that we have to define when programming, but there are others: number of lines in a file, indentation style, with different rules for methods, classes, and scopes. On top of that, each programming language has its syntax requirements and specificities.
In this article, we’ll discuss good practices to incorporate into our code since it would be impossible to expect the same style from all programmers. Besides that, when working in a team, the code is the communication tool between the members, so putting the program to work won’t be enough if the maintainability is considerably compromised.
The focus will be on ensuring that our code has excellent readability and that others will not give up on understanding our logic programming and software architecture. The examples are written in Java, but the concepts apply to any other programming language.
2. Vertical Formatting
One of the most famous analogies when it comes to writing clean code is the Newspaper Metaphor, which states that the process of reading code should be similar to that of reading a newspaper.
We start by reading only the headline. In the next step, we read a paragraph containing a summary. Then, as we’re interested in learning about that topic, we choose to go deeper into the details, which will be located at the bottom of the article.
In the same way, the title of the file and the key concepts and algorithms should be easily found on the top of our source code. Those functions and variables used for small and more specific tasks should be placed after the reader already has gotten a glance at what our code was made for.
2.1. Vertical Density
If a block represents a complete concept or thought – for example, a few lines to render a string in an HTML file – we should put a blank line between this block and its neighbors. This is called vertical openness between concepts.
This idea is closely related to the vertical density, which tells us that a line of code representing a clause or operation shouldn’t be separated from the following line of code if their purposes are tightly connected.
Let’s see an example of what we shouldn’t do:
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern = Pattern.compile("'''(.+?)'''", Pattern.MULTILINE + Pattern.DOTALL);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>");
/**
* Adds the HTML closing tag
*/
html.append(childHtml()).append("</b>");
return html.toString();
}
}
We can easily see that this code is not applying the concepts that we discussed before.
First, there is no vertical openness since the whole code is grouped as one block. Besides that, there is a useless comment separating lines of code that are intrinsically connected.
Now, let’s take a look at the same code after we’ve applied the excellent styling practices:
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern = Pattern.compile("'''(.+?)'''",Pattern.MULTILINE + Pattern.DOTALL);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("<b>");
html.append(childHtml()).append("</b>");
return html.toString();
}
}
After a few seconds of looking at the well-formatted code, we can understand what each code block is doing in our program.
Now, let’s take a new look at the first code. Imagine a file with thousands of lines written without any openness or density concerns. Most probably, even the author of this type of code won’t understand its work after a few months.
2.2. Vertical Distance
To prevent the reader from going over all of our files and lines, we should always keep correlated concepts vertically close to each other. Together with this, we should declare all variables as close to their usage as possible, especially those used in loops that are expected to exist only in one scope.
Let’s imagine that we defined a class to handle the employees of a company, with a class constructor and age, salary, and position assignments. In the following steps, we created a new class to generate random values and to perform several tests on the elements.
How frustrating would be for our reader to search for the constructor class after 500 lines or even worse, in another file, considering that the two classes are so linked?
2.3. Vertical Ordering
The order of our code should instinctively represent the dependency relation of our program. There should be a natural flow when reading each line.
If a high-level function calls a function considered low-level, the former should be positioned first in the source file.
This will give the correct impression for our reader that there is no reason to worry when a function calls a new function, since in the following few lines, and as soon as possible, the called function will be detailed.
3. Horizontal Formatting
How long should our lines of code be? A rule of thumb that remained for years as a good practice was to consider Hollerith’s punched cards for the US Census in 1890: 80 characters per line.
We should not look at this number as a strict rule, mainly because depending on the framework that we’re programming in, function names can be originally long, like in iOS.
Linus Torvalds himself declared against this limited view, arguing that longer lines are fundamentally helpful and natural.
So what should we strive for if there is no consensus?
3.1. Horizontal Openness and Density
Considering that we can find monitors with a much higher resolution than in the past, and following Uncle Bob’s suggestion, we can develop a good guideline for this.
We should try to make our lines have between 100 and 120 characters, avoiding as much as possible the need of scrolling to the right to be able to read the code.
Like the vertical formatting, we should use horizontal whitespace to identify things strongly connected and disassociate unrelated things.
Usually, whitespace encloses assignment operators:
public class CountCharsInString {
public static void main(String[] args) {
String exampleString = "This is just a sample string";
long totalCharacters = exampleString.chars().filter(ch -> ch != ' ').count();
System.out.println("There are total " + totalCharacters + " characters in exampleString");
}
}
We can notice how the spaces make evident the separation between left and right operators. But there’s no space between the functions’ names and their arguments since they are strongly related.
3.2. Horizontal Alignment and Indentation
When analyzing from the horizontal perspective, we shouldn’t aim to have perfect alignment. Instead, the lack of alignment is desired as long as it helps our reader to easily see the variable type or the assignment operator.
If we try to align each operator and declaration, we’ll lead the reader to read things in the wrong order, or even read the variables and not their assignments:
public FitNesseExpediter(Socket s,
FitNesseContext context) throws Exception
{
this.context = context;
socket = s;
input = s.getInputStream();
output = s.getOutputStream();
requestParsingTimeLimit = 10000;
}
Finally, one of the most important aspects that we should pay attention to when writing any code, regardless of the framework, is indentation.
A source file essentially represents a hierarchy, and for this reason, readers should spot levels effortlessly the first time they look at our code.
Variables that will be used only on a few lines of code shouldn’t be positioned at the same level as the main function or the class declaration.
Indentation is the visual way of noticing the dependency and scopes of our code. If we don’t use it correctly, our code will require a significant examination to understand the basic functioning:
public class Factorial{ public static void main(String[] args){ final int NUM_FACTS = 100; for(int i = 0; i < NUM_FACTS; i++) System.out.println( i + "! is " + factorial(i));}
public static int factorial(int n){ int result = 1;for(int i = 2; i <= n; i++) result *= i; return result;}}
When we apply indentation, the readability improves significantly:
public class Factorial{
public static void main(String[] args){
final int NUM_FACTS = 100;
for(int i = 0; i < NUM_FACTS; i++)
System.out.println( i + "! is " + factorial(i));
}
public static int factorial(int n) {
int result = 1;
for(int i = 2; i <= n; i++)
result *= i;
return result;
}
}
4. Conclusion
Although we have the freedom to choose how our code will be formatted, we must always have in mind that others might read it in the future. When working on a team, the rules must be agreed upon by everyone in the group to keep a consistent style in all files of a larger project, in such a way that the project will look professional and easy to update and understand.
As Robert C. Martin put it in his book, Clean Code, “The coding style and readability set precedents that continue to affect maintainability and extensibility long after the original code has been changed beyond recognition. Your style and discipline survive, even though your code does not.”