Tag Archives: Java

Clean Code: Java

Summarizing clean code practices for Java

Naming Conventions: Use meaningful names conveying the intent of the object.

Constants: Use Constants to manage static values (Constants help improve memory as they are cached by the JVM. For values that are reused across multiple places, create a constant file that holds static values.) use ENUMs to group constants.

Clean Code: Remove Console print statements, Remove Unnecessary comments

Deprecate Methods: Use @deprecated on method/variable names that aren’t meant for future use

Strings: If you need to perform a lot of operations on a String, use StringBuilder or StringBuffer.

Switch statement: Rather than using multiple if-else conditions, use the cleaner and more readable switch-case.

Exception Handling: https://kamalmeet.com/java/exception-handling-basic-principles/

Code Structure: Follow the Separation of Concerns strategy – controller, service, model, utility

Memory Leaks: Unclosed resources, e.g. unclosed URL connections can cause memory leaks. https://rollbar.com/blog/how-to-detect-memory-leaks-in-java-causes-types-tools/

Concurrent code: Avoid unnecessary synchronization, and at the same time identify areas to be synchronized where multiple threads can cause problems.

Lambdas and Streams: If you’re using Java 8+, replacing loops and extremely verbose methods with streams and lambdas makes the code look cleaner. Lambdas and streams allow you to write functional code in Java. The following snippet filters odd numbers in the traditional imperative way:

List<Integer> oddNumbers = new ArrayList<>();
for (Integer number : Arrays.asList(1, 2, 3, 4, 5, 6)) {
    if (number % 2 != 0) {
      oddNumbers.add(number);
  }
}

This is the functional way of filtering odd numbers:

List<Integer> oddNumbers = Stream.of(1, 2, 3, 4, 5, 6)
  .filter(number -> number % 2 != 0)
  .collect(Collectors.toList());

NullPointerException: When writing new methods, try to avoid returning nulls if possible. It could lead to null pointer exceptions.

Use final: When you want to make sure a method should not be overridden

Avoid static: Static can cause issues if not used properly as it shares variables at class level 

Data Structures: Java collections provide ArrayListLinkedListVectorStackHashSetHashMapHashtable. It’s important to understand the pros and cons of each to use them in the correct context. A few hints to help you make the right choice

Least visibility: Use of Public, private, and protected

Stay SOLID: https://kamalmeet.com/design/solid-principles-for-object-oriented-design/

DRY: Don’t Repeat Yourself, common code should be part of utilities and libraries

YAGNI: You Are not Going to Need It, code only what is needed

Static Code Review: SonarQube in Eclipse

Size of Class and Functions: Class and Function should be small – 400 / 40

Input checks: Inputs into methods should be checked for valid data size and range

Database Access: Use best practices like Connection Pool, JPA, Prepared statements, etc.

Java Updates since JDK-8

Found this interesting article on updates that have happened in Java since version 8- https://ondro.inginea.eu/index.php/new-features-in-java-versions-since-java-8/

Java 8 is still the most popular version of Java, though Java 17 is a recent Long term support version. Java 8 was an instant hit with the release of features like functional interfaces, Lambda expressions, Streams, Optional classes, etc. The post mentioned above talks about updates since Java 8. Here are some important features introduced as per my understanding

Clean Code: Error Handling, Testing, and Classes

In the clean code series, I will cover Error Jandling, Testing and Clean classes in this post.

Error Handling

  • Catch only Exceptions meant to be caught, for example, checked exceptions in Java
  • Log as much information as possible when an error or exception occurs for analysis
  • Null objects should not be returned, instead, return empty objects

Testing

  • Code Coverage targets should be set and achieved
  • TDD helps write testable code and reduce the number of issues
  • Use the F.I.R.S.T rule for testing:
    • The test is fast-running.
    • The tests are independent of others.
    • The test is repeatable in various environments.
    • The test is self-validating.
    • The test is timely.

