JavaBeansの使われ方がどうも納得できないって話

Pocket

ここ最近、Eclipse Plugin開発の方に興味がいっちゃってちっともアプリ作れてません(´・ω・`)
日記とか称して書いてみたもののすぐにやめちゃったし。
まあ趣味でやってるだけだからやりたいことをやればいいよね(って自分に言い聞かせてみる)。
いずれ備忘もかねてEclipse Pluginのノウハウをまとめられたらって思ってたんですけど、なんだかそういう気分にもならなかったので
ブログの更新も随分音沙汰でした。

それはそうと、いつも「ん・・・」ってなるのがJavaBeansの存在。
ちょっとJavaをいじったりした人(というかプログラミングした人)なら誰でも知ってるようなメジャーな子ですが、
どうも納得できないケースがよく発生する箇所でもあります。

というわけで、今回はこの子についてつらつらとつまらない独り言を書いていきます。

そもそもJavaBeansって?

まずは、JavaBeansについてよく言われている定義みたいなものについて。
これがあればJavaBeansだそうです。

  • publicで引数なしのコンストラクタを持つ
  • privateなメンバ変数とpublicなアクセサメソッド(getter/setter)を持つ
    ※getter/setterはいずれかでも可
  • アクセサメソッドは命名規約に従う
    ※メンバ変数がnameの場合、getName , setNameを持つ
  • シリアライズ可能(定義しない場合もある)

たとえば、こんなのがJavaBeansになります。

public class PersonBean implements Serializable {
    private String firstName;
    private String lastName;
    public PersonBean() {};

    public void setFirstName(String firstName) {
         this.firstName = firstName;
    }
    public void setLastName(String lastName) {
         this.lastName = lastName;
    }
    public String getFirstName() {
         return this.firstName;
    }
    public String getLastName() {
         return this.lastName;
    }
}

そんな難しいものじゃないですね。要はデータを格納しておくのが役割の子です。
中にはイベントの通知を行わせたりすることもあるそうですが、多くの場合はデータ格納以外の役割は持ちません(持たせません)。
「この値を持っておけ」と言われたらひたすら何もせず持ち続け、「値をよこせ」と言われたら何も文句も言わずにすっと差し出す。とても健気な子ですね。

JavaBeansにはいろんな呼び名がある

なお、JavaBeansには具体的な役割に応じて色々な呼び名がされています。
(にわか調べなのでちょっと違う部分もあるかも)

  • Bean :昔ながらの呼び名.ただちょろっとロジックを含むこともある(イベントとか)
  • Entity:DBデータを専用に扱う
  • DTO:データをクラスから別のクラスへ渡すためだけのクラス.ただ大体DBで使われる
  • VO:setterはもたせず,コンストラクタで値を定義するクラス.Immutableパターンと相性がいい
  • 珈琲豆:JavaBeansの日本語訳だそうです.えーホント?(´・ω・`)

※VOは、実際は上の定義から見てJavaBeansとは別人になります

JavaBeansの何がいいのか

JavaBeansの最大の貢献は、たぶん「getter/setterの命名規則が定義されている」ということかと思います。

要は、世界中の人が同じルールでプログラミングしてるんで、サード・パーティで利用しやすいということですね。
特にObjectMapperを用いてJSONデータやXMLデータ、DBの取得値を簡単にBeanに格納できるのはとても強力です。

さらには内容が簡単なため、再利用が簡単にできるというのも大きな強みですね。
実際どのようななプロジェクトでも、一番あちこちで渡したり受け取ったりと再利用されているクラスのひとつになっているのではないでしょうか。

ちなみに、話は少しそれますが下記の記事がgetter/setterについてとても丁寧に書かれてます。
getter/setterとはなんだったのか – プログラマーの脳みそ

じゃあ一体何が不満なのか

ここまではJavaBeansの良い部分を書いていきました。
というか、実際はJavaBeansの存在自体に不満はそんなありません。この子達によってぼくもたくさん恩恵をうけていますし、とてもシンプルで優れたデザインパターンのひとつだと思っています。やっぱりシンプルが一番ですよね。

何が納得いかないって、その使われ方とかです。
とはいっても、ぼくの周辺がそうなだけなのかもしれませんし、ぼくもついつい納得しないまま「まあ他の人もこう使ってるし」で行ってしまってることが多いのですが。
以下、その不満をつらつらと並べていきます。

