Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
848
Help us understand the problem. What is going on with this article?
@yokarikeri

プログラマーのための原則(2 万字)

はじめに

今でも語り継がれる「原則」は、それだけ価値のあるコンセプトです。
歴史を振り返ることは、失敗を防ぐための効率の良い方法になります。

👑 DRY (Don't repeat yourself)

「同じことを繰り返すな。」

Andy Hunt と Dave Thomas の著書『達人プログラマー』(1999 年)で提唱された原則で、プログラミングに関する最も重要な原則といっても過言ではありません。

DRY 原則だけでなく、どんなデザインパターンやベストプラクティスでも、同じ処理が重複することは基本的に許されていません
これにはどういう意図が込められているのでしょうか。

🔖 表面的な理由

この原則は、コードの再利用性を高め、そのために疎結合な状態を保つことは、極めて有用なことを示唆します。
1 箇所を直せば済むべき箇所をあちこちに分散させてしまうのは、自分で事故を招いているのと同じということです。

重複が一組なら問題は単純ですが、増えてしまえば問題は絡み合い、複雑さは指数関数的に増大します。
そうして、あっという間にバグの温床となって、コードの保守性を破壊することでしょう。
よくあることだと侮って放置すれば、すぐ手遅れになってしまうのです。

早期のリファクタリングだけが、この問題に対抗する唯一の手段です。

🔖 本質的な理由

もしあなたのコードに同じ内容の処理が 2 回も登場するなら、それは適切な抽象化に失敗している証拠かもしれません。

つまり、処理の重複は設計を見直す良いタイミングだということです。
重複する処理を切り出して、どこか 1 箇所に置こうとした時、置き場所に迷うようなら、それが見直しタイミングのサインです。

まずは、既存のクラス(ファイル)に置き場所がないかもう一度、慎重に確認しましょう。うっかり見落としているだけかもしれません。
本当になさそうなら、新しいクラスが必要なことが分かります。コードに境界線を引き、新しい文脈を与える時です。
新しいクラスを作るのは億劫ですが、将来の自分が楽になるかどうかを考えれば、それが「コスパの良い苦労」だと気付くでしょう。

ただし、既存の文脈を無視してはいけません
別々の役割を持つ処理同士で、無理にコードを共有する必要はありません。
ある時点でたまたま同じような処理だったとしても、本質的に別々の役割なら、状況やニーズが変われば別々の処理が必要となる可能性を忘れないでください。
それぞれの役割を正しく抽象化することが目的であって、コードの見た目を整理するために役割を用意するのではありません。

設計上の考慮漏れは起きないことが理想ですが、すべてを見通す力は誰にもありません。
よって、気付いた時にその都度改善する他ないのです。

🔖 見落としがちな側面

また、この原則はコード以外にも、具体的にはどんな作業にも適用すべきです。

例えば、設計書、仕様書、試験書なども、工夫しなければ長期的には「整合性を保つコスト」が高くなり過ぎてしまいます。
不整合が放置されたドキュメントは、誰も必要としなくなります。これでは折角の苦労が水の泡です。

少なくとも、ドキュメント間での重複は減らしましょう。
コードなどからドキュメントを自動生成できるのが理想的です。
自動化できることを避けるのも、DRY 原則に反することなのです。

👑 プログラマーの三大美徳

プログラマーには、次の美徳が絶対に必要です。
この美徳の一つでも欠く人は、プログラマーとなってはいけません。
また、これらの美徳を欠いているにも関わらずプログラマーという職業に就いてしまった人は、今すぐ転職を考えましょう。
それくらい重要な美徳です。

  1. 怠慢(Laziness)
  2. 短気(Impatience)
  3. 傲慢(Hubris)

ラリー・ウォール

良いプログラマーとは、3 時間かければ人力でできることを、12 時間かけてコードを書いて自動化します。
「別に 3 時間で終わるなら人力でいいでしょう?どうしてそんな苦行を?」と問われれば、「4 回で元が取れる」と答えるでしょう。

このプログラマーの道徳を端的に示した、Perl の生みの親であるラリー・ウォールが『プログラミング Perl』(1991 年)で提唱した三大美徳は非常に有名です。

この皮肉の効いた美徳の第一である「怠惰」とは、自分が最終的に楽をするためには手間を惜しまない気質です。
再利用すべきコードを飽きずに何度も打ち込んだり、ドキュメントを作らずに同じ質問にせっせと答えるような勤勉な姿勢のことではありません。
抽象化すべきところを回避してコピペで済ませるような手抜きは、偽りの怠惰です。

第二の美徳である「短気」は、コンピューターが無駄な回り道の計算をするようならすぐ怒り、言われたことを鵜呑みにして作られた拡張性や柔軟性に欠けるコードを決して許しません。
起こりうる問題を想像せず、発生する問題の対応だけに終始して、バグ対応を延々こなす状況にも無頓着になるような忍耐強さを発揮し続ける姿勢のことではありません。
再利用すべき既存のコードを無視して自分で作り始めてしまうのは、偽りの短気です。

最後の美徳である「傲慢」とは、人様に対して恥ずかしくないプログラムを書くというプライドと、それを正しく保守して守ろうとする意地です。
不明瞭で冗長なコードを書き、その状況を謙虚に受け入れるような姿勢のことではありません。
のめり込んで過剰な抽象化レイヤーを築き上げてしまうような行為は、偽りの傲慢です。

👑 車輪の再発明をしない

誰かがすでに生み出した何かを、自分で生み出そうとして時間を無駄にすることは、大抵の場合は賢明ではありません。
何でも自前で作ることの誘惑失敗の示唆は、1970 年代のアメリカで生まれた、この有名なフレーズだけが示すものではありません。

動物行動学では、イヌ、サル、トリ、サカナなど、動物界にほぼ共通して見られる傾向として、労働せず得られる餌よりも、労働して得る餌の方が価値が高いと認知する習性が知られています。
苦労や自分でやった気になったことに対し、無根拠に「ありがたみ」を感じるこの習性は「コントラフリーローディング効果」(逆たかり行動)と呼ばれます。

他のも有名なフレーズとして「NIH (Not invented here) 症候群」や「巨人の肩の上」など、この問題を示唆する表現には枚挙に暇がありません。

結局、個人または組織が「あなたが多く苦労すること」に価値を置いたとしても、苦労して作った自前の解決策は既存の解決策よりも劣ります
多くの人の手で磨かれてきた「先人の知恵」より、己一人の「無知とプライド」を優先した結果ですから当然です。
このよくある失敗のプロセスは「四角い車輪の再発明」と呼ばれます。

プログラマーがこの問題に立ち向かう最善の方法は、オープンソースソフトウェア(OSS)のフレームワークやライブラリを積極的に活用することです。
もちろん、その OSS のライセンスが、あなたのプロジェクトで利用可能かはよく確認してください。
OSS を導入後、バージョンアップなどの保守を実現するには、パッケージ管理ソフトも必要になるでしょう。

また、有償サービスシェアウェアの検討も忘れないでください。

うっかり四角い車輪を再発明しないように、例えばもし UTF-8 エンコーダーを自前で実装しそうになれば、何かおかしいことに早く気付くべきです。

👑 KISS (Keep it stupid simple.)

「単純で愚鈍にしておけ。」

ロッキードスカンクワークスのケリー・ジョンソンが提唱し、1960 年代にアメリカ海軍で使われたこの原則は、エンジニアにとって最も重要な考え方のひとつです。

不必要な複雑さを持った製品は、作る側にも使う側にもメリットがありません。
ではこんな分かりきった原則が、わざわざ提唱されるのはなぜでしょうか。

最も根源的な理由は、ヒトは心理学的特性として引くことによる改善を好まないからでしょう。
本能に抗えるほど理性的な人間は、そう多くありません。

そのおかげで、ソフトウェア開発では「機能追加主義」が横行しています。

🔖 機能追加主義

最悪のケースを考えてみましょう。

とにかく「新機能」をどんどん作ったとします。
そうすると、当然ながらコードが複雑化します。
新機能を追加するための拡張性や柔軟性は少しずつ低下し、コードが巨大化して保守は少しずつ面倒になります。

それでも立ち止まることなく、機能追加が強行され続けたとします。
すると、負のサイクルが本領を発揮し始めます。
コードの複雑さによって機能追加にはずいぶん時間がかかるようになっているので、機能追加以外の作業はすべてスキップされ始めます。
保守は最低限の対処療法しか実施されなくなり、小さな問題は無視されるのが日常となります。

利用者側にも重大な影響が出始めます。
機能が多すぎるので、初めて使う人には学習コストが高すぎて、好んでその製品を使わなくなります。
慣れた人でも、どの機能がどの操作なのか、混乱して手間取ることが増えてきます。

やがて開発者は、コードを正常化するには複雑になりすぎたこと、つまり手遅れであることに気が付き、絶望します。
利用者の疑いの心は限界に達し、競合製品との比較を始めれば、すぐにどちらが優秀か気付くでしょう。

この末路は、残念ながら決して珍しいものではありません。
「機能追加主義」はどこにでも発生し、今まで多くの優れた設計がつぶされ、ソフトウェアの寿命を縮めてきたのです

🔖 引くことによる改善

「機能追加主義」への対策は、「足し算」だけでなく「引き算」による改善を忘れないことです。
これはシンプルさを保つための唯一の方法です。

「引き算」は前述のとおり人間の本能に逆らう行為ですから、理性的になろうとよほど苦心しない限り、これを遂行することはできません。

例えば、思い切って利用人数の少ない古い機能は廃止しましょう。
1 割の利用者にしか使われない機能は、不要であるのと同じです。
残りの 9 割にとって、その機能を目にすることすら、時間の無駄だということです。

忘れられがちですが、利用者の目と手は 1 組しかありません
何かのソフトウェアを使うとき、大抵たった 1 つのことしか要求されていないことを思い出してください。
本当に提供したい価値以外のものは他のソフトウェアにすっぱり任せて、利用者の集中を妨げないようにすべきです。

また、立ち止まることも極めて重要です。
ソフトウェアの寿命を継続させたいなら、定期的にリファクタリングや設計の見直しなどの定期検診が必要となります。
病気に立ち向かうには、早めの発見と治療が一番です。
足元がおぼつかない状態で機能追加する末路は、前述のとおりです。

🔖 プロダクトの寿命

これもよく忘れられるのですが、ソフトウェアの拡張性は有限です。
拡張性は初期設計やフレームワークなどによって強力に制限されており、無限に成長することはできません。
それ故に KISS の原則は正しいのです。

「新機能」が「ベースとなる設計の枠組み」を超えるようなら、それはプロダクトが寿命を迎える前兆です。
もちろん、その新機能が馬鹿げていないことが前提です。

寿命の迫ったプロダクトにムチを打って、無理な新機能を追加すれば、前述の「機能追加主義」と同じの末路を辿るでしょう。
無理をすれば死んでしまうのは道理です。

もし賢明な判断をするなら、その新機能がプロダクトが本当に提供したかった価値に沿うかどうか、もう一度自問します。
「機能の価値」ではなく、「プロダクトの価値」をベースに考える点に注意してください。逆ではありません。
考えが変わらなければ、新しいプロダクトを用意すべき時です
寿命を迎えた古いプロダクトのノウハウを活かし、新しいプロダクトで新しい価値を届けるのです。

プロダクトが寿命を迎えるのは不名誉なことではありません。
頭の中のアイディアは、試さなければ確かなことは分からないからです。
ノウハウが蓄積され、正しい筋道が見えたので、これを未来に活かすことにしましょう。

👑 早期の最適化は諸悪の根源

クイックソートの考案などで有名なアントニー・ホーアの引用句です。

前提として、最適化は指数関数的にコストがかかることを覚えておいてください。
0 % から作り始めて 90 % の完成度にする時間と、90 % から 100 % にする時間はほぼ同じか、後者の方がずっと大きくなります。

もうひとつの前提として、最初に作ったプログラムはほぼ 100 % 失敗します
初めてのことや、ノウハウがないなら、失敗は当然です。ラッキーはまず起こりません。

そこで賢明な方法は、まずプロトタイプを作ることです。

プロトタイプに問題があれば、0 から作り直すことも検討してください。
最適化の前提を思い出してください。0 から作り直す方が、長期的には最小コストになるケースは非常に多いのです。

作り直しによる最適化は非常に効果が高く、もっとも強力な最適化ツールは BackSpace キーだと言われるほどです。
せっかく時間をかけたコードだったとしても、コードの複雑さが検討時間を増大させているので、余計なものなら思い切って全部消してしまいましょう。

なお、プロトタイプのコツは、最初に目的をメモに書き出すこと、そして動くようにした「後」で最適化することです。(これも当然のことなのですが、よく忘れられます。)
コードを書くことに熱中していると、簡単に目的を見失います。自分の記憶力や理性を当てにしても、痛い目にあうだけです。
また、細部にこだわるタイミングが早いと、コードが複雑化しているせいで、最適化コストが跳ね上がる点に注意してください。

👑 プログラミングの中心は、アルゴリズムではなくデータ構造だ

ベル研究所での UNIX 開発や、最近だと Go 言語の作成に関わるロブ・パイクによる『Notes on Programming in C』(1989 年)に登場し、後に「プログラミング 5 箇条」などと呼ばれる条項のひとつです。

要件をどのように実装するか悩む時、「何をどうするか?」を考えるあまり、「入出力のデータ構造」に対する想像が足りないと、必ずひどい失敗をします。

例えばあるアルゴリズムを説明する「フローチャート」と、その入力と出力の「表データ」があるとしましょう。
これらの内、「フローチャート」だけを見て「表データ」の形式を想像するのは難しいのですが、逆に入出力の「表データ」から「フローチャート」を想像するのは簡単であることに気付きます。

この驚くべき交換は、「データ構造」の重要性をよく説明しています。
例えば、設計する際はまずデータ構造を考え、同僚に処理を説明する時は最初にデータ構造を見せることを意識してみてください。
データ構造は、大抵の場合プログラマーにとって最もヒューマンフレンドリーな表現方法なのです。

なお、「フローチャート」や「シーケンス図」などの UML も極めて重要ですが、これは更に細部を説明するのに向いています。
道具は使い分けることが肝心です。

また、データはプログラムロジックより制御しやすいことも覚えておきましょう。
「コードの複雑さ」を「データの複雑さ」に置き換える方法は、積極的に検討すべき方法です。

👑 Unix 哲学

Unix 哲学といえば、ソフトウェア開発の規範となる普遍的な価値の宝庫であることが知られています。
その起源でもある、Unix の創始者のひとりで、パイプの開発者としても有名なダグラス・マキルロイによる『Unix Time-Sharing System』(1978 年)で書かれた原則を紹介します。

古典に分類される原則ですが、昨今に登場したアジャイル開発や、クラウドサービスによるモノリスからマイクロサービスのような新しい流れにも十分に通用することが分かると思います。

  1. 個々のプログラムは 1 つのことをうまくやれ。新しい仕事をするなら、新しい「機能」を追加して古いプログラムを複雑にするのではなく、まったく新しいプログラムを作れ。
  2. すべてのプログラムの出力は、他の未知のプログラムの入力になることを想定せよ。無関係な情報で出力を乱雑にしてはならない。(訳注: テキスト形式の出力の場合に)縦方向の並びを厳密に揃えたり、バイナリ形式を使ったりすることは避けること。対話的な入力にこだわらないこと。
  3. OS であっても、ソフトウェアは早く(できれば数週間以内に)試せるように設計、構築せよ。まずい部分は捨てて、作り直すことをためらうな。
  4. 技能の低い者に手伝ってもらうくらいなら、プログラミング作業を楽にするツールを使え。ツールを作るために遠回りする場合や、使った後で一部のツールを捨てることになることが分かっている場合でも、ツールを使え。

Unix philosophy - Wikipedia

👑 SOLID

オブジェクト指向でのソフトウェア設計で、最も有名な原則のひとつです。
2000 年に Robert C. Martin によってまとめられました。

多層構造で変更の激しいソフトウェアを安全に保守するための設計を考える上で、今日でも非常に有用な原則です。

なお、「オブジェクト指向」は多義的であるが故に曖昧な概念ですから、「SOLID 原則」などの原則がどこにでも適用できる唯一の答えを持つわけではありません
これは、少なくともプログラミング言語やそのフレームワークなどによって、ベストプラクティスが異なることからも明らかです。
そのため、オブジェクト指向に関する説明を見聞きしただけで理解した気にならず、ベストプラクティスとなる良いコードを読むなど、必ず自分自身で学習を進める必要があることを忘れないでください。

🔖 単一責任の原則 (SRP: Single responsibility principle)

クラスに任せる仕事は常に 1 種類に絞りましょう。

例えば、ユーザークラスに「ユーザープロパティの管理」と、「DB への保存」の 2 つの仕事を任せてはなりません。
クラスの関心事を 1 つに絞るために、クラスを分離してください。

これによってクラスはシンプルになり、変更時の他のクラスへ影響を小さくすることができます。
そうなれば、安心してクラスに手を加えることができます。

🔖 開放閉鎖の原則 (COP: Open–closed principle)

コードの変更による影響は、ごく小さくなるように工夫しましょう。

ポイントは 2 つです。
機能の拡張は、既存のコードを変更ではなく、追加によって実現できるように準備しましょう。
機能を修正しても、影響が出る箇所は、将来に渡って少なくなるように工夫しましょう。

例えば、EC サイトの決済処理で、最初の要件では「クレジットカード」にしか対応していなかったとしても、後から「現金」などの他の決済手段が追加できるようにしておくべきでしょう。
そのためには、各メソッドの役割を 1 つに絞って、役割ごとにメソッドを増やせるような柔軟性な設計にすべきです。

他にも、「再利用する処理」と「そうでない処理」を分離しておけば、1 つの問題で修正すべきコードの場所を限定できるので、修正が容易になります。

🔖 リスコフの置換原則 (LSP: Liskov substitution principle)

スーパークラス(親)の振る舞いを、サブクラス(子)で潰さないようにしましょう。

継承より合成」と言われてしまうほど、継承には利点もありますが注意点もあり、少なくとも多用を推奨することはありません
迂闊に継承を使ってしまうと、すぐにハマってしまう落とし穴を回避するために、この原則は非常に有効です。

例えば、「真円クラス(親)」を「楕円クラス(子)」が継承する場合を考えてみます。
スーパークラスの振る舞い(真円であること)を、サブクラス(楕円であること)では潰すことになるので、何か問題が起きそうです。

これを「is-a 関係」が成立しない、と呼びます。
「楕円は真円の一種」だと意味が通らず、実装に無理に出ることが容易に想像できます。
この場合は継承を使ってはいけません。

しかし、「is-a 関係」が成り立つからといって、継承を使おうとするのは早計です。
「is-a 関係」が絶対の法則ではないのは、自然言語上の解釈が、必ずしもコード上の設計にとって最適であるとは限らないからです。

長方形クラス(親)」を「正方形クラス(子)」が継承する場合を考えてみましょう。
「正方形は長方形の一種」が成立することから「is-a 関係」も成立するので、一見、何も問題なさそうです。

しかし長方形は「幅と高さの 2 つの辺の長さ」に関心がありますが、正方形は「ただ 1 つの辺の長さ」にしか関心がありません。
素直に正方形クラスを実装すれば、サブクラスの関心(正方形であること)に合わせて、スーパークラスから継承した余計な振る舞い(長方形であること)は潰すことになりそうです。

これもリスコフの置換原則に反することを意味しますが、違反すると何が起こるのでしょうか。
そのためには、継承を使う目的を思い出してみましょう。

継承を使う理由は、「長方形」と「正方形」のインスタンス(オブジェクト)を、どちらも同じ「長方形クラス」として扱えるようにしたかったはずです。
そうすれば、他のクラスで「長方形インスタンス」と「正方形インスタンス」の違いを意識せずに済むはずでした。
しかし「正方形インスタンス」は「長方形インスタンス」のように振る舞えないので(余計な振る舞いを潰されているので)、この継承はそもそも目的に適わないことをしようとしていたのです。

したがって、この例でも継承を使うべきではありません。
なお、スーパークラスとして「図形クラス」作り、「長方形クラス」や「正方形クラス」がサブクラスとして継承する場合は、何の問題もないでしょう。
図形としての振る舞いは、サブクラス(長方形や正方形であること)で潰されることは考えにくいので、他のクラスでも「図形クラス」を想定するだけで安全に「長方形インスタンス」や「正方形インスタンス」を扱うことができるでしょう。

このように「継承」はどこにでも使えるわけではないので、「委譲」(クラス内部に別クラスのインスタンスを持つこと。Forwarding、Delegation、DI)などの別の方法も併せて検討しましょう。

オブジェクト指向における「自然言語」と「プログラミング言語」のすれ違いについてはこの記事が詳しいので、もし理解できていない場合は参照してみてください。
「オブジェクト指向は現実世界をそのままソフトウェアに表現する」という説明は言葉の綾であって、本質的には間違っていることが分かると思います。

🔖 インターフェース分離の原則 (ISP: Interface segregation principle)

各用途の数だけ、特化したインターフェースを作るようにしましょう。

下手に使いまわしを考えてはいけません。
汎用的なインターフェースを無理に適用すると、不要なメソッドの実装を強制し、無用な混乱を招くからです。

🔖 依存性逆転の原則 (DIP: Dependency inversion principle)

大抵のアーキテクチャーは、コードを多層構造状のレイヤーに分けて管理します。

その場合の注意点は、上位レイヤー(呼び出し元)のクラスは、下位レイヤー(呼び出される側)のクラスを直接使わない(依存しない)方が良いケースが存在するということです。
この原則は、下手に依存すると密結合化して、下位レイヤーを変更した場合の影響が広くてコントロールし辛い場合に有効な対策です。

例えば、MVC の Model を構成する Service レイヤー(ビジネスロジックを扱う)と Repository レイヤー(データの永続化を扱う)の関係を考えてみましょう。
この場合、Service レイヤー(上位)が呼び出す側、Repository レイヤー(下位)を呼び出される側です。

Service レイヤー(上位)のクラスが Repository レイヤー(下位)のクラスを直接使う(依存する)とどうなるでしょうか。(具象から具象に依存する状態)
DB を扱う Repository レイヤーは変更が激しく、上位レイヤーの様々な場所で使われていますから、上位レイヤー側で影響範囲をコントロールする方法を考えた方が良さそうです。

そこで、上位レイヤーで「下位レイヤーのインターフェース」を持つようにしてみましょう。(上位レイヤーにある「下位レイヤーの抽象」に依存する状態)
下位レイヤーをこのインターフェースごとに実装することで、上位レイヤーの中だけで「使いたい下位レイヤーのインターフェース」を選択できるようになりました。
直接の依存関係がなくなり、上位レイヤーで下位レイヤーをコントロールできるようになったため、下位レイヤーをより安全に変更できるのです。

注意すべきは、直接依存するすべてのケースで、この原則を適用する必要はないということです。
レイヤー(アーキテクチャーの境界線)をまたぐクラスなら、このようなひと手間をかけるデメリットより、将来の影響範囲をコントロールできるメリットの方が上回ります。
それ以外の場合は、この手間による複雑さが見合わないケースがほとんどです。

この説明は、この原則に従う場合とそうでない場合の「クラス図」があった方が分かりやすいので、以下の記事を参照することをオススメします。

cf. 依存関係逆転の原則の重要性について. こんにちは!eureka の API チームでエンジニアをやっている@rikii です。… | by riki(Rikitake) | Eureka Engineering | Medium

👑 ホフスタッターの法則

「複雑な作業にかかる時間を、正確に見積もることができない。」

ダグラス・ホフスタッターによる一般向け科学書『ゲーデル、エッシャー、バッハ』(1979 年)で提唱された法則です。

解決策はデカルトの有名な言葉のとおり、「困難は分割せよ」。

補足:

この法則のもうひとつの示唆は、この困難を困難たらしめているのは問題設定が単純すぎることだ、ということです。

「作業時間の見積もり精度」が高いか低いかの 1 次元的な問題設定から解を出すことはできません。
それなら想像力を働かせて「別の観点」を用意すれば良いのです。

今回の例だと「作業粒度」という新しい軸を追加するとどうでしょう。
こうすることで問題設定が複雑さを持ち、「作業粒度」を下げることで「作業時間の見積もり精度」を高められることを発見できました。

この多次元化の思考メソッドは、様々な場所で応用ができます。
覚えておいて損はありません。

なお、次元が多ければ良いという話ではありません。大抵の困難は 2 軸で十分です。
2 軸で作る四象限マトリクス(下表)は、課題の洗い出しに最適な方法のひとつです。
こちらも覚えておくと、説明用の資料を作る場合などに役立ちます。

見積もり精度が低い 見積もり精度が高い
作業粒度が大きい 👎 Bad(現状) -
作業粒度が小さい - 👍 Good(理想)

👑 ブルックスの法則

「遅れているソフトウェアプロジェクトへの要員追加は、プロジェクトをさらに遅らせるだけである。」

フレデリック・ブルックスによる、ソフトウェア・マネジメントの古典的名著『人月の神話』(1975 年)で提唱される法則です。

その理由は以下のとおりです。

理由:

  • 新たに投入された開発者が生産性の向上に貢献するまでには、時間がかかる。
  • 人員の投下は、チーム内のコミュニケーションコストを増大させる。
  • タスクの分解可能性には限界がある。

なお、改訂版で著者本人が以下のよう語るほど、理想と現実には開きがあります。

この本は「ソフトウェア工学のバイブル」と呼ばれている。なぜなら、誰もがこの本を読んでいるが、誰もこの本で述べていることを実践しないからである。
―『人月の神話』、フレデリック・ブルックス

組織文化を変化させるには人や考え方の流動性が必要ですが、ある程度の大きさを超える組織の新陳代謝は恐ろしく低いものです。
ひとりの人間が組織内でコントロール可能な範囲は狭く、自分自身か、ごく小さいチームが精一杯なので、できることをやるしかありません。

👑 まずはコンセプト、技術はその次だ

最終的な目標(実現したい価値)と、最初の攻め口、目標にたどり着くためのアプローチ、必要な要素技術の特定とその開発など考えることは山ほどある。
こうした課題を一つ一つ潰し、イノベーションを成功させるには熟慮を重ねるしかない。そして、課題を潰すための突破口になるのが、コンセプトなのである。
ホンダ イノベーションの神髄』、小林三郎

良いコードを書くには、そのプロダクトが提供する価値について、よく知っておく必要があります。
統一されていない、雑然としたソフトウェアは、利用者にも開発者にも苦痛を与えるだけです。
プロダクトが提供する価値が共有されていないなら、各メンバーが好き勝手に実装することを止めることはできないのです。

この提供したい価値や目指すべき姿などを、ここでは総合して「製品コンセプト」と呼んでみましょう。

この原則は、日本初のエアバックの開発や量産に成功したホンダ社の小林三郎の言葉です。
製品コンセプトを考える上で陥りがちな問題への対策として、重要なことを示唆しています。

🔖 バズワードに踊らされない

バズワード(英: buzzword)とは、技術的な専門用語から引用したり、それを真似た言葉で、しばしば、素人がその分野に精通しているように見せるために乱用される、無意味だが、かっこいい、それっぽい言葉のことである。
バズワード - Wikipedia

「コンセプト」より「技術」を重要視するのは、「目的」と「手段」を取り違えるのと同義です。
つまり最悪です。

経験が浅い場合に陥りがちな間違いとして、製品コンセプトを検討する際に「流行りの技術」を使おうとします。
具体的には、「ビッグデータ」、「AI」、「IoT」などのバズワード(流行り言葉)を使いたがります。
例えば「ビッグデータを採用する製品を企画しよう」といった風に、問題解決のための「手段」である技術革新が、問題解決の「目的」に位置してしまいます。

そうなってしまう気持ちは分かります。
その分野に詳しくない素人にとって、バズワードは突然現れた魔法の解決策に見えるのです。

ただしそれは無知ゆえに魔法だと感じているだけに過ぎません。
実際には魔法は存在しません。万能の解決策はすべて幻です。

非連続に見えるどんなブレイクスルーも、本当は連続した知識の上に成り立っていることを思い出しましょう。
一見すると急激な変化も、冷静に捉えれば小さな努力の積み重ねに過ぎません。
正しい知識で技術革新の本質を知れば、バズワードは受け手によって無制限に意味を解釈できる「ただの曖昧なキーワード」だと気付けるはずです。

バズワードが魔法ではなく、神聖視する理由がないと分かれば、例え「ビッグデータを使いたい」という思いつきをしても、冷静な判断ができるでしょう。
もしかすると、自分たちの扱うデータがペタバイト級には存在せず、従来の RDB や KVS で十分取り扱える量であるかもしれないのです。

製品コンセプトを検討する際は、「手段」のことは一旦忘れて、先に「目的」を持ちましょう。
バズワードのことは忘れて、「解決したい問題」にフォーカスするだけで良いのです。
そうすれば「目的」と「手段」の逆転を防ぐことができます。

🔖 思考の深さ

目的のない技術選定が無意味なことは理解できました。
では製品の目的を定める「製品コンセプト」とは、具体的にはどのようなものでしょうか。

あなたが製品コンセプトを考えるべき時のために、最低限必要な項目について考えてみましょう。

  • 製品名は?
    • ものには適切な名前が必要です。
  • 何のために使う?
    • 使用者に提供したい価値が、製品の目的であり、購入/使用される理由そのものです。
    • 最初に「一言で言い表せる単一の使用目的」を決めましょう。さもなければ曖昧な製品になります。
      • 使用者がソフトウェアを起動する際の目的が、複数であるケースは存在しません。
    • 使用動機であるニーズの検討を忘れてはいけません。使用者を無視したシーズ志向は、誰にも相手にされません。
      • 開発者も想定していない使い方を、使用者が発見してくれることに期待してはいけません。
      • なぜなら、どんな人も欲しくない製品をわざわざ買いませんし、真面目に使用しないからです。
    • この項目が製品コンセプトの中心となるため、最も大切です。
  • 誰が使う?
    • ターゲティングは、詳細かつ明確にイメージしましょう。曖昧なイメージには意味がありません。
    • 自分向けに開発する場合を除けば、使用者はあなたの知らない人間です。 ですから使用者をあなたのよく知る人間に変えるため、正確にイメージしておかないと、必ず使いにくい製品になります。
  • いつ使う?
    • 使う人をイメージする時、使われるタイミングも重要です。
    • 使われる前後の流れも忘れずに定義しましょう。
  • どのように使う?
    • 使用目的に沿うため、製品が「やること」と「やらないこと」を決めましょう。
    • 「やること」と「やらないこと」は、使用目的を細分化した「利用シーン」ごとに時系列に沿って詳細に書き出しましょう。
    • 頭の中だけで考えず、箇条書きや図表を使って想像力を働かせます。アウトプットを惜しまないことが、利用シーンを掘り下げるコツです。
  • 長期的な視点での理想形は?
    • 将来、あなたの考えは必ずこの製品コンセプトから脱線します。ゴールやマイルストーンを設定しないと、脱線を修正できません。
    • 他の項目が「今やるべきこと」を決めるのに対し、この項目は「将来実現したいこと」を扱います。
    • 多少難しいと思うことでも、それが本当に提供すべき価値に必要なら、この項目に記載しましょう。 難しい目標ほど、達成には強い意志が必要となります。頭の中だけに留めておいてはいけません。
  • 理想形のために挑戦中の中期的な取り組みは?
    • 理想までの道のりは非常に長いので、困難は分割しましょう。着実に一歩ずつ進むには、分割した目標を忘れないことです。
    • 中期的な目標には期限を設け、数ヶ月に一回の周期で振り返る PDCA サイクルを回りましょう。

どの質問に答えるのも、簡単ではないはずです。
それに、誰でも思いつく程度の答えに価値はありません

競合製品より、世の中の誰より、自分の製品が届けたい価値について深く考えなければなりません。
自分に「なぜ?」を何度も問いかけ、思考の海にどこまでも深く潜る作業が必要になります。

苦しい作業なので、こんなことは誰もやりたがりません。
だからこそ、競争相手を出し抜くチャンスとなるのです。

🔖 船頭多くして船山に登る

繰り返しますが、製品コンセプトには「思考の深さ」が要求されます。
そのための最適な方法は「個人」で担当することです。この作業は、チームで分担できるものではありません。

チームメンバーなどからレビューをもらうのは、もちろん重要です。
ただしその前に、コアとなる検討を一人の人間が限界まで知恵をしぼって産み出さなければなりません。

もし、複数人で検討した場合、限界まで深く考えることはできません。
他人の意見を早く取り入れてしまって、深く考える時間がとれないまま検討が終了してしまうのです

浅いコンセプトは、曖昧で不安定な方針です。
そんなものは、チームにとって害にしかなりません。

一人で絞り出す検討フェーズと、他の人に意見を聞くヒアリングフェーズは、交互に何度も実施することで、考えをブラッシュアップできます。
ヒアリングの注意点は 3 つあります。

  • ✔ ヒアリング対象者の知識レベルを揃える
    • 理解度が異なる状態では、その説明のために時間が取られてしまい、議論に集中することができません。
  • ✔ ヒアリング対象者の人数を絞る
    • 一度にヒアリングする人数が多いと、同調圧などによってヒアリングできる内容が浅くなりがちです。
  • ✔ ヒアリング対象者を厳選する
    • あなたの小学校や中学校の同級生達でさえ、各自の能力に大きな差があったように、大人も個人の能力には大きな差があります。 検討に向いていない者をヒアリングしても、プラスにはならないでしょう。

なお、製品コンセプトを固める時にブレインストーミングが使われるケースがありますが、これはまったくオススメしません。
これは参加メンバーの発想を刺激するためのツールであるため、結論を出すためには役に立ちません。
また、このツールはよほど慣れているか慎重でない限り、ノリと勢いによる浅い考察しか生まれないのも難点です。

製品コンセプトが整えば、これが設計書や仕様書など、今後のすべてのドキュメントのベースとなります。

🔖 遅すぎるということはない

製品コンセプトは、本来最初に考えられるべきものですが、それができなかったチームも存在します。
その場合は後からでも、気付いたタイミングで製品コンセプトを検討しましょう。

目指すべき道が共有できていないのなら、いつまで経っても製品の成長の方向性を修正できないままです。
傷口は小さいうちに対処すべきですから、いつでも遅すぎるということはありません。

👑 誤った問題を正しく解くよりも、正しい問題を誤った方法で解く方が良い

ベル研究所などで活躍したリチャード・ハミングが、コンピューター黎明期(1950 年代)に述べた言葉です。
当時は「機械操作」と「プログラミング」は別の専門要員が担っていました。(クローズドショップ)
しかし、プログラムの利用者が最もプログラムのニーズに近いのだから、同じ要員が行うこと(オープンショップ)が絶対に必要だ、と主張するために使われた言葉だそうです。

さて、「正しい問題設定」は何より重要です。
何の問題を解くかを考えなければ、どう問題を解くかを検討できません。
ですから、偶然に期待しない限り、優れた発見は正しい問題からしか生まれないのです。

この特徴は、Unix ツールや、AWS の各種サービスなどにも見出すことができます。

🔖 問題設定が変わらなければ、解き方を変えるのは難しくない

例えば、コマンドラインシェルを bash から zsh などの後継のプログラムに置き換えることは簡単です。

問題の解き方(実装)が間違っていることが分かった場合でも、解決すべき問題自体は変わらないのですから、後から別のプログラムに交換することは簡単なのです。
これはシステムの総体としての寿命を健全に延ばすことが簡単であることと同義です。

逆にそうでない場合は悲惨です。
誤った問題設定によって生まれた実装を交換しようとすると、システム全体を作り直さなければなりません。
システムの腐った部分を交換するのが困難なら、そのシステムはすぐ時代遅れになってしまうでしょう。

🔖 正しい問題設定は、普遍的な価値を持つ

別の例として、AWS で EC2 を使おうが S3 を使おうが、あるいは今後登場する新しいサービスでも CloudWatch でログを監視することができるでしょう。
これは、解決しようとした問題設定が正しいおかげで、未来に渡って必要とされ続けるためです。

正しい問題は、永遠でないにしろ、ちょっとやそっとの時間が経っても重要性が変化しないのです。

システムを構成する技術スタックのほとんどすべては、珍しい問題を解決しているわけではありません。
個々の部品の多くは、一般的な問題を解決しているだけであることに注目してみましょう。

正しい問題設定でさえあれば、一般的な問題を解決する部品を組み合わせるだけで、新しい問題にも簡単に対応できるでしょう。
逆に、誤った問題設定は特殊なケースでしか必要とされないので、新しい問題に対応するには、新しいシステムを用意するしかなくなります。

🔖 最初が肝心

このように、正しい問題設定は交換が容易な部品を生むため、自然と疎結合化してバグの入り込む隙きを減らしつつ、普遍的な価値を持つために機能拡張には強くなるため、システムの生涯全体コストを大幅に下げることに貢献できます。

しかし、「正しい問題設定」を検討できるのは、大抵の場合は最初の設計時のみです。
設計が軽視されることで、システムの生涯全体コストが膨れ上がるのは、誰の得にもなりません。

なお、誤った問題設定から作り始めてしまえば、もう腐った部分を交換するのは困難でしょう。(最初は小さかった腐った部分はあっという間に全体に広がります。)
無限の寿命など存在しないにせよ、最初のタイミングで全力が出せるかどうかは、システムの寿命を左右にする重要な分岐点になります。

🔖 正しい実装はあるか?

この原則を見て「誤った問題を正しく解くことは可能か?」という疑問を持った方もいると思います。

当然ながら、「誤った問題設定」では多くの利用者の要求は満たせません。
「誤った問題設定」の寿命は短い故に、仮に「正しい実装」が存在しても、問題設定と共にすぐに陳腐化してしまいます。

これは「正しい問題設定」にも当てはまり、利用者の要求が移ろえば、問題設定は陳腐するのです。
要求が未来永劫変化しないということはありませんから、どんなに優れた実装でも、時間と共に必ず陳腐化するのです。

しかし、実装のすべてが同時に陳腐化するわけではありません
この注目すべき問題については、「メカニズムとポリシーと分離」という原則で詳しく説明します。

👑 メカニズムとポリシーと分離

cf. 機構と方針の分離 - Wikipedia

この原則は X Window System の設計がよく例として挙げられます。

X は、ほとんどの Unix 系システムで標準的に採用されるウィンドウシステム(GUI でおなじみの矩形の表示領域)を提供するための機構のひとつです。
このシステムの特徴は、極端なまでに普遍化されたグラフィックエンジン(メカニズム)のみを提供し、UI の使用感(ポリシー)については一切提供しないことです。

UI の使用感はすべてアプリケーションレベルに先送りし、メカニズムの提供に終始することで、ポリシーを上手に分離しているのです。

これが非常に強固な設計戦略であることは、X が未だに現役であることからもよく分かります。
GUI のポリシーであるルックアンドフィールの流行はすぐに変わるのに対し、グラフィックエンジンに求められるメカニズムへの要求は変化が緩やかである点に注目しましょう。

ある限定された目的のためのシステムでも、すべての実装が同時に陳腐化するわけではないのです。
陳腐化しやすい部分を分離することで、システムの寿命を延ばすという戦略は非常に強力です。

この原則に則った設計は、メカニズムを壊さずに安全にポリシーを変更できるために変更に強く、変化しにくい部分が明確なためにテストによって品質を担保しやすいため、まさに理想的な設計といえます。
逆にもしこのふたつが分離できていなければ、長期間に渡って品質を保つことはまず不可能です。

なお、ポリシーとメカニズムを後から分離するような根本的な改修を、システムの寿命が尽きる前に完了させるのは至難の業です。
つまり、ソフトウェア開発においてベースとなる設計ほど重要なものはないことが分かるでしょう。

別の言い方をすれば、最初の設計こそがソフトウェアの寿命を決定づけるのです。
システムの全体の設計は最初に考える他に良い方法はないということを肝に銘じておきましょう。

👑 銀の弾丸などない

私たちは、この狼人間を魔法のように鎮めることができる銀の弾を探し求める。
― 『銀の弾などない— ソフトウェアエンジニアリングの本質と偶有的事項』、フレデリック・ブルックス

AGI(汎用人工知能)などの「強い AI」や「ブレイン・マシン・インタフェース」のような、インターネット以降の世の中をがらりと変えるほどの SF の産物は、まだまだ実現の兆しが遠いままです。

結局、プログラミングで解決できる問題のほぼすべては、令和になろうが紙とペンで解決できることの延長に過ぎないのです。
華美な新技術やメディアで騒がれるバズワードの影に隠れて、この基本的な事実は忘れられ、プログラミングとて魔法ではないにも関わらず、過度な期待をされがちです。

「オブジェクト指向」、「関数型プログラミング」、「デザインパターン」、「クリーンアーキテクチャー」、「マイクロサービス」、「ディープラーニング」などの有用な「知恵」もまた、魔法のように問題を解決したりしません。

「知恵」も設計やプログラミングなどの作業の助けでしかなく、ソフトウェアが解決すべき問題は、残念ながら我々エンジニア自身の手でどうにかするしかありません
知恵が勝手に我々の仕事を終わらせてくれるわけではなく、解決すべき問題が複雑だからこそ、我々の仕事があるとも言えます。
(実際には、我々の技量が足らないが故に問題が複雑化するケースの方が多いのですが、それでも我々自身が悩む他ないということに変わりはありません。)

銀の弾丸のようなオカルトを信じる前に、想像力を働かせ、自分の頭で考え、日々の研鑽を怠らないようにしたいものです。

あわせて読みたい

848
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
848
Help us understand the problem. What is going on with this article?