Clean Class

  • Single Responsibility
  • Open for extension and closed for Modifications
  • Readability: self-documenting class names, function names, and variable names

Clean Code: Comments, Formatting, and Objects

In continuation with the clean code series, here I am covering Comments, Formatting, and Objects and data structures.

Comments

  • Code should be self-explanatory, the purpose of the code is that humans should be able to understand and not that computer is able to execute it
  • Comment only where logic is complex
  • Private API should not have comments
  • Use comments when you want to caution other developers, for example, why List instead of Queue was chosen and should not be modified
  • Comments should answer why (was a decision made) and not how the code works

Formatting

  • Formatting is a way to communicate with fellow developers
  • Readability = Maintainability = Extensibility
  • Verticle Alignment: Keep connected functions together for better readability
  • Horizontal Alignment: one should never need to scroll right
  • Team Formatting Rules: everyone should follow the same rules, braces, ident size, spaces/tabs

Objects and Data Structures

  • Follow OOPS Principles, e.g. parameters and behavior should be encapsulated
  • Law of Demeter:  M method of an object O can only consume services of the following types of objects:
    • The object itself, O.
    • The M parameters.
    • Any object created or instantiated by M.
    • Direct components of O.
  • Avoid Static methods wherever possible

Clean Code: Naming and Functions

Inspired by Clean Code by Robert C Martin, trying to summarize coding best practices. Starting with Naming and Functions best practices in this post.

Naming

  • Names should encode the intent, for example, studentBirthYear.
  • Use Good distinction: Do not use list1, list2, etc
  • Use Pronounceable name: dobmmyy vs dateOfBirthInMonthAndYear
  • use searchable names: int i, j, when you will try to search you will find a lot of them in code
  • Do not add type: phoneString, name String, name and phone should be sufficient
  • Avoid unclear prefixes: m_name vs manager_name
  • nouns for names and verbs for functions: employee for class and paySalary for function
  • Use Consistent concept: controller vs manager
  • Don’t use the same name twice to mean 2 different things. paymentInfo at one place returns bank details and another place user payment
  • Use Domain specific names
  • Avoid too long or too short names: Long is fine if it conveys better information, but not too long that makes it difficult to pronounce

Functions

  • Write Small functions, functions larger than 20 lines should be avoided
  • Make sure the function does only one thing
  • Use minimum arguments: max 2, if the function takes too many arguments, it is doing too much
  • DRY, Do not Repeat yourself: IF you are doing the same thing in multiple functions, move it to commonplace
  • Don’t use flag element, parameters of the Boolean type as a parameter already clearly state that it does more than one thing.

Java Modules

Modularization introduced in Java 9 is to help developers call out explicitly what are they going to use in their application and what features are they ready to expose.

You can see that JDK itself now is viewed as a combination of modules rather than a single monolith unit. One can mention all modules they are going to use in the application and keep the application lightweight. This can be done by adding a “requires” section in the module-info file.

Another problem Modules solve is when you are sharing your library (a jar), it exposes all the packages, though you might not want users to play around with internal helper files. The “exports” keyword gives you control over what is being exposed from the package.

Sample module-info.java

module java.mymodule {
  requires java.sql;
  exports java.util.string;
}

Recommended Reading

Collection Framework

Sometime back I wrote about Collections Framework. Here let me take a slightly deeper look into some of the concrete implementations with examples.

Hierarchy of the Collection Framework
https://www.geeksforgeeks.org/collections-in-java-2/

List Implementations

ArrayList: Simply put, this provides dynamic array implementation in Java.

LinkedList: Implementation of Linked List data structure. Provides methods like add and remove.

Vector: It is a synchronized ArrayList.

Stack: Implementation of Stack data structure. Provides methods like push and pop.

Queue Implementations

PriorityQueue: Implement First In, First Out. If Comparator is provided, it will be used for Priority management.

public class PriorityQueueDemo {
    
