このエントリでは、Yegor Bugayenkoによる記事、Getters/Setters. Evil. Period.を紹介する。 (Yegorから和訳と転載の許可は得た。) 以下はその全文の和訳だが、意訳超訳が混じっているので、もとのニュアンスを知りたければ元記事を読んでもいいし、読まなくてもいい。


2003年にAllen Holubが書いたWhy getter and setter methods are evilという有名な記事に端を発する古い議論がある。それは、getter/setterはアンチパターンで避けるべきものなのか、 もしくはオブジェクト指向プログラミングに必須なものなのかというもの。 この議論に少しだけ私の意見を加えたいと思う。

上記記事の要旨はこうだ。 getterやsetterはひどい慣習で、これらを使うやつらはゆるせん。誤解の無いようもう一度言うが、 私はget/setを可能な限り避けるべきだと言っているのではない。それらは君のコードに決して現れてはいけないのだ。

横柄で目につく物言いだろう? 君は15年来get/setパターンを使い続けている尊敬を集めるJavaアーキテクトなんだろう? どこぞの馬の骨にこんなデタラメを言われたくはないだろう? ああ、その気持ちはわかる。私がDavid WestのObject Thinkingという本に出会ったとき、 私もほとんど同じことを感じた。 Object Thinkingは、私が今まで読んだオブジェクト指向プログラミングについての本の中で最高のものだ。 だからお願いだ。ひとまず落ち着いて。私に説明させてほしい。

既存の論拠

オブジェクト指向の世界で、アクセッサ(getterやsetterの別名)に反対する論拠はいくつかあるが、 私にはそれら全てが十分に有力であるとは思えない。ひとつひとつ簡単に見ていこう。


  • 頼め、尋ねるな

    Allen Holub曰く、「ある処理をする際、その処理のために君が欲しい情報をオブジェクトに尋ねてはいけない。 その情報を持ったオブジェクトにその処理をするよう頼みなさい。」


  • カプセル化原則違反

    setterを通してどんな新たなデータも入力できるので、 一つのオブジェクトをその他の様々なオブジェクトが様々に扱うことができてしまう。 また、だれでもオブジェクトを変更できるので、 オブジェクトが単純に自身の状態を安全にカプセル化できない。


  • 実装の詳細の暴露

    あるオブジェクトから他のオブジェクトを取得できる場合、前者のオブジェクトの実装の詳細に過度に依存してしまう。 もし明日その実装、例えば返すオブジェクトの型が変わったら、周辺のコードも書き換えないといけない。


これらの全ての論拠は正当だが、重要なポイントが抜けている。

根本的な誤解

ほとんどのプログラマはオブジェクトはメソッドを持ったデータ構造だと考えている。 ここでBozhidar Bozhanovによる記事、Getters and Setters Are Not Evilから引用する。

しかし、人々がgetterやsetterをつけるオブジェクトのほとんどが、単純なデータホルダだ。

この思い違いが巨大な誤解の結果だ! オブジェクトは単純なデータホルダではない。オブジェクトはメソッド付きのデータ構造ではない。 このデータホルダというコンセプトは、CやCOBOLといった手続き型言語からオブジェクト指向プログラミングに持ち込まれたものだ。 もう一度言う。オブジェクトはデータとそれを操作する関数をセットにしたものではない。 オブジェクトはデータエンティティではない。では何か?

ボールと犬

真のオブジェクト指向プログラミングでは、 オブジェクトは生きている生物だ。私や君と同じように。 オブジェクトは生きている有機体で、それ自身の挙動や、特性や、ライフサイクルを持っている。

生きている有機体はsetterを持てるだろうか? 犬にボールを”set”できるだろうか? 無理だろう。 だが、以下のコードはまさにそれをしている。

Dog dog = new Dog();
dog.setBall(new Ball());

これをどう感じる?

また、ボールを犬から取得できるだろうか? まあ、できるかもしれない、もしその犬がボールを食べて、君が手術をするのであれば。 この場合、確かに、犬からボールを”get”できる。以下のコードが今話したようなことをやっている。

Dog dog = new Dog();
Ball ball = dog.getBall();

またさらにばかげた例がこれだ。

Dog dog = new Dog();
dog.setWeight("23kg");

現実世界でこの処理がどんなか想像できるかな?

君が毎日書いているコードはこれに似ているかい? もしそうなら、君は手続き型プログラマだ。認めなさい。 David Westが彼の本の30ページでそれについて以下のように言っている。

成功した手続き型開発者が成功するオブジェクト開発者に移行するための最初のステップは、ロボトミーだ。

