😇

ソフトウェア設計は「頭がパンクするのを防ぐ」ためにある

に公開

初めに

オブジェクト指向、ドメイン駆動、クリーンアーキテクチャ…
設計に関連する用語はどれも難解で、さらっと聞くだけではその必要性がわかりにくかったりします。

私自身、新卒2年目の若手エンジニアとして、これらの用語と日々格闘しています。難しいですが、最近になって少しずつではあるものの設計について理解できてきました。本記事では、

  • 設計について学びたいが難しくて挫折した初心者エンジニアの方
  • プログラマーが熱心に設計について語っているが意義が理解できないマネージャーの方
  • 設計について誰かに教えたいが何から説明していいのかわからない人

などに向けて、設計の必要性を説明しようと思います。難しい専門用語は出てきません。なるべく簡易に説明するよう心がけて書いたので、肩の力を抜いて読んでいただけると幸いです。

私たちは「処理の詳細」を知らずに生きている

いきなりですが、役所で住民票をもらう場面を想像してください。諸々書類を書いたり身分証明書を出したりがあるかもしれませんが、基本的には、「住民票をください」と言うだけです。

住民票をもらう側はただ「ください」と言うだけですが、実際に住民票が渡されるまでの工程はかなり複雑なはずです。

受付の山田さんが生活課の吉井さんに連携し、吉井さんは管理者の岸田さんにパスワードを入力してもらって内山さんが管理する住民ネットワークに接続し、そこで氏名と住所を入力してデータを取得し、事務の加藤さんに印刷を依頼する…

上記の手続きは「こんな複雑なことが起こっているかも」という例として挙げただけで、実際の業務とは恐らく一致しません。私が適当に想像で書いています。ただ、「この記事の筆者は役所の業務を知らない」という点は非常に重要です。私は何度も役所で住民票をもらっていますが、住民票が渡されるまでの詳細な処理を知らないのです。

住民票を例として挙げましたが、テレビはどうでしょうか?テレビの電源を付けようとしたときに、テレビ内で走っている電気信号やその複雑な制御を意識するでしょうか?しないはずです。ドライヤーにせよ、レストランにせよ、何にせよ同じです。人は、完成された製品やサービスに対して、その内部的な処理を詳細に把握しようとしません。より正確に言えば、詳細な処理に干渉することが許されていません。

例えば、「この住民票は生活課の山田さんにお願いしたい」といった要求が受理されるでしょうか?
「この住民票はどのメーカーのプリンターで印刷されたんですか?」と聞いて教えてくれるでしょうか?
どちらも、厄介なクレーマー気質の客としてあしらわれるはずです。
また、「テレビ内のコンデンサーの静電容量が気に食わなかったから別のコンデンサーと取り換えた。そしたら壊れた。返品して欲しい。」という要求も通らないでしょう。メーカーは正しい使い方をしているのに壊れてしまった商品に関しては返品しますが、内部構造を変更されたものに関しては品質保証義務を負いません。

客に許されるのは、「住民票をください」という入力に対して、「住民票」という出力を期待すること。それだけです。その内部的な処理に干渉してはいけません。同様にテレビに関しても、「電源ボタンを押す」という入力に対して、「電源が ON/OFF される」という出力を期待することしかできません。リモコンや本体についているボタンの範疇から外れる操作をした場合に起こる結果に関しては何も保証されません。

このように、世の中の製品やサービスの多くは、処理の詳細を知らないで使うことが前提とされています。

なぜ処理の詳細を知らなくてよいのか

私たちは多くの処理の詳細を知りません。何なら、処理の詳細が隠蔽され干渉できないようになっています。これは何故でしょうか?それは私達の脳に、この世の全てを理解する知能が無いからです。

この世の製品・サービスの数は途方もないです。無限と言って差し支えないです。この全ての内部構造を理解することなど、人の一生では確実に不可能です。AI でも難しいでしょう。使う側にとって、処理の詳細を知らなくても、特定の入力をすれば特定の出力が返ってくるということは、脳の負荷を減らすために不可欠な約束事なのです。

また、製品・サービスの提供側にとっても、処理の詳細を隠すことは重要です。特定の入力に特定の出力という約束があるからこそ、マニュアルや機械による効率化を実現できます。「住民票の内容を印刷じゃなくて受付の山田さんに手書きして欲しい。できれば筆ペンで。」みたいな無秩序で処理の詳細にかかわるような入力を受け付けてしまうと、仕事の流れが無限通りに分岐するので共通化できず、マニュアル作成や機械化が困難になります。

処理の詳細を意識せずに済むための3つの条件

人がいつでも処理の詳細を意識しないのか、というとそうではありません。処理の詳細を知らずにいるためには以下の条件が満たされる必要があります。

  1. 共通の約束が同じカテゴリ内で共有されていること
  2. 他所に影響が及ばないこと
  3. 期待する操作が準備された方法で実現できること

