Bersihkan Coding di Java

1. Ikhtisar

Dalam tutorial ini, kita akan membahas prinsip-prinsip pengkodean yang bersih. Kami juga akan memahami mengapa kode bersih itu penting dan bagaimana mencapainya di Java. Selanjutnya, kita akan melihat apakah ada alat yang tersedia untuk membantu kita.

2. Apa Itu Kode Bersih?

Jadi, sebelum kita masuk ke detail kode bersih, mari kita pahami apa yang kita maksud dengan kode bersih. Sejujurnya, tidak ada satu jawaban yang bagus untuk ini. Dalam pemrograman, beberapa masalah mencapai dan karenanya menghasilkan prinsip-prinsip umum. Tapi kemudian, setiap bahasa pemrograman dan paradigma menghadirkan nuansa mereka sendiri, yang mengamanatkan kita untuk mengadopsi praktik yang sesuai.

Secara umum, kode bersih dapat diringkas sebagai kode yang dapat dibaca dan diubah oleh pengembang mana pun dengan mudah . Meskipun ini mungkin terdengar seperti penyederhanaan konsep yang berlebihan, kita akan melihat nanti di tutorial bagaimana hal ini dibangun. Di mana pun kita mendengar tentang kode bersih, kita mungkin menemukan beberapa referensi ke Martin Fowler. Inilah cara dia menjelaskan kode bersih di salah satu tempat:

Setiap orang bodoh dapat menulis kode yang dapat dimengerti oleh komputer. Pemrogram yang baik menulis kode yang bisa dipahami manusia.

3. Mengapa Kita Harus Peduli dengan Kode Bersih?

Menulis kode yang bersih adalah masalah kebiasaan pribadi dan juga masalah keterampilan. Sebagai pengembang, kami tumbuh melalui pengalaman dan pengetahuan dari waktu ke waktu. Tapi, kita harus bertanya mengapa kita harus berinvestasi dalam mengembangkan kode bersih? Kami mengerti bahwa orang lain mungkin akan lebih mudah membaca kode kami, tetapi apakah itu cukup insentif? Ayo cari tahu!

Prinsip pengkodean yang bersih membantu kami mencapai banyak tujuan yang diinginkan terkait dengan perangkat lunak yang ingin kami buat. Mari kita bahas untuk memahaminya dengan lebih baik:

  • Basis Kode yang Dapat Dipelihara : Setiap perangkat lunak yang kami kembangkan memiliki masa pakai yang produktif dan selama periode ini akan memerlukan perubahan dan pemeliharaan umum. Kode bersih dapat membantu mengembangkan perangkat lunak yang mudah diubah dan dipelihara dari waktu ke waktu.
  • Pemecahan Masalah Lebih Mudah : Perangkat lunak dapat menunjukkan perilaku yang tidak diinginkan karena berbagai faktor internal atau eksternal. Ini mungkin sering membutuhkan perputaran cepat dalam hal perbaikan dan ketersediaan. Perangkat lunak yang dikembangkan dengan prinsip pengkodean yang bersih lebih mudah untuk memecahkan masalah .
  • Orientasi Lebih Cepat : Perangkat lunak selama masa pakainya akan membuat banyak pengembang membuat, memperbarui, dan memeliharanya, dengan pengembang bergabung di berbagai titik waktu. Ini membutuhkan orientasi yang lebih cepat untuk menjaga produktivitas tetap tinggi , dan kode yang bersih membantu mencapai tujuan ini.

4. Karakteristik Kode Bersih

Basis kode yang ditulis dengan prinsip pengkodean yang bersih menunjukkan beberapa karakteristik yang membedakannya. Mari kita bahas beberapa karakteristik berikut:

  • Terfokus : Sepotong kode harus ditulis untuk memecahkan masalah tertentu . Itu seharusnya tidak melakukan apa pun yang benar-benar tidak terkait dengan pemecahan masalah yang diberikan. Ini berlaku untuk semua level abstraksi dalam basis kode seperti metode, kelas, paket, atau modul.
  • Sederhana : Sejauh ini, ini adalah karakteristik kode bersih yang paling penting dan sering diabaikan. Desain dan implementasi perangkat lunak harus sesederhana mungkin , yang dapat membantu kami mencapai hasil yang diinginkan. Kompleksitas yang meningkat dalam basis kode membuatnya rentan terhadap kesalahan dan sulit untuk dibaca dan dipelihara.
  • Dapat diuji : Membersihkan kode, meskipun sederhana, harus menyelesaikan masalah yang dihadapi. Ini harus intuitif dan mudah untuk menguji basis kode, lebih disukai secara otomatis . Ini membantu menetapkan perilaku dasar basis kode dan membuatnya lebih mudah untuk mengubahnya tanpa merusak apa pun.