JavaBeansの全メンバ変数にsetterとgetter両方を持たせたがる

これは他のサイトでもよく記事として扱われていますが、基本的にsetterメソッドが多く使われているシステムは頭のなかでアラートが鳴ってしまいます。setterメソッドがあるだけならまだしも、それが実際にあちこちで使われているのを見ると、もう…ねぇ(´・ω・`)セッターキライ

setterメソッドは悪だ、とまで言うつもりはありません。上にも書いたようにJavaBeansの恩恵を(ObjectMapperなどで)受ける場合にはsetterメソッドは必要になりますし。それについても色々と言われてますが、デファクタなんだから仕方ありません。

ただ、何も考えずにとりあえずsetterを使うのは乱暴だと思います。「setter/getterはカプセル化を壊す」という指摘もありますし、getterとsetterはそういうリスクをわかった上で使うべきだと思います。getterはまだしも、setterは特にカプセル化を壊す諸刃の剣のような危険な子です。闇だよ、この子闇を抱えてるよってsetterを見るたびに思います。

どうもプログラマはReadonlyまたはImmutableなオブジェクトを嫌う傾向があるような気がします(ぼくもそんな好きか嫌いかでいうと嫌いです)。getter/setterを作らないというのは制約に他なりませんが、自由にできないというのが嫌なのでしょう。
でも、クライアント側からの入力には「こんな値論外だよ!」「おっとこの入力は受け付けないよ!」「これは読み込み専用だよ!」といろんな入力チェックを行って制約を入れますよね。そうやって品質を保証し、クラッシュしない堅牢なシステムを作っていきますよね。
それと同じことはプログラム上でも言えるはずで、自らのコードにも制約を入れていくことでより堅牢なプログラムが作れるはずじゃないかなって思います。でも中には「え、setterとgetter両方つくるのが常識でしょ?」という人もときどきいますね。必ず作ろうと教えてるサイトや書籍すらありますし。

要は(´・ω・`)セッターキライです。

JavaBeansはsetter/getterだけを持つクラスと思われてる

たとえばこんなコードをよく見ます。

public class PersonEntity {
    private int personId;
    private int age;
    private String firstName;
    private String lastName;
    // 以下getter/setterのみ
}

public class CompanyEntity {
    private List<PersonEntity> employees;
    private String companyName;
    // 以下getter/setterのみ
}

public class CompanyService {
    private CompanyEntity company;
    private CompanyDao dao;

    // 以下ロジック処理
}