上記3つが守られている時にのみ、処理の詳細を知る必要がなくなります。以下、この3つの要素を解説します。

共通の約束が同じカテゴリ内で共有されていること

「役所で住民票をもらう」という言葉、深く考えると実は結構不思議です。というのも、「役所」には種類があります。市役所かもしれないし区役所かもしれない、県庁か村役場の可能性もある。また、「住民票をもらう」というのにも無限のバリエーションがあります。受け付けるのは山田さんか内山さんか花田さんか、印刷するのは吉田さんか井上さんか花道さんか…

それでも、私たちは「役所で住民票をもらう」と聞くと、ある程度共通したイメージを思い浮かべることができます。大きい駐車場がある建物の中、簡素な内装と事務員、札を貰って椅子で番号が呼ばれるのを待ち、受付に身分証を見せて多少の書類を書き、また少し待つ…そして、実際にそのイメージ通りにするとどんな役所でも住民票がもらえます。このイメージは、「こうすれば公的な書類がもらえる」という住民と役所の間の約束と言えます。

このような約束が、市役所にも区役所にも県庁にも村役場にも、山田さんにも内山さんにも花田さんにも共有されている、ということが非常に重要です。これにより私たちは、どんな役所なのか、誰が作業するのか、といった詳細を意識することなく、「住民票をください」と言うことができるのです。

もしこういった約束が全国の役所で共有されていなければ、各役所ごとの処理フローや事務員の性格といった、「処理の詳細」に配慮して住民票を貰うことになるでしょう。

他所に影響が及ばないこと

前述した通り、多くの人は役所で住民票をもらう際にその詳細な処理を意識することは無いです。しかし、例えば、「役所で住民票をもらうと自宅の水道水が酸っぱくなる」というように、役所での行動が役所の外に影響し始めたらどうなるでしょうか。

あなたは自分の生活の安定を守るために、役所の排水方法や使われている工業製品、事務処理フローやそこに関わっている人間の調査をすることになるでしょう。

このように、ある物の状態が他の物に影響を与える場合、人は「処理の詳細」を意識せざるを得ません。

期待する操作が準備された方法で実現できること

テレビの電源がONになることを望むなら、ただ用意されている電源ボタンを押せばよいです。しかし、ONでもOFFでもない、画面の右半分だけ明るくしたい、みたいな状態を望むのであれば、テレビの構造について調べ、解体して基盤を改造するといった複雑なことをしなければいけません。

テレビには「電源を切り替える方法」が準備されています。そのため、私たちはテレビの内部を知らずに電源をON/OFFできます。逆に、テレビには「画面の右半分を明るくする」方法は準備されていません。これを実現するには、テレビの内部構造に詳しい必要があります。

このように、ある物に期待する状態が、その物に備わったボタン等で実現できる場合、人は「処理の詳細」を気にしなくてもよくなります。逆にその物に備わってない何かを実現したいときは、「処理の詳細」を熟知する必要が生じます。

詳細を意識しないで生きるのは分業しているから

これまでの話をまとめると、以下のようになります。

  • 人は処理の詳細をあまり意識しない
  • これにより脳の負荷を上げずに製品・サービスを使える
  • 以下の条件にあてはまると処理の詳細を無視できる
    • 共通の約束が同じカテゴリ内で共有されていること
    • 他所に影響が及ばないこと
    • 期待する操作が準備された方法で実現できること

これらを踏まえた上で、そもそもなぜ私たちは、処理の詳細を知らないで生きているのでしょうか?これは、私たちが分業して様々な製品を作り、それらを組み合わせて生きているからです。分業によって、人は自分が関わっている範囲の仕事にはとことん詳しくなりますが、逆にそれ以外の知識は薄くなります。

そしてこの「分業」が、これまでの話とソフトウェア開発を繋げるキーワードになります。

ソフトウェアは分業の塊

ソフトウェアは、色々な部品の組み合わせで出来ています。関数、メソッド、クラス、モジュール、コンポーネント…その呼び名や概念は様々ですが、とにかく部品ごとにプログラムを分けて、それらを組み合わせるというのは、現代のソフトウェア開発においては基本です。また、その部品たちを複数人で分業して開発する、というのも普通です。

しかしそうなると、問題が発生します。分業で作られた大量の部品、その全てのプログラムを理解しようとすると頭がパンクします。そのため、私たちが普段の生活でしているのと同じように、エンジニアは他のエンジニアが作った部品の「処理の詳細」を無視して、「こういう入力をすれば、こういう出力が返ってくるだろう」という点にのみ注目して部品同士の連携をしていくことになります。

しかしながら、これがなかなかうまくいきません。初心者がソフトウェアを開発すると、

  • 部品 A,B,C,D は同じ種類の部品なのに D だけ使い方が違う
  • 部品 A のプログラムを修正したら部品 B が壊れる
  • 部品 A の状態変更のために部品 A のデータを部品 A に用意されていない方法で書き換える

