LinneoTech

Loading

Archives June 2023

Java Documentation Best Practices

Java documentation is important to do, but hard to love. In this post we discuss the reasons to document every piece of code you write, and the Javadoc best practices to make it easier, and more effective.

Java Documentation: A Love Story

I am pretty sure that as developers, we are all in love with technical documentation. We love reading docs, writing them, and don’t even start me on talking about maintaining docs: absolutely love it!

Also, I know that every time you create a class or a method you think about documenting it. And I am sure that you enjoy it as much as an occasional tasty burger. But sometimes, just sometimes, you have thoughts about having something lighter and maybe skip the docs just this one time. Unfortunately, this practice gets out of hand pretty quickly.

So in this post I want to talk about this crucial but often overlooked and forgotten part of a developer’s life. Hopefully, you’ll fall in love with documenting, come to a better understanding of why your code works, and ultimately help you, your team, and countless people who use your software.

Tips for Java Documentation

Usually developers never forget the code they wrote two weeks ago. Neither after two months or after even longer periods of time. Even if we take for granted that we’ll never forget any piece of code that we created there’s another much more important reason to document things.

1. Clarify Your Mind Before Coding

I will take my very own example: when I have an idea of developing my brand new exciting feature in SlideshowFX, I just would like to jump two feet into the code and implement it. But I know I am not the only passionate developer working on it. So my typical behavior goes something this:

  1. Write the following class body public class BurgersManager { }
  2. Think: “Well I should have some CRUD ops in that manager”
  3. Write some: public…
  4. Think “What should I return? Well void will be good for now”
  5. public void addBurger(Burger burger) { // TODO implement that later } public …
  6. Think: “Should I return the instance of the eaten burger or void will fit? Well, like #4 rules …”
  7. public void eat(Burger burger, boolean fast) { // TODO …
  8. Tell to myself: “Oh dang, coffee time. Where is my coffee …”
  9. Searching, drinking, talking to colleagues
  10. Then telling to myself: “Let’s get back to work. What was I doing?”

I know, you recognize yourself in this example, don’t you? At the beginning of creative work, our mind is a bit messy so if you start with the code it’ll be messy as well. Thinking documentation before code will help you clarify your mind and lay out clearly what you need to achieve with the code. So the first step could be writing the following code:

 
/**
 * This class allows to manage burgers by providing CRUD operations using burgers and 
 * acts as a singleton. In order to get the instance of this manager, the method 
 * {@link #getInstance()} can be used. Then CRUD operations can be called like the following:
 * 

{@link #addBurger(Burger)} in order to add a burger that will be managed by the * singleton instance ;

* @author Thierry Wasylczenko * @version 0.1 * @since BurgerQueen 1.0 */ public class BurgersManager { }

Well this is a short example which:

  • forces you to think what is the purpose of the class you are creating
  • helps you identify your needs
  • reminds you what you are doing, even after taking your break
  • helps you estimate what is still to be done

2. You’re on a Team

You are probably not working alone, and you may have colleagues you respect and with whom you love drinking coffee and talking about things. Well the point is that, because you love them, an you want to help them participate in the development of your exciting BurgerQueen implementation. And for that, the very best practice is to make sure that when they read your code, they could refer to perfect documentation, even if they ask you questions two weeks after you wrote the code, you could answer them without any hesitation.

That is another reason why documentation matters: it avoids people going back to you multiple times with questions about how your complex algorithm works or why burgers added to the manager are not also added to the statistics of the employees manager. In a team documentation avoids:

  • people being interrupted in they work and then having difficulties to recover from the interruption
  • people looking for the right person who could answer a question and disturbing other teammates to know if they know who could answer
  • people waiting for teammates to be available to answer their questions

So doing documentation helps the team increase its productivity and focus on the development.

3. Take Java Documentation to the Next Level

This point is much more personal. Doing my Java document makes me proud because when I reuse my APIs, I have documentation while I’m coding that helps me ensure that I don’t forget any little details. Even if usually I forget nothing, knowing that I can and the docs are there to back my memory is great.

Seeing my IntelliJ IDEA displaying my doc makes me feel like “Hey, look, I’m like a pro, my stuff is awesome, I even have the documentation“. In some way it’s true, isn’t it? Because when you’re using a lib with a method log(String s, int i) without a description of these wonderfully well-named arguments and if you must be like me, telling to yourself “What on earth could be the possible values for i?

I don’t know about you but I find the new design of the Javadoc pretty sweet. And I think having my documentation neat like that is awesome. But as I said, this is very personal.

JavaDoc Best Practices (With Examples)

In Javadoc you have nine tags which are the following:

  • @author
  • @version
  • @param
  • @return
  • @exception/@throws
  • @see
  • @since
  • @serial/@serialField/@serialData
  • @deprecated

But the purpose of this article is not to explain all of them in detail, but instead, as a documentation writer as well as a developer, I want to share some tips I use when I write my Java document.

1. Use @link and @linkplain for Pointing to Some Code

In my Javadoc I like to refer to classes and methods if there is a dependency or if it is just useful for the documentation. In order to make it easier to navigate through methods and classes, you can use @link. It works like this:

  • {@link BurgersManager} to point to a class
  • {@link BurgersManager burgers manager} to point to a class with a given label
  • {@link #eat(Burger, boolean)} to point to a method inside the same class
  • {@link #eat(Burger, boolean) eat} to point to a method inside the same class with a given label
  • {@link BurgersManagers#eat(Burger, boolean)} to point to a method inside another class
  • {@link BurgersManagers#eat(Burger, boolean) burgers manager eat} to point to a method inside another class with a given label

The difference between @link and @linkplain is that the latter one doesn’t produce a monospaced code font.

2. Use @code for Code Snippets

Often you can find some code inside a Javadoc to illustrate how to use a method, class or to provide some other example. In order to display the code correctly and preventing some markup to be interpreted such as <String> and so on, you can use the @code.


 
{@code 
List burgers = new ArrayList<>();
  for(int index = 0; index < 10; index++) {
    burgers.add(new Burger(“Burger #” + index)); 
  }
}


The @code will generate the <pre> markup for you.

3. Use @value to Insert the Value of a Field in the Documentation

When you have a constant, you may want to display its value in the documentation. So you have two options:

  • Insert the value yourself. But if the value changes, you will have to update your documentation, and as you will not forget to do it, you can choose this option safely
  • Use @value which will insert the value for you, and you won’t need to worry about updating your documentation

Well I will discuss the second option which is for me the best way to take advantage of the Javadoc tool. Indeed using a single attribute is really helpful:


/**
 * The default value for this field is {@value}.
*/
public static final String BURGER_SHOP_NAME = "Thierry's shop";


But you can also refer to another constant, for example:


/**
 * The default value for this field is {@value} when the value 
 * of {@link #OWNER} is {@value #OWNER}.
 */
public static final String BURGER_SHOP_NAME = "Thierry's shop";

/**
 * The default owner of this awesome burger shop.
 */
public static final String OWNER = " Thierry";

4. Indicate when the Features Have Been Available With @since

It is often useful to indicate when a class or a method became available in your code. For this you use the @since tag followed by the version/year since the feature or class has been implemented:


/**
 * This awesome class is for doing awesome things
 * @since burger-core-0.1
 * @version 0.2
*/
public class BurgersManager {

  /**
   * Allows to eat burgers
   * @since burger-core-0.2
   */
  public void eat(Burger burger, boolean fast) {
    // TODO
  }
}


As you can see I use it on both methods and classes and not only with a version number. Indeed nowadays we have applications with multiple modules which can have different life cycle and so, versions. Saying that a class or a method are available since version 0.2 doesn’t have a particular meaning. Version 0.2 of what? This is why I always put a relevant @since for helping my teammates understand when something has been available at the first sight.

Moreover, I can identify an advantage of this tag which is helping you building release notes. Wait, what? No, really, go to your favorite IDE, IntelliJ IDEA for instance, and search for files containing “@since burger-core-0.2” and voila you can identify what has been added. This doesn’t tell you what have been updated of course, only what’s new. But you should agree, such a straightforward trick is useful.

5. Don’t Be Anonymous, Use @author

There is something I really dislike: people not owning their code and not indicating it is them, who have written this awful code for an awful reason. If you write code, own it, or go to being a manager. You have the @author tag you can use to identify you’re the author of a class or method. I think it is a good practice to put it on both classes and methods because the author of a class might not write all methods of it.

Another best practice is to put all authors for a class or method. Imagine you and your teammate have written an awesome method and you’re identified as the only author. And one day, when you are on vacation, someone is reading your wonderful method and doesn’t understand it very well and would like some details. But as you’re listed as the only author they don’t know that the information is easily accessible from your colleague who worked on this code with you. You see where I’m going, don’t you? Always document the code authorship with @author.

6. For Non-Void Methods, Always Use @return

Well this is one really hits home with me. Sometimes I read the code like on the example below and just cringe.


/** Get the address.
 * @return
 */
public String getAddress() { /* … */ }


Why!? Really, why you don’t fill the @return? “It says just 1 line below, get the address”.

NO. NO. PLEASE NO. If you answer like that, it’s because your documentation is, well, how can I say that, subpar! Yes, because instead of having the poor documentation like you just saw above, you can easily have a better version, look:


/**
 * Get the address of this burger shop. The address is of the following format:
 * {@code address line 1
 * address line 2
 * zipcode city}
 * @return the address of this burger shop or {@code null} if not filled.
 */


Much better, right? Here your javadoc is useful. I always try to find a proper way of documenting the code because sometimes readers only read the @return, others the text above and you can easily avoid confusion by typing a couple more words.

6. Clarify What Parameters Mean With @param

What is more frustrating than seeing a method that takes a parameter named something unclear, like i with no documentation? Sometimes you can guess the purpose of that parameter thanks to the method’s name. Then again, sometimes you cannot. So in your documentation you should use @param in order to indicate what this parameter means and, potentially, indicate what the valid values are. In our case, i could be the level of the log and the values will be INFO, DEBUG or TRACE. Another case where this tag is particularly useful is when the value corresponds to an index. In some case indexes start at 0 and in other at 1. @param is the right tag to describe such differences.

JavaDoc Utility Options

It is very good to have documentation in the code, but now you have to generate it. So you can have to use the javadoc tool provided in the JDK to generate it. By executing something like:


javadoc {packages|source-files} [options]


You can specify the packages, space separated, or source files, also space separated, for which you want to generate the documentation.

Here’s a short description of some options javadoc utility accepts:

  • -author for generating the @author tag in the generated documentation
  • -d directory for generating the documentation elsewhere than in the current directory
  • -nodeprecated for avoiding the generation of documentation for code marked as @deprecated
  • -protected to include protected and public class and class members -private to include private class and class members
  • -public to only include public class and class members

Tools such as IDEs are also able to generate your documentation but also preview it to this if it is well formatted.

Dependency management tools like Maven and Gradle also include a phase or task to generate your documentation. And this is great! Because your documentation can always be produced with the release and so it will always be up to date.

IT , Java

Best Practices in Java 8

Summary:

  • For Default methods – use 1 default method per interface, and don’t enhance functional interfaces. Instead, you’ll focus on conservative implementations for those enhancements.
  • For Lambdas – use expressions instead of statements, refactoring to use method references and chaining Lambdas.
  • For java.util.Optional – use plain objects within fields and method parameters and optional for return values. Then, instead of get(), you’ll want to use orElse()

Read More:

Using Default Methods in Interfaces

The ability to specify default method implementations in interfaces was added into JDK 8 so that collections could evolve without breaking backward compatibility. Previously, we couldn’t just add a method to an interface without requiring all the implementing subclasses to specify an implementation of the new method. Breaking backward compatibility is a deal-breaker for Java. So since version 1.8 we can mark a method with the default keyword and provide the body of the method right in the interface. This, as any powerful feature does, opens all sorts of doors for un-maintainable, confusing code, if abused. However in small doses one can enhance existing interfaces and make them more useful in the codebase.

The main rule of thumb for using default methods is not to abuse them and not to make the code messier than it would be without it. For example, if you want to add some sort of functionality to Java classes without polluting their hierarchy with a common superclass, consider creating a separate interface just for this one utility method. Here’s an example of an interface called Debuggable that uses the reflection API to get the access to the object’s fields and provides a decent toString() implementation for the object that prints the fields values.

public interface Debuggable {
default String debug() {
StringBuilder sb = new StringBuilder(this.getClass().getName());
sb.append(” [ “);
Field[] fields = this.getClass().getDeclaredFields();
for(Field f: fields) {
f.setAccessible(true);
try {
sb.append(f.getName() + ” = ” + f.get(this));
sb.append(“, “);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
sb.append(“]”);
return sb.toString();
}
}

The important part here is the default keyword on the method signature. One could now use this for any class that you need to peek at, like this:

public class Main implements Debuggable {
int a = 100;
String b = “Home”;
public static void main(String[] args) {
Main m = new Main();
System.out.println(m.debug());
}
}

Which prints the expected line: “Main [ a = 100 b = Home ]”.

Functional interfaces of Java 8 deserve a special mention here. A functional interface is one that declares a single abstract method. This method will be called if we use it with the lambda syntax later. Note that default methods don’t break single abstract method contract. You can have a functional interface bearing many default methods if you choose. Don’t overuse it though. Code conciseness is important, but code readability trumps it by far.

Using Lambdas in Streams

Lambdas, oh sweet lambdas! Java developers have been eagerly waiting for you. For years Java has received the label of not being an appropriate programming language for functional programming techniques, because functions were not the first class citizens in the language. Indeed, there wasn’t a neat and accepted way to refer to a code block by a name and pass it around. Lambdas in JDK 8 changed that. Now we can use method references, for better or worse, to refer to a specific method, assign the functions into variables, pass them around, compose them and enjoy all the perks the functional programming paradigm offers.

The basics are simple enough, you can define functions using the arrow (->) notation and assign them to fields. To make things simpler when passing functions as parameters, we can use a functional interface, with only one abstract method.

There are a bunch of interfaces in the JDK that are created for almost any case: void functions, no parameters functions, normal functions that have both parameters and the return values. Here’s a taste of how your code might look using the lambda syntax.

// takes a Long, returns a String
Function<Long, String> f = l -> l.toString();
// takes nothing gives you Threads
Supplier<Thread> s =Thread::currentThread;
// takes a string as the parameter
Consumer<String> c = System.out::println;

The caveat here is that the code is tough to manage if you let your anonymous functions grow over a certain threshold. Think about the fattest lambda you’ve seen? Right. That should have never existed.

The most natural place for a lambda to exist is code which processes data. The code specifies the data flow and you just plug in the specific functionality that you want to run into the framework. The Stream API immediately comes to mind. Here’s an example:

// use them in with streams
new ArrayList<String>().stream().
// peek: debug streams without changes
peek(e -> System.out.println(e)).
// map: convert every element into something
map(e -> e.hashCode()).
// filter: pass some elements through
filter(e -> ((e.hashCode() % 2) == 0)).
// collect the stream into a collection
collect(Collectors.toCollection(TreeSet::new))

That’s pretty self-explanatory, right?

In general, when working with streams, you transform the values contained in the stream with the functions you provide for example using the lambda syntax.

Lambda Takeaways

  • If the code doesn’t specify the framework for the data flow into which you plug your functions, consider avoiding multiplying lambdas. A proper class might be more readable.
  • If your lambda grows above 3 lines of code – split it: either into several map() invocations that process the data in steps or extract a method and use the method reference syntax to refer to it.
  • Don’t assign lambdas and functions to the fields of the objects. Lambdas represent functions and those are best served pure.

Using java.util.Optional

Optional is a new type in Java 8 that wraps either a value or null, to represent the absence of a value. The main benefit is that it your code can now gracefully handle the existence of null values, as you don’t have to explicitly check for nulls anymore.

Optional is a monadic type you can map functions into, that will transform the value inside the Optional. Here’s a simple example, imagine you have an API call that might return a value or null, which you need to then process with the transform() method call. We’ll compare code with and without using Optional types.

User user = getUser(name); // might return null
Location location = null;
if(user != null) {
location = getLocation(user);
}

vs.

Optional<User> user = Optional.ofNullable(getUser(user));
Optional<Location> location = user.map(u -> getLocation(user));

It isn’t nice to have null checks polluting your code, is it? The best part is that we can now live inside this Optional world and never leave it, since all functions can be mapped into it. What about the functions that already return an Optional? Have no fear with the flatMap method, you won’t end up double wrapping Optionals. Check out flatMap’s signature:

public <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)

It takes care of the required unwrapping so you have just one level of Optionals!

Using Optional Types

Now, before you rewrite all your code to have Optionals all over the place. Hold on for a minute longer. Here’s a rule of thumb for where you want to use Optional types:

  • Instance fields – use plain values. Optional wasn’t created for usage in fields. It’s not serializable and adds a wrapping overhead that you don’t need. Instead use them on the method when you process the data from the fields.
  • Method parameters – use plain values. Dealing with tons of Optionals pollutes the method signatures and makes the code harder to read and maintain. All functions can be lifted to operate on optional types where needed. Keep the code simple!
  • Method return values – consider using Optional. Instead of returning nulls, the Optional type might be better. Whoever uses the code will be forced to handle the null case and it makes for cleaner code. 

All in all, using the Optional type correctly helps you keep your codebase clean and readable. And that’s very important! Disregarding that, both in life and in your code, is a recipe for disaster!