はじめに
クライアントから提示された要件が変わってプロジェクトが炎上すること、ありませんか?
仕様変更、再設計、追加コスト…。
多くの開発者が経験するこの混乱の根本原因は3つあります。
- 人間の予測能力・認知能力に限界があるため、仕様に漏れが発生してしまう
- 時期・環境によって最適解が変化し、当初は必要なかった機能が追加される
- 変化に適応できない開発プロセス・システム構造になっている
1と2については、人間社会では避けられない部分になっているので、「完璧な設計」というものは大規模な仕組みであればある程成立が困難になります。そのため、今回は「変化に適応できないシステム構造になっている」という点について分析をしています。
変化に強い「アジャイル開発」
ゴールを最初から決めて開発するいわゆる「ウォーターフォール開発」は、受注案件の場合は、見積もりや契約書を交わすために有用な手段ですが、初期段階で要件と設計を固定化するため、構造が硬直化しやすく、正に「変化に適応できないシステム設計・コード」を生みがちです。
その弱点を克服するために生まれた開発手法が「アジャイル開発」です。
アジャイル開発は、要件定義→設計→実装→テストといった開発工程を短いスパン(1週間や1ヶ月等)で繰り返してシステムを開発する手法で、要件とシステムを1対1で適応・進化させていくことが特徴です。
「変化を予測する」のではなく、「変化に適応する力」を持った開発手法と言えます。
ただし、この「アジャイル開発」という手法はどんどん機能改修を繰り返していく都合上、やり方を間違えるとウォーターフォール開発以上にやり直しが頻繁に発生してしまい、プロジェクトが停滞する危険も秘めています。
重要な観点は「進化しやすさ」
それを防ぐために大事な考え方があります。
それは、「システムの進化しやすさ」を意識して設計・開発を進めることです。
この「進化しやすさ」とは具体的にどういったものなのかを以下の3点に分けて解説します。
①設計編:最初は“単細胞”のように、シンプルに作る
②モジュール編:「分かりやすさ」と「正しさ」のバランスを取る
③データ構造編:データを適切な最小単位で管理する
システムの進化のしやすさとは、概念的にはシステム工学で言う変更容易性に近い考え方です。それを元に、環境変化に応じて内部構造を柔軟に再構成できる設計とは何か?を私なりの視点で解析してました。
①設計編:最初は“単細胞”のように、シンプルに作る
システム開発をする際に陥りがちな問題が、新機能を追加するために大規模なシステム改修が必要になってしまう、と言う問題です。
システムで必要となる「機能」は、用途によって必要となるデータ構造や関数、インターフェースなどのディティールが全く異なります。
例えば「発注機能」を実装するにしても、通販サイトの発注と工場の部品発注の仕組みは全く異なります。
- 通販サイト: 発注側はユーザーの選択式。出品者は需要を予測して商品を供給。
- 工場の部品発注システム: 発注側は生産計画に応じて自動発注。出品者も生産計画を元に部品を準備。
そのため、特にアジャイル開発のように「何を目指しているか」がハッキリしていない状態で細部まで作り込んでしまうと、後になって他の機能との連携が上手くいかず作り直すことになります。
なぜ最初に細部を作り込むのが危険なのか
これはシステムを「生物」に、機能を「部位」に例えるとイメージがしやすいと思います。
生物毎に頭、手、足といった部位は違ったディティールをしています。早く走る為に指を無くした馬や、飛ぶために翼を持ったコウモリ、物を加工したり掴むために器用さが進化した人など、その生物の「生存戦略」に応じて部位が全く異なります。そしてそれらの部位を交換しても、移植先では上手く使いこなせないはずです。
システムも何かの「目的」や「価値」を実現するために生まれているものなので、その方針によって必要なディティールは異なります。なので先に「目的」を決め打ちで機能を作り込んでしまうと、あとから目的が進化した際に、最新の目的や、他の機能とかみ合わず、作り直しが発生します。
具体的には、他のモジュールで必要とされている機能と、既に持っている機能がまったく一致しなかった場合や、元の機能が複雑化しすぎて手が付けられない場合に、既存の機能を捨てて新しく作る必要性が出てきます。
具体的にどうするべき?
まずは単純な仕組みを構築し、要件が固まるにつれてどんどん機能のバージョンアップを図っていくのが良いです。
基本的にシンプルな仕組み程変更が容易で、複雑な仕組みは変更が困難です。
これは複雑な仕組みは様々な機能が複雑に絡み合っており、変更の際に影響範囲が予測できないためです。
そのため、最初のリリース時は「便利機能」「例外機能」といった機能を複雑化させかねない部分は一旦「想定」だけしておいて実装しないのがおすすめです。
また、この「単純な仕組み」を作る際にどの機能にフォーカスして作るべきかと言ったところですが、
システムの中枢機能かつ、目的と手順が明確な部分を抽出して作るのがおすすめです。
注意点
すぐに作れそうなところや、見栄えの良いものを優先して作りたい場合もありますが、それがシステムの末端機能だった場合、最終的に全く違う物が求められる可能性もあるので、注意する必要があります。
また、“単純に作る”とは“行き当たりばったりで作る”ことではありません。
システムの核となる概念(ドメイン)をしっかり押さえつつ、構造の自由度を残すことが重要です。
まとめ
- まずは単純な仕組みを構築。要件の進化と共に機能も進化させる。
- 最初から作り込むと機能変更が難しくなり、要件の進化に適応できなくなるため危険。
②モジュール編:「分かりやすさ」と「正しさ」のバランスを取る
モジュール分割やコーディング時に意識すべきポイントは、「分かりやすさ」と「正しさ」のバランスを取って最適な粒度に落とす事です。
「分かりやすさ」は、勿論人間もそうですが、昨今のAIコーディング時代においては生成AIの認識性も考える必要があります。
生成AIは人間のように意図を推測するのではなく、文脈や依存関係を統計的に捉えます。
そのため、命名や責務の一貫性があるほど、AIによるコード補完やリファクタリングが安定して意図通りに動作します。
分かりやすさと正しさは時にトレードオフ
「分かりやすさ」と「正しさ(理論的正確性やパフォーマンス等)」は時に両立でき、時にトレードオフの関係になる場合があります。
特にトレードオフになるケースは以下の様な場合です。
- パフォーマンスが良いが複雑なコードと、パフォーマンスは悪いが簡単なコード
- 凝集度・結合度は悪いが、実際にはメンテナンスしやすいコードと、凝集度・結合度は良いがメンテナンス性が悪いコード
- 業界用語の命名で、知識があれば即中身が理解できる関数名と、誰でも中身が理解できるが長い関数名
コードやモジュール構造の「分かりやすさ」は、変更を加える上で、変更点と影響範囲を特定しやすさに直結します。ですが、分かりやすさだけを重視すると今度はパフォーマンスが悪くなる場合もあります。
そのため、「パフォーマンス」と「分かりやすさ(変更しやすさ)」の2指標で考えて、お互いが最もバランスの取れた形のコーディング・モジュール分割を進めるのがおすすめです。
理論は“分析手段”であって目的ではない
「分かりやすさ / 変更しやすさ」を実現するための指標として「サイクロマティック複雑度」「凝集度」「結合度」と言った考え方があります。
ですがこれらはあくまでも「手段」であり、目的はメンテナンスのしやすさを評価するための視点の一つにすぎません。大事なのはこれらの理論値を追求する事ではなく、人間が扱いやすい形をつくることです。
凝集度と結合度で考えると以下の様になります。
-
凝集度が低い場合:様々な要素が入り混じっており、可読性が低くメンテナンス性も悪い
-
凝集度を必要以上に高めた場合:どんどんコードがファイル分割されていき、全体の流れが理解できない
-
結合度が高い場合:コードを跨がないとロジックを理解できず何が起きているか理解しづらい
-
結合度を必要以上に低めた場合:渡す引数が膨大になったり、本来共通化できる関数を別々で用意する必要が出てきて、使いづらい関数が生まれる
凝集度・結合度・複雑度といった指標が生まれた目的は、美しい構造を作ることではなく、
チーム全員が理解し、変更できる構造を保つことにあります。
手段を極めた結果、逆にメンテナンス性が悪くなる場合もあります。
目的はメンテナンス性を高める事。この目的だけはぶれないようにしてください。
これを意識する事で、メンテナンス時に「硬すぎて逆に変更の手間がかかる」という逆転現象を防ぎ、「変更すべき点が分かりやすいコード」を作成でき、開発スピードを向上させることが可能です。
まとめ
- 理論的正確性・パフォーマンス性とメンテナンス性はトレードオフになる場合があり、バランスが大事
- 理論を追い求めた結果、メンテナンス性が悪くなる場合は構造を見直すべき。
③データ構造編:データは適切な最小単位で管理する
データ構造は「進化の骨格」ともいえる重要な部分です。
コードやUIは何度でも書き換えられますが、データ構造が硬直すると、それに引きずられて、システム全体で仕様変更が困難になります。
データベースの置き換えは、実際に運用が始まると途端にハードルが上がります。
既存データの損失リスクや変換コストを考えながら慎重に変更する必要が出てくるためです。
データ構造を「作成時の最適構造」で作ると、変更時に破綻する
データベースの構造変更を運用後に行うのは非常に難しいです。特にデータ管理の「単位」を変化させることは、ゼロベースのデータ作成とほぼ同じ意味を持ちます。
そのため、将来を見据えた設計が大事になります。
ここで、将来を見据えずに「現状最適のデータ構造」を作成するとどうなるか見て見ましょう。
- 画面で表示する単位でデータ設計をした結果、汎用性が無いデータになり別用途に転用できなくなった
- その時点ではテーブル分割する規模ではなかったので、複数の概念を1レコードで扱うテーブルを用意したところ、機能改修時にそのテーブルに列を追加し続けて肥大化し、データ重複が発生し、メンテナンス時にデータの不整合が頻発するようになった
- ひとまず現状の管理単位をそのままデータベース化した後に、より詳細にデータを管理したいという追加要望が出て、データフローの作り直しが発生
データベースは「データ」を保持するという特性から、ソフトウェアより構造の変化が難しく、システムのネックになる事が多いです。
具体的にどうすればよい?
①考えられる範囲で最小単位のデータ構造を設計する
データはサマリ化する事は出来ても、分解する事は困難です。
そのため、なるべく最小の管理単位でデータを管理するのが効果的です。
「最小単位」は、将来的に再利用・結合しやすい粒度で設計するのが重要です。
逆に、実際に利用しない粒度まで分解すると、逆に保守コストが増すので「概念的な最小単位」ではなく「利用する可能性のある最小単位」までにとどめるのが良いでしょう。
②結合しやすい構造
レコードをID管理したり、レコード同士のデータの粒度を揃えたり、
なるべくノイズとなるデータが入らないようにすることが重要です。
これが出来ていれば後から別のテーブルを用意して結合する事も容易になります。
③柔軟性とメンテナンス性のバランスを意識する
データ構造の細分化は、柔軟性を高める一方で、整合性や可視性を損なうリスクも伴います。
したがって、データ設計時には「柔軟さ」と「メンテナンス性」がトレードオフの関係になりやすいです。
そのため、「将来の要件の進化に対応できる柔軟性」と「なるべくシンプルでメンテナンス性が高い構造」をバランスを取って両立することが重要です。
こういったデータ構造の柔軟性とメンテナンス性はシステムの進化速度そのものに直結します。
特にデータ構造は、アジャイル開発のように徐々に設計していく場合に破綻しやすい部分ですので、意識して設計するだけで開発速度の向上が期待できます。
まとめ
- データベースは詳細データをサマリ化する事は出来ても、サマリから細かいデータを作る事は出来ない
- データベースで管理する情報は、"将来的に使用する可能性のある"最小単位で持つと作り直しが最小限になる
総括
システム開発とは、固定化された構造を作ることではなく、変化の中で適応できる構造を生み出すことです。
生命が環境と共進化するように、システムもユーザーや環境とともに進化し続ける存在です。
システムの変更コストは、開発初期では小さく、リリース後に急速に増大します。
この「後半ほど高騰するコスト曲線」を緩やかにするのが、“進化しやすい構造”の最大の目的です。
それを意識して開発を進めて行くと、世のエンジニアが最も恐れる事象である「仕様変更の為の作り直し」を最小限に抑えることが出来ます。是非ご活用いただければ幸いです。