はじめまして。転職会議ユニットでエンジニアをしている狩野です。
元々PHPerでしたが、最近はRuby(Rails)やReactを触ったりして楽しくやっています。
今回は、中堅エンジニア*1が最近受けた質問とそれに対する回答を、今までのシステム開発で得た知見と併せてブログを書きたいと思います。
はじめに
最近、若手のエンジニアから「クラスとかメソッドってどう分けるんですか?」という質問を受けました。
私は「実世界をそのまま表現するだけですよ。」と答えました。
真面目に答えたのですが、若手エンジニアには全く伝わっていないようでした*2。
良い機会なので、実世界の抽象化について少し掘り下げて書いてみたいと思います。
システムとは?
基本的に、システムは「あるドメインを体系的に表現した仕組み」です。
システム開発*3とは、それを作る行為になります。
「ドメイン」とは全体の中に定義される一部の領域のことを指します。
日本語にすると「領域」です。事業を展開する領域*4と考えるのが分かりやすいです。
実際にその領域にいる人々*5に対して、我々はサービスを作って届けています。
ではどのように表現するのでしょうか。実世界は複雑です。そのまま表現はできません。ですので、モデル化(モデリング)して表現をします。
モデル化とは、抽象化のプロセスです。抽象化を行なうには知識(経験やセンス)が必要になります。
抽象化と言うと「曖昧にする」という印象ですが、寧ろ「強調したい一面を明確にする」為に抽象化を行います*6。
抽象化について
例えば「人間」についてモデルを考えてみましょう。
「人間」とはなんでしょう?辞書を調べると「哺乳類、道具を使う、複雑な言語を持つ」と出てきました。「人間」を抽象化すると「哺乳類、道具を使う、複雑な言語を持つ」ということになります。
ここで疑問が生じます。例えば、生まれたばかりの赤ん坊は道具も使えないし言葉も話せません。そうなると赤ん坊はこの定義から外れます。赤ん坊は人間ではないのでしょうか。
このように、我々の行うモデルは曖昧です。
この場合、より具体的な「人間」という表現では赤ん坊は含みますが、より抽象的な「道具を使う、複雑な言語を持つ」いう表現では赤ん坊は含まないことになります。
この抽象化が絶対的な正解ということではなく、抽象化の進む方向は領域によって異なります。
対象としている領域に対して矛盾なく抽象化していくことが必要になります。
抽象化を行い、いくつかの正解(様々な解釈)の中から採用するモデルを決定します。改めて、関係する側面を抽象化して、集中するモデルを決定します。これを繰り返します。
この過程を経て、モデル化が進みシステムの表現がゴールへと向かいます。
さて、抽象化はどの程度行うべきなのでしょう。
答えは「時と場合による」です。
先にも書いたとおり、抽象化が絶対的な正解はありません。
身も蓋もないのですが、これが事実です。
よくある例えで、地図の話があります。
地図はモデルです。モデルは対象の概念を表現します。
例えば、近所のお店に行く為に世界地図を用意はしないでしょう。反対に、太平洋の航海を行う為に住んでいる街の地図を用意もしないでしょう。
近所のお店に行く時は住んでいる街が対象であり、太平洋を航海する時は世界が対象になるからです。
時と場合によって、適切な地図を使用します。
適切さについて
「適切さ」こそがシステム開発の肝になります。
しかしながら、またしても身も蓋もないのですが、適切さを追い求める方法に正解は無いと思います。
ただそれで終わる訳にもいかないので、主観にはなりますが、3つほど進め方のヒントを挙げてみたいと思います。
1. サイクルを回す
適切さについて、サイクルを回して定期的に見直します。
有名なサイクルとして、PDCA(計画、実行、評価、改善)サイクルや、イテレーションの開発サイクルなどがあります。
最初から完璧な適切さを求めることはできないので、定期的に見直す必要があります。無理のない範囲で小さいサイクルで回して細かく合わせていくのが良いと思います*7。
そういった意味でも、開発手法はウォータフォールよりもアジャイルが適しています。
アジャイル開発の中で、習慣的にリファクタリングを繰り返して、コードをきれいな状態に保ちます。
重要なモデルを追加すると同時に、役に立たないモデルを取り除きます。
可能な限りをコードで表現して、ドキュメントとしても価値のあるコードを書きます。コード単体で意図の伝わることが理想です。
それでも「何故」などはコードに残せないので、必要があればWhyをドキュメントとして表現します*8*9。
実世界をモデル化する作業を「野球のグローブ」や「革のジャンパー」に例えた話もあります。
最初はしっくりきていなくても、使っている間に自分の手や身体に合った形に馴染んでくる例えです。
モデルも使い込んで見直していく間に蒸留されて、より適切な形になっていきます。
2. コードの見通しを良くする
コードの見通しを良くすることで、適切さを維持します。
「見通し良さ」とはなんでしょう?
コメントをたくさん書けば良いという訳ではありません。寧ろ冗長になって見通しが悪くなることもあります*10。
寧ろ、コメントを書かずに「シンプルに保つ」ことこそが見通しを良くする手段です。
簡単なコメントなら書かなくても伝わるようなコードを考えましょう。また、簡単なコメントを書かなければいけないのであればコードの表現が誤っている可能性があります。
先に述べたように、コメントを書かない訳ではありません。ただ、極力コメントを書かず、コード単体で意図の伝わることを目指します。
コメントは書き換えなくてもコードは動きます。
コードとコメントが密である間はまだ良いですが、改修を続けていると徐々にコードとコメントが離れていきます。
例えば、別ファイルに依存したコメントなどを書いてしまうと、その別ファイルのコードを更新した際にコメントを修正しなければいけないですが、別ファイルに記述されているコメントに気付くのはほぼ不可能です。そうなると実体を表さないコメントだけ残ることになり負債となります。
コメントも運用や保守の対象であることを認識して、コメントを書かなくても意図が伝わるコードを心掛ける必要があります。
コードの読み易さにこだわりましょう。
また、技術的な層とビジネスを表現した層を混在させないことも重要です。
責務の分割して層を分けて、見通しを維持します。
可能であれば、技術的な層はフレームワークやライブラリに任せるのが良いでしょう。
フレームワークが用意している思想に合わせると、共通の認識が持ちやすく、結果的に見通しも良くなります。
その一方で、得た知識を盲信してエンジニアの思想に閉じたコードになってしまわないよう、注意が必要です*11。
オーバーエンジニアリングは柔軟性を与えてくれるように見えて、実は邪魔になることが多くあります。
適切に表現されたモデルはシンプルなものが多いです。
シンプルであることこそが、最適な抽象化です。最もシンプルな表現を心掛けます。
モデルは、それを変更しようとする別の人にとっても分かりやすくなければいけません。シンプルであれば変更も容易です。
YAGNI(You Ain't Gonna Need It.)や、KISS(Keep It Simple, Stupid.)などの法則もあります*12。
3. 共通の言語を持つ
共通の言語を使うことで、適切さを正していきます。
共通の言語というのは「システムに関わる全ての人が、同じ領域においては同じ言語を使う」ということです。
このような言語は「ユビキタス言語」などと呼ばれることもあります。
一例で「あるサービスの名称」について考えてみます。
同じサービスを指す名称であっても、「サービスの商品名」「システム上でサービスを示す呼称」など、別名で呼ばれることがあります。
これらは本質的には同じものを指しているので、理想的には一致しているが望ましいです。「同じものは同じ名前を付ける」が鉄則で、脳内でのマッピングを回避できて理解しやすくなります。
しかしながら、下記に挙げるような制約で「仕方なく別名にしている」というのが現状です。
- システム上では英語で呼ばれ、商品名は日本語で呼ばれる
- 実体を表さない商品名
- etc.
既存でそれなりに実体を表す共通の呼称があれば、それを採用するのが良いと思います。
良くあるのが、商品名は顧客に対する表面的な名称として使いつつ営業内で使用し、サービスの呼称はエンジニア内で使用する、という状況です。
ここで問題となるのが、商品名しか知らない営業とサービス名しか知らないエンジニアの会話です。
その解決策としては、下記に挙げる三択のいずれかになります。
- 営業がサービスの呼称を知る
- エンジニアが商品名を知る
- 通訳を設ける
理想的なのは「営業がサービス名の呼称を知る」です。なぜなら、サービス名の呼称こそがそのものの実体を表現した名称になっている(はずだ)からです*13。
実体を表さない商品名をエンジニアが常にキャッチアップするのは難しいです*14。
通訳を設けるのであれば常にディレクターなどを同席させての会話が必要になります。
こういった前提があり、エンジニアに限らず浸透しやすく、誤解なく実体を表す命名が必要となります。
省略形の命名なども、対象となる領域で浸透していないのであれば、誤解の元となるので避けた方が良いでしょう*15。
更に進んで、最初にサービスを示す呼称を決めきることは難しく、多いので時間の変化と共に見直していくのが良いです。
これは先に述べた「サイクルを回す」で対応できます。
しかしながら、技術的制約で名称変更が難しい箇所などは、最初の段階でどこまで想定して名前付けを行えるかが肝となります。
新しく定義した「user」や「status」という言葉が別の人に正しく伝わるとは限りません。
寧ろ、正しく伝わらない前提で進めるくらいが良いでしょう。
そういった観点が、より正しい言語を探す道へと続きます。
「群盲象を評す」という寓話があります。
目の見えない人たちが全員で同じ象を触っているのに全員が違うことを言う、という例え話です。
サービスも目に見えないものなので、これと同じような状況にあることがあります。
全員が同じモデルを持つには、共通の言語が必要になります。
開発に着手する前に、対象を言葉で表現してみます。言葉で表現(言語化)できないものは理解できていない可能性があります。
名前付けは、運用や保守にとても重要です。寧ろモデル化の大部分は名前で決まると言っても過言ではありません。
名前がしっくりこない場合は、モデル化の構造自体を見直した方が良いでしょう。
言葉で表現できるようになってから、開発に取り掛かりましょう。
進化する実世界
ここまで話して、実世界の対象となる領域をモデル化してシステム開発を進める方法は、何となくは伝わったかと思います。
しかしながら、システムは完成しません。
なぜなら、実世界は進化を続けているからです。
一般的な例として、サービス領域の拡張などがあります。
大きいものでは、任天堂が花札からビデオゲーム機に主力商品を変えたり、Amazon.comが書店から総合ECサイトになったり、などが挙げられます。
小さいものであれば、身近にも無数にあるかと思います。
VUCAという言葉があるそうで、Volatility(変動)、Uncertainty(不確実)、Complexity(複雑)、Ambiguity(曖昧)の頭文字を並べた用語です。
これはサービスの領域そのもので、システム開発でも言えます。
システムは実世界に同調し続けなければいけません。
サービスの領域について絶対的な表現は無く、その時点での最善で最適な表現を見つけます。
最後に
繰り返しになりますが、実世界は複雑です。
複雑な実世界を表現する作業であるシステム開発が複雑なのは、必然です。
ここで述べたことはサービスに関わるチーム全員が考えなければいけないことであり、エンジニアだけの問題ではありません。
抽象化が進まないと、強調したい箇所が分かり難くなりモデルの表現力が弱まります。
モデルの表現力が弱まると、サービスとコードの乖離が進み、小手先の技術的な解決が増えます。
柔軟性や拡張性が失われ、要求に応えることが難しくなり、保守が難しくなります。
複雑さの泥沼にはまり込み、障害やバグの頻発する価値の薄いシステムに成り下がる末路が待っています。
良いシステムを維持し続ける為に、常に実世界と向き合って、適切な抽象化を繰り返してモデル化していく必要があります。
適切な抽象化はサービスの表現に力を与えます。
これからのシステム開発の手助けになれば、幸いです。
*1:いわゆる「おっさん」です。
*2:「こいつは何を言ってるんだ?」くらいに思われた気がします。
*3:このブログでの「システム開発」は「Webサービスのシステム開発」について語っていきます。
*4:「事業ドメイン」という言葉は聞き覚えがあるかもしれません。
*5:利害関係者やステークホルダーなどを指します。
*6:要らない箇所を捨てる取捨選択の作業です。
*7:手法は合ったものを適切に取り入れましょう。手法の原理主義者になってはいけません。
*8:たまにWhatやHowをコメントに書いているコードを見掛けますが、冗長になるのでやめた方が良いでしょう。
*9:https://twitter.com/t_wada/status/904916106153828352
*10:「丁寧」と「冗長」を混同してはいけません。
*11:実世界に則さない過度な共通化などは、そのひとつです。
*12:実際のコードを書くテクニックとしては、「条件分岐は短く」「否定の否定など書かない」などありますが、ここでは割愛します。
*13:もちろん、本来であればこの命名に営業の方を巻き込めるのが最適ではあります。
*14:それでも、その商品名自体が浸透してくれば、そちらを共通の言語として使用することもあります。
*15:必要以上に省略形を使ったり、新しい語彙を増やしたがる人は、注意が必要です。