Do you have time to talk about best coding practices with Java?
Besides studying hard, to become a good software developer in Java or any other language, you must master concepts and code conventions to make a clean code and easy to maintain. There are many reasons why it is recommended to adopt best coding practices in our day-to-day life:
- Hardly any code will ever be kept and evolved by the same developer. That way, the better it is, the less time a second developer will spend to understand it;
- Team productivity increases with well-written code because it facilitates reading, understanding, and certainly reduces the necessity of training;
- It reduces the excessive code complexity in new functionalities since the maintenance of a poorly structured code can be costly and delay the development of new modules. Also, it prevents that a change or addition, even if simple, causes an unrelated functionality to stop working;
- It reduces the number of bugs because it tends to be easier to review and test, as well as doing what it promises and delivering value to the customer;
- Finally, conventions are a set of established practices that are well known to developers most experienced in the area or even unconsciously adopted by other developers. These practices serve as a guide for newbies to create a body of knowledge and to program correctly.
Without further ado, here are some straightforward and essential tips to keep your Jedi level code:
1. Meaningful Names
We must name variables, functions, parameters, classes or methods according to their functionalities. It is essential for a good understanding of the code. That is, no variables with letters as the name (eg. int i, to save the age of a person. The ideal is personAge, for example).
Usually, the size used is 1 (one) character for counters in loops; 1 (one) word for loops and conditional variables; 1–2 words for method names; 2–3 words for class names; 3–4 words for global variable names. Do not use very extensive words as variables names (more than 50 characters). Otherwise, it will be difficult to read, besides, that it may not run on some compilers because of the size limitation of the lines.
Also, do not use “_” (underscore) anywhere except for constants and enums. Do not reuse the same variable name in the same class in different contexts. Another tip: decide by using a natural language only. For example, English only.
2. Avoid “Hadouken” code. Simplify it
The code is a language and writing a code it is just as much as writing a text. The simpler this text is the clearer the meaning of what is being done. The same rule applies to software development: to have a clean code, you need to create simple, small, clear and low complexity functions, avoiding to have suffered a Hadouken (if you already played Street Fighter, you know what I’m talking about, right? For those who don’t know is a code with many nested conditionals).
Our Clean Code guru, Robert Martin (a.k.a Uncle Bob), teaches us the following rule: functions should have a unique responsibility and follow the most straightforward path possible. Thus, it can be reused as many as necessary, facilitating its maintenance in the long term.
3. Comments are good, but only what is needed
There is a maxim about the comments: if the code needs many comments, it means that it is not clean code. So comment only as necessary. A code is modified continuously, as opposed to comments.
If a comment does not evolve along with the code, it may no longer have relevant meaning and may guide the code reader to misunderstanding. So if you need to comment, never forget to review and leave them according to with what the code represents.
Finally, use Javadoc in creating classes and methods. Javadoc is essential for other people to understand your code correctly.
4. DRY — Don’t repeat yourself
In other words, “Do not copy and paste code indiscriminately”. Avoiding repetition is a good practice because, conceptually, the system parts must have a single responsibility. This concept is part of the SOLID principles, introduced by Uncle Bob, and has a specific name for it: Single Responsibility Principle.
The problem with repetition is that as a system grows, keeping a duplicated code generates a very high complexity. The correct is to use the abstraction concept in the duplicate code snippet (extract a method or class/interface, centralizing the implementation) and reuse it where it is needed.
5. Defensive programming
You should be pessimistic, always think in the worst case scenario and prepare to deal with the problems that may eventually happen. In Java, the most common features for dealing with errors are Exceptions and the use of try-catch.
Exceptions are features that indicate a different flow than expected may occur in that method. For example, try to update a null object in a code snippet or try to insert a variable of type text into a numeric one. The try-catch flow serves to catch this kind of exceptions.
A good practice in a try-catch implementation is to use Finally, especially if your code snippet uses some database connection feature or uses a buffer to reading and writing files: it is very useful to control system resources after their use (eg.: bufferReader.close() or connection.close() method calls). This function is to always run the code snippet inside it, even if an exception is thrown.
Another good practice to avoid errors in your code is not to use “null”, either as a parameter or as a return function. Most of the time, these implementations require unnecessary checks that, once forgotten, cause errors in the process.
Also, when throwing an exception, be specific. Avoid generic exceptions and do not directly throw Exception or RuntimeException. Generic exceptions sin because of the lack of detail of the type of error that occurred. If none exists, create your exception class that specifies enough.
6. Design Patterns
If you’ve been in the software industry for some time, you may have heard some senior developer talk about Design Patterns. If you are still a student and did some more advanced software engineering discipline, you have probably heard some teacher talk about Design Patterns as well. It sounds like something incredible and revolutionary, but it is a set of solutions to our day-to-day problems.
In a simplified way, these patterns are divided into three categories: creational, structural, and behavioral. As the article focus is not Design Patterns, I will list three common situations.
- a) When we have a very complex object to create: see the Sale class that we use in the examples in item 6.
A great way to solve this problem is to apply the Builder pattern. Whenever we have an object that has many attributes or a more complex creational logic, we can encapsulate this in a Builder. In this way, the code is centralized, facilitating its maintenance and evolution. Here’s how the implementation gets simpler and cleaner:
- b) When we have a behavior in one class that uses a behavior from another: for example, we have the Discount abstraction, which represents a discount charged on sale, which can be decorated by other types of discounts.
Then, we implement concrete discounts (MDR — Merchant Discount Rate, administrative fee, card machine rental rate, etc …) that, after performing their service, invoke the next discount to be calculated to have the Sale’s total amount. Whenever we realize that are behaviors that can be composed by behaviors of other classes involved in the same hierarchy, the idea is to use the Decorator pattern.
- c) When we have several similar structures repeated in various parts of the code or even the system: in this case, the Template Method pattern is a good way to solve this problem. With it, we can define a generalized structure and leave “holes”, which will be implemented differently as a specific implementation needs. See the example below of the abstract class Report. It is a simple template that contains all parts of a report: header, content, and footer. From this template, we can create specific reports, e.g., QuarterlySalesReport.
Instead of repeating code, we have been able to reuse it and facilitate possible evolutions, both in its generic structure and of the concrete implementation.
7. Optimize code with newer language features
Always be up to date with the new features of the language that you work. Specifically, in Java, the most consolidated current versions are Java 8 and 9, which has many features and APIs that make development easier and reduces code complexity. We have some great features that are worth commenting on:
- Lambda Expressions: are very widespread features in functional languages and greatly facilitate the developer’s life. This feature is nothing new to anyone who has ever worked with Groovy, Scala or Clojure. The great advantage of lambda expressions is to reduce the amount of code needed to write functions. A lambda function is a function without a declaration, i.e., it is not necessary to put a name, a return type and the access modifier. The idea is to declare a method in the same place where it will be used. As an example, imagine a financial system, in which we must sort all daily sales by the number of installments.
The syntax used in the Comparator is an example of a lambda in Java 8.
- Stream API: brings new classes and methods to handle collections more simply and efficiently. Among the most interesting features of the collections API is the stream() method, which enables you to chain method calls, depending on the operations that are required to get the result you want. For example, imagine the same financial system as the example above, but now we need to filter all daily sales paid with a credit card.
Here’s an example of an implementation with Java 7:
Now, notice the same logic using stream() in Java 8:
The code gets a lot cleaner, does not it?
- Date and Time API: dealing with dates in older Java versions was not always a simple task and over the years some good libraries were designed to bypass typical conversion and precision problems. In version 8, a more powerful API was created, based on the famous JodaTime library. The new package, named java.time, has several classes to work with objects that store only dates, times, or even both simultaneously. For example, we want to represent a DateTime in the São Paulo timezone.
- Default Methods: introduces the possibility of having concrete methods in interfaces, through the use of the default modifier. These methods can be inherited by any class that implements interfaces with these characteristics. See the implementation of the CustomerTimezone interface:
Note that I can extend this interface to create another one to handle cases where timezone is invalid.
Creating clean code should be, undoubtedly, the mission of any developer who cares about a software product. The key to creating a clean code is: give meaningful names, create simple and well-defined functions with unique responsibilities, not to be repetitive, comment only when necessary, pay attention to possible errors and optimize it using the best available resources of the language that you work or in theory.
Finally, the most important tip for anyone who wants to be more productive as a developer: study! Study always! There is no point in promoting behavioral improvements such as being self-organized and more focused, or memorizing tips like these without engaging in practice. Constancy in seeking knowledge and have discipline to apply them in the correct context, will certainly be the fuel for your professional evolution.
I hope you enjoyed the post. Thanks!
1. The Clean Coder: A Code of Conduct for Professional Programmers (Robert C. Martin, 2011)
2. Java Code Conventions (http://www.oracle.com/technetwork/java/codeconventions-150003.pdf)
3. Alura — Cursos Online de Tecnologia — Design Patterns Java I: Boas práticas de programação
4. Alura — Cursos Online de Tecnologia — SOLID com Java: Orientação a Objetos com Java
5. Alura — Cursos Online de Tecnologia — Java 8: Tire proveito dos novos recursos da linguagem