アクセス修飾子の選択ミスとJava設計トラブル5選
アクセス修飾子の選択ミスが招くJava設計トラブル集
Javaの public
, private
, protected
, 無指定(package-private)といったアクセス修飾子は、
一見単純な構文選択に見えて、設計の責務や構造の健全性に直結する要素です。
しかし実際には「テストのために public
にした」「とりあえず protected
」「無指定だったのを忘れてた」など、
設計上の意図を持たずに選ばれているケースが多く、その結果、後から大きな構造破綻や保守負債を生むことがあります。
本記事では、現場で頻出するアクセス制御の失敗パターンをもとに、レビューすべき設計観点を明確にしていきます。
アクセス修飾子の設計的役割
Javaのアクセス修飾子は、単に「外からアクセスできるか」を決めるものではありません。
クラスの責務がどこに向けて公開されているか、または隠蔽すべきかを、構文上で明示する手段です。
アクセス修飾子別:設計意図と選択基準
修飾子 | 可視範囲 | 主な設計用途 |
---|---|---|
public |
すべてのクラスからアクセス可能 | 外部との契約・API公開 |
protected |
サブクラス + 同一パッケージ | 継承による拡張責務 |
(無指定) | 同一パッケージのみアクセス可能 | パッケージ内の構造共有、密結合回避 |
private |
同一クラス内のみアクセス可能 | 実装詳細の隠蔽、補助ロジックの封じ込め |
アクセス修飾子は可視性の設定ではなく、「責務が公開されるべきかどうか」の設計判断です。
以降、この修飾子判断を誤ることで発生する実際のトラブルを見ていきましょう。
ケース1:private
にしすぎてテスト不能になる
症状
- 単体テストでprivateメソッドを直接検証したいがアクセスできない
- 仕方なく
public
に変更 → 本番でAPIとして誤認されてしまう
public class TokenService {
private String hash(String input) {
...
}
}
問題の本質
- アクセス制御の問題ではなく、構造の分離ができていない
- 責務をユーティリティや別クラスに追い出せば
public
にする必要がなくなる
public
化は“設計敗北”テストしやすさは構造で担保すべき。修飾子の緩和は設計の代償行為になっていないか、レビューで確認が必要です。
ケース2:protected
を“内部用”と誤解して仕様を晒す
症状
- 継承される前提がないのに
protected
にしてしまい、外部で勝手に継承されて仕様が漏れる - 想定外の拡張でセキュリティや仕様変更に影響が及ぶ
public class TokenGenerator {
protected String buildSecret() { ... } // 外部から継承・呼び出し可能に
}
問題の本質
- 「継承前提か否か」の判断がなく、設計契約のつもりが意図せず公開に
- Javaでは
protected
= 継承 + パッケージアクセス。事実上のpublic化になる可能性がある
ケース3:package-privateがリファクタで破綻
症状
- パッケージ内で閉じる設計だったが、構成整理で別パッケージに分離 → アクセスエラー続出
- 急遽
public
に変更 → 層境界やドメインモデルの破壊
class DiscountPolicy {
int calculate(int price) { ... } // パッケージ内限定のつもりが...
}
問題の本質
- package-privateは強力な構造制御だが、パッケージ構成変更に脆弱
- パッケージ設計の成熟度と密接に関わるため、長期設計では
protected
やpublic
との併用判断が重要
ケース4:インターフェースがpublic
で実装がpackage-private
症状
- APIとして
public interface
を定義したが、実装クラスをclass XxxImpl
のまま無指定にしてしまい、DIでは動くが明示的に使えない
public interface Formatter {
String format(String input);
}
class HtmlFormatter implements Formatter {
...
}
→ Springでは注入できるが、new HtmlFormatter()
が書けない → テストや一部ユースケースで混乱
APIとして公開するなら、実装が必要な場合にどう提供するか(Factory, DIなど)まで構造に含めて設計すべきです。
ケース5:実装をpublic
にしすぎて責務が流出する
症状
- ライブラリやユーティリティクラスが何でもかんでも
public
→ 呼び出し側が内部実装に依存 - 実装の差し替えや削除が困難に
public class DateUtil {
public static String formatDate(Date d) { ... }
public static long parseCustomEpoch(String raw) { ... } // 本来内部用
}
→ parseCustomEpochを使うコードが乱立 → 削除不可の仕様化
public
にした瞬間、それは“API”になるレビューでは「これは本当に公開されるべき責務か?」を、契約としての公開という観点で評価すべきです。
レビュー観点:アクセス制御が責務と一致しているか?
チェックリスト
アクセス修飾子は“可視性”ではなく“設計契約”の表明である。
責務がどこまで開かれるべきか、その設計判断が構文レベルで表現されているかを確認する。
まとめ:アクセス修飾子は「都合」ではなく「契約」で選ぶ
- テストの都合で
public
にする - 継承しないのに
protected
- 無指定のまま構造が壊れる
こうした設計ミスは、「コードが動くかどうか」ではなく、構造として成立しているかどうかで判断されるべきです。
アクセス修飾子は設計の“意志”をコードで明示する手段です。
レビューアーはその構文の裏にある設計意図を読み取り、「設計と可視性のズレ」がないかを常に評価すべきです。
次回は、「継承より委譲が設計上優れるケース」について構造・責務の観点から解説します。