  public static void main(String args[])
  {
      PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>();

      pQueue.add(10);
      pQueue.add(20);
      pQueue.add(15);

      System.out.println(pQueue.peek());
      System.out.println(pQueue.poll());
      System.out.println(pQueue.peek());
  }
}

Output

10
10
15

Dequeue Implementation

ArrayDequeue: Double-ended queue implementation. Provides methods like add, addFirst, addLast.

Set Implementations

HashSet: Implementation of hash table data structure. Do not guarantee order.

LinkedHashSet: Uses a doubly linked list and hence maintains the order.

Sorted Set Implementation

TreeSet: Ordering is maintained as natural order or explicit comparator based ordering.

public class TreeSetDemo {  
  public static void main(String args[])
  {
      TreeSet<String> ts = new TreeSet<String>();

      ts.add("this");
      ts.add("is");
      ts.add("just");
      ts.add("a");
      ts.add("test");

      Iterator<String> itr = ts.iterator();
      while (itr.hasNext()) {
          System.out.println(itr.next());
      }
  }
}

Output

a
is
just
test
this

Java Collection Framework and Generics

As the name suggests, a Java Collection is a collection of objects represented as a single unit. The idea is to provide a set of operations on the logically grouped elements like searching, sorting, etc.

collection framework hierarchy
source: https://techvidvan.com/tutorials/java-collection-framework/

As the image above shows, there are two core interfaces, Collection and Map. The collection further has interfaces for List, Queue, and Set. For each of these interfaces than we have a concrete set of classes implementing the functionality.

Let’s take a very simple example

Collection values = new ArrayList();
values.add(1);
values.add(2);
values.add("kamal");

We can see collection interface helps us create a new ArrayList, which gives us flexibility over the array where the List can grow during runtime.

But one challenge we can see in the code above is that when we are able to add numbers as well as String to the ArrayList. This is in contrast with the type safety provided by Java. To solve this Java provides us Generics to control over types of elements that can be added to a collection.

Generics Example

class MyClass<T>{
T value;

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}

Coming to our previous code for creating collections

Collection<Integer> values = new ArrayList<>();
values.add(1);

In this example, we cannot add a String to values now.

What if we want to get or add into ArrayList at a particular index. List Interface adds these features on top of Collection. As already depicted in the hierarchy, List inherits Collection and then adds features on top of it.

List<Integer> values = new ArrayList<>();
values.add(1);
int num = values.get(0);

Iterating over a Collection

There are multiple ways to iterate over a collection

List<Integer> values = new ArrayList<>();
values.add(1);
values.add(2);
values.add(3);
Iterator<Integer> i= values.iterator();
while(i.hasNext()) {
    System.out.println(i.next());
}

Another way is to use for loop

for(Integer num:values) {
   System.out.println(num);
}

Or we can use Streams

values.stream().forEach(System.out::println)

Comparable vs Comparator

Another important thing one would like to do with the collection is to arrange elements in sorted order. For example, say we have a List created for Student class objects, and we want to arrange based on Roll numbers.

The easiest way is to make the class implement comparable

class Student implements Comparable<Student>{

 int rollno;
 String name;

 public Student(int rollno, String name) {
  this.rollno=rollno;
  this.name=name;
 }

 @Override
 public int compareTo(Student o) {
  return this.rollno<o.rollno?-1 : (this.rollno==o.rollno)?0:1;
 }
}

// In calling code, we can sort list made up of students 
Collections.sort(list);

There can be cases where it is handy to maintain comparison based on multiple criteria. For example, along with roll number, we want to make sure we can sort the list of students based on names in alphabetical order. In such a case it makes sense to implement Comparator as we can implement multiple comparators based on our need.

class NameComparator implements Comparator<Student>{
 @Override
 public int compare(Student o1, Student o2) {
  return o1.name.compareTo(o2.name);
 }
}

// In calling code, we will send compatator instance 
Collections.sort(list, new NameComparator());