Inilah yang membantu kami mencapai tujuan yang dibahas di bagian sebelumnya. Sangat bermanfaat untuk mulai mengembangkan dengan karakteristik ini dibandingkan dengan refactor nanti. Ini mengarah pada total biaya kepemilikan yang lebih rendah untuk siklus hidup perangkat lunak.

5. Bersihkan Coding di Java

Sekarang kita telah melalui latar belakang yang cukup, mari kita lihat bagaimana kita dapat menggabungkan prinsip-prinsip pengkodean yang bersih di Java. Java menawarkan banyak praktik terbaik yang dapat membantu kita menulis kode yang bersih. Kami akan mengelompokkannya dalam kelompok yang berbeda dan memahami cara menulis kode bersih dengan contoh kode.

5.1. Struktur Proyek

Meskipun Java tidak memberlakukan struktur proyek apa pun, selalu berguna untuk mengikuti pola yang konsisten untuk mengatur file sumber, pengujian, konfigurasi, data, dan artefak kode lainnya . Maven, alat build populer untuk Java, menetapkan struktur proyek tertentu. Meskipun kami mungkin tidak menggunakan Maven, selalu menyenangkan untuk tetap berpegang pada konvensi.

Mari kita lihat beberapa folder yang disarankan Maven untuk kita buat:

  • src / main / java : Untuk file sumber
  • src / main / resources : Untuk file sumber daya, seperti properti
  • src / test / java : Untuk file sumber pengujian
  • src / test / resources : Untuk file sumber daya pengujian, seperti properti

Mirip dengan ini, ada struktur proyek populer lainnya seperti Bazel yang disarankan untuk Java, dan kita harus memilih satu tergantung pada kebutuhan dan audiens kita.

5.2. Konvensi penamaan

Mengikuti konvensi penamaan bisa sangat membantu dalam membuat kode kita dapat dibaca dan karenanya, dapat dipelihara . Rod Johnson, pencipta Spring, menekankan pentingnya konvensi penamaan di Spring:

“… Jika Anda tahu apa yang dilakukan sesuatu, Anda mendapat peluang bagus untuk menebak nama kelas Spring atau antarmuka untuk itu…”

Java mengatur seperangkat aturan yang harus dipatuhi ketika harus menamai apa pun di Java. Nama yang dibentuk dengan baik tidak hanya membantu dalam membaca kode, tetapi juga menyampaikan banyak hal tentang maksud kode tersebut. Mari kita ambil beberapa contoh:

  • Kelas : Kelas dalam pengertian konsep berorientasi objek adalah cetak biru untuk objek yang sering mewakili objek dunia nyata. Oleh karena itu, sangat berarti menggunakan kata benda untuk memberi nama kelas yang cukup menggambarkannya:
public class Customer { }
  • Variabel : Variabel di Java menangkap status objek yang dibuat dari sebuah kelas. Nama variabel harus mendeskripsikan maksud variabel dengan jelas:
public class Customer { private String customerName; }
  • Metode : Metode di Jawa selalu merupakan bagian dari kelas dan karenanya umumnya mewakili tindakan pada status objek yang dibuat dari kelas tersebut. Oleh karena itu berguna untuk memberi nama metode menggunakan kata kerja:
public class Customer { private String customerName; public String getCustomerName() { return this.customerName; } }

While we've only discussed how to name an identifier in Java, please note that there are additional best practices like camel casing, which we should observe for readability. There can be more conventions related to naming interfaces, enums, constants as well.

5.3. Source File Structure

A source file can contain different elements. While Java compiler enforces some structure, a large part is fluid. But adhering to a specific order in which to places elements in a source file can significantly improve code readability. There are multiple popular style-guides to take inspiration from, like one by Google and another by Spring.

Let's see how should a typical ordering of elements in a source file look:

  • Package statement
  • Import statements
    • All static imports
    • All non-static imports
  • Exactly one top-level class
    • Class variables
    • Instance variables
    • Constructors
    • Methods

Apart from the above, methods can be grouped based on their functionality or scope. There is no one good convention, and the idea should be decided once and then followed consistently.

Let's see a well-formed source file:

# /src/main/java/com/baeldung/application/entity/Customer.java package com.baeldung.application.entity; import java.util.Date; public class Customer { private String customerName; private Date joiningDate; public Customer(String customerName) { this.customerName = customerName; this.joiningDate = new Date(); } public String getCustomerName() { return this.customerName; } public Date getJoiningDate() { return this.joiningDate; } }

5.4. Whitespaces

We all know that it is easier to read and understand short paragraphs compared to a large block of text. It is not very different when it comes to reading code as well. Well-placed and consistent whitespaces and blank lines can enhance the readability of the code.

