Saubere Codierung in Java

1. Übersicht

In diesem Tutorial werden wir uns mit sauberen Codierungsprinzipien befassen. Wir werden auch verstehen, warum sauberer Code wichtig ist und wie dies in Java erreicht werden kann. Außerdem werden wir sehen, ob Tools verfügbar sind, die uns helfen.

2. Was ist sauberer Code?

Bevor wir uns also mit den Details von sauberem Code befassen, wollen wir verstehen, was wir unter sauberem Code verstehen. Ehrlich gesagt kann es keine gute Antwort darauf geben. Bei der Programmierung greifen einige Bedenken auf und führen daher zu allgemeinen Grundsätzen. Aber dann präsentiert jede Programmiersprache und jedes Paradigma ihre eigenen Nuancen, die uns dazu verpflichten, angemessene Praktiken anzuwenden.

Im Allgemeinen kann sauberer Code als Code zusammengefasst werden, den jeder Entwickler leicht lesen und ändern kann . Dies mag nach einer zu starken Vereinfachung des Konzepts klingen, aber wir werden später im Tutorial sehen, wie sich dies aufbaut. Überall, wo wir von sauberem Code hören, stoßen wir vielleicht auf einen Hinweis auf Martin Fowler. So beschreibt er sauberen Code an einer der Stellen:

Jeder Dummkopf kann Code schreiben, den ein Computer verstehen kann. Gute Programmierer schreiben Code, den Menschen verstehen können.

3. Warum sollten wir uns für sauberen Code interessieren?

Das Schreiben von sauberem Code ist sowohl eine Frage der persönlichen Gewohnheit als auch der Geschicklichkeit. Als Entwickler wachsen wir im Laufe der Zeit durch Erfahrung und Wissen. Aber wir müssen uns fragen, warum wir doch in die Entwicklung von sauberem Code investieren sollten. Wir stellen fest, dass andere es wahrscheinlich leichter finden werden, unseren Code zu lesen, aber ist dieser Anreiz genug? Lass es uns herausfinden!

Saubere Codierungsprinzipien helfen uns, viele wünschenswerte Ziele in Bezug auf die Software zu erreichen, die wir produzieren möchten. Lassen Sie uns sie durchgehen, um es besser zu verstehen:

  • Wartbare Codebasis : Jede Software, die wir entwickeln, hat eine produktive Lebensdauer und erfordert in diesem Zeitraum Änderungen und allgemeine Wartung. Sauberer Code kann bei der Entwicklung von Software helfen, die im Laufe der Zeit leicht geändert und gewartet werden kann.
  • Einfachere Fehlerbehebung : Software kann aufgrund einer Vielzahl interner oder externer Faktoren ein unbeabsichtigtes Verhalten aufweisen. In Bezug auf Korrekturen und Verfügbarkeit kann häufig eine schnelle Abwicklung erforderlich sein. Software, die mit sauberen Codierungsprinzipien entwickelt wurde, lässt sich leichter bei Problemen beheben .
  • Schnelleres Onboarding : Software wird während ihrer Lebensdauer von vielen Entwicklern erstellt, aktualisiert und gewartet, wobei Entwickler zu unterschiedlichen Zeitpunkten beitreten. Dies erfordert ein schnelleres Onboarding, um die Produktivität hoch zu halten , und sauberer Code hilft, dieses Ziel zu erreichen.

4. Eigenschaften von Clean Code

Codebasen, die mit sauberen Codierungsprinzipien geschrieben wurden, weisen mehrere Merkmale auf, die sie auszeichnen. Lassen Sie uns einige dieser Eigenschaften durchgehen:

  • Fokussiert : Ein Teil des Codes sollte geschrieben werden, um ein bestimmtes Problem zu lösen . Es sollte nichts tun, was nicht unbedingt mit der Lösung des gegebenen Problems zusammenhängt. Dies gilt für alle Abstraktionsebenen in der Codebasis wie Methode, Klasse, Paket oder Modul.
  • Einfach : Dies ist bei weitem das wichtigste und häufig ignorierte Merkmal von sauberem Code. Das Software- Design und die Implementierung müssen so einfach wie möglich sein , damit wir die gewünschten Ergebnisse erzielen können. Die zunehmende Komplexität einer Codebasis macht sie fehleranfällig und schwer zu lesen und zu warten.
  • Testbar : Sauberer Code ist zwar einfach, muss aber das vorliegende Problem lösen. Es muss intuitiv und einfach sein, die Codebasis zu testen, vorzugsweise auf automatisierte Weise . Dies hilft beim Festlegen des Basisverhaltens der Codebasis und erleichtert das Ändern, ohne dass etwas beschädigt wird.

Dies hilft uns, die im vorherigen Abschnitt erörterten Ziele zu erreichen. Es ist vorteilhaft, mit der Entwicklung dieser Eigenschaften im Vergleich zum Refactor später zu beginnen. Dies führt zu niedrigeren Gesamtbetriebskosten für den Software-Lebenszyklus.

5. Bereinigen Sie die Codierung in Java

Nachdem wir genügend Hintergrundinformationen erhalten haben, wollen wir sehen, wie wir saubere Codierungsprinzipien in Java integrieren können. Java bietet viele bewährte Methoden, mit denen wir sauberen Code schreiben können. Wir werden sie in verschiedene Buckets einteilen und verstehen, wie man sauberen Code mit Codebeispielen schreibt.

