読者です 読者をやめる 読者になる 読者になる

/home/by-natures/dev*

ソフトウェア開発者として働く人の技術的なメモ

DevLove 「オブジェクト指向でコードが書けるようになろう。」に参加しました

DevLove「オブジェクト指向でコードが書けるようになろう。」に参加してきました。

講師の方は、有限会社システム設計の増田さんです。2012年に参加した Heroku Junior Camp でもお世話になりました。(「Heroku Junior Camp に参加しました」も合わせてどうぞ。)

今回は増田さんが CodeIQ に出題した問題を、みんなで解きました。最初は個人で解き、グループで話し合って、着眼点に違いがあるのを感じます。そして増田さんの解説、という流れです。

問題

問題はこちら(CodeIQ から抜粋):

あるWebサービスの会員登録の画面で、以下の入力が必要です。 ・氏名 ・メールアドレス ・パスワード ・パスワード(確認用) 入力は、以下のルールを満たす必要があります。 ・氏名は20文字以内 ・メールアドレスは、正しい形式 ・パスワードは、半角英数字で、大文字・小文字・数字の三つが混在 ・パスワードと確認用パスワードが一致する この入力チェックを実現するためのクラスを考えてください。 ※ 一つの問に複数のクラス名を解答してもかまいません。 ※ 同じクラス名を複数の問に重複して回答してかまいません。

「コードを書かせない CodeIQ」というのも面白い出題ですが、こういう問題の方が CodeIQ でエンジニアの志向を測るにはよいのかも、と感じました。時間は10分で、回答するのはクラスの実装ではなく「クラス名」と「クラス数」のみという非常にシンプルな問題です。

回答と解説

私はいくつか案を考えた後、「名前」「メールアドレス」「パスワード」で共通する、値を確認する操作を1つ括りだしてインタフェースを利用することにしました。Java 風に書けば以下の通りです:

public interface InputValue{
  public boolean validate();
}

public class Name implements InputValue{
  private String name;
  @Override
  public boolean validate(){
  ...
  }
}

public class MailAddress implements InputValue{
  private String mailAddress;
  @Override
  public boolean validate(){
  ...
  }
}

public class Password implements InputValue{
  private String password1;
  private String password2;
  @Override
  public boolean validate(){
  ...
  }
}

対象(ドメイン)と操作をくくりにすることを、ドメイン駆動設計と呼ぶそうです。この場合だと、名前と名前に対する操作(validate)が1つのクラスになっていることを指します。僕の場合は大学のころに習ったオブジェクト指向の考え方が元になっているのですが、ドメイン駆動設計としての考え方を改めて知ることができました。

他の参加者の回答

単純な問題のようですが、参加者からは実に様々な意見がでました。以下、箇条書きでご紹介します:

  • Validationごとに1つのクラスとして独立させる
  • User というクラスに「名前」「メールアドレス」「パスワード」のフィールドを持たせ、それとは別に Validator クラスを用意する
  • User クラスについても、管理者と一般ユーザが考えられるため、BaseUser クラスと AdminUser クラスを用意する
  • パスワードの同値チェックについて、パスワードオブジェクトを2つ生成し、別クラスでその同値性を確認する
  • 仕様が小さいため、Validator クラスは設けずに全て正規表現等で処理する

実際の処理についても、オブジェクト生成時にコンストラクタで異常値を弾くようにするとか、アノテーションを利用して異常値を確認するなど様々な意見がでました。

最も個性が出るのが、クラス名やメソッド名の付け方です。Validate / Check、Validator / Checker、prefix / suffix (NameChecker / CheckName)、NameChecker / NameFormatChecker 、User / Member、はたまた「会員登録」というシナリオを考えて Register / Registry, etc etc...

増田さんの解説

「何かが正しい/間違っている設計、というのはないと語る増田さんですが、増田さんが設計する上で意識していることを教えて頂きました。何度も繰り返していたのが、「他の人が見て保守しやすいように設計すること」です。他人というのは、数年後の自分かもしれません。参加者の方で「納期が短く、設計をあまり考えずに納品したプログラムが、数年後に機能追加要望が来て、自分の書いた複雑なプログラムを再開発する羽目になった」と仰っている方がいました。

納期が短い場合は、どこで折り合いを付けるか…という話もありましたが、先々を考えて、「仕様だけ満たすだけではなく、もう一歩先へ進んでほしい」と増田さんは教えてくれました。今回の問題も、仕様を満たすだけではなく、こういう追加要望があるのではないかなど、顧客の要望以上のものを設計・開発することが大切だと言います。

これを踏まえ、オブジェクト指向で大切なことを教えて頂きました:

単一責任

役割を小さくすれば、メンテナンス性や保守性があがります。

データ+ロジック

ドメイン駆動設計で、データとロジックを一つにしてクラスにすると良いとのことです。

関心ごとの分離

MVC 間で名前が混ざり合わないようにしたり、プレゼンテーションとモデルを分けて考えるなど、変数名・クラス名など命名規則でも関心ごとの分離は大切です。

例えば、今回の問題で NameForm というクラス名にした場合、"Form" というのは技術的な関心で、本来の「名前」という機能には関係ないものであるため、技術的関心とモデルが混ざり合っている状態になっています。更に "Form" は取り払っても意味が通じるため、なるべくシンプルにしておくべきです。

メソッドについては、パスワードの話が上がりました。パスワード文字列内に、大文字・小文字・数字が全て含まれていなければならない仕様ですが、増田さんはこのメソッドに対して "passwordStrength" などの名前を付けるそうです。メソッドを使う側からすれば、関心があるのは「大文字・小文字・数字が全て含まれているか?」ではなく、「このパスワードの強度は大丈夫だろうか」ということなので、仕様は隠して "Strength" という関心をメソッド名に込めるとのこと。

手続き的な設計とオブジェクト指向らしい設計

「Validator クラスや Utils クラスのような、ロジックの置き場所を作ると、考えることを止めてしまう」という増田さん。オブジェクト指向で開発することに慣れるために、クラスが大きくなってきた場合、分割できないかを考えるべきだと言います。特にメソッドや機能が頻繁に追加し、肥大化しているようなクラスは、「とりあえずこのクラスに」という考え方で肥大化してしまっていることが多く、恰好のリファクタリング対象だとのことです。

この「大きなリファクタリング」については、マーチン・ファウラー/ケントベックの本「リファクタリング」にも紹介されています。

感想

2回目の DevLove でしたが、やっぱり勉強会って楽しいですね。

私は先輩からの紹介で参加しましたが、参加者の多くが「社内の人で、外の勉強会に行く人があまりいないので、自分で率先して行って社内の雰囲気を変えたい」と仰っている人が何人もいて、そんな前向きな人たちとオブジェクト指向の話ができるので、とてもよい刺激になりました。

この勉強会を教えてくれた先輩と、増田さんを始め DevLove 運営、CodeIQ の方々、ありがとうございました!

(自分の回答を修正してみる)

interface が邪魔な気もしてきます。。

public interface MemberProperty{
  public boolean validate();
}

public class Name implements MemberProperty{
  private String name;
  @Override
  public boolean validate(){
  ...
  }
}

public class MailAddress implements MemberProperty{
  private String mailAddress;
  @Override
  public boolean validate(){
  ...
  }
}

public class Password implements MemberProperty{
  private String password1;
  private String password2;
  @Override
  public boolean validate(){
  ...
  }
}

public class Member{
  private Name name;
  private MailAddress mailAddress;
  private Password password;
  ...
}