1. Overview
1.概述
In this tutorial, we’ll go through clean coding principles. We’ll also understand why clean code is important and how to achieve that in Java. Further, we’ll see if there are any tools available to help us out.
在本教程中,我们将了解清洁编码原则。我们还将了解为什么干净的代码很重要,以及如何在Java中实现这一目标。此外,我们将看看是否有任何工具可以帮助我们。
2. What Is Clean Code?
2.什么是清洁代码?
So, before we jump into the details of clean code, let’s understand what do we mean by clean code. Honestly, there can not be one good answer to this. In programming, some concerns reach across and hence result in general principles. But then, every programming language and paradigm present their own set of nuances, which mandates us to adopt befitting practices.
所以,在我们进入清洁代码的细节之前,让我们了解一下清洁代码是什么意思。老实说,这不可能有一个好的答案。在编程中,一些关注点会贯穿始终,从而形成一般原则。但是,每一种编程语言和范式都有自己的细微差别,这就要求我们采取适当的做法。
Broadly, clean code can be summarized as a code that any developer can read and change easily. While this may sound like an oversimplification of the concept, we’ll see later in the tutorial how this builds up. Anywhere we hear about clean code, we perhaps come across some reference to Martin Fowler. Here is how he describes clean code in one of the places:
广义上讲,干净的代码可以被概括为任何开发人员都能轻松阅读和修改的代码。虽然这听起来像是对这个概念的过度简化,但我们将在本教程的后面看到这一点是如何建立起来的。在我们听到清洁代码的任何地方,我们也许都会遇到一些对Martin Fowler的参考。以下是他在其中一个地方对清洁代码的描述。
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
任何傻瓜都能写出计算机能理解的代码。好的程序员写的代码是人类可以理解的。
3. Why Should We Care About Clean Code?
3.我们为什么要关注清洁代码?
Writing clean code is a matter of personal habit as much as it’s a matter of skill. As a developer, we grow through experience and knowledge over time. But, we must ask why we should invest in developing clean code after all? We get that others will probably find it easier to read our code, but is that incentive enough? Let’s find out!
编写干净的代码是一个个人习惯的问题,也是一个技能的问题。作为一个开发者,我们通过经验和知识随着时间的推移而成长。但是,我们必须问,为什么我们要投资开发干净的代码?我们明白,别人可能会觉得阅读我们的代码更容易,但这一激励措施是否足够?让我们来了解一下
Clean coding principles help us achieve a lot of desirable goals related to the software we intend to produce. Let’s go through them to understand it better:
清洁编码原则帮助我们实现了很多与我们打算制作的软件有关的理想目标。让我们通过它们来更好地了解它。
- Maintainable Codebase: Any software that we develop has a productive life and during this period will require changes and general maintenance. Clean code can help develop software that is easy to change and maintain over time.
- Easier Troubleshooting: Software can exhibit unintended behavior due to a variety of internal or external factors. It may often require a quick turnaround in terms of fixes and availability. Software developed with clean coding principles is easier to troubleshoot for problems.
- Faster Onboarding: Software during its lifetime will see many developers create, update, and maintain it, with developers joining at different points in time. This requires a quicker onboarding to keep productivity high, and clean code helps achieve this goal.
4. Characteristics of Clean Code
4.清洁代码的特点
Codebases written with clean coding principles exhibit several characteristics that set them apart. Let’s go through some of these characteristics:
用干净的编码原则编写的代码库表现出几个特点,使它们与众不同。让我们来看看其中的一些特点。
- Focused: A piece of code should be written to solve a specific problem. It should not do anything strictly not related to solving the given problem. This applies to all levels of abstraction in the codebase like method, class, package, or module.
- Simple: This is by far the most important and often ignored characteristic of clean code. The software design and implementation must be as simple as possible, which can help us achieve the desired outcomes. Increasing complexity in a codebase makes them error-prone and difficult to read and maintain.
- Testable: Clean code, while being simple, must solve the problem at hand. It must be intuitive and easy to test the codebase, preferably in an automated manner. This helps establish the baseline behavior of the codebase and makes it easier to change it without breaking anything.
These are what help us achieve the goals discussed in the previous section. It’s beneficial to start developing with these characteristics in mind compared to refactor later. This leads to a lower total cost of ownership for the software lifecycle.
这些都是帮助我们实现上一节讨论的目标。与后来的重构相比,开始开发时考虑到这些特性是有益的。这将导致软件生命周期的总拥有成本降低。
5. Clean Coding in Java
5.在Java中进行清洁编码
Now that we’ve gone through enough background, let’s see how we can incorporate clean coding principles in Java. Java offers a lot of best practices that can help us write clean code. We’ll categorize them in different buckets and understand how to write clean code with code samples.
现在我们已经了解了足够的背景,让我们看看如何在Java中加入清洁编码原则。Java提供了很多最佳实践,可以帮助我们编写干净的代码。我们将把它们归为不同的类别,并通过代码样本了解如何编写干净的代码。
5.1. Project Structure
5.1.项目结构
While Java doesn’t enforce any project structure, it’s always useful to follow a consistent pattern to organize our source files, tests, configurations, data, and other code artifacts. Maven, a popular build tool for Java, prescribes a particular project structure. While we may not use Maven, it’s always nice to stick to a convention.
虽然Java没有强制执行任何项目结构,但遵循一致的模式来组织我们的源文件、测试、配置、数据和其他代码工件总是有用的。Maven是一种流行的Java构建工具,规定了一种特定的项目结构。虽然我们可能不使用Maven,但坚持一个惯例总是好的。
Let’s see some of the folders that Maven suggests we create:
让我们看看Maven建议我们创建的一些文件夹。
- src/main/java: For source files
- src/main/resources: For resource files, like properties
- src/test/java: For test source files
- src/test/resources: For test resource files, like properties
Similar to this, there are other popular project structures like Bazel suggested for Java, and we should choose one depending on our needs and audience.
与此类似,还有其他流行的项目结构,如Bazel建议用于Java,我们应该根据我们的需求和受众选择一个。
5.2. Naming Convention
5.2.命名公约
Following naming conventions can go a long way in making our code readable and hence, maintainable. Rod Johnson, the creator of Spring, emphasizes the importance of naming conventions in Spring:
遵循命名约定可以在很大程度上使我们的代码具有可读性,从而具有可维护性。Spring的创建者Rod Johnson强调了命名约定在Spring中的重要性。
“… if you know what something does, you got a pretty good chance guessing the name of the Spring class or interface for it …”
“……如果你知道某个东西是做什么的,你就有很大机会猜到它的Spring类或接口的名字……”
Java prescribes a set of rules to adhere to when it comes to naming anything in Java. A well-formed name does not only help in reading the code, but it also conveys a lot about the intention of the code. Let’s take some examples:
Java 规定了一套规则,当涉及到在Java中命名任何东西时,必须遵守这些规则。一个格式良好的名称不仅有助于阅读代码,而且还能传达很多关于代码意图的信息。让我们举几个例子。
- Classes: Class in terms of object-oriented concepts is a blueprint for objects which often represent real-world objects. Hence it’s meaningful to use nouns to name classes describing them sufficiently:
public class Customer {
}
- Variables: Variables in Java capture the state of the object created from a class. The name of the variable should describe the intent of the variable clearly:
public class Customer {
private String customerName;
}
- Methods: Methods in Java are always part of classes and hence generally represent an action on the state of the object created from the class. It’s hence useful to name methods using verbs:
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.
虽然我们只讨论了如何在Java中命名一个标识符,但请注意,还有一些额外的最佳实践,比如骆驼的大小写,为了便于阅读,我们应该遵守。在命名接口、枚举、常量方面,也可以有更多的约定。
5.3. Source File Structure
5.3.源文件结构
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.
一个源文件可以包含不同的元素。虽然Java编译器强制执行一些结构,但很大一部分是流动的。但是坚持按照特定的顺序在源文件中放置元素,可以大大改善代码的可读性。有多种流行的风格指南可供借鉴,如Google的指南和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
5.4.白色空间
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
5.5.缩进
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.
虽然很微不足道,但几乎所有的开发者都会保证,缩进良好的代码会更容易阅读和理解。在Java中没有单一的代码缩进惯例。这里的关键是,要么采用一个流行的惯例,要么定义一个私有的惯例,然后在整个组织内一致地遵循它。
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<String> customerIds = customer.stream()
.map(customer -> customer.getCustomerId())
.collect(Collectors.toCollection(ArrayList::new));
5.6. Method Parameters
5.6 方法参数
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
5.7.硬编码
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
5.8.代码注释
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:
Java允许两种类型的注释。实现注释和文档注释。它们有不同的目的,也有不同的格式。让我们更好地理解它们。
- 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
5.9.伐木
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:
在Java中有很多用于记录的库和框架,包括SLF4J、Logback。虽然它们使代码库中的日志记录变得非常简单,但必须注意日志记录的最佳实践。否则,日志会成为维护的噩梦,而不是任何帮助。让我们来看看其中的一些最佳实践。
- 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?
6.这就是全部了吗?
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
6.1 固体
SOLID is a mnemonic acronym that draws from the five principles it sets forth for writing understandable and maintainable software:
SOLID是一个记忆性的缩写,它来自于它提出的五项原则,用于编写可理解和可维护的软件。
- 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
6.2. 干燥和亲吻
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.
DRY是 “不要重复自己 “的意思。这一原则指出,一段代码不应该在整个软件中重复出现。这一原则背后的原理是为了减少重复,增加可重用性。然而,请注意,我们应该小心翼翼地采用这个原则,而不是从字面上看。一些重复的代码实际上可以提高代码的可读性和可维护性。
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.
KISS是 “保持简单,愚蠢 “的意思。这一原则指出,我们应该尽量保持代码的简单。这使得它易于理解和长期维护。按照前面提到的一些原则,如果我们保持我们的类和方法的重点和小,这将导致更简单的代码。
6.3. TDD
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.
TDD是 “测试驱动开发 “的缩写。这是一种编程实践,要求我们只有在自动化测试失败的情况下才写任何代码。因此,我们要从自动测试的设计开发开始。在Java中,有几个框架可以编写自动化单元测试,如JUnit和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
7.帮助的工具
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.
然而,我们不一定要在代码审查期间手动验证所有这些原则和最佳实践。Freddy Guime来自Java OffHeap谈到了将一些质量检查自动化的价值,以便最终与代码质量一直保持一定的界限。
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:
在Java生态系统中,有几种工具,它们至少可以从代码审查员那里分担一些责任。让我们来看看其中的一些工具是什么。
- 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
8.结语
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.
在本教程中,我们已经了解了清洁编码原则的重要性和清洁代码所表现出的特征。我们看到了如何在实践中采用其中的一些原则,即用Java开发。我们还讨论了其他有助于保持代码可读性和可长期维护的最佳实践。最后,我们讨论了一些可用的工具来帮助我们完成这项工作。
To sum up, it’s important to note that all of these principles and practices are there to make our code cleaner. This is a more subjective term and hence, must be evaluated contextually.
总而言之,重要的是要注意,所有这些原则和实践都是为了使我们的代码更干净。这是一个比较主观的术语,因此,必须根据具体情况进行评估。
While there are numerous sets of rules available to adopt, we must be conscious of our maturity, culture, and requirements. We may have to customize or for that matter, devise a new set of rules altogether. But, whatever may be the case, it’s important to remain consistent across the organization to reap the benefits.
虽然有许多套规则可供采用,但我们必须意识到我们的成熟度、文化和要求。我们可能不得不进行定制,或者说,完全设计一套新的规则。但是,无论情况如何,重要的是在整个组织内保持一致,以获得好处。