5.1. Projektstruktur

Während Java keine Projektstruktur erzwingt, ist es immer nützlich, einem konsistenten Muster zu folgen, um unsere Quelldateien, Tests, Konfigurationen, Daten und andere Code-Artefakte zu organisieren . Maven, ein beliebtes Build-Tool für Java, schreibt eine bestimmte Projektstruktur vor. Obwohl wir Maven möglicherweise nicht verwenden, ist es immer schön, sich an eine Konvention zu halten.

Sehen wir uns einige der Ordner an, die Maven vorschlägt:

  • src / main / java : Für Quelldateien
  • src / main / resources : Für Ressourcendateien wie Eigenschaften
  • src / test / java : Für Testquelldateien
  • src / test / resources : Für Testressourcendateien wie Eigenschaften

Ähnlich gibt es andere beliebte Projektstrukturen wie Bazel, die für Java vorgeschlagen wurden, und wir sollten je nach unseren Bedürfnissen und unserer Zielgruppe eine auswählen.

5.2. Namenskonvention

Das Befolgen von Namenskonventionen kann einen großen Beitrag dazu leisten, unseren Code lesbar und damit wartbar zu machen . Rod Johnson, der Schöpfer von Spring, betont, wie wichtig es ist, Konventionen im Frühling zu benennen:

"... wenn du weißt, was etwas tut, hast du eine ziemlich gute Chance, den Namen der Spring-Klasse oder der Schnittstelle dafür zu erraten ..."

Java schreibt eine Reihe von Regeln vor, die eingehalten werden müssen, wenn etwas in Java benannt werden soll. Ein wohlgeformter Name hilft nicht nur beim Lesen des Codes, sondern vermittelt auch viel über die Absicht des Codes. Nehmen wir einige Beispiele:

  • Klassen : Klasse in Bezug auf objektorientierte Konzepte ist eine Blaupause für Objekte, die häufig reale Objekte darstellen. Daher ist es sinnvoll, Substantive zu verwenden, um Klassen zu benennen, die sie ausreichend beschreiben:
public class Customer { }
  • Variablen : Variablen in Java erfassen den Status des aus einer Klasse erstellten Objekts. Der Name der Variablen sollte die Absicht der Variablen klar beschreiben:
public class Customer { private String customerName; }
  • Methoden : Methoden in Java sind immer Teil von Klassen und stellen daher im Allgemeinen eine Aktion für den Status des aus der Klasse erstellten Objekts dar. Es ist daher nützlich, Methoden mit Verben zu benennen:
public class Customer { private String customerName; public String getCustomerName() { return this.customerName; } }

Während wir nur besprochen haben, wie man einen Bezeichner in Java benennt, beachten Sie bitte, dass es zusätzliche Best Practices wie Kamelhüllen gibt, die wir zur besseren Lesbarkeit beachten sollten. Es kann weitere Konventionen geben, die sich auch auf die Benennung von Schnittstellen, Aufzählungen und Konstanten beziehen.

5.3. Struktur der Quelldatei

Eine Quelldatei kann verschiedene Elemente enthalten. Während der Java- Compiler eine gewisse Struktur erzwingt, ist ein großer Teil fließend . Die Einhaltung einer bestimmten Reihenfolge zum Platzieren von Elementen in einer Quelldatei kann jedoch die Lesbarkeit des Codes erheblich verbessern. Es gibt mehrere beliebte Style-Guides, von denen Sie sich inspirieren lassen können, beispielsweise einen von Google und einen von Spring.

Mal sehen, wie eine typische Reihenfolge von Elementen in einer Quelldatei aussehen sollte:

  • Paketanweisung
  • Anweisungen importieren
    • Alle statischen Importe
    • Alle nicht statischen Importe
  • Genau eine Klasse der Spitzenklasse
    • Klassenvariablen
    • Instanzvariablen
    • Konstruktoren
    • Methoden

Abgesehen davon können Methoden basierend auf ihrer Funktionalität oder ihrem Umfang gruppiert werden . Es gibt keine gute Konvention, und die Idee sollte einmal entschieden und dann konsequent befolgt werden.

Sehen wir uns eine wohlgeformte Quelldatei an:

# /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. Leerzeichen

Wir alle wissen, dass es im Vergleich zu einem großen Textblock einfacher ist, kurze Absätze zu lesen und zu verstehen. Auch beim Lesen von Code ist das nicht sehr unterschiedlich. Gut platzierte und konsistente Leerzeichen und Leerzeilen können die Lesbarkeit des Codes verbessern.

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.

Zusammenfassend ist festzuhalten, dass all diese Prinzipien und Praktiken dazu dienen, unseren Code sauberer zu machen. Dies ist ein subjektiverer Begriff und muss daher kontextuell bewertet werden.

Obwohl zahlreiche Regeln zur Verfügung stehen, müssen wir uns unserer Reife, Kultur und Anforderungen bewusst sein. Möglicherweise müssen wir neue Regeln anpassen oder entwickeln. Was auch immer der Fall sein mag, es ist wichtig, im gesamten Unternehmen konsistent zu bleiben, um die Vorteile zu nutzen.