The idea here is to introduce logical groupings in the code which can help organize thought processes while trying to read it through. There is no one single rule to adopt here but a general set of guidelines and an inherent intention to keep readability at the center of it:

  • Two blank lines before starting static blocks, fields, constructors and inner classes
  • One blank line after a method signature that is multiline
  • A single space separating reserved keywords like if, for, catch from an open parentheses
  • A single space separating reserved keywords like else, catch from a closing parentheses

The list here is not exhaustive but should give us a bearing to head towards.

5.5. Indentation

Although quite trivial, almost any developer would vouch for the fact that a well-indented code is much easier to read and understand. There is no single convention for code indentation in Java. The key here is to either adopt a popular convention or define a private one and then follow it consistently across the organization.

Let's see some of the important indentation criteria:

  • A typical best practice is to use four spaces, a unit of indentation. Please note that some guidelines suggest a tab instead of spaces. While there is no absolute best practice here, the key remains consistency!
  • Normally, there should be a cap over the line length, but this can be set higher than traditional 80 owing to larger screens developers use today.
  • Lastly, since many expressions will not fit into a single line, we must break them consistently:
    • Break method calls after a comma
    • Break expressions before an operator
    • Indent wrapped lines for better readability (we here at Baeldung prefer two spaces)

Let's see an example:

List customerIds = customer.stream() .map(customer -> customer.getCustomerId()) .collect(Collectors.toCollection(ArrayList::new));

5.6. Method Parameters

Parameters are essential for methods to work as per specification. But, a long list of parameters can make it difficult for someone to read and understand the code. So, where should we draw the line? Let's understand the best practices which may help us:

  • Try to restrict the number of parameters a method accepts, three parameters can be one good choice
  • Consider refactoring the method if it needs more than recommended parameters, typically a long parameter list also indicate that the method may be doing multiple things
  • We may consider bundling parameters into custom-types but must be careful not to dump unrelated parameters into a single type
  • Finally, while we should use this suggestion to judge the readability of the code, we must not be pedantic about it

Let's see an example of this:

public boolean setCustomerAddress(String firstName, String lastName, String streetAddress, String city, String zipCode, String state, String country, String phoneNumber) { } // This can be refactored as below to increase readability public boolean setCustomerAddress(Address address) { }

5.7. Hardcoding

Hardcoding values in code can often lead to multiple side effects. For instance, it can lead to duplication, which makes change more difficult. It can often lead to undesirable behavior if the values need to be dynamic. In most of the cases, hardcoded values can be refactored in one of the following ways:

  • Consider replacing with constants or enums defined within Java
  • Or else, replace with constants defined at the class level or in a separate class file
  • If possible, replace with values which can be picked from configuration or environment

Let's see an example:

private int storeClosureDay = 7; // This can be refactored to use a constant from Java private int storeClosureDay = DayOfWeek.SUNDAY.getValue()

Again, there is no strict guideline around this to adhere to. But we must be cognizant about the fact the some will need to read and maintain this code later on. We should pick a convention that suits us and be consistent about it.

5.8. Code Comments

Code comments can be beneficial while reading code to understand the non-trivial aspects. At the same time, care should be taken to not include obvious things in the comments. This can bloat comments making it difficult to read the relevant parts.

Java allows two types of comments: Implementation comments and documentation comments. They have different purposes and different formats, as well. Let's understand them better:

  • Documentation/JavaDoc Comments
    • The audience here is the users of the codebase
    • The details here are typically implementation free, focusing more on the specification
    • Typically useful independent of the codebase
  • Implementation/Block Comments
    • The audience here is the developers working on the codebase
    • The details here are implementation-specific
    • Typically useful together with the codebase

So, how should we optimally use them so that they are useful and contextual?

  • Comments should only complement a code, if we are not able to understand the code without comments, perhaps we need to refactor it
  • We should use block comments rarely, possibly to describe non-trivial design decisions
  • We should use JavaDoc comments for most of our classes, interfaces, public and protected methods
  • All comments should be well-formed with a proper indentation for readability

Let's see an example of meaningful documentation comment:

/** * This method is intended to add a new address for the customer. * However do note that it only allows a single address per zip * code. Hence, this will override any previous address with the * same postal code. * * @param address an address to be added for an existing customer */ /* * This method makes use of the custom implementation of equals * method to avoid duplication of an address with same zip code. */ public addCustomerAddress(Address address) { }

5.9. Logging

Anyone who has ever laid their hands onto production code for debugging has yearned for more logs at some point in time. The importance of logs can not be over-emphasized in development in general and maintenance in particular.

There are lots of libraries and frameworks in Java for logging, including SLF4J, Logback. While they make logging pretty trivial in a codebase, care must be given to logging best practices. An otherwise done logging can prove to be a maintenance nightmare instead of any help. Let's go through some of these best practices:

  • Avoid excessive logging, think about what information might be of help in troubleshooting
  • Choose log levels wisely, we may want to enable log levels selectively on production
  • Be very clear and descriptive with contextual data in the log message
  • Use external tools for tracing, aggregation, filtering of log messages for faster analytics

