Java 是在世界各地最流行的编程语言之一, 但是看起来没人喜欢使用它。而 Java 事实上还算是一门不错的语言,随着 Java 8 最近的问世,我决定编制一个库,实践和工具的清单,汇集 Java 的一些最佳实践。 本文被放到了 Github 上。你可以随意地提交贡献,并加入自己的有关 Java 方面的建议和最佳实践。
|
风格
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class DataHolder {
private String data;
public DataHolder() {
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return this.data;
}}
|
这有点麻烦,并且也有点浪费。尽管你的 IDE 也能自动的生成这样的代码,但那也是种浪费。所以,别这么做。
相反,我更愿意选择编写类 C 的结构体风格的类,类里面只容纳数据:
1
2
3
4
5
6
|
public class DataHolder {
public final String data;
public DataHolder(String data) {
this.data = data;
}}
|
这样就在代码行数上减少了一半。此外,这个类是不能被修改的,除非你对它进行了扩展,因此我们可以更加容易的理解它,因为我们明白它不可以被修改。
如果你要保存像 Map 或者 List 这样容易被修改的对象,就应该使用 ImmutableMap 和 ImmutableList,这一点会在不可变性质的那一节被讲到。
Builder 模式
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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
}
}}
|
然后这样去使用它:
1
2
3
4
|
final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder()
.data("set this")
.num(523)
.build();
|
还有其它关于构建器的更好的例子 ,而这里提供给你浅尝辄止。这样做最终会得到许多的我们努力去避免的样板式代码,不过这也让你得到了不可变的对象和一个非常流畅的接口。
依赖注入
|
避免空值
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 这样的方法,这使得其感觉跟只检查空值的做法小同大异。但是它能让你写出像下面这样的语句:
1
2
3
4
|
final Optional<FooWidget> fooWidget = maybeGetFooWidget();
final Baz baz = fooWidget.flatMap(FooWidget::getBar)
.flatMap(BarWidget::getBaz)
.orElse(defaultBaz);
|
这样就比链条时的 if 空值检查看起来好多了。使用 Optional 的唯一缺陷就是标准库并没有对 Optional 有很好的支持,因此针对空值的处理还是需要的。
默认不可被改变
|
1
|
final FooWidget fooWidget;if (condition()) {
|
1
2
3
4
5
6
7
|
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,那样它就不能被扩展和被改变了。
避免许多的工具类
|
1
2
3
4
5
6
7
8
|
public class MiscUtil {
public static String frobnicateString(String base, int times) {
// ... etc
}
public static void throwIfCondition(boolean condition, String msg) {
// ... etc
}}
|
这些类一开始看起来很吸引人,因为它们里面包含的方法并不真的属于任何一块。所以你就以代码重用的名义将它们扔到了一块儿。
治病比生病更糟糕。将这些类放到原本属于它们的地方,要不如果你必须要有像这么一些方法的话,就考虑使用 Java 8 的接口上的默认方法。然后你就可以将公共方法统统扔到接口中去。而因为他们是接口,你就可以多次实现它们。
1
2
3
4
5
6
7
8
|
public interface Thrower {
default void throwIfCondition(boolean condition, String msg) {
// ...
}
default void throwAorB(Throwable a, Throwable b, boolean throwA) {
// ...
}}
|
然后每个有需要的类都可以简单的实现这个接口。
格式化
|
这样做的必然结果就是对于不需要加注文档的就不要去加注文档. 如果就一个参数代表的是什么你不想多费口舌,因为答案很明显,就不要为其加注文档。样板一样的文档比没有文档更糟糕,因为这对于会思考此处为何要加注的文档的用户而言这会是一种戏弄。 流 Java 8 有了一个不错的流和 lambda 语法。你可以像下面这样编写代码:
而不是再像以前这样写:
这就让你能写出更加流畅的代码,更具可读性。 发布发布 Java 通常有点棘手。如今有两种主要的 Java 发布方式 : 使用一套框架,或者根据灵活性的本地增量方案。 |
框架因为发布 Java 并不容易,现有的框架可能会有所帮助。最好的两个就是 Dropwizard 和 Spring Boot。Play 框架 也可以被考虑也作为这些部署框架的其中之一。 它们全都试图降低让你的代码发布出去的门槛. 它们在你是名Java新手或者希望能快速运行起来时特别有帮助. 单个的JAR部署比复杂的WAR和EAR部署更简单. 不过,它们可能不怎么灵活,而且详单笨拙,因此如果你的项目不适合框架开发者为你的框架所做出选择,你就得自己集成一个更加手动的配置了。 |
Maven
|
你的所有的 Maven 项目都将包含你的根 POM,以及他所有的版本信息。这样,你就能得到你的公司所选择的每一个外部依赖的版本,以及所有的正确的 Maven 插件。如果你需要拉入外部依赖,就会像下面这样运作:
如果你想要外部的依赖,那就应该被每一个独立的项目部分管理起来。否则就很难保持根 POM 的有序性。 依赖收敛Java 最好的部分就是大量的第三方库能帮助你做任何事情。基本上每一个 API 或者工具包都有一个 Java SDK,并且很容易用 Maven 获取。 |
而那些 Java 库自身则还要依赖于其它的特定版本的库. 如果你引入了够多的库,就会发生版本冲突, 那会像下面这样:
哪个版本会引入到你的项目中呢? 使用 Maven 的依赖收敛插件, 构建就会在你的依赖没有使用相同的版本时报错。之后要解决冲突,你可以有两种选择:
选择哪种方案要视你的情形而定:如果你想要跟踪一个项目的版本,那么就用排除的方案。另外一方面,如果你想要明确的指定它,你就可以挑选一个版本,虽然你将需要在更新其它依赖的同时对它进行更新。 |
持续集成
|
Maven 资源库
|
库
|
Guava
|
1
2
3
4
5
|
// Instead of
final Map<String, Widget> map = new HashMap<String, Widget>();
// You can use
final Map<String, Widget> map = Maps.newHashMap();
|
还有针对 Lists, Maps, Sets 以及更多集合的静态类。 他们更清晰和可读。
如果你还在 Java 6 或者 7 的坑里面, 你可以使用 Collections2 类, 它拥有像 filter 和 transform 这样的方法. 能让你在没有 Java 8 对流的支持下写出流畅的代码。
Guava 也有一些简单的东西, 比如 Joiner 能用分隔符将字符串连接起来,以及一个通过忽略它们来 处理中断的类.
Gson
|
1
2
3
|
final Gson gson = new Gson();
final String json = gson.toJson(fooWidget);
final FooWidget newFooWidget = gson.fromJson(json, FooWidget.class);
|
相当简单且令人愉悦。Gson用户手册 有许多的示例。
Java Tuples
Java经常令我头疼的一点就是他的标准库里面并没有内置元组。幸运的是, Java tuples 项目解决了这个问题。
它易于使用而且表现很棒:
1
2
3
|
Pair<String, Integer> func(String input) {
// something...
return Pair.with(stringResult, intResult);}
|
Joda-Time
Joda-Time 是我所使用过的最棒的时间库. 简答,直接,易于测试. 夫复何求?
所以你只要在如果没有使用Java8时使用这个库,因为Java8有了新的 日期时间 库。
Lombok
|
1
2
|
public class Foo {
@Getter @Setter private int var;}
|
现在你可以这样做:
1
|
final Foo foo = new Foo();foo.setVar(5);
|
还有 更多的东西。我还没有将 Lombok 用于生产环境,但我迫不及待的想要这么做了。
Play framework
在Java中实现 RESTful web 服务又两个主要的阵营 : JAX-RS 和其余一切。
JAX-RS 是传统的方式。你可以使用诸如Jersey之类的东西来将注解结合接口和实现来组织 web 服务。这里有意思的是你可以简单的从接口类创建出客户端。
Play framework 是 JVM 的 web服务的一个异类:你会有一个路由文件,然后你要编写在这些路由中被引用的类。它实际上是一个完整的 MVC 框架, 但是你可以简单地只把他用于 REST web 服务。 它在 Java 和 Scala 上都能用。它稍有偏向 Scala 优先,不过在Java中也还好. 如果你用过Python中向Flash这样的微型框架, 你就能熟悉 Spark。它在 Java 8 上能运行得很好。 SLF4J有许多Java日志的解决方案。我喜欢的就是 SLF4J 因为它的极度的可插入性,并且可以同时结合来自不同日志框架的日志. 有没有过一个使用了 java.util.logging, JCL, 以及 log4j 的古怪项目? SLF4J 为你而生。 两页篇幅的操作手册 就是你入门所需要的。 |
jOOQ
|
1
2
3
4
5
6
7
8
9
|
// Typesafely execute the SQL statement directly with jOOQ
Result<Record3<String, String, String>> result =
create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
.from(BOOK)
.join(AUTHOR)
.on(BOOK.AUTHOR_ID.equal(AUTHOR.ID))
.where(BOOK.PUBLISHED_IN.equal(1948))
.fetch();
|
使用这个和 DAO 模式, 你可以使得访问数据库变得轻而易举。
测试
测试对于你的软件至关重要。这些包能使得测试更简单。
jUnit 4
jUnit 不需要介绍了。它是Java中单元测试的标准工具.
而你可能不会发挥 jUnit 的全部潜能。jUnit 支持 参数化测试,,让你不用编写过多样板代码的 规则,随机测试特定代码的 理论, 以及 假设。
jMock
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class FooWidgetTest {
private Mockery context = new Mockery();
@Test
public void basicTest() {
final FooWidgetDependency dep = context.mock(FooWidgetDependency.class);
context.checking(new Expectations() {{
oneOf(dep).call(with(any(String.class)));
atLeast(0).of(dep).optionalCall();
}});
final FooWidget foo = new FooWidget(dep);
Assert.assertTrue(foo.doThing());
context.assertIsSatisfied();
}}
|
这里通过 jMock 设置了一个 FooWidgetDependency,然后加入了一个预期( expectation)。我们预期 dep 的 call 方法会使用某个字符串被调用一次并且 dep 的 optionalCall 方法会被调用0到多次。
如果你要一次又一次设置相同的依赖,你可能应该将那些放到一个 测试夹具(est fixture)中,并将 assertIsSatisfied 放到一个 @After 夹具中。
AssertJ
|
1
2
3
4
5
|
final List<String> result = some.testMethod();
assertEquals(4, result.size());
assertTrue(result.contains("some result"));
assertTrue(result.contains("some other result"));
assertFalse(result.contains("shouldn't be here"));
|
这都是烦人的样板代码。 AssertJ 会把这些都干掉。你可以将同样的代码转换成这样:
1
2
3
|
assertThat(some.testMethod()).hasSize(4)
.contains("some result", "some other result")
.doesNotContain("shouldn't be here");
|
这样流畅的接口让你的测试更加可读. 夫复何求?
工具
IntelliJ IDEA
最好的Java IDE是 IntelliJ IDEA。 它有大量超赞的功能特性,并且真正让开发 Java 相关的所有细节都暴露无遗。自动补全很棒, 检查也是顶尖的,还有重构工具真的很有帮助。
免费的社区版本对我而言已经很好的,而旗舰版本则还有许多很棒的功能,比如数据库工具,对 Spring Framework 和 Chronon 的支持。
Chronon
|
检查框架(Checker Framework)Java 的类型系统很不咋地. 它不能区分字符串和实际上是正则表达式的字符串,也没有做 坏点检查。不过, Checker Framework 能做到并且能做到更多。 它使用像 @Nullable 这样的注解来检查类型. 你甚至可以 自己定义注解 来是的静态分析如虎添翼。 Eclipse 内存分析器即使是在Java中,内存也会发生泄漏。幸运的是,有针对这个问题的工具。我用过的最好的解决这些问题的工具就是 Eclipse 内存分析器。它能获取到堆栈,让你可以找出问题何在。 有几种方法可以获取到一个 JVM 进程的堆栈 , 而我使用的是 jmap:
之后你就可以用内存分析器打开 heapdump.hprof 文件,并快速的看到到底发生了什么. 资源能帮助你成为 Java 大师的资源 数据博客 |
本文转自:开源中国社区 [http://www.oschina.net]
本文标题:Java 的最佳实践
本文地址:http://www.oschina.net/translate/better-java
参与翻译:leoxu, 无若