I have always been a strong proponent of clean code that's written with maintainability in mind. It's my firm opinion that performance should be addressed with proper architecture. In most applications, code trickery contributes very little to overall performance. What exactly causes code to be less or more maintainable? Simplicity and clear semantics are two main important things that contribute to this aspect.
What makes the code simpler to understand? Code with many execution branches is harder to understand and to test. Personally, I dislike deep nested if-then-else conditions. Even having overly complicated boolean expressions as part of if conditions, makes the code harder to understand. Straight-line code without explicit branches and loops would be ideal. But can non-trivial code be written like this? It's quite possible.
Keeping the semantics completely unambiguous requires some thinking. In last few years, I have also developed an acute case of "null-phobia". In Java (and many other languages), usage of "null" can be problematic. Especially while describing semantics. What does a return value of null mean? Is something not present, or is present and empty, or did something go wrong? There are ways to deal with this dilemma. Documentation, exceptions are part of the solution. Now Java 8 has borrowed the Optional from other languages that makes it very easy to deal with this.
In this post, I want to explain one simple measure that can be employed to eliminate some branching and null-checks.
Consider a common service layer pattern. The service layer needs to provide an API to read the business objects from the database. The database layer provides a similar API to return the record from the database associated with the id given.
In the above only the code relevant to this discussion is shown.
Even with this simple structure, it is clear that the usage of null is problematic. Each layer needs to check nulls, and forgetting to do that and passing the result around would generate a NullPointerException in some other part of the code. The programmer must read the documentation in order to understand that null values might be returned, there is no help from the compiler. These are some of the reasons for potential bugs in future.
It's very easy to replace this with the new Optional and remove any ambiguity, and provide a very clear semantics.
That's much better than using nulls as a return value. With the Optional as a return value, it provides a clear signal to the users of the method that they have to check for the existence of the actual result. The compiler enforces the usage of an explicit get() call, and most likely your IDE will warn you if use the get() without the isPresent() method. Now it takes willful ignorance to cause a NullPointerException. That's a win.
Of course the code as written, is nothing but a glorified null-check. The Java 8 Optional allows you to refine the code even further by using the streamlike fluent API. The Optional has much more than just isPresent() and get().
The Optional::map() method is smart. It can be used to convert Optional of one type to another. The isPresent() check is handled for you. It accepts a lambda which will be executed only if the Optional is not empty.
Of course, with a simple lambda like that it can be even further simplified using the new method references.
This is not code trickery at all. This is the correct usage of Optional. The unfamiliarity of the lambdas and the new Java 8 APIs will eventually go away.
This was a small snippet to convey the idea. I strongly argue that such concise straight line code is much easier to understand. It's also extremely easy to maintain due to lack of branching, and lack of the need to keep performing null-checks. To be precise, the branching and null checks have not disappeared, but they have been moved from our code to the JDK library code. That's still a huge win for maintainability.
I am also aware that lambdas, if not used in such concise manner, suffer from the same issues of anonymous classes. My rule of thumb is, lambdas should be simple one-liners, just a method call with descriptive name. Nothing more.
There are many such simple measures that can be employed to write code that has minimal branching, and clear semantics. More in future posts.
It's very easy to replace this with the new Optional and remove any ambiguity, and provide a very clear semantics.
That's much better than using nulls as a return value. With the Optional
Of course the code as written, is nothing but a glorified null-check. The Java 8 Optional allows you to refine the code even further by using the streamlike fluent API. The Optional
The Optional
Of course, with a simple lambda like that it can be even further simplified using the new method references.
This is not code trickery at all. This is the correct usage of Optional. The unfamiliarity of the lambdas and the new Java 8 APIs will eventually go away.
This was a small snippet to convey the idea. I strongly argue that such concise straight line code is much easier to understand. It's also extremely easy to maintain due to lack of branching, and lack of the need to keep performing null-checks. To be precise, the branching and null checks have not disappeared, but they have been moved from our code to the JDK library code. That's still a huge win for maintainability.
I am also aware that lambdas, if not used in such concise manner, suffer from the same issues of anonymous classes. My rule of thumb is, lambdas should be simple one-liners, just a method call with descriptive name. Nothing more.
There are many such simple measures that can be employed to write code that has minimal branching, and clear semantics. More in future posts.
Thanks Abhay for posting a fantastic example to show how to use Optional and lambda to handle nulls in an elegant way. I didn't know you are still in tech field, I thought you have become a poet... fulltime :).
ReplyDeleteCheers,
Paresh