Sunday, September 23, 2012

Getter や Setter を使わない

先々週あたりでしたか, Twitter の TL 上で流れてくる話題の中で, 「getter や setter を使うな」 というものがありました. 『ThoughtWorks アンソロジー』 という書籍の中で, オブジェクト指向プログラミングの基本ルールのひとつとして挙げられているようです. わたしはまだ読んだことがありませんが, にわかに気になってきました. これまで, 「インスタンスフィールドは常に private とし, フィールドへのアクセスには getXXX や setXXX といったメソッドを使いましょう. これはオブジェクト指向プログラミングの基本でもあります」 程度の理解しかありませんでしたので, 「getter や setter を使うな」 というルールをはじめて目にして, 少したじろいでいます.

さすがに, あらやるフィールドに対して getter や setter を用意するということは, わたしは経験がありませんし, それが少なからぬ現場で普通に実践されている開発上の慣習になっているらしいことも, わたしには未知の世界の物語ではあります. とはいえ, getter を使いたくなるケースは少なくない気がしますし, getter ほどではないにせよ setter を使いたくなるケースもなくはないような気がします.

この話題について, 少しインターネット上で検索してみましたところ, 日本語圏では id:bleis-tift さんによる フィールドごとに getter/setter を用意するな (Jan 26, 2009) や 前提がおかしい (Jan 27, 2009) という記事が目にとまった以外はあまり見かけないのですが, 英語圏ではけっこう論じられているようです. (わたしのブラウザの検索オプションがおかしいだけかもしれませんが...)

いまのところのわたしなりの理解で, getter や setter を使ってはいけないという命題を, もう少しやわらかく言い換えるとしたら, 次のようになりましょうか. 「そのクラスの責務は何ですか? 単に値を保持するだけなのですか? そうではないでしょう? 本当に getter や setter が必要なのか考えましょう. 他のクラスが, getter や setter を使っておこないたいことは, 実はそのクラスの内部でそのクラスの責務としておこなうべきことなのではないですか?」

頭でわかったつもりでも, 実際に実践してみないと何とも言えませんが, 要は "Tell. Don't Ask" ということのようです.

ひとつだけ, しょっぱい例で, 考えてみたいと思います. たとえば, 平面上の点をあらわすクラスを作ってみます. 点の位置は, 直交座標系であらわします. なお, equalshashCodetoString は割愛しています. また, double 型の値が NaN や無限大をあらわす可能性についても, ここでは割愛します.

public class Point {
    private final double x;
    private final double y;
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return x;
    }
    public double getY() {
        return y;
    }
}

いま, ふたつの点 A (2, -1) と B (-1, 3) が存在すると想定し, 二点間の距離を得たいとします. クライアントコード (テストケース) はたとえば次のようになるでしょうか.

    @Test
    public void testUsingGetters() {
        Point pointA = new Point(2, -1);
        Point pointB = new Point(-1, 3);
        double dx = pointB.getX() - pointA.getX();
        double dy = pointB.getY() - pointA.getY();
        double distance = Math.sqrt(dx * dx + dy * dy);
        assertTrue(Double.compare(distance, 5.0) == 0);
    }

これに対して, 今度は getter を使わない方法を考えてみます. 上記の例では, 二点の座標をもとに二点間の距離を計算するのはクライアント側でした. そうではなく, 今度は Point クラス自身の責務として, それをおこなうことにしてみます.

具体的には, Point クラスに以下のメソッドを追加します. (メソッド名がいまいちでごめんなさい.)

    public double distantFrom(Point p) {
        double dx = p.x - this.x;
        double dy = p.y - this.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

先ほどと同じ条件で二点間の距離を得るクライアントコード (テストケース) は, 今度は次のようになろうかと思います.

    @Test
    public void testUsingNoGetter() {
        Point pointA = new Point(2, -1);
        Point pointB = new Point(-1, 3);
        double distance = pointA.distantFrom(pointB);
        assertTrue(Double.compare(distance, 5.0) == 0);
    }

これなら, getXgetY も不要です. おそらく, むやみに getter などを用意するのではなく, その getter を使っておこなわれる処理を当該クラスの中でおこなうようにするほうが, むやみに実装を外部に暴露する必要もなくなるし, よりオブジェクト指向的になると言えるのかもしれません. 当該クラスのインスタンスに対して, 内部の状態を尋ねるのではなく (Don't Ask), やるべきことをやらせる (Tell) ということなのだろうと思います.

ひょっとしたら, クライアントが点の座標を使って何をしたいのかを事前に予想するのは難しいので, 単純に getter を用意するほうが楽なのではなかろうか, という意見もあるかもしれません. それに対しては, 必要になったらそのつどメソッドを追加すればいいだけのことでしょう? という反論がありえましょうか. YAGNI ですかね.

ただ, JavaBeans とか DTO とか Entity とか, 暗黙的にせよ明示的にせよ, getter や setter の存在を要件とするフレームワークは少なくないような気もします. そういう場合はどう考えたらよいのでしょう. フレームワークによって注入される以外にクライアント側が下手にアクセスできないかぎりは, まあいいんじゃないの? ということなのでしょうか. ちょっと, ごめんなさい, わたしはまだ全然よくわかっていないかもしれません.

No comments:

Post a Comment