public宣言あるいはprotected宣言されたデータメンバに対するアクセスを制御するのは困難である。攻撃者はこれらのメンバを想定外の方法で操作する可能性がある。そのため、データメンバはprivate宣言すべきである。クラスが宣言されたパッケージの外にクラスのメンバを公開する場合、ラッパーアクセッサーメソッドを使うこと。ラッパーメソッドを使うことで、データメンバに対する変更を適切に監視したり、制御したりできるようになる(たとえばディフェンシブコピーの作成、入力値の検証、ログの保存など)。ラッパーメソッドを使うことで、クラスの不変条件(invariant)を維持することもできる。
違反コード (publicなプリミティブ型のフィールド)
以下の違反コードでは、メソッドadd()とremove()がコンテナに要素を追加したり削除したりするのに合わせ、要素の総数をデータメンバtotalで管理する。
public class Widget { public int total; // 要素の数 void add() { if (total < Integer.MAX_VALUE) { total++; // ... } else { throw new ArithmeticException("Overflow"); } } void remove() { if (total > 0) { total--; // ... } else { throw new ArithmeticException("Overflow"); } } }
public データメンバであるtotalの値は、add()メソッドやremove()メソッドとは独立して、外部のコードにより変更されるかもしれない。public クラスのフィールドを公開するのは悪しき習慣である。[Bloch 2008]
適合コード (private 宣言)
以下の適合コードではtotalをprivate 宣言し、publicなアクセッサーメソッドを用意することで、パッケージ外部から必要なメンバにアクセスできるようにしている。add()メソッドとremove()メソッドはクラスの不変条件に違反することなく値を変更する。
privateな可変オブジェクトへの参照をアクセッサーメソッドを通じて提供する場合、慎重に行うこと。詳細は「OBJ05-J. privateかつ可変なクラスメンバへの参照を返す前にそのディフェンシブコピーを作成する」を参照。
public class Widget { private int total; // private 宣言する public int getTotal () { return total; } // add() と remove() の定義は変更なし }
add()、remove()、getTotal()メソッドを使用してprivateな内部状態にアクセスするのはよい習慣である。内部状態を変更する前に入力値検査やセキュリティマネージャの検証を行うなど、これらのメソッドに他の機能を追加することもできる。
違反コード (publicな可変フィールド)
以下の違反コードでは、static宣言した可変なハッシュマップにpublicなアクセス権を付与している。
public static final HashMap<Integer, String> hm = new HashMap<Integer, String>();
適合コード (可変メンバへのアクセスを制限し、ラッパーを提供する)
staticかつ可変なデータメンバはprivate宣言しなくてはならない。
private static final HashMap<Integer, String> hm = new HashMap<Integer, String>(); public static String getElement(int key) { return hm.get(key); }
ラッパーメソッドでは、必要とされる機能に応じて、HashMapへの参照、HashMapのコピー、HashMapが持つ値、のいずれかを返すようにする。この適合コードでは、HashMapのインデックスに対してその要素の値を返すラッパーメソッドを実装している。
例外
OBJ01-EX0: Sunが提供するドキュメント「Code Conventions for the Java Programming Language」には以下のように記載されている[Conventions 2009]。
インスタンス変数を public にしてよい例の1つは、そのクラスが本質的に動作を持たないデータ構造である場合である。つまり、クラスの代わりにstructを使用しているとすると(Javaがstructをサポートしているとして)、クラスのインスタンス変数をpublicにするのは適切である。
OBJ01-EX1: 「クラスがパッケージプライベートであるか、private 宣言された入れ子クラスであるなら、クラスが提供する抽象化を適切に記述する限りにおいて、そのデータフィールドを外部に公開するのは何ら間違ったことではない。このアプローチを利用すると、クラス定義においてもそれを利用するクライアントコードにおいても、アクセッサーメソッドを使ったアプローチよりもスマートなコードになる」[Bloch 2008] この例外は、可変フィールド、不変フィールドの両方に適用される。
OBJ01-EX2: 数学的定数を保持するstatic finalフィールドはpublic宣言してもよい。
リスク評価
データメンバをprivate宣言しないと、カプセル化が破綻することがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
OBJ01-J | 中 | 高 | 中 | P12 | L1 |
自動検出
publicおよびprotectedなデータメンバの検出、セクセッサメソッドの存在有無のヒューリスティックな検出は容易である。しかし、このルールの例外を考慮することなく単純に検出したすべてのケースを通知すれば、誤検知(false positive)がむやみに多くなるであろう。例外を考慮しつつ適切に違反コードを検出するのは不可能である。ヒューリスティックな手法は役に立つかもしれない。
関連ガイドライン
CERT C++ Secure Coding Standard | OOP00-CPP. Declare data members private |
MITRE CWE | CWE-766. Critical variable declared public |
Secure Coding Guidelines for the Java Programming Language, Version 3.0 | Guideline 3-2. Define wrapper methods around modifiable internal state |
参考文献
[Bloch 2008] | Item 13. Minimize the accessibility of classes and members; Item 14. In public classes, use accessor methods, not public fields |
[JLS 2005] | §6.6, Access Control |
[Long 2005] | §2.2, Public Fields |
翻訳元
これは以下のページを翻訳したものです。
OBJ01-J. Declare data members as private and provide accessible wrapper methods (revision 96)