Effective Java 第3版を読んだので、本書で紹介されていた全90項目のプラクティスを簡潔にまとめる。
https://www.maruzen-publishing.co.jp/item/?book_no=303408

本記事は第8章「メソッド」の項目について記載する。

第8章 メソッド

項目49 パラメータの正当性を検査する

パラメータの正当性を検査しないと、エラーアトミック性を失うことになる。

メソッドやコンストラクタを書く場合、引数のパラメータに制約があるか考える。

制約は文章化するべきであり、メソッドの最初の処理で明示的に検査するべきである。

検査手法

null検査をするには、Objects.requireNonNullメソッドを用いるとインラインで実行できるため柔軟で便利である。

Objects.requireNonNull(message, "messageはnullです");

公開されていない(publicではない)メソッドは、アサーションを用いて検査をおこなう。
アサーションは、実行時に-enableassertionsオプションを有効にしないとアサーションされない。
そのため、通常の正当性検査と区別することができる。

private static void sub(String message) {
    assert message != null : "message is null";
}

項目50 必要な場合、防御的にコピーする

提供するクラスが可変な要素を返却する場合は、クラスはその要素を防御的にコピーする必要がある。

最も良い解決策は、クラスの要素として不変なオブジェクトを選択すること。

可変な要素を含むクラスの例

public final class RegisterDate {
    private final Date registerDate;
    
    public RegisterDate(Date registerDate) {
        this.registerDate = registerDate;
    }
    
    public Date registerDate() {
        return registerDate;
    }
}
public static void main(String[] args) {
    Date date = new Date();
    RegisterDate registerDate = new RegisterDate(date);
    date.setYear(1992); // RegisterDateのインスタンスの中身まで書き換わってしまう。
}

この問題を回避するには、Dateの代わりにInstant、LocalDateTime、ZonedDateTimeを使うことである。

要素を防御的にコピーする

要素の初期化時と返却時に防御的にコピーすることで不変を維持することができる。

public final class RegisterDate {
    private final Date registerDate;
    
    public RegisterDate(Date registerDate) {
        this.registerDate = new Date(registerDate.getTime()); // 防御的にコピーしたもので初期化する
    }
    
    public Date registerDate() {
        return new Date(registerDate.getTime()); // 防御的コピーを返却する
    }
}

項目51 メソッドのシグニチャを注意深く設計する

メソッド名を注意深く選ぶこと

メソッド名は標準命名規則に従うべきである。

最も大切なことは、理解が可能で、他のメソッド名と矛盾のない命名とすること。

また、長いメソッド名は避けるべきである。

便利なメソッドを提供しすぎない

個々のメソッドは最小とする。

メソッドを多く提供することにより、テストや保守を困難にする。

長いパラメータのリストは避ける

引数の個数は4個以下とするべき。

クライアントは多すぎる引数を覚えることが困難なため、できる限り少なくすることが大切である。

引数の数を減らす手法としては、三つの技法が考えられる。

  • メソッドを分割して、各メソッドが引数のサブセットだけを必要となるようにする。
  • パラメータを集約したクラスを作成する。
  • Builderパターンを採用して、オブジェクトの生成からメソッドの呼び出しに適用する。

パラメータ型に関しては、クラスよりもインタフェースを選ぶ

インタフェースを引数の型に選ぶことにより、渡せるクラスの選択肢が増えるため。

項目52 オーバーロードを注意して使う

オーバーロードとオーバーライドのメソッドの選択の仕組みは以下のように異なる。

  • オーバーロードされたメソッドの選択は静的(コンパイル時)
  • オーバーライドされたメソッドの選択は動的(実行時)

また、オーバーロードの振る舞いはプログラマを混乱させることがあるため、注意して利用する必要がある。

以下のような安全的で保守的な方針を検討する。

同じパラメータ数の2つのオーバーロードされたメソッドを提供しない

引数の数が同じ場合、引数の型でメソッドを区別する必要があるため、メソッドの定義をプログラマが把握しなければならない。

オーバーロードする代わりにメソッドに別名をつける

