BrilworksarrowBlogarrowTechnology PracticesarrowJava Best Practices [Part 1]

Java Best Practices [Part 1]

Colin Shah
Colin Shah
September 20, 2023
Clock icon7 mins read
Calendar iconLast updated December 18, 2024
Banner-Java-Best-Practices-Part-1

When we talk about Java best practices, we often learn about clean code. It is all about making your code easy to read and understand for yourself and others. When your code is well-written, organized and standard, you don't have to explain it in detail to your teammates because it speaks for itself.

The “Standard” refers to the norms developed by the developer’s community on how to write different aspects of code.

It’s needless to explain the benefits associated with clean code writing practice; as a coder you might be aware of them. However, if we have to summarise the key benefits of well-written code, below are the key advantages:

  • Code that follows a standard is easier to read and understand.
  • When written using standards and thumb of rules, it becomes easier to maintain and modify the code.
  • It makes it easier to collaborate on well-written code
  • It is easier to debug.
  • You have peace of mind knowing that the code is readable, maintainable, and bug-free.

Standard or clean code is a set of practices commonly used by professional developers. These practices assist individuals in creating well-organized and tidy code. In this article, we will walk you through the methods that will help you master the art of clean code writing and give you a clear understanding of what clean code is all about.

Recap: What is the Clean Code and Why Does it Matter?

Yes, It matters a lot. Especially in today’s digital world, when apps are getting bigger and more complex, so are our codebases. Therefore it becomes essential to develop a habit of writing clean code.

Remember, your job is not writing code that is only understandable to a machine; it should be easier to read for humans. 

1. KISS, DRY, and YAGNI

These three acronyms represent three valuable rules of thumb in software development as the following:

Keep It Simple, Stupid: The simplest solution is often the best one.

When you're writing code, don't try to be too clever. You want to be innovative, but don't let that get in the way of writing good code. Over-engineering can make your code harder to understand.

The principle of KISS is all about keeping things simple. Let’s understand it with an example; you're developing a method in Java that calculates the sum of two numbers. Following the KISS principle, the code would look like this:

public int calculateSum(int a, int b) {
    return a + b;
}


Now the complex implementation is as the following;

