0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 5.31:APIの柔軟性向上

Posted at

5.31:APIの柔軟性向上

要点

公開 API は具象実装や不必要な制約を露出せず、できるだけ抽象(インターフェース・ジェネリクス・ワイルドカード)で表現して、将来の実装変更や利用法の拡張を妨げないように設計する。

なぜ重要か

  • 利用者が多様な型を渡せる・受け取れるようにすると API の再利用性が上がる。
  • 実装を変えても API を壊さずに内部改善(効率化・バグ修正)できる。
  • 過度に具体的な型を要求すると呼び出し側のコードが不便になり、将来の互換性コストが増える。

具体的設計ルールと理由

1. 受け取る型は「最も抽象的な意味のあるインターフェース」で受ける

// NG: 実装の詳細を強制している
public void process(ArrayList<String> list) { ... }
// OK: 呼び出し側は List の任意の実装を渡せる
public void process(List<String> list) { ... }

理由:List にしておくと ArrayList LinkedList ImmutableList など何でも渡せる。
 
 
2. 返す型もインターフェースで返し、内部表現を露出しない

// NG: 内部配列や内部可変リストを直接返すと状態が壊される
public String[] getNames() { return names; }
// OK: インターフェースで返し、必要なら不変コピーを返す
public List<String> getNames() { return List.copyOf(namesList); }

理由:内部実装を変えられる、安全にイミュータブルなビューを提供できる。

 
3. ジェネリクス&ワイルドカードで型の互換性を高める(PECS等)

// 汎用的で安全(Producer-extends, Consumer-super を活用)
public static <T> void copy(List<? super T> dest, List<? extends T> src) { ... }

理由:具体型に縛られず、幅広い呼び出しを許す。
 
 
4) 引数の数が多い/オプションが多い場合は Builder やファクトリを用意
悪い:長いコンストラクタ/多数のオーバーロード
良い:Builder パターン、static factory(引数名で選べる)で読みやすく将来の拡張に強くなる。

 
5. 配列よりコレクションを使う(特に公開 API)
公開 API は List を返す/受け取る方が安全。配列は共変性や防御的コピーの問題がある。

 
6. Optional を使って「値がない」ことを明示する(null を返さない)
Optional を返すことで、呼び出し側に欠如処理を強制できる(null を返す API は拡張しにくい)。

// OK: シグネチャで「ないかもしれない」を明示
public Optional<User> findUser(String id) { ... }

// 呼び出し側の例
Optional<User> userOpt = repo.findUser("alice");
userOpt.ifPresent(user -> sendWelcome(user));
User u = userOpt.orElse(defaultUser);
User u2 = userOpt.orElseThrow(() -> new NoSuchElementException("not found"));

 
 
7. 便利オーバーロードを用意する(だが乱発しない)
たとえば addAll(Collection< T >) と addAll(T... items) の二つを用意すると使い勝手は良くなるが、varargs × ジェネリクスの安全性には注意。

悪い例:
public class Bag<T> {
    private final List<T> items = new ArrayList<>();

    // 危険: ジェネリック varargs を直接内部で配列ベースのビューに渡したり露出させると危険
    public void addAll(T... items) {
        // Arrays.asList(items) や直接配列を何かに保持すると配列が露出する可能性がある
        Collections.addAll(this.items, items); // 一見OKでも呼び出し側が配列を渡していると危険性がある
    }

    public void addAll(Collection<T> coll) {
        items.addAll(coll);
    }
}
良い例:
public class Bag<T> {
    private final List<T> items = new ArrayList<>();

    public void addAll(Collection<? extends T> coll) {
        items.addAll(coll);
    }

    // 使い勝手をよくする varargs オーバーロード(安全実装)
    @SafeVarargs
    public final void addAll(T... elems) {
        // elems の配列自体を外に渡さない(要素を新しい ArrayList にコピーしてから使う)
        for (T e : elems) items.add(e);
    }
}

8. パラメータに具象例外や実装依存型を使わない
実装固有の例外型や内部例外を API(公開メソッドのシグネチャ)に露出させると利用者が依存してしまう。可能なら標準の例外や専用の高レベル例外を使う。

悪い例
// Repository が JDBC を内部で使っていると仮定
public class UserRepository {
    // NG: SQLException をそのまま宣言している(実装依存を公開)
    public User findById(String id) throws SQLException {
        // ... JDBC code ...
    }
}
良い例
// API 層でラップしたり、より高レベルな例外を投げる
public class DataAccessException extends RuntimeException {
    public DataAccessException(String msg, Throwable cause) { super(msg, cause); }
}

public class UserRepository {
    public User findById(String id) {
        try {
            // ... JDBC code ...
        } catch (SQLException ex) {
            // 実装依存の例外を API 用の例外でラップして投げる
            throw new DataAccessException("Failed to find user " + id, ex);
        }
    }
}

まとめ

API の柔軟性を上げる基本は「抽象化して公開すること」。具体的にはインターフェースやジェネリクスで表す、内部表現を露出しない(不変ビュー/防御的コピー)、Builder/Factory を用意する、といった実務テクニックを使うことで、利用者にとって使いやすく、実装者にとって将来の変更に強い API がつくれる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?