2015/05/18
ウォーターフォールのキャリア・パスが、道具の活用を阻害する
前回の続き、どうしてRailsのような優れた道具を活用する設計がうまくできないのかという話です。
私は、その原因はウォーターフォールのキャリア・パスにあると考えています。
学習には、実践が必要である
ところで皆様、オブジェクト指向って、どうやって勉強しました?オブジェクトについて書かれた本を読んで?私は違います。たしかに本も読みましたけど、オブジェクト指向を正しく理解できたのは、オブジェクト指向言語でオブジェクト指向なプログラムを組んでいた時でした。突然コードからif
やswitch
がなくなってとても読みやすくなって、ああこれがポリモルフィズムなんだと唐突に理解しました。プログラムを組まずにオブジェクト指向を理解できるのは、よほど頭が良い人だけなんじゃないかな。
マーチン・ファウラーのリファクタリング本。本を読んだ時には難しくてなんだか分からなかったのですけれど、実際のプログラミングでリファクタリングしまくってみたら、気がついたら体が勝手に綺麗なコードを書くようになっていました。最初は本の通りに手順をなぞるのも大変だったのにね。今では、リファクタリングはあまり頭を使わなくて済む、それでいて効果が高い楽しい作業となっています。
クロージャー(言語機能のclosureの方)の使い方も、集合操作のmap
やreduce
の使い方も、リレーショナル・データベースのテーブル設計も、あとは車の運転もバイクの運転も、私は、何度も繰り返して実際にやることで学びました。
たぶん、Railsの流儀も同様で、実際にやってみないと身につかないんだと思います。
でも、設計者は実践しない
Railsを実際にやってみるのは、それほど大変ではありません。とても良く出来たチュートリアルがありますから、それをやればオッケー。実際にコードを手で入力して、いろいろ変えて試してみて、どうしてこのような仕様になっているのかを考えながらやれば完璧です。Railsの論理はとても優れていますから、チュートリアルが終わる頃にはWebアプリケーションがどうあるべきなのかを語れるようになっているでしょう。
でも、設計者と呼ばれる人たちは、それをやりません。彼らはプログラマーではなくて、プログラミングは業務範囲外ですから。だから、Railsの流儀をいつまでも理解できない。大昔に彼らがプログラマーだった頃に学んだVisual Basic 6.0の流儀、画面に処理をのせていく形で、今日も設計してしまいます。
昨日も今日も明後日も、そして10年後もCOBOLでメインフレームのバッチを組むなら、過去のプログラマー経験を活用しての設計は可能だったでしょう。売上ファイルと商品マスターをマッチングして売上レポート・ファイルを作る処理(SQLなら1行ですね)すらコードをガリガリ書かなければならないような生産性が低い状況なら、プログラマーと設計者を明確に分けることに生産性を向上させる効果があったのでしょう。
進化した道具を、設計に活用すればいいのに
でも今は、新しい技術が次から次へと出てきていて、それらの技術を正しく使えばプログラミングの生産性は大きく向上します。
たとえば、この前ClojureでO/Rマッピング・ツールを作ってみたら、そのコードの行数は全部で453行でした。O/Rマッピング・ツールの設計書は、たぶん453行よりも多くなりますよね?いまどきの言語なら、内容が明確で無駄がない設計書として、コードが使えるんじゃないかな。
話を元に戻して、Railsの場合で考えてみます。前回書いたように、入力値の検証を実施するコードはとても簡潔です。設計書に「必須」と書き入れる作業と変わりません。そして、rails console
でRailsのコンソールを起動すれば、実際に動かして試すことができます。ユニット・テストのコードを書けば、繰り返してテストすることもできます。ツールを使えば、モデルをグラフィカルに見ることもできます。費用は同じくらいなのに、効果ははるかに大きいんです。
たったら、Excelでテーブル定義表を書く代わりに、画面定義書に入力チェックについて書き込む代わりに、Railsのモデルを定義(モデルのメソッドについては、これまで通りに日本語の設計書に書いても構いません)した方が得なのではないでしょうか?もしそうできるのであれば、前回挙げた問題が解消されてRailsの便利機能を活用できるようになりますから、システム開発は楽になるはずです。お客様への説明用の画面定義を別途書く必要があるかもしれませんけど、モデルがあるならその作業は楽になるはずです。少なくとも、項目の型や一般的な入力検証については書かなくて済みます。
このとても楽しそうなセカイの実現を邪魔しているのが、ウォーターフォールのキャリア・パスなんです。プログラマーを卒業して設計者になったのだから、プログラミングなどという下等な作業はやらないという考え方です。過去に様々な技術を経験した蓄積があるのだからRailsのチュートリアル程度は1週間もかからずにできるはずなのに、やらない。RailsやRubyというプログラマーと共通の道具を使っても設計という異なる作業はできるのに、やらない。ただ、それだけ。
実にもったいない話だと、思いませんか?
2015/05/14
道具が進化しているのに、システム開発が楽にならないのは何故?
いまどきの道具、たとえばRuby on Railsを使うと、ビックリするくらい簡単にWebアプリケーションを作れます。このような優れた道具を活用できるのだから、システム開発はいつも大成功で儲かってウハウハ……なんてことはなくて、プロジェクトは必ず炎上して働いても働いても我が暮らしは楽になりません。いったい、どうしてなのでしょうか?
仮説 #1: 実はRailsは便利じゃない←やっぱり便利だった
実はRailsが便利じゃないなら、システム開発が楽にならないのは納得できます。
ただ、自分で書いておきなからなんなのですけれど、この仮説は明らかに間違ってそう。Railsの便利機能を紹介して、仮説が間違っていることを証明しましょう。Railsには様々な便利機能があるのですけど、今回は、検証機能を取り上げます。
Railsでは、入力値の検証を、モデルに対して記述します。たとえば社員モデルの社員番号と指名を必須入力としたいなら、以下のように記述します。とにかくやたらと簡単。
class Employee < ActiveRecord::Base
validates :code, :presence: true # この1行だけで、社員番号は必須入力になります。
validates :name, :presence: true # 氏名に関しても同様。
end
検証機能を使っている場合、そのユニット・テストの作成も簡単です。以下のような感じ。
test "validations" do
employee = employees(:employee_1) # テスト用のデータを取得
employee.validate! # テスト用のデータが正しいことを確認。
assert_raise ActiveRecord::RecordInvalid do # 社員番号の必須入力が正しく機能しているか確認。
employee.code = nil
employee.validate!
end
assert_raise ActiveRecord::RecordInvalid do # 氏名も同様に確認。
employee.name = nil
employee.validate!
end
end
少し補足します。必須チェックでエラーになるケースには、nil
の他にも""
(空文字列)や" "
(空白のみの文字列)や"\t"
(タブ文字のみの文字列)などが考えられます。もしRailsの検証機能を使わずにif
文でチェックしていたなら、これらのすべてのケースをテストしなければなりません。Railsの検証機能を使っている上のコードでは、それらがたった1つのnil
の場合のテストだけで済んでいるわけです。
あ、そうそう、Railsの流儀に従っているなら、入力値検証のテストはこれで終了です。ブラウザを開いての手動テストは不要。Railsのビューは、一般に以下のようなコードになるのですけど……。
<%= form_for(@employee) do |f| %>
<% if @employee.errors.any? %>
<div id="error_explanation">
<h2><%= @employee.errors.count %>件のエラーがあります。保存できませんでした。</h2>
<ul>
<% @employee.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :code %><br>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
1行目で使っているform_for
や途中のlabel
、text_field
は、Railsには検証機能があることを知っています。なので、エラーがある項目については、<label>
や<input type="text">
をエラーがあることが分かるように表示してくれます(赤い枠で囲まれる)。if @employee.errors.any?
のブロックで、エラーの内容も表示できています。早い話が、Railsの流儀に従って上のコードのようにビューのコードを書くだけで、以下のキャプチャのように、エラーがある場合は適切な表示がなされるというわけです。
#次の仮説の検証コードなので画面に好きな言語が入っていますけど、無視してください。あと、CSSを書いていないため見た目が悪いですが、ご容赦ください。
うん、やっぱりRailsは便利ですね。わずか数行のコードを書いてRailsの流儀に従うだけで、手動のテストなしで入力値の検証を実現できるのですから。
仮説 #2: Railsでは機能不足←機能は十分だった
「先程のは、要件が単純だから上手くいっただけだ。実際の開発ではもっと要件が難しくて……」みたいな意見があるかもしれません。検証してみましょう。
社員には「好きな言語」と「好きな業界」という属性があって、社員番号の最初の1桁が「D」(Developerの略。SEなどという日本でしか通用しない用語は絶対に使いません)の場合は「好きな言語」が必須、「S」(Sales staff)の場合は「好きな業界」が必須となるような場合を考えてみます。
この場合に対応するためにRailsのリファレンスを見てみたら、値が空でないかの検証に:if
というオプションがありました。これを使用してみます。
class Employee < ActiveRecord::Base
validates :code, presence: true
validates :name, presence: true
validates :favorite_language, presence: {if: -> (employee) { employee.code.try(:at, 0) == "D" }} # 1文字目がDなら、必須。
validates :favorite_industry, presence: {if: -> (employee) { employee.code.try(:at, 0) == "S" }} # 1文字目がSなら、必須。
end
ちょっと面倒なコードになっているのは、code
がnil
の場合を考慮しなければならないためです。なので、指定した箇所の文字を取得するメソッドであるat
を、try
で囲って呼び出しています。-> (parameter) { body }
は、Rubyのラムダ式です。
「いや、もっと要件が複雑な場合があり得る。職掌は、他の情報も含めて総合的に判断する。社員番号の1桁目が「D」であっても、営業部に所属している場合は営業として扱う」なんて場合もあるかもしれません。
ふむふむ、なるほど。社員番号は社員を一意に識別する番号なので、変更できない。でも、職掌は変更になり得る。だから、実は社員番号の1桁目での判断なんてのはすでに破綻していて、今は他の情報を使って無理やり判断している……ってそれ、単なる設計ミスじゃあないですか!
そもそも、「カラムXの値がαだったらカラムYの値はβでなければならない」ってのは、リレーショナル・データベースの鉄則である正規化に抵触します(言語や業界を別テーブルに分割していないという正規化違反は、すみません、無視します)。第三正規化は、キー以外は全て主キーに非推移的に関数従属するという、まぁ、私のような文系人間には何を言っているのかわからない話ではあるのですが、主キーの値が決まれば値が決まる属性Xの値が決まると値が決まる属性Yがある場合、属性Xと属性Yを別のテーブルにしましょうというものみたい(品番が「20BS0040JP」の商品(ThinkPad X1 Carbon)の商品カテゴリ番号は「1234」で、商品カテゴリ番号が「1234」の商品カテゴリ名は「コンピューター」のようば場合、商品カテゴリを別テーブルに分離する)。
第三正規化のポイントは全ての属性は主キーにのみ依存しなければならないというもので、もちろん正規化というのは値の話なのですけれど、意味的なところまで拡張して考えれば、favorite_languageやfavorite_industryの存在の有無が、他の属性に依存しているのはおかしいと考えられる訳です。
というわけで、オブジェクト指向屋ならば継承、Railsでは単一テーブル継承を使ってこの問題を根本解決してしまいましょう。
class Employee < ActiveRecord::Base
validates :code, presence: true
validates :name, presence: true
end
class Developer < Employee
validates :favorite_language, presence: true
pend
class SalesStaff < Employee
validates :favorite_industry, presence: true
end
ほら、面倒臭かった:if
の部分がなくなりました。これならとても簡単です。
少し補足。Railsの単一テーブル継承は、子孫クラス全てのカラム+文字列型のtype
というカラムを持ったテーブルを作ると、Railsが自動的にtype
の値と同じ名前のクラスのインスタンスを作ってくれるというものです。上の例で言えば、type
カラムの値が「Developer」ならばDeveloper
クラスのインスタンス、「SalesStaff」ならばSalesStaff
クラスのインスタンスが作成されるというわけ。そうそう、type
はごく普通の属性なので、一般的なオブジェクト指向言語では難しいクラスの変更もできます。なので、職掌の変更も可能です。残った問題は、社員番号の最初の一桁を今後はどうするかお客様と打ち合わせるだけ。
やっぱり、Railsには十分な機能がありますね。正しくモデルを設計すれば、一つ前の仮説検証の時と同じような単純なコードで入力値の検証ができるのですから。
仮説 #3: Railsは難しい←簡単だった
どんなに便利な道具であっても、使いこなすのが難しいのであれば、上手く使いこなせなくて生産性が上がらないかもしれません。
でもね、Railsって難しくないと思うんですよ。以下に、私がRailsを簡単だと考えている理由を挙げます。
- 論理的に作られているので、ある部分を理解していれば他の部分も同じ論理の応用で理解できる。理解しなければならないことも、覚えなければならないことも少ない。
- 文書が豊富。Web上に記事も多い。このエントリーを書くまで
validates :presence
の:if
を知らなかったのですけれど、検索して3分で見つけられました。 - チュートリアルの出来が良い。Twitterみたいなアプリケーションを作るチュートリアルがあって、これをやるだけでRailsの考え方を理解できます。
そもそも、私は普段はRubyではなくClojureを使っていて、実はRubyもRailsも初心者です。そして私は物忘れが激しくなった45歳のおっさん。それなのに普通に使えちゃうんですから、これはRailsが簡単である証拠と言えるのではないでしょうか。
仮説 #4: Railsの機能を活用する設計ができない←たぶんコレ!
これはもうアレだ。にわかには信じられないけれど、せっかくの便利機能を敢えて使ってないんじゃ……。
私がこんな無茶な仮説を出すのは、これまでの経験で無茶なやり方の開発がされるのを見てきたからです。
システム開発の現場では、画面の設計から入る方式をよく見ます。どのような入力項目があって、どのような入力値の検証をするかを書いた文書を作る方式です。このやり方の場合、画面Aと画面Bで入力値検証が異なる危険性があります。画面Aと画面Bでは同じだったとしても、このあと作られる画面Cの設計書でも同じ入力値検証になるかは、設計が終わるまで分かりません。だからそう、画面の設計を進めていく開発プロセスの場合、モデルに対して検証内容を設定していくRailsの検証機能は使えないわけです。if
を使って入力値を検証するロジックを書いてWebブラウザを開いて手動でテストしまくるわけで、それじゃあ生産性が上がるはずなんかありません。
オブジェクト指向のクラスのような大きな塊から順に詳細化していくのではなく、細かな項目である属性をいきなり決定していく設計の方式もよく見ます。「得意な言語に記入される内容は?どのような場合に入力が必須になりますか?入力がなかった場合のエラー・メッセージは?」みたいにヒアリングを進めていく感じ。このようなやり方で行く限り、単一テーブル継承を使って整理した場合のような単純なモデルは出来上がらないでしょう。:if
を使った面倒なロジックと、そのロジックを使う場合に対応するための面倒なテストが必要になってしまいます。こんなのが積み重なるのですから、そりゃあ、残業が増えますよ。
あと、そもそもRailsでは、データに対する操作画面のあり方を定義しています。Railsのルーティング情報はconfig/routes.rb
に書くのですけれど、ここにresources :employee
と書けば、それだけで社員を一覧、閲覧、登録、更新、削除するためのルーティングが定義されます。でも、「まず画面遷移を設計して……」という開発プロセスの場合は、このような便利機能は使えず、画面毎にURLやパラメーターを決めていくことになります。その場合はRailsの前提が壊れるので、使えない便利機能も出てきます。たとえば、普段は何も意識しないでも自動で実行されるのでこのエントリーでは触れていない、Webアプリケーションの脆弱性の一つであるCSRF(Cross Site Request Forgeries)への対策とかね。RailsとCSRFでGoogle検索するとCSRF対策を無効にする方法が見つかるわけですけれど、CSRF対策をオンにしたままだと動作できないようなRailsの流儀から外れた画面遷移を実装しなければならない場合は、まぁ、CSRF対策を無効にするしかありませんよね。で、アプリケーションを脆弱なまま放っておくわけにはいかないので、独自のCSRF対策を入れ、頑張ってテストする。Railsは生産性が高いというのを前提にしたコスト計算をしているなら、プロジェクトが炎上するのも当たり前でしょう。
うん、これが正しそうです。Railsの流儀から外れた設計を、それもモデルではなく画面として設計するプロジェクトが多いから、私は貧乏らしい。
でも、いったいどうして、Railsの流儀に従ったり、モデルとして抽象化してアプリケーション全体を設計したりすることができないのでしょうか?それについては、また今度考えさせてください。
2015/04/11
ifとandの私的使い分けルール
Wikipeaidのif文のページの下の方の「論理積・論理和による擬似的なif文」のところに、andやorでif文と同様なことができると書いてあります。
たしかに
(if (odd? x)
(println "TVの音量は偶数で!"))
は、
(and (odd? x) (println "TVの音量は偶数で!"))
と同じ動作をしますもんね(Clojureのand
は、内部でif
を使っているマクロですし)。
ただし、この2つのコード、戻り値は異なっているんですよ。
if
で条件が偽でelse
が省略されていた時はnil
で、and
で最初の引数が偽だった場合は最初の引数(上の例の場合はfalse
)が返ります。そしてClojureでは、nil
とfalse
を異なるものとして扱う場合があります。例として、私が大好きなkeep
関数に登場していただきましょう。
(keep #(if (= (:programming-language %) "Clojure")
(:name %))
systems)
;; この程度だとfilterとmapで書いた方がキレイなのですけど、例なのでご容赦ください。
このコードでは、if
の代わりにand
を使うことはできません。and
を使うと、Clojureなシステムの場合は名前を、そうでない場合はfalse
を返すという無意味な処理になってしまいますもんね。
あと、cons
やconcat
、mapcat
のようなシーケンスを作る関数では、nil
を要素数がゼロのシーケンスとしてみなしてくれます。もしこれらの関数にfalse
を渡したら、実行時エラーになってしまいます。
つまり、nil
はfalse
よりも使い勝手がいいんですよね。偽を表現する場合にも、存在していないことを表現する場合にも、空の集合を表現する場合にも使える。
だから、結果がどのような使われ方をするか分からない関数の戻り値に関係するような場合、私はif
を使います。そうでない場合、結果を同じ関数内で消費するような場合は、一行で書いても違和感が少ないand
を使うようにしています。これが私のコードを書くときのルールなわけですな。
#あと、どうしてもelse
が必要な場合は、if
を使うしかないのでif
を使っています。
そうそう。最初に挙げたTVの音量の場合はどちらにするかといえば、println
という処理をするかどうかの判断なので、処理のための分岐であることが明確になるように、私ならwhen
を使います。