君はロボトミーが必要か? 因みに、WestのObject Thinkingを読んでいた時、私には明らかに必要だったので受けた。

オブジェクト思考

オブジェクト思考を開始すると、君は即座にメソッド名を変更し、多分以下のコードに辿り着く。

Dog dog = new Dog();
dog.take(new Ball());
Ball ball = dog.give();

今、私たちは犬を実際の動物として扱っている。この犬は、ボールを私たちから受け取り、頼めば返してくれる。 ここで特筆すべきは、この犬は NULL を返すことはできない。犬は NULL が何なのかなんて知らないからね。オブジェクト思考は即座にNULL参照をコードから排除する。

さらに、オブジェクト思考はオブジェクト不変性につながる。 犬の体重の例を、君は以下のように書き換えるだろう。

Dog dog = new Dog("23kg");
int weight = dog.weight();

この犬は不変な生きた有機体であり、だれも外からその体重やサイズや名前などを変更することはできない。 この犬は要求に応じて体重や名前を教えてくれる。 オブジェクトの中身を要求するパブリックメソッドには何の問題もないが、 こういったメソッドは”getter”ではなく、”get”というプレフィックスは決して付かない。 私たちは犬から何かを取ろうというのではない。犬から名前を取るのではなく、犬に名前を教えてくれるよう頼むのだ。 この違いが分かるかな?

語義論の話をしているというわけでもない。 手続き型プログラミング思考とオブジェクト指向プログラミング思考とを区別しようというのだ。 手続き型プログラミングでは、私たちはデータを扱い、必要に応じてそれを操作したり取得したりセットしたり消したりする。 私たちはデータの責任者で、そのデータは単なる受動的なコンポーネントだ。 犬は私たちとは何の関係もなく、ただのデータホルダだ。それは生命を持っていない。 私たちはそれから必要なものを何でも自由に取得できるし、どんなデータでもセットすることができる。 これがCやCOBOLやPascalなどの手続き型言語のやりかただ。

それに対して、真のオブジェクト指向の世界では、オブジェクトを生きた有機体のように扱い、 オブジェクトには生まれた日と死ぬ瞬間がある。また、君が望むなら、アイデンティティや性質を持たせてもいい。 犬にはデータの一部(例えば体重)をくれるよう頼むことができるし、犬はその情報を返してもよい。 ただ、この犬は能動的なコンポーネントであることを忘れてはいけない。 こちらの要求に対し、何をするかは犬が決めるのだ。

以上が、getやsetで始まるメソッドをオブジェクトに持たせることが概念的に間違っている理由だ。 それは、多くの人々が主張するように、カプセル化を崩すということではない。 それは、君がオブジェクト的な思考をしているか、もしくは今だCOBOLをJavaのシンタックスで書いているかということだ。

追伸: そうだ、君はこう尋ねるかもしれない。JavaBeans、JPA、JAXBなどのget/set表記に頼るJava APIはどうなんだ? Rubyに付属するアクセッサ生成を簡易化する機能は? ああ、それらは全て私たちにとっての不幸だ。 手続き型COBOLの原始的な世界に留まることは、真のオブジェクトからなる美しい世界を正しく理解し感謝するのに比べてはるかに簡単だ。

追追伸: 言い忘れたが、setterを使った依存性注入もひどいアンチパターンだ。 それについてはいずれ書く。


以上がYegorの記事。

Javaを始めた当初から今まで、Getter/Setterは絶対正義だと信じ、クラスを作れば無心でIDEの言いなりにそれを生成していたので、 この記事はなかなかに刺激的だった。(まあfinalなフィールドが好きなのでsetterの方はあまり作らなかったが。)

ただ、記事の本質としては、Getter/Setterパターン、つまり、 オブジェクトのフィールドをprivateにし、メソッドを介してアクセスさせるようにすることで、実装の詳細を隠蔽し、 APIと分離させることを図るデザインパターンの技術的役割や目的を否定しているわけではなく、 オブジェクト指向の哲学的な部分にも則り、Getter/Setterパターンを真のオブジェクト界に向けて昇華させましょうと言っているように読める。

犬とボールのやり取りをするコードのビフォーアフターはsetBall/getBallがtake/giveになっただけで、 これだけ見れば処理が変わるわけでもないし、コンパイラに言わせればどっちでもいいだろとなる。 ただ、プログラマにボールを無下につっこまれるビフォーの犬よりも、 自ら能動的にボールを受け取り返してくれるアフターの犬の方が幸せそうで愛らしいのは確かだ。 真のオブジェクト界を垣間見た気がする。