1. 程式人生 > >【Java 程式設計】Java 開發實踐與經驗:BetterJava

【Java 程式設計】Java 開發實踐與經驗:BetterJava

文章目錄


原文: https://www.seancassidy.me/better-java.html

Github

Java 當然是一門很好的程式語言,它非常流行,但也不可否認比較難用,冗長、笨拙、刻板,這是用 Java 程式設計時常會有的體驗。Java 8 帶來了很多新特性,讓程式設計師可以寫出更簡潔、流暢的 Java 程式碼。

1. 風格

傳統的 JavaBean 風格的程式碼讓程式顯得拖沓、煩冗!

比如:

public class DataHolder {
    private String data;

    public
DataHolder() { } public void setData(String data) { this.data = data; } public String getData() { return this.data; } }

這樣的程式碼好嗎?

好的程式碼應該是讓複雜的事情變簡單,而不是讓簡單的事情變複雜。儘管大部分程式碼都可以通過 IDE 自動生成,但這種程式碼資訊密度太低,極大的影響可讀性。So, Don’t do this!

這時,可以嘗試 C 結構體風格的 Java 類:

public
class DataHolder { public final String data; public DataHolder(String data) { this.data = data; } }
  • 刪除掉多餘的 setter,getter 方法
  • 預設為 immutable,簡化邏輯

對於複雜的物件,可以使用建造者模式來構建,例如:

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
        }  
    }
}
  • Builder 是一個內部類,用於構建物件;
  • Builder 是 mutable 的;
  • 當呼叫 build() 方法時,會產生一個 immutable ComplicatedDataHolder 類例項。

使用示例:

final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder()
    .data("set this")
    .num(523)
    .build();

這是構建者模式的一種例項,通過它可以讓程式碼顯得更加流暢、可讀。

2. 依賴注入

依賴注入(Dependency injection)讓軟體具有更好的可測性,在面向物件的設計與開發中廣泛應用。

Java 可以使用 Spring 框架提供的 DI 功能。注入的方式有三種:xml 配置檔案,註解,編碼。

除了 Spring,也可以考慮使用 Dagger 或 Guice。

3. 避免使用 null

儘可能的避免使用 null,也不要在方法中返回 null。必須使用 null 的場景,需新增 @Nullable 註解。

Java 8 提供了 Optional 型別,當一個值可能為 null 時,用 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;
    }
}

這樣的話,返回結果就永遠不會是 null 了,Optional 有類似 isPresent 的方法,可以編寫如下所示的程式碼:

final Optional<FooWidget> fooWidget = maybeGetFooWidget();
final Baz baz = fooWidget.flatMap(FooWidget::getBar)
                         .flatMap(BarWidget::getBaz)
                         .orElse(defaultBaz);

這樣就不用去寫一連串的 check null 程式碼了。

目前,標準庫對 Optional 的支援還不夠完善,可以考慮使用 guava 等三方庫提供的 Optional 特性。

4. Immutable-by-default

變數、類、集合預設應該為 immutable(不可編輯),除非有特殊需要。

變數和類物件可以通過 final 宣告為 immutable。

集合可以使用 Guava 提供的 ImmutableMap,ImmutableList,或者 ImmutableSet 類。它們都有自己的 Builder 類,用來動態構建,最後呼叫 builder() 方法返回構建的 Immutable 集合例項。

5. 介面預設方法代替各種 util 類

專案中常常會有各種各樣的 util 類,有時候用著挺方便,但這並不是一種良好的開發習慣。

Java 8 中的介面可以定義 default 方法,可以試著將通用方法放在介面中,這樣任何想使用這些方法的類都可以通過實現介面的方式獲得。

public interface Thrower {
    default void throwIfCondition(boolean condition, String msg) {
        // ...
    }

    default void throwAorB(Throwable a, Throwable b, boolean throwA) {
        // ...
    }
}

6. Streams

Java 8 提供了 stream 和 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());
    }
}

是不是清爽了很多,又可以愉快的程式設計了!

7. 部署

框架(Framework)讓 Java 應用的部署變得簡單,比如 DropwizardSpring Boot,除此之外,還有 Play framework,也可以考慮。

這些框架就是為了降低部署程式的複雜度,讓程式變得開箱即用。對於新手來說,使用框架能讓其專注於程式設計本身;對於專案來說,使用框架可以實現快速構建!

Jar 的部署方式,比 War 或 Ear 更簡單。

當然,框架只能保證通用性需求,如果專案需要有特殊的部署需求的話,則需要進行定製。

8. Maven

https://books.sonatype.com/mvnex-book/reference/index.html

Maven 依然是構建、打包、測試 Java 程式的標準工具!如果你是新手,你應該從 Maven 開始。同時 Gradle 作為後起之秀,也有它的優點。

Maven repository,可以使用 Artifactory 或 Nexus。

9. 持續整合

專案開發越來越敏捷化,因此持續整合也就變得越來越重要。

Jenkins 和 Travis-CI 是必然選擇。

程式碼覆蓋率很有用,可以考慮 Cobertura 提供的 Maven 外掛。

10. 自動化管理

Chef,Puppet,Ansible

11. 常用庫

Java 最大的優點就是擁有豐富的三方庫,下面列出的是一些比較常用的:

Apache Commons

Apache Commons

  • Commons Codec: 大量的 Base64/hex 字串的編解碼工具,不用自己去寫了
  • Commons Lang: 字符集/字串的構建、操作,各種常用雜散工具
  • Commons IO:FileUtils.copyDirectory, FileUtils.writeStringToFile, IOUtils.readLines 等等

Guava

guava 是 google 提供的 Java 通用庫,包含了很多重要但 JDK 沒有提供的特性。

  • Cache:
  • Immutable:
  • Events

Gson

google 提供的 JSON 庫

final Gson gson = new Gson();
final String json = gson.toJson(fooWidget);

final FooWidget newFooWidget = gson.fromJson(json, FooWidget.class);

Java Tuples

Java 語言本身沒有 tuple 型別,可以使用 javatuples 庫,如下:

Pair<String, Integer> func(String input) {
    // something...
    return Pair.with(stringResult, intResult);
}

Joda-Time

最好的時間處理庫,Java 8 之前的專案可以考慮使用。

Lombok

一個有趣的庫,通過註解,可以編寫如下程式碼:

public class Foo {
    @Getter @Setter private int var;
}

jOOQ

如果你不喜歡厚重的ORM框架,如Hibernate,可是試著用 jOOQ,使用它可以編寫如下風格的 SQL 程式碼:

// 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();

既靈活、又簡潔!

Eclipse Memory Analyzer

記憶體分析工具,可以排查記憶體洩漏問題。

可以使用 jmap 來生成 heap dump 檔案:

$ jmap -dump:live,format=b,file=heapdump.hprof -F 8152
Attaching to process ID 8152, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 23.25-b01
Dumping heap to heapdump.hprof ...
... snip ...
Heap dump file created