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

足し算ではなく掛け算で実装する

一人アドベントカレンダー 2016 14 日目 by @takkyuuplayer

1行まとめ

足し算ではなく掛け算で実装する。

概要

昔は足し算的に実装していた。最近は掛け算を意識した実装になったと思う。

足し算的実装

例えばユーザーの新規登録を考えるときに

  1. POSTされたフォームデータをバリデーションして
  2. バリデーションが通れば、それをデータベースに保存する

という流れを思いついく。若かりし頃、流石にコントローラにべた書きすることは無いものの、その流れをそのまま実装していた。

sub controller {
  my $self = shift;
  my $params = $self->request->params;
  my $validator = Validator::Signup->new($params); # Validator::Signup は事前に別途作っておく

  if (not $validator->is_valid) {
    $self->template_values->{errors} = $validator->errors;
    return $self->render('signup-form');
  }

  my $handle = Model->create_handle;
  Model::User->create($handle, $validator->params);

  $self->redirect('signup-complete')
}
package Model::User;

sub create {
  my ($handle, $signup_form_values) = @_;

  $handle->insert(user => {
    user_id => $signup_form_values->{user_id},
    email => $signup_form_values->{email},
    ...
  });
}

といったところだ。

掛け算的実装

それを最近は

  • フォームをバリデーションする機能を持った Validator
  • 新規登録用のデータを受け取って、データを保存する Model

の「独立した」2つの機能を組み合わせれば、後は順番通りに呼び出せば実現できる、と考えるようになった。「独立した」機能ということで、モデルはこう書き換わる。

package Model::User;
use Params::Validate qw(validate SCALAR);

sub create {
  my $handle = shift;

  my %args = validate(@_, {
    user_id => { required = 1, type => SCALAR, }
    email   => { required = 1, type => SCALAR, }
    ....
  })

  $handle->insert(user => \%args);

}

モデルの中に validate が追加されただけで、ほとんど違いが無いが、これにはどういうメリットが有るのだろうか?

更新された Model::User->create には2つ大きなメリットがある。

例えば今後 google だとか Facebook といった 3rd party と連携してアカウント作成をしたくなったとする。その際

  • Model::User->create を再利用できる

というのは、渡すパラメータが $signup_from_values に限定されていないことから容易に分かる。

他にも重要なメリットがある。それは

  • 集中すべき範囲を限定できる

事だ。新規登録時にどの情報を必須として扱いどれがオプショナルなのかは Model::User 内に全て記載されていて、変なデータを渡すとバリデーションエラーで弾かれる。だから 3rd party ログインの実装中は「3rd party およびユーザーから何の情報を受け取るべきか?」に集中できる。

連携する 3rd party が増えれば増えるほど、掛け算で効果が現れてくるだろう。

とはいえ、初回でいきなり独立した機能として実装するのは時間のムダではないのか?

断言しよう。独立した機能として実装する方が短時間で実装できる。モデルの作成中にフォームからこういうハッシュが渡ってくるはずだから、こんな感じで insert すればいいか、などと考える必要はない。ただDBやビジネスの制約上「ユーザー登録にはこのデータは必須で、オプションでこういうデータも受け付ける事が可能」ということだけに注力すればよい。逆にフォームを作るときは、ただ「モデルが受け付け可能と言っているデータ」を受け取ることだけに注力すればいい。

これが足し算的実装方針だと、実装中にモデルやフォームを行ったり来たりするハメになる。後で「やっぱりこのデータも必要だ」とフォームを変更したり、逆に「フォームからこんなデータも渡ってくるらしいぞ」とモデルを変更したりしたくなるからだ。

集中すべき範囲を限定できる分、独立した機能として実装するほうが短時間で終わる。その素晴らしさがあまり実感できない場合は、受験で考えると分かりやすいかもしれない。国数英理社それぞれ1時間ずつのテストと、まとめて5時間のテストどちらが良いだろうか?ある教科がものすごく得意で30分で終わらせられる分他の苦手科目に時間多く割ける!という人は別として、どれもやはり1時間くらいかかるのだとしたら分かれている方がやりやすいという人が多いだろう。まとめて5時間のテストだと英語を解いている最中に突然さっきもう少しで解けそうだった数学の問題が気になってそっちに戻ったり、理科は時間がかかりそうだから先に社会を解こうとしたらそれも結構な論述が必要で、一体どっちから先にやっつけるべきか?と混乱しかねない。

かつての足し算的実装とはなんだったのか?

全てをコントローラにべた書きしている状態に比べ「データのインサートをテストしやすい」という利点はあるものの、本質的にはコントローラにべた書きしているようなものだったと思う。 それは再利用性に乏しく、また実装中は複数の気にすべきことが同時に頭の中に存在しており集中力も半減する。

まとめ

足し算ではなく掛け算で実装する。

次回予告

明日の担当も @takkyuuplayer です。