Let's see an example of descriptive logging with right level:

logger.info(String.format("A new customer has been created with customer Id: %s", id));

6. Is That All of It?

While the previous section highlights several code formatting conventions, these are not the only ones we should know and care about. A readable and maintainable code can benefit from a large number of additional best practices that have been accumulated over time.

We may have encountered them as funny acronyms over time. They essentially capture the learnings as a single or a set of principles that can help us write better code. However, note that we should not follow all of them just because they exist. Most of the time, the benefit they provide is proportional to the size and complexity of the codebase. We must access our codebase before adopting any principle. More importantly, we must remain consistent with them.

6.1. SOLID

SOLID is a mnemonic acronym that draws from the five principles it sets forth for writing understandable and maintainable software:

  • Single Responsibility Principle: Every interface, class, or method we define should have a clearly defined goal. In essence, it should ideally do one thing and do that well. This effectively leads to smaller methods and classes which are also testable.
  • Open-Closed Principle: The code that we write should ideally be open for extension but closed for modification. What this effectively means is that a class should be written in a manner that there should not be any need to modify it. However, it should allow for changes through inheritance or composition.
  • Liskov Substitution Principle: What this principle states is that every subclass or derived class should be substitutable for their parent or base class. This helps in reducing coupling in the codebase and hence improve reusability across.
  • Interface Segregation Principle: Implementing an interface is a way to provide a specific behavior to our class. However, a class must not need to implement methods that it does not require. What this requires us to do is to define smaller, more focussed interfaces.
  • Dependency Inversion Principle: According to this principle, classes should only depend on abstractions and not on their concrete implementations. This effectively means that a class should not be responsible for creating instances for their dependencies. Rather, such dependencies should be injected into the class.

6.2. DRY & KISS

DRY stands for “Don's Repeat Yourself”. This principle states that a piece of code should not be repeated across the software. The rationale behind this principle is to reduce duplication and increase reusability. However, please note that we should be careful in adopting this rather too literally. Some duplication can actually improve code readability and maintainability.

KISS stands for “Keep It Simple, Stupid”. This principle states that we should try to keep the code as simple as possible. This makes it easy to understand and maintain over time. Following some of the principles mentioned earlier, if we keep our classes and methods focussed and small, this will lead to simpler code.

6.3. TDD

TDD stands for “Test Driven Development”. This is a programming practice that asks us to write any code only if an automated test is failing. Hence, we've to start with the design development of automated tests. In Java, there are several frameworks to write automated unit tests like JUnit and TestNG.

The benefits of such practice are tremendous. This leads to software that always works as expected. As we always start with tests, we incrementally add working code in small chunks. Also, we only add code if the new or any of the old tests fail. Which means that it leads to reusability as well.

7. Tools for Help

Writing clean code is not just a matter of principles and practices, but it's a personal habit. We tend to grow as better developers as we learn and adapt. However, to maintain consistency across a large team, we've also to practice some enforcement. Code reviews have always been a great tool to maintain consistency and help the developers grow through constructive feedback.

However, we do not necessarily have to validate all these principles and best practices manually during code reviews. Freddy Guime from Java OffHeap talks about the value of automating some of the quality checks to end-up with a certain threshold with the code quality all the time.

There are several tools available in the Java ecosystem, which take at least some of these responsibilities away from code reviewers. Let's see what some of these tools are:

  • Code Formatters: Most of the popular Java code editors, including Eclipse and IntelliJ, allows for automatic code formatting. We can use the default formatting rules, customize them, or replace them with custom formatting rules. This takes care of a lot of structural code conventions.
  • Static Analysis Tools: There are several static code analysis tools for Java, including SonarQube, Checkstyle, PMD and SpotBugs. They have a rich set of rules which can be used as-is or customized for a specific project. They are great in detecting a lot of code smells like violations of naming conventions and resource leakage.

8. Conclusion

In this tutorial, we've gone through the importance of clean coding principles and characteristics that clean code exhibits. We saw how to adopt some of these principles in practice, which developing in Java. We also discussed other best practices that help to keep the code readable and maintainable over time. Finally, we discussed some of the tools available to help us in this endeavor.

Singkatnya, penting untuk dicatat bahwa semua prinsip dan praktik ini ada untuk membuat kode kita lebih bersih. Ini adalah istilah yang lebih subyektif dan karenanya, harus dievaluasi secara kontekstual.

Meskipun ada banyak perangkat aturan yang tersedia untuk diterapkan, kita harus sadar akan kedewasaan, budaya, dan persyaratan kita. Kami mungkin harus menyesuaikan atau dalam hal ini, merancang seperangkat aturan baru sama sekali. Namun, apa pun masalahnya, penting untuk tetap konsisten di seluruh organisasi untuk menuai manfaat.