要はPersonEntityとCompanyEntityはDBから取得するデータで、それをCompanyServiceで呼び出してgetterなり呼び出してごにょごにょしています。
データアクセスのロジックをServiceクラスなどで行うこと自体はいいと思います。データアクセス処理はデータ不整合が発生しないようにロジッククラスでのみ扱うべきだと思いますし。
ただ、PersonEntityとCompanyEntityっていうのがすごく違和感があるんです(´・ω・`)特にgetterとsetterだけ持ってるって、もうその時点でgetter使って値を取り出す前提じゃないですかヤダー。

「いや、Entityクラスなんだから当然だろ。DBデータを専用に扱うって自分でも書いてるじゃん」

って指摘されるかもしれないですが、どうしてgetterとsetter以外持ってはいけないんでしょ
getter/setter自体を持つのはいいと思います。今やDaoを扱う場合ってほとんどObjectMapperを使うでしょうし、その場合だいたいは両方持たせるのは必須になると思いますし。
でも、たとえば上記のケースで「全員のフルネームを取り出す」場合、きっとCompanyServiceでこんなことするわけですよね。

  CompanyEntity com = this.company.getCompany();
  List<String> employeeFullNames = new ArrayList<String>();
  for (PersonEntity person : com.getEmployees()) {
       employeeFullNames.add(person.getFirstName() + person.getLastName());
  }

上記のソースだけでgetterメソッドを4回呼び出しているとかね。

ライブラリやフレームワークが扱う場合は仕方ないとして、プログラマがgetter/setterを呼び出すのは極力しない方がいい、と思うんですよね。
特に上記はドメインモデル貧血症臭がします。ちょっと進んで「全員のフルネームはあちこちで取り出すからユーティリティクラスへ移動させよう!」なんてなったら更に貧血臭がぷんぷんします。

そういう場合は「CompanyEntity」「PersonEntity」内に処理を入れちゃえばいいじゃない。

public class PersonEntity {
    // メンバ変数を定義    
    public String fullName() {
        return firstName + lastName;
    }
    // 以下getter/setter
}

public class CompanyEntity {
    // メンバ変数を定義    
    public List<String> employeeFullNames() {
        if(employees == null) {
            return new ArrayList<String>();
        }
        // Java8の書き方
        return employees.stream()
                        .map(PersonEntity::fullName)
                        .collect(Collectors.toList());
    }
    // 以下getter/setter
}

public class CompanyService {
    private CompanyEntity company;
    private CompanyDao dao;

    // 以下ロジック処理
    List<String> employeeFullNames = company.employeeFullNames();
}

ただ、○○Entityって感じのクラスにすると、なんだか「setterとgetterしか作っちゃいけない(他のロジックは入れちゃいけない)」みたいに思っちゃいますよね(すくなくともぼくは不安になっちゃう)。
実際はどうなんだろ? ただ、JavaBeansの定義ってあくまでも「アクセサメソッドを持つ」ことであって、「アクセサメソッドしか持たない」ことではないですしね。

とはいってもそれが他のメンバにとって常識的になっている場合はしないほうがいいかもですね。混乱の元になるでしょうから。
その場合は○○Entityなんて書かずに「Person」「Company」ってクラス名にしちゃうほうがいいのかなって気がします。

JavaBeansのsetterメソッドは簡素にしないといけないと思われている気がする

これも納得いかないです。ただ単純にメンバ変数にセットするだけしちゃいけないというような暗黙の風習。
それってメンバ変数をpublicにするのとどう違うんでしょ?
(まあ、セットする必要があるのならメンバ変数をpublicにするよりはsetterを用意したほうがいいと思いますが)

もちろん無理に何か入れる必要はないですが、そこで入力チェックなどを行うことで事前にエラーを発生させることもできます。
エラーが起きる箇所を制御できるというのは、よくわからない場所でエラーが発生するよりもよほど修正がしやすいですよね。

※注記※
たぶん、getterは単純に値を返すだけにとどめておいたほうがいいと思います(他に使う人もそれを期待してることがほとんどのはずなので)

まとめ

こういう話をしだすと「じゃあどういう作り方がベストなのか」という終わりのない話になってしまいがちですが、正直ぼくも何が正解なのかよくわからないですし、setter/getterが悪と断定するほど悪いものとも思いません。結局は使い方次第です。

ただ、JavaBeansひとつを作るにしても以下のことは意識してないといけないんじゃないかと最近思い始めています。
(むしろJavaBeansという再利用性が高い箇所だからこそともいえますが)

  • メソッド=動作の定義であるということ
    全てのメソッドには意図を持たせなければいけない
    なんとなくgetter/setterを作りがちですが,それはやめた方がいいと思う
    何も考えずに作りたいように作ってるとひどいことなります(体験談`)
  • 自由=リスクであるということ
    なんでもできることが必ずしも良いこととは限らない
    値を自由に変えられる場合はなおさら

補足:setterをプログラマに使わせない方法

そうはいってもObjectMapperだったりフレームワークの仕様だったりで全部にsetterをつけなければならないケースって多いと思います。
そういう時、@deprecatedをつけると便利です。

/**
 * @deprecated プログラマは使ったちゃダメ
 */
@Deprecated public void setXXX(String set) {
    //...
}

こうやっておけば、誤って使ってもすぐに警告として出てくるんで早期に使用の有無を知ることができます。

ところで

○○Beanとか○○DTOとか○○Entityとか書いてるクラスにsetter/getter以外のメソッドを入れることに抵抗を感じるのはぼくだけなのかな(´・ω・`)
いつも暗黙の了解的にいれないでいるけど、そうすると値のロジックどこに入れようってなっちゃう。
(いつも「なら○○Beanなんてつけなきゃいいじゃない」で解決してる or プロジェクトでそういう命名規則になってる場合はもう諦めてるけど、それでいいのかなぁ。。)

JavaBeansの使われ方がどうも納得できないって話” への3件のコメント

    • コメントありがとうございます(^-^)/
      やっぱり気になってる人もいるんですね。ちょっと安心しました!

      リンク先をみると、結構バラバラだけどDTOが比較的多いみたいですね。

  1. ピンバック: 新人さんにオブジェクト指向について教えたときの説明 | たそがれブランチ

@br_branch へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です