翻译于 2015/09/22 21:30
10 人 顶 此译文
Java is one of the most popular programming languages around, but no one seems to enjoy using it. Well, Java is actually an alright programming language, and since Java 8 came out recently, I decided to compile a list of libraries, practices, and tools to make using Java better.
This article is on Github. Feel free to contribute and add your own Java tips and best practices.
Style
Javadoc
The Builder Pattern
Structs
Dependency injection
Avoid Nulls
Immutable-by-default
Avoid lots of Util classes
Formatting
Streams
Deploying
Dependency Convergence
Frameworks
Maven
Continuous Integration
Maven repository
Configuration management
Libraries
jUnit 4
jMock
AssertJ
Apache Commons
Guava
Gson
Java Tuples
Joda-Time
Lombok
Play framework
SLF4J
jOOQ
Missing Features
Testing
Tools
Chronon
IntelliJ IDEA
JRebel
The Checker Framework
Eclipse Memory Analyzer
Resources
Books
Podcasts
Java 是在世界各地最流行的编程语言之一, 但是看起来没人喜欢使用它。而 Java 事实上还算是一门不错的语言,随着 Java 8 最近的问世,我决定编制一个库,实践和工具的清单,汇集 Java 的一些最佳实践。
本文被放到了 Github 上。你可以随意地提交贡献,并加入自己的有关 Java 方面的建议和最佳实践。
风格
Javadoc
构建器模式
结构
依赖注入
避免空值
默认不可变更
避免大量的工具类
格式化
流
发布
依赖收敛
框架
Maven
持续集成
Maven 资源库
配置管理
库
jUnit 4
jMock
AssertJ
Apache Commons
Guava
Gson
Java Tuples
Joda-Time
Lombok
Play framework
SLF4J
jOOQ
Missing Features
Testing
工具
Chronon
IntelliJ IDEA
JRebel
Checker 框架
Eclipse 内存分析器
资源
书籍
播客
Traditionally, Java was programmed in a very verbose enterprise JavaBean style. The new style is much cleaner, more correct, and easier on the eyes.
One of the simplest things we as programmers do is pass around data. The traditional way to do this is to define a JavaBean:
public class DataHolder { private String data; public DataHolder() { } public void setData(String data) { this.data = data; } public String getData() { return this.data; }}
This is verbose and wasteful. Even if your IDE automatically generated this code, it's a waste. So, don't do this.
Instead, I prefer the C struct style of writing classes that merely hold data:
public class DataHolder { public final String data; public DataHolder(String data) { this.data = data; }}
This is a reduction in number of lines of code by a half. Further, this class is immutable unless you extend it, so we can reason about it easier as we know that it can't be changed.
If you're storing objects like Map or List that can be modified easily, you should instead use ImmutableMap or ImmutableList, which is discussed in the section about immutability.
通常,我们会以一种非常详细繁杂的企业级 JavaBean 的风格进行 Java 代码的编写。新的风格则更加清晰,正确,且看上去也更加的简单。
作为程序员的我们要做的最简单的事情之一,就是传递数据。一般的方式就是定义一个 JavaBean:
public class DataHolder { private String data; public DataHolder() { } public void setData(String data) { this.data = data; } public String getData() { return this.data; }}
这有点麻烦,并且也有点浪费。尽管你的 IDE 也能自动的生成这样的代码,但那也是种浪费。所以,别这么做。
相反,我更愿意选择编写类 C 的结构体风格的类,类里面只容纳数据:
public class DataHolder { public final String data; public DataHolder(String data) { this.data = data; }}
这样就在代码行数上减少了一半。此外,这个类是不能被修改的,除非你对它进行了扩展,因此我们可以更加容易的理解它,因为我们明白它不可以被修改。
如果你要保存像 Map 或者 List 这样容易被修改的对象,就应该使用 ImmutableMap 和 ImmutableList,这一点会在不可变性质的那一节被讲到。
If you have a rather complicated object that you want to build a struct for, consider the Builder pattern.
You make a subclass in your object which will construct your object. It uses mutable state, but as soon as you call build, it will emit an immutable object.
Imagine we had a more complicated DataHolder. The builder for it might look like:
public class ComplicatedDataHolder { public final String data; public final int num; // lots more fields and a constructor public static class Builder { private String data; private int num; public Builder data(String data) { this.data = data; return this; } public Builder num(int num) { this.num = num; return this; } public ComplicatedDataHolder build() { return new ComplicatedDataHolder(data, num); // etc } }}
Then to use it:
final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder() .data("set this") .num(523) .build();
There are better examples of Builders elsewhere but this should give you a taste for what it's like. This ends up with a lot of the boilerplate we were trying to avoid, but it gets you immutable objects and a very fluent interface.
如果你有一个相当复杂的对象想要去为其构建一个结构,可以考虑使用 Builder 模式。
你可以在对象中创建一个能帮助你构建出这个对象的子类。它使用了可变语句,但是一旦你调用了build,它就会提供给你一个不可变的对象。
想象一下我们要有一个更加复杂的 DataHolder。针对它的构建器看起来可能像是下面这样:
public class ComplicatedDataHolder { public final String data; public final int num; // lots more fields and a constructor public static class Builder { private String data; private int num; public Builder data(String data) { this.data = data; return this; } public Builder num(int num) { this.num = num; return this; } public ComplicatedDataHolder build() { return new ComplicatedDataHolder(data, num); // etc } }}
然后这样去使用它:
final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder() .data("set this") .num(523) .build();
还有其它关于构建器的更好的例子 ,而这里提供给你浅尝辄止。这样做最终会得到许多的我们努力去避免的样板式代码,不过这也让你得到了不可变的对象和一个非常流畅的接口。
This is more of a software engineering section than a Java section, but one of the best ways to write testable software is to use dependency injection(DI). Because Java strongly encourages OO design, to make testable software, you need to use DI.
In Java, this is typically done with the Spring Framework. It has a either code-based wiring or XML configuration-based wiring. If you use the XML configuration, it's important that you don't overuse Spring because of its XML-based configuration format. There should be absolutely no logic or control structures in XML. It should only inject dependencies.
Good alternatives to using Spring is Google and Square's Daggerlibrary or Google's Guice. They don't use Spring's XML configuration file format, and instead they put the injection logic in annotations and in code.
这是更偏向软件工程而不是 Java 的一节。编写可测试软件的最佳方式之一就是使用依赖注入(DI)。因为 Java 非常鼓励 OO 设计,为了创造出可测试的软件,你需要使用DI。
在 Java 中,一般使用 Spring 框架 的 DI 实现。它同时支持基于代码的装配和基于 XML 配置的装配。 如果你使用的是 XML 配置,因为其基于 XML 的配置, 不去过分使用 Spring 这一点很重要。XML 中绝对不能有任何逻辑或者控制结构,只能用来注入依赖。
使用 Spring 的好的选择就是 Google 和 Square 的 Dagger 库以及Google 的 Guice。他们不使用 Spring 的 XML 配置文件格式,而是将依赖逻辑放到注解和代码中。
Try to avoid using nulls when you can. Do not return null collections when you should have instead returned an empty collection. If you're going to use null, consider the @Nullable annotation. IntelliJ IDEA has built-in support for the @Nullable annotation.
If you're using Java 8, you can use the excellent new Optional type. If a value may or may not be present, wrap it in an Optional class like this:
public class FooWidget { private final String data; private final Optional<Bar> bar; public FooWidget(String data) { this(data, Optional.empty()); } public FooWidget(String data, Optional<Bar> bar) { this.data = data; this.bar = bar; } public Optional<Bar> getBar() { return bar; }}
So now it's clear that data will never be null, but bar may or may not be present. Optional has methods like isPresent, which may make it feel like not a lot is different from just checking null. But it allows you to write statements like:
final Optional<FooWidget> fooWidget = maybeGetFooWidget(); final Baz baz = fooWidget.flatMap(FooWidget::getBar) .flatMap(BarWidget::getBaz) .orElse(defaultBaz);
Which is much better than chained if null checks. The only downside of using Optional is that the standard library doesn't have good Optional support, so dealing with nulls is still required there.
尽你所能避免空值。如果你可以返回一个空的集合,就不要返回一个空值。如果你要使用空值,就考虑使用 @Nullable 注解。IntelliJ IDEA 内置有对于 @Nullable 注解的支持。
如果你使用的是 Java 8,就可以利用其优秀的新的 Optional 类型。如果一个可能存在也可能不存在,那就像下面这样把它封装到一个 Optional 类中:
public class FooWidget { private final String data; private final Optional<Bar> bar; public FooWidget(String data) { this(data, Optional.empty()); } public FooWidget(String data, Optional<Bar> bar) { this.data = data; this.bar = bar; } public Optional<Bar> getBar() { return bar; }}
这样现在就能很确定数据永远都不会是空值了, 不过 bar 可能存在也可能不存在。Optional 有一些诸如 isPresent 这样的方法,这使得其感觉跟只检查空值的做法小同大异。但是它能让你写出像下面这样的语句:
final Optional<FooWidget> fooWidget = maybeGetFooWidget(); final Baz baz = fooWidget.flatMap(FooWidget::getBar) .flatMap(BarWidget::getBaz) .orElse(defaultBaz);
这样就比链条时的 if 空值检查看起来好多了。使用 Optional 的唯一缺陷就是标准库并没有对 Optional 有很好的支持,因此针对空值的处理还是需要的。
Unless you have a good reason to make them otherwise, variables, classes, and collections should be immutable.
Variable references can be made immutable with final:
final FooWidget fooWidget;if (condition()) { fooWidget = getWidget();} else { try { fooWidget = cachedFooWidget.get(); } catch (CachingException e) { log.error("Couldn't get cached value", e); throw e; }}// fooWidget is guaranteed to be set here
Now you can be sure that fooWidget won't be accidentally reassigned. The finalkeyword works with if/else blocks and with try/catch blocks. Of course, if thefooWidget itself isn't immutable you could easily mutate it.
Collections should, whenever possible, use the Guava ImmutableMap,ImmutableList, or ImmutableSet classes. These have builders so that you can build them up dynamically and then mark them immutable by calling the build method.
Classes should be made immutable by declaring fields immutable (via final) and by using immutable collections. Optionally, you can make the class itself final so that it can't be extended and made mutable.
除非你有一个好的理由要这样做,那么变量、类和集合都是不应该被修改的。
变量的引用可以用 final 来变成不可被修改的:
final FooWidget fooWidget;if (condition()) {
fooWidget = getWidget();} else { try { fooWidget = cachedFooWidget.get(); } catch (CachingException e) { log.error("Couldn't get cached value", e); throw e; }}// fooWidget is guaranteed to be set here
现在你就可以确信 fooWidget 不会突然被重新赋值了。final 关键字一般同 if/else 块和 try/catch 块一起使用。当然,如果 fooWidget 不是不可被修改的,那你就可以很轻易了修改它了。
集合就应该无论何时都尽量使用 Guava 的 ImmutableMap,ImmutableList,或者 ImmutableSet 类。这些都拥有构建器,因此你可以动态地构建它们,并通过调用 build 方法来将它们标记为不可变。
类应该(通过 final)声明其属性域不可变和使用不可变的集合而变成不可变的。你也可以选择使得类自身为 final,那样它就不能被扩展和被改变了。
Be careful if you find yourself adding a lot of methods to a Util class.
public class MiscUtil { public static String frobnicateString(String base, int times) { // ... etc } public static void throwIfCondition(boolean condition, String msg) { // ... etc }}
These classes, at first, seem attractive because the methods that go in them don't really belong in any one place. So you throw them all in here in the name of code reuse.
The cure is worse than the disease. Put these classes where they belong, or if you must have common methods like this, consider Java 8's default methods on interfaces. Then you could lump common actions into interfaces. And, since they're interfaces, you can implement multiple of them.
public interface Thrower { default void throwIfCondition(boolean condition, String msg) { // ... } default void throwAorB(Throwable a, Throwable b, boolean throwA) { // ... }}
Then every class which needs it can simply implement this interface.
在你发现自己添加了太多的方法到一个工具类中时要小心。
public class MiscUtil { public static String frobnicateString(String base, int times) { // ... etc } public static void throwIfCondition(boolean condition, String msg) { // ... etc }}
这些类一开始看起来很吸引人,因为它们里面包含的方法并不真的属于任何一块。所以你就以代码重用的名义将它们扔到了一块儿。
治病比生病更糟糕。将这些类放到原本属于它们的地方,要不如果你必须要有像这么一些方法的话,就考虑使用 Java 8 的接口上的默认方法。然后你就可以将公共方法统统扔到接口中去。而因为他们是接口,你就可以多次实现它们。
public interface Thrower { default void throwIfCondition(boolean condition, String msg) { // ... } default void throwAorB(Throwable a, Throwable b, boolean throwA) { // ... }}
然后每个有需要的类都可以简单的实现这个接口。
Formatting is so much less important than most programmers make it out to be. Does consistency show that you care about your craft and does it help others read? Absolutely. But let's not waste a day adding spaces to if blocks so that it "matches".
If you absolutely need a code formatting guide, I highly recommendGoogle's Java Style guide. The best part of that guide is theProgramming Practices section. Definitely worth a read.
Documenting your user facing code is important. And this means using examples and using sensible descriptions of variables, methods, and classes.
格式化比起大多数程序员所认为的更加不被重视。那么它是不是同你对于自己技术水平的在意目标一致,还有是不是能有助于其他人的对于代码的解读呢?当然是。但我们也不要浪费一整天加空格来使得 if 的括号能“匹配”。
如果你绝对需要一个代码格式手册,我强烈推荐 Google 的 Java 代码风格 指南。该指南的最佳部分就是编程实践这一节。绝对值得一读.
为你的用户所要面对的代码加注文档是很重要的。而这就意味着要使用示例和对于变量、方法和类的极值描述。
The corollary of this is to not document what doesn't need documenting. If you
don't have anything to say about what an argument is, or if it's obvious,
don't document it. Boilerplate documentation is worse than no documentation at
all, as it tricks your users into thinking that there is documentation.
Java 8 has a nice stream and lambda syntax. You could write code like this:
final List<String> filtered = list.stream() .filter(s -> s.startsWith("s")) .map(s -> s.toUpperCase());
Instead of this:
final List<String> filtered = Lists.newArrayList();for (String str : list) { if (str.startsWith("s") { filtered.add(str.toUpperCase()); }}
This allows you to write more fluent code, which is more readable.
Deploying Java properly can be a bit tricky. There are two main ways to deploy Java nowadays: use a framework or use a home grown solution that is more flexible.
这样做的必然结果就是对于不需要加注文档的就不要去加注文档. 如果就一个参数代表的是什么你不想多费口舌,因为答案很明显,就不要为其加注文档。样板一样的文档比没有文档更糟糕,因为这对于会思考此处为何要加注的文档的用户而言这会是一种戏弄。
流
Java 8 有了一个不错的流和 lambda 语法。你可以像下面这样编写代码:
final List<String> filtered = list.stream() .filter(s -> s.startsWith("s")) .map(s -> s.toUpperCase());
而不是再像以前这样写:
final List<String> filtered = Lists.newArrayList();for (String str : list) { if (str.startsWith("s") { filtered.add(str.toUpperCase()); }}
这就让你能写出更加流畅的代码,更具可读性。
发布 Java 通常有点棘手。如今有两种主要的 Java 发布方式 : 使用一套框架,或者根据灵活性的本地增量方案。
Because deploying Java isn't easy, frameworks have been made which can help. Two of the best are Dropwizard and Spring Boot. The Play framework can also be considered one of these deployment frameworks as well.
All of them try to lower the barrier to getting your code out the door. They're especially helpful if you're new to Java or if you need to get things done fast. Single JAR deployments are just easier than complicated WAR or EAR deployments.
However, they can be somewhat inflexible and are rather opinionated, so if your project doesn't fit with the choices the developers of your framework made, you'll have to migrate to a more hand-rolled configuration.
因为发布 Java 并不容易,现有的框架可能会有所帮助。最好的两个就是 Dropwizard 和 Spring Boot。Play 框架 也可以被考虑也作为这些部署框架的其中之一。
它们全都试图降低让你的代码发布出去的门槛. 它们在你是名Java新手或者希望能快速运行起来时特别有帮助. 单个的JAR部署比复杂的WAR和EAR部署更简单.
不过,它们可能不怎么灵活,而且详单笨拙,因此如果你的项目不适合框架开发者为你的框架所做出选择,你就得自己集成一个更加手动的配置了。