DEV Community

CoderGears Team
CoderGears Team

Posted on

10 Best practices to design and implement a Java class

To let your code easy to understand and maintain, it's better to enforce its code quality by following best practices.
He is some best practices to design and implement a java class:

1- Modularize your code using Package-by-feature approach

Package-by-feature uses packages to reflect the feature set. It places all items related to a single feature (and only that feature) into a single namespace. This results in namespaces with high cohesion and high modularity, and with minimal coupling between namespaces. Items that work closely together are placed next to each other.

2- Abstraction

Data abstraction is one of the most essential and important features of object-oriented programming in Java. Abstraction means displaying only essential information and hiding the details. Data abstraction refers to providing only essential information about the data to the outside world, hiding the background details or implementation.
Even if this best practice is recommended by many books, web resources, conference speakers, experts. However, in many projects, this rule is ignored and we can have many class details not hidden.

3- Small classes are better

Types with many lines of code should be split into a group of smaller types.
To refactor a big Class you'll need patience, and you might even need to recreate everything from scratch. Here is some refactoring advice:
• The logic in the BigClass must be split into smaller classes. These smaller classes can eventually become private classes nested in the original God Class, whose instances objects become composed of instances of smaller nested classes.
• Smaller class partitioning should be driven by the multiple responsibilities handled by the Big Class. To identify these responsibilities it often helps to look for subsets of methods strongly coupled with subsets of fields.
• If the Big Class contains way more logic than states, a good option can be to define one or several static classes that contains no static field but only pure static methods. A pure static method is a function that computes a result only from inputs parameters, it doesn't read nor assign any static or instance field. The main advantage of pure static methods is that they are easily testable.
• Try to maintain the interface of the Big Class at first and delegate calls to the new extracted classes. In the end, the Big Class should be a pure facade without its own logic. Then you can keep it for convenience or throw it away and start to use the new classes only.
• Unit Tests can help: write tests for each method before extracting it to ensure you don't break functionality.

4-Few methods per class as you can

Types with more than 20 methods might be hard to understand and maintain.
Having many methods for a type might be a symptom of too many responsibilities implemented.
Maybe you are facing a class that controls way too many other classes in the system and has grown beyond all logic to become The Class That Does Everything.

5- Enforce Low coupling

Low coupling is desirable because a change in one area of an application will require fewer changes throughout the entire application. In the long run, this could alleviate a lot of time, effort, and cost associated with modifying and adding new features to an application.
Low coupling could be achieved by using abstract classes or using generic types and methods.

6- Enforce High cohesion

The single responsibility principle states that a class should not have more than one reason to change. Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class. There are several LCOM metrics. The LCOM takes its values in the range [0–1]. The LCOM HS (HS stands for Henderson-Sellers) takes its values in the range [0–2]. A LCOM HS value highest than 1 should be considered alarming. Here are to compute LCOM metrics:
LCOM = 1 - (sum(MF)/M*F)
LCOM HS = (M - sum(MF)/F)(M-1)
Where:
M is the number of methods in class (both static and instance methods are counted, it includes also constructors, properties getters/setters, events add/remove methods).
F is the number of instance fields in the class.
MF is the number of methods of the class accessing a particular instance field.
Sum(MF) is the sum of MF over all instance fields of the class.

The underlying idea behind these formulas can be stated as follow: a class is utterly cohesive if all its methods use all its methods use all its instance fields, which means that sum(MF)=M*F and then LCOM = 0 and LCOMHS = 0.
LCOMHS value higher than 1 should be considered alarming.

7- Comment only what the code cannot say

Comments that parrot the code offer nothing extra to the reader. A prevalence of noisy comments and incorrect comments in a codebase encourages programmers to ignore all comments, either by skipping past them or by taking active measures to hide them.

8- Don't repeat yourself as possible

It's known that the presence of duplicate code has negative impacts on software development and maintenance. Indeed a major drawback is when an instance of duplicate code is changed for fixing bugs or adding new features, its correspondents have to be changed simultaneously.
The most popular reason for duplicate code is the Copy/Paste operations, and in this case, the source code is exactly similar in two or more places, this practice is discouraged in many articles, books, and websites. However, sometimes it's not easy to practice the recommendations, and the developer chose the easy solution: the Copy/Paste method.
Using the appropriate tool makes easy the detection of the duplicate code from the copy/paste operations, however, there are some cases where cloned code is not trivial to detect.

9-Immutability is your friend for multithreading programming.

Basically, an object is immutable if its state doesn't change once the object has been created. Consequently, a class is immutable if its instances are immutable.
There is one important argument in favor of using immutable objects: It dramatically simplifies concurrent programming. Think about it, why does writing proper multithreaded programming is a hard task? Because it is hard to synchronize threads access to resources (objects or others OS resources). Why it is hard to synchronize these accesses? Because it is hard to guarantee that there won't be race conditions between the multiple write accesses and read accesses done by multiple threads on multiple objects. What if there are no more write accesses? In other words, what if the state of the objects accessed by threads, doesn't change? There is no more need for synchronization!
Another benefit about immutable classes is that they can never violate LSP (Liskov Subtitution Principle) , here's a definition of LSP quoted from its wiki page:

Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).

10-Use standard libraries as you can

In the Java world, we can find for each technical need hundreds of libraries for a specific need. However, it's better to take your time and check if a need it's not already present in the Java standard library, high coupling with many external libraries might increase quickly the complexity and the support of a project.

How to enforce the check of these best practices?

JArchitect provides a code query language named CQLinq to query the code base like a database. Developers, designers, and architects could define their custom queries to find easily the bug-prone situations.
With CQlinq we can combine the data from the code metrics, dependencies, API usage, and other model data to define very advanced queries that match some bug-prone situations.

Writing CQLinq queries and constraints is straightforward because JArchitect provides a CQLinq editor which supports:

Code completion / IntelliSense,
Live compile error description,
Integrated tooltip documentation.

cqlinq

Also, once the query is compiled, it gets executed immediately and its result is well displayed and browsable:
cqlinq

Powerful and elaborated queries and rules can be written with CQLinq, like the following one for example:

cqlinq

Short CQLinq queries can be written (or even generated) to get some immediate answers to questions about a codebase:

Is the code layered correctly?
cqlinq

Which methods have been refactored since the last release?
cqlinq
Which classes inherit from a particular class?
cqlinq
What are the 10 most complex methods?
cqlinq
Which methods assign a particular field?
cqlinq
Which complex method is not enough commented?
cqlinq

I don't want that my User Interface layer to depend directly on the DataBase layer:
cqlinq

Top comments (0)