といったことが多発し、処理の詳細を無視できる条件が満たされなくなります。こうなるとエンジニアはあらゆる部品の処理詳細を意識することになり、頭がパンクします。実際に、「パンクしました。助けてください。」と言う人は少ないですが、パンクの影響は進捗の遅れやバグ、残業や認識合わせ会議の増加といった目に見える形で現れます。

分業でエンジニアの頭をパンクさせないためには?

頭がパンクすると当然生産性は下がります。生産性が下がれば、エンジニアもエンジニアを雇っている側も双方不幸です。では、生産性を下げないためにはどうすればよいのでしょうか。

  1. 共通の約束が同じカテゴリ内で共有されていること
  2. 他所に影響が及ばないこと
  3. 期待する操作が準備された方法で実現できること

なるべくこれらを満たすように部品を作ればよいのです。幸い、メジャーなプログラミング言語(Java、C#、Python、TypeScript 等)はこれらの条件を満たすことができるような機能を持っています。そして、そのような機能を有効に使うために必要なのが「設計」の知識です。しっかりと設計されたコードは、処理の詳細に目を向けることなく、部品同士を連携させることができます。

子供を表す部品、大人を表す部品、シニアを表す部品があったとして、それらからテーマパークの入場料金を引き出すとしましょう。その際に、良く設計されたコードでは、部品内の処理の詳細を知る必要はありません。部品を使う側で何か計算をする必要もありません。ただ、「あなたの入場料金を教えて」と命じるだけです。まるで「住民票をください」と言えば住民票をくれる役所のようです。また、仮にスーパーシニアという部品が追加されたとしても、使う側はそのスーパーシニアにこれまでと同じように「あなたの入場料金を教えて」と命じるだけです。仮に48個目の都道府県ができたとしても、私たちはその場所で他の都道府県でしてきたように「住民票をください」と言うだけでしょう。これと同じです。

また、各部品にはその部品に関する処理が全て集まっており、逆に他の部品に関する処理は含まれていません。そのため、例えば子供料金を上げても大人料金が下がることはありません。

加えて、各部品に行うべき操作は全て各部品内で定義されており、逆にそれ以外の操作は禁止されます。テレビのボタンのように、部品外に特定部分だけを露出させそこのみを他の存在との接続部とします。

前述したとおり、こういった部品の作成は、メジャーなプログラミング言語では"可能"です。むしろ、JavaやC#は最初からこうした設計を意識して言語仕様が作られています。

パンクさせない設計は可能だが…

可能なんですが、そのためには適切な設計が必要です。直観的に書いたのでは理想的な部品になりません。というのも、ソフトウェアの設計には抽象的な概念の理解が必要なんですね。かなり専門的なスキルです。

また、設計の良さが効いてくるのは、「分業」が多い場合です。つまり、開発の規模が大きく、開発が長期間に及ぶプロジェクトほど設計の意義は大きくなる、ということです。少人数の開発だったり、開発に関わったその後には責任を持たない契約のエンジニアが多数を占める現場だったりした場合、エンジニアが設計を頑張る意義はどうしても薄くなりがちです。

このような事情が重なり、ソフトウェアの設計は軽視されがちです。(厳密に統計を取ったりしたわけではありませんが、設計の本を読むとその著者の多くが設計が軽視された現場で苦しんだ過去があるようです。)

僕としては、設計が軽視されると残業時間が増えて困るので、積極的に設計の必要性をアピールしたいところ(この記事もそのアピールの一環)なのですが、いかんせんまだまだ半人前なもので、プロジェクトの開発方針を変更するような力はありません。

もしこの記事をマネージャー層の方が読んでくださっており、自分のチームが進捗の遅れやバグ、残業や認識合わせ会議の増加といった頭のパンクの影響に悩まされていたら、設計について今一度考えていただけると大変嬉しいです。

まとめ

  • 人は「処理の詳細」を意識しないことで、分業された社会の中で頭をパンクさせずに生活できる
  • それをソフトウェア開発でもできるようにするのが「設計」

長々と書きましたが、結論は上記2点です。自分自身の知識の棚卸や言語化訓練を兼ねて書いた記事ですが、これが皆さんの設計に関する理解の一助になれば、とても嬉しいです。最後まで読んでいただき、本当にありがとうございました。

余談

本文中で出てきた

  1. 共通の約束が同じカテゴリ内で共有されていること
  2. 他所に影響が及ばないこと
  3. 期待する操作が準備された方法で実現できること

これらの条件は、インターフェース、多態性、ドメインオブジェクト、単一責任、疎結合、カプセル化、APIといった専門的な概念をまとめてなるべく一般的な言葉で言い換えて書いたものです。この記事の内容からステップアップして、より技術的に詳細な内容を知りたい場合は、これらの言葉の意味を調べてみてください。また、下記の書籍もわかりやすい上に非常に勉強になります。おすすめです。
良いコード/悪いコードで学ぶ設計入門
現場で役立つシステム設計の原則

Discussion