public static int calculateSum(int... a) {

    int result = 0;

    for (int i = 0; i < a.length; i++) {

        result += a[i];

    }

   return result;

Don’t Repeat Yourself – Avoid duplicating parts of code by putting it into a single system or a method. 

Simply put, always try to write reusable logic, functions, components, modules, etc. Let’s understand it with this example:  Without adhering to dry principle:

public class AreaCalculatorWithoutDry {

  public static void main(String[] args) {

    // Calculate areas

    double rectangleLength = 5;

    double rectangleWidth = 8;

    double rectangleArea = rectangleLength * rectangleWidth;

    System.out.println("The area of the rectangle is: " + rectangleArea);

    double circleRadius = 3;

    double circleArea = 3.14 * circleRadius * circleRadius;

    System.out.println("The area of the circle is: " + circleArea);

    // More code...

    // Later in the code, you need to calculate the area of another rectangle

    double anotherRectangleLength = 6;

    double anotherRectangleWidth = 10;

    double anotherRectangleArea = anotherRectangleLength * anotherRectangleWidth;

    System.out.println("The area of the rectangle is: " + anotherRectangleArea);

    // More code...

  }

}

Code adhering to dry principle:

public class AreaCalculatorWithDry {

   public static double calculateRectangleArea(double length, double width){

    return length * width;

  }

  public static double calculateCircleArea(double radius) {

    return 3.14 * radius * radius;

  }

  public static void main(String[] args) {

    // Calculate areas

    double rectangleLength = 5;

    double rectangleWidth = 8;

    double rectangleArea = calculateRectangleArea(rectangleLength, rectangleWidth);

    System.out.println("The area of the rectangle is: " + rectangleArea);

    double circleRadius = 3;

    double circleArea = calculateCircleArea(circleRadius);

   System.out.println("The area of the circle is: " + circleArea);

   // More code...  

    // Later in the code, you need to calculate the area of another rectangle

    double anotherRectangleLength = 6;

    double anotherRectangleWidth = 10;

    double anotherRectangleArea = calculateRectangleArea(anotherRectangleLength, anotherRectangleWidth);

   System.out.println("The area of the rectangle is: " + anotherRectangleArea);

    // More code...

  }

}

You Ain’t Going to Need It – It advocates the principle of implementing the only required functionality in development.

Avoid adding unnecessary (or extra) features. In simple words, build only what you need now.

import java.util.HashMap;

import java.util.Map;

public class ShoppingCart {

    private Map<String, Double> items;

    public ShoppingCart() {

        this.items = new HashMap<>();

    }



    public void addItem(String itemName, double price) {

        items.put(itemName, price);

    }



    public void removeItem(String itemName) {

        items.remove(itemName);

    }



    public double calculateTotal() {

        double total = 0;

        for (double price : items.values()) {

            total += price;

        }

        return total;

    }



    public static void main(String[] args) {

        ShoppingCart cart = new ShoppingCart();



        // Adding items to the cart

        cart.addItem("Shirt", 25.99);

        cart.addItem("Jeans", 39.99);



        // Calculating the total price

        double total = cart.calculateTotal();

        System.out.println("Total price: $" + total); // Output: Total price: $65.98



        // Removing an item

        cart.removeItem("Shirt");



        // Recalculating the total price after removing an item

        total = cart.calculateTotal();

        System.out.println("Updated total price: $" + total); // Output: Updated total price: $39.99

    }

}

In this example, we have created a simple ShoppingCart class that allows users to add items, remove items, and calculate the total price. We have not included unnecessary features like handling quantities, discounts, payment methods, or shipping options, as the focus is on building only what is needed for a basic shopping cart functionality.

2. Single Responsibility

Each module or class in your code should only do one thing. It is known as the “Single Responsibility Principle” (SPR). When a class or method has a single responsibility, it becomes easier to maintain, understand, and test the codebase. Having multi-functionality can affect unrelated parts of the program. So, if you find yourself writing a module or class that does too many things, break it up into smaller, more focused modules or classes.

Ensure that each method has a single responsibility. Split complex methods into smaller, focused ones, which improves code readability, testability, and reusability. For example:

// Avoid

public void processStudentData() {

    // Lengthy code block

}

// Prefer

public void calculateAverageGrade() {

    // Code to calculate average grade

}

public void updateStudentRecord() {

    // Code to update student record

}

3. Well-described commits

A commit is a snapshot of the changes made to a repository at a specific point in time. It helps developers track changes over time. Therefore, it should be clear, concise (keep it under 50 characters if possible), and meaningful. Start your commit message with a capitalized verb that describes the change you made, e.g., "Fix bug with global search input deleting user text."

4. Descriptive Names

Meaningful and consistent naming is always important, which makes code more readable.

For example, consider the following variable names.

int a = 10;

double b = 3.14;

String c = "Hello";

These names are too short and vague. They do not tell us anything about what they store or what they are used for. A better way to name them would be:

int age = 10;

double pi = 3.14;

String greeting = "Hello";

5. Naming Conventions

Conventions are not strict rules. It refers to a set of agreed-upon rules, practices, or guidelines that developers follow to maintain consistency and improve code readability. It involves appropriately naming variables, functions, classes, and other elements.

5.1. Packages: Package names should be in lowercase, with multiple words separated by periods. For example com.Example.MyApp.

Package Name: com.example.myapp [Good practice]

Package Name: com.Example.MyApp [Bad practice]

5.2. Classes and Interfaces: Class and interface names should be written in CamelCase, starting with an uppercase letter. For example: MyClass, MyInterface is a proper way of naming classes and interfaces. While, Myclass, Myinteface can lead to confusion and may make code harder to understand.

5.3. Methods: Method names should be written in camelCase, starting with a lowercase letter. For example: calculateArea(), sendMessage().

Good Practice:

  • calculateArea()
  • sendMessage()

Bad Practice:

  • CalculateArea() (Doesn't start with a lowercase letter)
  • send_Message() (Uses underscore instead of camelCase)

5.4 Variables: Variable names should also be in camelCase, starting with a lowercase letter. For example: firstName, age, totalAmount.

Good Practice:

  • firstName
  • age
  • totalAmount

Bad Practice:

  • FirstName (Doesn't start with a lowercase letter)
  • age_of_person (Uses underscore instead of camelCase)
  • Total_Amount (Mixed use of underscore and camelCase)

5.5 Constants: Constant names should be written in uppercase letters, with words separated by underscores. For example: MAX_VALUE, DEFAULT_TIMEOUT, PI.

Good Practice:

  • MAX_VALUE
  • DEFAULT_TIMEOUT
  • PI

Bad Practice:

  • MaxValue (Should be uppercase)
  • DefaultTimeout (Should be uppercase and use underscores)
  • Pi (Should be uppercase)

5.6 Enums: Enum names should be written in CamelCase, starting with an uppercase letter. Enum constants should be written in uppercase letters, separated by underscores. For example:  enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY,FRIDAY, SATURDAY, SUNDAY }

Good Practice:

enum DayOfWeek {

    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY

}

Bad Practice:

enum dayOfWeek {

    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday

}

enum DayOfWeek {

    monday, tuesday, wednesday, thursday, friday, saturday, sunday

}

6. Document Code using Comments

  • Use comments to explain the purpose of the code, not the mechanics.
  • Use Javadoc-style comments to document classes, methods, and fields.
  • Use single-line comments for quick explanations or to comment out code temporarily.

Comments are notes that programmers add to their code, explaining what the code does. The machine ignores comments, but humans can read them. They are really helpful for finding specific sections of code. If you've properly commented on your code, you will easily find the section you're looking for, even if you last saw it a while ago.

Documenting your code with comments is essential, especially when working on a project with others or planning to revisit your code. Comments can help others understand your code and also aid you in remembering what your code does.

In Java programming, comments are of two types: single-line and multi-line.

Here are some examples of single-line and multi-line comments:

// This is a single-line comment

/* This is a multi-line comment

that can span multiple lines */

For example, consider the following code:

// This method calculates the area of a circle

public static double calculateArea(double radius) {

// The formula for the area of a circle is pi * r^2

return Math.PI * Math.pow(radius, 2);

}

This code has too many comments that are redundant or obvious. A better way to write it would be:

 // Calculates the area of a circle given its radius

public static double calculateArea(double radius) {

return Math.PI * Math.pow(radius, 2);

}

6. Ordering Class Members by Scopes

It is a widely followed convention in object-oriented programming languages. Ordering class members by scope makes the program readable.  In Java, class members have four scopes: public, protected, default, and private. Public members can be accessed from anywhere, protected members can be accessed from the same package or subclasses, default members can only be accessed from the same package, and private members can only be accessed from the same class.

A good convention for ordering class members by scope is to put public members first, then protected members, then default members, and then private members. You can also put constructors and nested classes in their sections.

  1. public fields
  2. protected fields
  3. default fields
  4. private fields
  5. public constructors
  6. protected constructors
  7. default constructors
  8. private constructors
  9. public methods
  10. protected methods
  11. default methods
  12. private methods
  13. nested classes

You can also use the IDE plugin or code formatter tools to arrange and organize the members based on their scope. You can use the Eclipse Code Formatter plugin for Eclipse or the Reformat Code feature for IntelliJ IDEA.

7. Class Members should be private

Encapsulation is one of the fundamental principles of object-oriented programming languages. It refers to bundling data and methods and only exposing the select information.

You can achieve encapsulation by making your class members (fields and methods) private by default. This means they can only be accessed within the same class. If you need to expose some of your class members to other class, you can provide public or protected accessors(getters and setters) or other methods to define the interface of your class.

By making your class members private, you ensure that all the elements inside the class stick together nicely and work well as a team. It also helps prevent outsiders from messing with the class's inner workings. Plus, reducing how much the class relies on other stuff makes testing and maintaining your code a whole lot easier!

For example, consider this class that represents a bank account:

public class BankAccount {

// Bad practice: exposing fields as public

public String owner;

public double balance;

// Good practice: making fields private and providing accessors

private String owner;

private double balance;

public String getOwner() {

return owner;

}

public void setOwner(String owner) {

this.owner = owner;

}

public double getBalance() {

return balance;

}

public void deposit(double amount) {

if (amount > 0) {

balance += amount;

}

}

public void withdraw(double amount) {

if (amount > 0 && amount <= balance) {

balance -= amount;

}

}

}

In the above example, making the fields private and providing accessors ensures that the owner and balance are only accessible and modifiable through the defined methods. This way, you can enforce some validation and logic on the bank account's operation.

8. Use Underscores in Numeric Literals

Java has a cool feature that lets you make your numeric more readable. If you don’t know, you can underscore the lengthy numbers that look more readable(it’s like separating numbers, so it becomes easier to read if it is long).

It's especially handy for dealing with large numbers or when working with binary, octal, or hexadecimal values. By using underscores, you can separate digits and create more organized and legible numeric representations.

For example, you can write:

// Without underscores

long creditCardNumber = 1234567890123456L;

long socialSecurityNumber = 999000111L;

float pi = 3.141592653F;

long hexBytes = 0xFFECDE5E;

long hexWords = 0xCAFE_BABE;

long maxLong = 0x7fff_ffff_ffff_ffffL;

byte nybbles = 0b0010_0101;

long bytes = 0b11010010_01101001_10010100_10010010;

// With underscores

long creditCardNumber = 1234_5678_9012_3456L;

long socialSecurityNumber = 999_00_0111L;

float pi = 3.14_15_92_65_3F;

long hexBytes = 0xFF_EC_DE_5E;

long hexWords = 0xCAFE_BABE;

long maxLong = 0x7fff_ffff_ffff_ffffL;

byte nybbles = 0b0010_0101;

long bytes = 0b11010010_01101001_10010100_10010010;

As you can see, using underscores in numeric literals makes them easier to read and compare. You can also logically group the digits, such as by thousands, bytes, words, etc.

However, some rules and limitations exist for using underscores in numeric literals. For example:

  • You cannot use underscores at the beginning or end of a number.
  • You cannot use underscores adjacent to a decimal point.
  • You cannot use underscores in the exponent of a floating-point number.
  • You cannot use underscores prior to an F or L suffix.

Conclusion

Clean code helps in writing maintainable and bug-free code. By following the outlined practices in this blog post, you can write code that is easy to understand and maintain. Furthermore, we will discuss more Java best practices in part two of this series. We hope you find this article helpful and informative.

FAQ

Java best practices are the golden rules of coding in Java. They help make your code clean, efficient, and easy to work with. Following these practices is important because they ensure your code is readable, scalable, and performs well.

Coding standards are the style guide for your Java code. They cover things like how you name your variables, how you format your code, and how you write comments. For example, using meaningful variable names like `totalPrice` instead of `x` and keeping your code neatly indented and easy to read are some common coding standards.

SOLID principles and design patterns are like the secret sauce of Java coding. They help you write code that's modular, flexible, and easy to maintain. For instance, SOLID principles guide you on things like keeping each class focused on one job and making your code open to extension but closed to modification. 

Performance optimization and memory management - every Java developer's favorite topics. Best practices here involve things like being mindful of how many objects you create (because too many can slow things down), choosing the right data structures and algorithms for the job (because some are faster than others), and being smart about how you use memory (because nobody likes memory leaks).

Colin Shah

Colin Shah

As a lead Java developer with 8+ years of experience, I design and develop high-performance web applications using Java, Spring Boot, Hibernate, Microservices, RESTful APIs, AWS, and DevOps. I'm dedicated to sharing knowledge through blogs and tutorials.

You might also like

Get In Touch


Contact us for your software development requirements

Brilliant + Works

Hello, we are  BRILLIAN’S.Trying to make an effort to put the right people for you to get the best results. Just insight !

We are Hiring hiring-voice
FacebookYoutubeInstagramLinkedInBehanceDribbbleDribbble

Partnerships:

Recognized by:

location-icon503, Fortune Business Hub, Science City Road, Near Petrol Pump, Sola, Ahmedabad, Gujarat - 380060.

© 2024 Brilworks. All Rights Reserved.