このような命名パターンの利点は、メソッド名を見るだけで処理が判断できる。

  • writeBoolean(boolean bool)
  • writeInt(int number)
  • wirteLong(long number)

そのため、オーバーロードせずに別々のメソッドを定義するほうがプログラマは理解しやすくなる。

ただし、コンストラクタの場合は命名することができないので、複数のコンストラクタは必ずオーバロードとなる。

そのような場合は、staticファクトリメソッドを提供させるべきである。  

項目53 可変長引数を注意して使う

可変長引数メソッドは、指定された型の0個以上の引数を受け付ける。

引数は0個以上であるため、少なくとも1個以上の引数が必要なメソッドの場合に不向きである。

static int hoge(int... args) {
    if (args.length == 0) {
        throw new IllegalArgumentException("パラメータは少なくとも1個以上必要");
    }
    // 略
}

このようなメソッドで、クライアント側が引数なしでメソッドをコールする場合には、コンパイル時ではなく実行時に失敗する。

これを防ぐためには、必須のパラメータを可変長引数ではなく通常の型で受け取るようにする。

static int hoge(int firstArg, int... args) {
    // 略
}

項目54 nullではなく、空コレクションか空配列を返す

空コレクションや空配列の代わりに、nullを返却するべきではない。

nullを返却すると、クライアント側が取り扱うのが困難となる。

項目55 オプショナルを注意して返す

Optional型をメソッドから返却する場合に、以下の点に留意する必要がある。

Optionalを返却する場合はnullで返してはいけない。

Optional.of(value)にnullを渡すとNullPointerExceptionがスローされてしまう。

そのため、値がnullである場合は、Optional.ofNullable(value)Optional.empty()で空のOptionalを返却させる。

コレクション、ストリーム、マップ、配列などのコンテナ型をOptionalでラップしない。

List型の場合、`Optional<List>型としてではなく、単純に空のListで返却するべきである。

他のコンテナ型もそれぞれで空の状態を表現できるため、Optionalを使うべきではない。

項目56 すべての公開API要素に対してドキュメントコメントを書く

ドキュメントコメントは、APIを文書化するうえで最も良い方法であり、すべての公開APIに対して必須であるべき。

また、保守可能なコードを書くために、詳細である必要はないものの、公開されていないAPIについてもドキュメントコメントは記載するべきである。

Javaにおけるドキュメントコメント

JavaではJavadocを用いてドキュメントコメントを記載する。

基本的に、クラス、インタフェース、コンストラクタ、メソッド、フィールドの宣言の前にドキュメントコメントを記載する。

ドキュメントコメントには、用途ごとのタグが提供されている。

タグ 概要 使用例
@param メソッドのパラメータの説明 @param index インデックス番号
@return メソッドの戻り値の説明 @return E インデックス番号に格納されている要素
@throws メソッドがスローするすべての例外の説明 @throws IndexOutOfBoundsException 配列の要素を超えたインデックスが指定された場合にスロー
@code 任意のコードを埋め込む際に使用する {@code index < 0 || index >= this.size() }
@implSpec 自己利用パターンの記載(継承のためのクラス設計時などに利用) @implSpec この実装は @code this.size() == 0 を返却します
@literal HTMLのメタ文字をエスケープする {@literal |r| < 1}
@summary 概要説明を明示的に説明 {@summary 指定されたインデックス番号に格納されている要素を返す }
@index Javadocが生成するHTMLのクライアント側索引用の用語を定義 {@index 要素 インデックス }
@inheritDoc 継承可能なクラスからメソッドコメントを引用する {@inheritDoc}

特別な配慮が必要なドキュメントコメント

ジェネリック型、ジェネリックメソッド

すべての型パラメータにドキュメントコメントを記載する。

enum型

型とすべての公開メソッドだけでなく、すべての定数にもドキュメントコメントを記載する。

アノテーション

型だけでなく、すべてのメンバにもドキュメントコメントを記載する。

パッケージレベル

package-info.javaファイルにドキュメントコメントを記載する。

モジュールレベル

module-info.javaファイルにドキュメントコメントを記載する。