『Setterは必要なのか』という記事を見て、コメントしようと思ったけれど、長文になりそうだったので記事にしました。
メンバとかプロパティとかオブジェクトとかインスタンスとか、そういう単語に(表現)に揺らぎのあるものはいくつかありますが、「文意通りに理解してください。」(意訳:わかりにくかったらツッコんでください。)
論旨
- 「私が「Getter/Sestterの"乱用"はアンチパターン」という理由と前提」あたりもご一緒にどうぞ。
- Setter も Getter も、 常に public なわけじゃない 。
- 私の場合は、むしろ protected のほうが多い。
- プロパティ は private よりも広い範囲に公開すべきでない。( protected では広すぎる)
- Constructor では インスタンス生成に必要十分なだけわたす 。コンストラクタの引数にある必要・理由がなければ渡さない。
- すべてのプロパティをコンストラクタで渡せるようにしようとするな。
- 他のものはinitなどからSetterで設定する。
- init で渡すようなものは、「データリポジトリから取ってくるようにする」ような変更が発生することが多い。
- その場合は、 Constructor と init はそれぞれ別に制御できることを期待される。
- コンストラクタで、データリポジトリへのアクセス(DBアクセスとか)は論外レベルでダメ。
- Constructor 内で init を呼び出しているなら、その呼出の制御を Constructor に bool で渡すようにしとくと、あとで修正しやすかったりする。
- 「"可能な限り"Immutable Objectにすべき」という意見には賛同する。
- しかし、現実的には、Immutable Object にできるものは少ない。
- 思考停止してすべてのプロパティに Setter と Getter を作るくらいなら、すべてのプロパティを public にしたほうが「間違ったコードは間違って見えるようにする」という点でマシ。
- 比較的マシというだけであって、どっちにしろ「論外レベルの悪手」であることには変わりない。
Setter が 常に public なメソッドだといつから錯覚していた?
以下のようなコードのように、publicでないSetterにも需要があります。
明確に「外部から直接プロパティを触られたくない」という意思表示になる からです。
<?php
class File
{
protcted function setName($newName) {
$this->name = $newName;
}
public function rename($newName) {
// `$name` 「普段はクラス自身ですら意識したくない」命名規則が存在する場合の、フィルタリング
//// こういうことやりたいっていう需要は、わりとあると思う。少なくとも私はよく遭遇する
$newName .= 'XXX';
$this->setName($newName);
}
}
「 Setterは常にpublicだ(=外から メンバを変更できるインターフェースだ)」という既成概念をやめたほうがいい と思います。私はその理解の仕方は 誤りだと思っています 。
ちなみに、私がやむを得ず生やした Setter の過半数は、 protected だったりします。それだけ、public な Setter の出番は少ないです。
コンストラクタ以外からメンバへ直接アクセスすることのリスクと、Setterの存在意義
例えばlaravelの Illuminate\Container\Container
で、「 $resolved
プロパティが(継承先のクラスも含めて)常にValidな値しか保持していない」ことを、如何にして低コストで確認するのでしょう。このくらいの比較的小さいクラスなら数千行読めば継承先まで確認できるのでしょうけど、心理的につらいし、俺はやりたくないです。
ということで、少なくとも継承先のメソッド内から、親クラスで宣言・定義されているプロパティへ直接変更したくないので、 Setter を使うようにします。
以下に列挙するような処理は、 Setter 内で行っても怒られない程度には高速でしょうし、むしろ一々プロパティの型などに神経質にならなくて済むようになるので、 Setter に実装すべきでしょう。
- 型のバリデーション(
InvalidArgumentException
が生成される感じのやつ ) - 範囲のバリデーション(
DomainException
、LengthException
、OutOfRangeException
が生成される感じのやつ)
ただし、これらのバリデーションは オブジェクト内で完結させる ことを期待されます。必要なデータは、初期化時(大抵の場合はインスタンス生成時)にすべて揃えて、プロパティに持っておくべきです。
バリデーションのために Setter 内でのDBアクセスや Singleton へのアクセスすることは、絶対にやってはいけません。一般的に、 Setter は「プロパティの値を上書き(set)する」だけのメソッドなので、内包しないインスタンスに依存してるとは考えません。
「"どこの誰でもどんな時でも自由に”メンバ変数を書き換えられることはバグの温床となりうる。」という論旨は、そもそも Setter もメソッドの一つだということを忘れている
Setter は 「"どこの誰でもどんな時でも自由に”メンバ変数を書き換えられる」機会を提供するもの ではない です。むしろプロパティを保護するためのもの です。代入の直前にhookできる機会を作るものっていう認識をすべきです。
外部から変更されたくないなら Setter を作るなと言う話なんですよ。
まとめ
- Setter と Getter は「プロパティへのアクセスにフックできる機会」という捉え方をする。
- Setter や Getter も他のメソッド同様、不要なら作らない。
- Setter や Getter も他のメソッド同様、適切な visibility にする。