🤡

読書メモ:Modern Software Engineering

2023/03/27に公開

はじめに

「Modern Software Engineering:Doing What Works to Build Better Software」、という本を読んだので読書メモとしてまとめました。

https://www.amazon.com/Modern-Software-Engineering-Discipline-Development/dp/0137314914

特にバックエンドエンジニアは読んでおいて損のない内容だと思います。エンジニア一年目とかだとちょっと内容が難しいかもしれません。ある程度コーディングに慣れてきて、次の成長ステップとして、設計やTDDについて考え始めた段階で読むのがいいかもしれないです。(ちなみにとても長い本です)

TDDや継続的デリバリーはソフトウェア開発において必須なんだなという感想を持ちました。

和訳もされているようです。和訳を見ると継続的デリバリーについて書かれた本という印象を受けるかもしれません(僕もそうでした)が、それだけではなく原著副題の「Doing What Works to Build Better Software」がこの本の特徴をよく表している気がします。

https://www.amazon.co.jp/継続的デリバリーのソフトウェア工学-もっと早く、もっと良いソフトウェアを作るための秘訣-David-Farley-ebook/dp/B0BP9JRZS8/ref=sr_1_1?adgrpid=143732066356&hvadid=636355051838&hvdev=c&hvlocphy=1009318&hvnetw=g&hvqmt=e&hvrand=16974566322817878234&hvtargid=kwd-1930976115483&hydadcr=27492_14587064&jp-ad-ap=0&keywords=継続的デリバリーのソフトウェア工学&qid=1679797183&sr=8-1

この本のポイント。

  • この本の核心にあるアイデアは、「少しずつ進歩するにつれて学習していくこと」
  • ソフトウェア開発において、「仮定→検証→フィードバック→学習」の反復が重要であると述べており、これを構築するためにTDDや継続的デリバリーが重要であると主張している
  • 継続的デリバリーやアジャイル開発、TDDについてその具体的な実践方法について書かれているのではなく、それら技術のアイデアや、生まれた背景について、ソフトウェア工学の観点、歴史的経緯、そして著者の経験から述べられている
  • しかし、この本は、答えを提供することを意図したものではない。答えがわからない場合でも安全に進歩できるようにするためのアイデアとテクニックを提供することを目的としている(実際に複雑なシステムを作成している場合は、常にそうである。完成するまで答えはわからない)

About this book

  • この本の著者は大学でコンピュータサイエンスを専攻し、ソフトウェアエンジニアリングという科目を履修したが、実務の現場でうまくそこで学んだ知識を活用することができていなかった。

  • 10年後、著者は金融取引所の現場で、とある同僚と一緒に働く機会を得て、そこでペアプログラミング、TDD、Continuous Deliveryを学び、高品質で高性能なアプリケーションを提供することができた。

  • この同僚と働いていて気づいたのが、同僚が実際にコードをタイプする時間が非常に少ないという事実だった。実際、書く前に考えることで、時間と労力を節約できることがあるということが分かった。

  • 著者は、ここでの経験をこの本にまとめ、ソフトウェアエンジニアリングに適用される概念を抽象的で難解なものとして扱わず、実践的な問題解決に役立つものとして理解しやすくまとめている。

what is software engineering

ソフトウェアとは何か

著者の考えでは、ソフトウェアエンジニアリングとは、問題に対して、経験的で科学的なアプローチを用いて、効率的かつ経済的な解決策を見つけることを指す。

エンジニアリング!=コード

エンジニアリング!=コードであり、コードはアウトプットに過ぎない。
そのコードを生成するまでの設計、過程、テクニック、手法を含めて、エンジニアリングである。

トレードオフ

全てのエンジニアリングには、トレードオフが付きまとう。システムをより安全にすれば使いにくくなる。分散化をすれば統合が難しくなる。人員を増やせば、コミュニケーションの齟齬や複雑性が増す等。直面しているトレードオフを理解することは、エンジニアリングの意思決定の重要かつ基本的な側面。

進歩の幻想

技術の進歩はそれほど大事ではない。例えばサーバーレスについて。
サーバーレスへの移行は興味深いものであるが、AWS、Azure、GCPで提供されているものの違いは重要ではない。ツールの機能面だけでなく、システム設計への影響を考える。

サーバーレスの選択はシステムの設計に何らかの影響を及ぼす。
状態をどこに保存するか?どこで操作するか?システムの機能をどのように分割するか?
これらの質問は、どのプラットフォームを使用するかよりも、はるかに重要である。

変化の業界?

よくこの業界は変化が激しいと言われているが、ソフトウェア開発への考え方や実践方法に影響を与えるような大きな変化はそうそうない。また新しいと言われている技術が既存のものより優れているとは必ずしも言えない。

技術の選定と物差し

使用する言語、ツール、およびフレームワークは、時間の経過とともに、またプロジェクトごとに変化する。
これらを学習し、作成するシステムの複雑さに対処できるようにするアイデアこそが、エンジニアの真のツールである。

技術を選定する際には、「これにより、作成するソフトウェアの品質が向上するか?」「効率が向上するか」と自問する必要がある。

「品質が向上するか」これは「安定性」の指標によって測定される。
「効率が向上するか」これは「スループット」によって測定される。

安定性、スループット

安定性は、以下によって測定される。

  • 変更失敗率: プロセスの特定の時点で変更によって欠陥が生じる率
  • Recovery Failure Time : プロセスの特定の時点で障害から回復するのにかかる時間

スループットは、以下によって測定される。

  • リードタイム: 開発プロセスの効率の尺度。1 行の変更が「アイデア」から「動作するソフトウェア」になるまでにどのくらいかかりますか?
  • 頻度: 速度の尺度。変更が本番環境にデプロイされる頻度は?

Optimize for learning

反復

ウォーターフォール思考

ウォーターフォール型の考え方は、「十分に考え、十分に努力すれば、最初からうまくいく」という前提から始まる。

アジャイル思考

アジャイル思考はこれとは逆の考えた方であり、それは、「私たちが間違いを犯すことは避けられない」という前提から始まる。

「ユーザーが何を望んでいるのか理解できない」「すぐにデザインが思いつかない」「自分が書いたコードのバグをすべて見つけたかどうかわからない」など。アジャイルチームは間違いを犯すだろうと想定して開始するため、アジャイルチームは間違いのコストをかなり意図的に軽減する方法で作業できる。

アジャイル思考の前提に基づいて、チーム、プロセス、テクノロジーの組織に取り組み、安全に間違いを犯し、間違いを簡単に観察し、変更を加え、理想的には次回より良い結果を出せるようにする。
これは、以前の予測ベースのウォーターフォール アプローチよりも、あらゆる種類のソフトウェア開発に非常に適している。

反証可能性

アジャイル思考のこの考えは科学と共通している。懐疑的な観点からアイデアにアプローチし、アイデアが正しいことを証明するのではなく、間違っていることを証明しようとすること (「反証可能性」) は、より科学的な考え方に固有のもの。

反復作業の利点

アジャイルな計画は、小さな作業断片に分解して1回のスプリントまたはイテレーションで機能を完成することを目指しており、進捗状況を測定する方法として始まったが、質や適切性に関する決定的なフィードバックを提供することで、学習速度を向上させた。
このデザインは、ユーザーが気に入っているか、システムが十分に高速か、バグはすべて修正されているか、コードがうまく動作しているかなど、重要な質問に対するフィードバックを定期的に提供することができる。

防御的な設計戦略としての反復

繰り返し作業することで、設計に対して防御的なアプローチを取ることが奨励される。

ソフトウェア開発は、作業開始前にいくら分析しても、「すべての作業を完全に理解する」ことから始まることはありえない。
どんなに入念に計画を立てても、定義されたプロセス、モデルまたはウォーターフォールアプローチは最初のハードルで失敗する。


ウォーターフォールの変化のコスト

ソフトウェア開発をこの不適切な型にはめ込むことは不可能である。ソフトウェア開発は探索と発見の演習であり、驚き、誤解、間違いは正常であると言える。

変化することが必然であるのなら、その変化のコストを平坦化させるのに必要なものは何か。
それは反復である。
アイデアを顧客やユーザーの手に渡せるように、十分な分析、設計、コーディング、テスト、およびリリースを行い、何が実際に機能するかを確認する必要がある。

フィードバックを定期的に提供し、それについて考え、失敗しながら、その学習を踏て、それをソフトウェアに適応させ、改善していく。
これが継続的デリバリーの中心にあるアイデアの1つである。


アジャイルの変化のコスト

反復作業の実践

反復作業を実践するために、小さなバッチから作業を始める。基本的に小さければ小さいほどよい。
これにより、テクニック、アイデア、テクノロジーをより頻繁に試すことができる。

小さなバッチで作業するということは、作業時間を短く保つことを意味する。そのため、不特定要素が作業に影響を与える可能性が少なくなり、問題が起こる可能性が低くなる。
また、状況の変化や、誤解によって小さな一歩が無効になったとしても、失われる仕事は少なくなる。

よって小さな一歩が本当に重要であると言える。

アジャイルチームにおけるこのアイデアの明らかな具現化は、イテレーションまたはスプリントのアイデアである。
アジャイルは、タスクを分割し、短い固定期間内で、対応したコードを本番環境にリリースする。
ただしこれは反復作業の具現化の一つの例にすぎない。

継続的インテグレーション(CI)とテスト駆動開発(TDD)の実践も、本質的に反復作業であると考えることができる。

継続的インテグレーション(CI)とテスト駆動開発(TDD)

継続的インテグレーション(CI)では、1日に複数回、頻繁に変更をコミットできる。書いたコードが他の人のコードと一緒に機能するかどうかを学習し、理解する機会が増える。

TDDは、下記のようによく説明される。

赤: テストを作成して実行し、失敗することを確認
緑: テストに合格するのに十分なだけのコードを記述し、実行して、合格することを確認
リファクタリング: コードとテストを変更して、明確で、表現力があり、エレガントで、より一般的なものにする。小さな変更ごとにテストを実行し、合格することを確認

これは非常にきめ細かい、反復的なアプローチの実践と言える。

たとえば、実際のコーディング作業では、ほとんどの場合、新しいクラス、変数、関数を一連の小さなリファクタリングステップを介して導入し、進行中にテストを実行して、コードが引き続き機能することを頻繁に確認する。
これは、非常に細かい解像度での反復作業である。
プロセスの各時点で、自分の考えやデザインとコードの方向性を簡単、かつ安全に再評価して変更できる。

これらの特性は、反復作業が非常に価値があり、ソフトウェア開発にとっての基本的かつ重要なプラクティスである証左と言える。

フィードバック

フィードバック

フィードバックがなければ、推測に基いて物事を進めるしかない。

自分の選択と行動の結果を知り、理解しない限り、進歩はできない。
これはビジネス、ソフトウェア開発でも同じである。

フィードバックは物事を進める上での判断基準の証拠となる。必然的に意思決定の質も向上する。

設計へのフィードバック

TDDはコーディングへのフィードバックとして非常に有用。
テストコードを書くのが難しいコードの場合、それは設計に問題があるということの示唆でもある。

シンプルで効率的なテストが書けることと、良い設計であることの2つには共通点がある。
より良い設計のコードの特徴。

  • モジュール性
  • 関心事の分離
  • 高い結束
  • 情報隠蔽(抽象化)
  • 適切なカップリング

TDDでは、最初にテストを記述する。
テスト以外のコードを書く前に最初にテストを書いているので、テストを作成している時点で、コードへのインターフェースも設計していることになる。
テストには結果が必要なので、関心のある結果を簡単に取得できるようにテスタブルにコードを設計する必要がある。

テスタブルなコードには以下の特徴がある。

  • モジュール式です
  • 懸念事項が適切に分離されている
  • 高い凝集力を発揮
  • 情報隠蔽(抽象化)を使用
  • 適切に結合されている

テストの役割

開発への古典的なアプローチでは、テストはプロジェクトの最後に演習として残されることもあれば、顧客に任せられることもあり、時間的なプレッシャーに押しつぶされてほとんど完全に消えてしまうこともあった。

この種のアプローチで作成されたテストは、本質的に役に立たない。コーディングや設計で導入されたエラーは、開発チームがプロジェクトをリリースし、メンテナンスをプロダクションサポートチームに引き渡すまで発見されないこともある。

TDDおよびCIのアプリケーションは、これをひっくり返し、テストを開発プロセスの最前線と中心に据えた。
これにより、フィードバックループが数秒に短縮され、ミスに関するフィードバックがほぼ瞬時に提供されるようになる。

この考え方では、テストが開発プロセスを推進し、さらに重要なことに、ソフトウェア自体の設計を推進した。TDD を使用して作成されたソフトウェアは、TDDを使用せずに作成されたソフトウェアとは見た目が異なる。ソフトウェアをテスタブルにするためには、期待される動作を評価できるようにすることが重要。

これにより、デザインは特定の方向に進む。「テスタブル」なソフトウェアはモジュール式で、疎結合で、高い結束力を示し、関心事が適切に分離され、情報隠蔽が実装されている。これらは、ソフトウェアの品質の指標として広く認識されているものでもある。そのため、TDDはソフトウェアの動作を評価するだけでなく、その設計の品質を向上させた。

ソフトウェアでのテストは非常に重要。ソフトウェアは、他のほとんどのものと同じように壊れやすいもの。最も小さな欠陥(場違いなコンマ)が壊滅的な障害につながる可能性がある。

TDDは多くの分野で物議を醸すアイデアのままであるが、このアプローチは、システムのバグ数を劇的に減らすことができ、システムの設計の品質にプラスの影響を与える。

TDDは、客観的に「高品質」なコードを作成するよう圧力をかける。これは、ソフトウェア開発者の才能や経験に関係がない。悪いソフトウェア開発者を偉大にするわけではないが、「悪いソフトウェア開発者」をより良くし、「優れたソフトウェア開発者」をより大きくする。

早期のフィードバック

一般に、決定的なフィードバックをできるだけ早く得ようとすることは効果的な方法である。
コーディング中は、IDEの機能や型システムなどの手法を使用して、入力時にコード内のエラーを発見できる。これは、最も高速で安価なフィードバックループであり、最も価値のあるものの1つである。

また、TDDアプローチの結果として作成された自動化されたユニットテストは、ローカルの開発環境で作業し、定期的に実行するときに、第2レベルのフィードバックを提供してくれる。

CICDアプローチにより、コードをコミットすると、単体テストとその他のテストの完全なスイートが実行される。これにより、自分のコードが他の人のコードと連携して動作することを、より徹底的に検証できる。第3レベルのフィードバック。

受け入れテスト、パフォーマンステスト、セキュリティテスト、および変更の有効性を理解するために重要と見なされるその他すべてのことにより、作業の品質と適用可能性に対する信頼が高まるが、これらは結果を返すまでに時間がかかる。

そのため、最初にコンパイル機能 (開発環境で特定)、次に単体テストで欠陥を特定することを優先するように取り組み、それらの検証が成功した後でのみ、他の形式のより高度なテストで欠陥を特定することは、最も早く失敗し、最高品質で最も効果的なフィードバックを得ることができる。

継続的デリバリーとDevOpsの実践者は、初期の失敗を優先するこのプロセスをシフトレフトと呼ぶことがある。

製品設計へのフィードバック

ソフトウェア開発者への報酬は、適切に設計されたテスタブルなソフトウェアを作成するためには支払われない。報酬は組織のために何らかの価値を創造することで得られる。

これは、ほとんどの組織において、よりビジネスに焦点を当てた人々と、より技術に焦点を合わせた人々との間に
起こる緊張の1つである。
これは、有用なアイデアを継続的に本番環境に提供できるようにすることに焦点を当てることで解決される問題である。

ソフトウェア開発者が作成するアイデア、製品が優れたものであることをどのようにして知ることができるか?
本当の答えは、アイデアの消費者 (ユーザーまたは顧客)からフィードバックを得るまでわからない。

製品アイデアの作成と生産への価値の提供に関するフィードバックループを閉じることは、継続的デリバリーの真の価値である。それこそがこのアプローチが世界中の組織で非常に人気がある理由である。
より狭い(それでも重要な)技術的な利点でのみ語られているわけではない。

システムにテレメトリを追加して、システムのどの機能がどのように使用されているかに関するデータを収集できるようにすることは、今や標準になっている。
収集される情報は提供されるサービスよりも価値が高く、顧客自身でさえ意識していない顧客の欲求、ニーズ、および行動に関するフィーダバックを提供できる。

漸進主義

増分主義・漸進主義

大規模なシステムを作る場合には、段階的に進めることが基本。完璧に完成することはない。
システムは、学びと理解が蓄積されるにつれて、少しづつ形成されていくもの。

増分設計

著者はソフトウェア開発に対するアジャイルなアプローチを長年支持している。
アジャイルを重要なステップ、つまり「無限の始まり」のステップと見なしている。これは、すべての答えが得られる前に作業を開始できることを意味する。この本の核心にあるアイデアは、少しずつ進歩するにつれて学習していくことにある。

これは、多くのソフトウェア開発者の先入観に挑戦する。多くの人々は、作成したいデザインの詳細なアイデアを得る前に、コードを記述できるようになるというアイデアに懐疑的である。

変化、失敗、予期せぬことによる影響は、すべて避けられないものであることを受け入れる必要がある。
自分が知らないことを受け入れ、自分が知っていることを疑って、速く学ぼうとすることは、高品質なエンジニアリングへの第一歩である。

経験主義

経験主義

科学哲学における経験主義は、「特に実験で発見された証拠を強調すること」と定義されている。

ソフトウェアは開発の段階では、その開発者のアイデア、ロジックの最善の推測にすぎない。ソフトウェアを本番環境に公開し使用して初めて、学習の機会となる。

現実の証拠と観察に基づいて意思決定を行う経験主義は、賢明な進歩を遂げるために不可欠である。その分析と反省がなければ、組織、個人は当て推量のみに基づいて進み続け、お金や評判を失うアイデアに投資し続けることになる。

実験

実験的であること

問題を解決するために実験的なアプローチを取ることは非常に重要。
科学とその核心にある実験的アプローチが、現代のハイテク社会を、先行する農業社会と何よりも区別させるものである。

「実験的であること」をアプローチとして定義する4つの特徴がある。

  • フィードバック: フィードバックを真剣に受け止める必要がある。
  • 仮説: 評価しようとしているアイデアを念頭に置いておく必要がある。無作為にうろつき回ってランダムにデータを収集しているのでは不十分。
  • 測定: 仮説でテストしている予測を、どのように評価するかについて、明確なアイデアが必要である。
  • 変数を制御する: 実験が私たちに送っているシグナルを理解できるように、できるだけ多くの変数を排除する必要がある。

実験としての自動テスト

実験にはさまざまな形があるが、ソフトウェア開発には、素晴らしい実験プラットフォームである、コンピューターがあるという点で、他のどの分野よりも大きな利点がある。必要に応じて、毎秒数百万回の実験を実行できる。

ソフトウェアを検証するための自動テストは、十分に努力すれば実験と見なすことができる。ただし、コードを記述した後に自動テストを記述すると、実験の価値が低下する。実験はある種の仮説に基づいている必要があり、コードが機能するかどうかを判断するのはかなり不十分な仮説となる。

「この特定の状況が与えられた場合、このことが起こると、私たちはこの結果を期待します。」
このような仮説をTDDアプローチでテストを作成し、コードを完成させて実験を実行したときに、テストケースの予測が満たされていることを確認する。

このTDDアプローチは、さまざまなレベルの粒度で運用できる。動作駆動開発 (BDD)とも呼ばれる受け入れテスト駆動開発(ATDD)の手法を使用して、ユーザー中心の仕様を作成することから始めることができる。これらのアプローチを使用して、よりきめ細かく、より技術的な単体テストを作成する。

これらの手法を使用して開発されたソフトウェアは、従来の方法で開発されたソフトウェアよりもバグが大幅に少なくなる。

モジュール

モジュール性

モジュール性とは、システムのコンポーネントを分離および再結合できる度合いである。

モジュール性は、作成するシステムの複雑さを管理する上で非常に重要。現代のソフトウェアシステムは、多くの場合、本当に複雑なものとなっている。最新のシステムのほとんどは、人間がすべての詳細を頭の中に保持する能力を超えている。
この複雑さに対処するには、構築するシステムをより小さく、より理解しやすい部分に分割する必要がある。

現代のソフトウェアシステムは、OSやその他のソフトウェアの抽象化とモジュール化を改善することで確実に前進してきた。

しかし、多くのシステムはそれ自体がモジュール式には見えない。モジュール式にシステムを設計するのは大変な作業を伴う。

ソフトウェア開発が学習の積み重ねであるとすれば、学習するにつれて、理解は進化し、変化する。
そのため、どのモジュールが理にかなっていてどのモジュールが理にかなっていないかについての見解も、時間の経過とともに変化する可能性がある。
これがソフトウェア開発の醍醐味であり、これは、熟練者によって書かれたコードと、初心者によって書かれたコードとを最も区別する特性である。(筆者の考え)

優れたデザインの重要性を過小評価する

業界として、ソフトウェア設計の重要性は過小評価されてきた傾向にある。私たちは言語とフレームワークに執着し、IDEとテキスト エディター、またはオブジェクト指向プログラミングと関数型プログラミングについて議論してきた。
しかし、これらのどれも、モジュール性や関心の分離などのアイデアほど重要ではない。

プログラミング言語、またはツールが何であれ、優れたモジュール性と適切な関心の分離を備えたコードがあれば、コードはより、テスタブルになり、変更、作業がしやすいものとなる。

モジュール性のための設計は、プログラミング言語の構文を知ることとは異なる種類のスキルである。ある程度の習熟度を高めたい場合は、取り組む必要があるスキルであり、一生を費やしても、おそらく完璧にすることはできない。

テスト容易性の重要性

TDDは、著者のキャリアの中でソフトウェア開発における最も重要なステップの1つ。
著者がTDDを高く評価している理由は、それが「テスト」とはほとんど関係がないということ。
それはコードをテスタブルにすることで品質が向上するのと、テストから設計の品質に関する迅速で正確なフィードバックが得られるためである。

テスト容易性を考慮した設計によりモジュール性が向上

例えば、飛行機の翼の効果をテストするために、飛行機を全て組み立ててから飛ばす。
これは恐ろしく非効率であり、何かを学ぶ前にすべての作業を最初に行う必要があり、複雑でテスト再現性も低い。
代わりに、制御された環境で翼の小さなモデルを作り、風洞で実験することで、変数を制御し、より再現性の高い結果を得ることができる。モデルの部品はモジュール化され、単純な風洞で行うことができる。
テスト容易性を考慮したモジュール化により、測定できるものをより細かく制御し、精度を高めることができる。

モジュール性とテスト容易性はフラクタルである。

サービスとモジュール性

サービスは、ある「サービス」を他のコードに提供し、その「サービス」を提供する方法の詳細を隠すコードと考えることができる。これは「情報隠蔽」の考え方であり、システムが成長するにつれてシステムの複雑さを管理したい場合に非常に重要である。

また、サービスは、システムの詳細を隠す小さなコンパートメントのアイデアであり、これは便利なアイデアであり、システムのモジュールと考えることができる。

結束、凝集

凝集とは

関係ないものは離す。関係あるものは近づける。

複雑さを管理するために推奨される原則は、実際にはシステムを区分化することに関するものである。より小さく、理解しやすく、テストしやすく、個々の部品からシステムを構築する必要がある。そのためには、「関係のないものを引き離す」技術が必要であるが、「関係のあるものを近づける」技術も必要となる。そこに結束が生まれる。
無関係なものをコレクションするだけではモジュール化とは言えない。

まとまりのあるソフトウェアを実現する方法

結束の重要な尺度は、変化の程度またはコストである。
変更を加えるために多くの場所でコードベースを変更する必要がある場合、それはまとまりのあるシステムではない。

下記はコードのまとまりを改善する3つの簡単な例を示している。各例では、アイテムをショッピングカートに追加し、それをデータベースに保存して、カートの合計値を計算している。

コードサンプル
public function addToCart1($item) {
	$this->cart->add($item);
	$conn = new SQLite3('my_db.sqlite');
	$stmt = $conn->prepare('INSERT INTO cart (name, price) VALUES (:name, :price)');
	$stmt->bindValue(':name', $item->name);
	$stmt->bindValue(':price', $item->price);
	$stmt->execute();
	$conn->close();

	return $this->calculate_cart_total();
}

public function addToCart2($item) {
	$this->cart->add($item);
	$this->store->store_item($item);
	return $this->calculate_cart_total();
}

public function addToCart3($item, $listener) {
	$this->cart->add($item);
	$listener->on_item_added($this, $item);
}

最初の関数は明らかにまとまりのないコードである。多くの概念と変数がごちゃ混ぜになっており、*本質的な複雑さと偶発的な複雑さが完全に混在している。

本質的な複雑性、偶発的な複雑性

*本質的な複雑性:ドメインに固有の複雑性。たとえば、金利の計算やショッピングカートへのアイテムの追加等
*偶発的な複雑性:コンピューター上で実行しているためにシステムに課される複雑性。DB接続やAPI通信等

2番目の例はずいぶん改善されている。これはより一貫性があると言える。この関数の概念は関連しており、より一貫したレベルの抽象化を表してる。$this->store->store_itemはおそらく議論の余地があるが、少なくともこの時点で偶発的な複雑さの詳細を隠している。

3番目はよりまとまりのあるコードとなっているが、責任が分散されすぎているとも言える。この関数を見ただけでは何が起こっているのか分からない。

結束力の低下によるコスト

ここでのポイントは結束にはスイートスポットがあるということ。

あまりにも多くの概念をごちゃ混ぜにすると、まとまりが失われる。 addToCart1では、すべての作業が1つのメソッド内で行われていると主張できるが、これは単なるまとまりにすぎない。

addToCart3は設計としてはより柔軟であるが、明確さが欠けている。ここでは、責任が広範囲に分散し多くのコードを読んで理解しないと全体像を理解できない。
ルーズな結束は柔軟性をもたらすが、明快さとのトレードオフになる。

まとまりの悪さを見つける簡単で主観的な方法がある。コードを読んで「このコードが何をするかわからない」と思ったことがあるなら、それはおそらくまとまりが悪いからである。

関心の分離

関心事の分離

関心の分離の簡単な口語表現は、「1つのクラス、1つのこと。1つの方法、1つのこと。」
モジュール、クラス、または関数が複数のことを行う場合、懸念事項は実際には分離されていない。

関心の分離は主に、結合を減らし、コードとシステムの結束とモジュール性を向上させるために採用できる手法である。

本質的な複雑さと偶発的な複雑さを分離する

設計の品質を向上させる効果的な方法は、特定の方法で懸念事項を分離すること。

システムの本質的な複雑さは、ドメイン固有の複雑性のこと。銀行口座の価値の計算方法、ショッピングカート内のアイテムの合計方法等。

偶発的な複雑性は、それ以外のこと。つまり、コンピューターで何か有用なことを行うことの副作用として、解決せざるを得ない問題のこと。これらは、データの永続化、画面への表示、クラスタリング、セキュリティの側面など、実際にはドメイン固有の問題の解決に直接関係のないもの。

関心の分離を通じて設計を改善するための効果的なアプローチは、システムの偶発的および本質的な複雑さの関心を分離することに明確に焦点を当てること。

例えば車の運転を気にするシステムのロジックは、画面に表示するロジックから分離する。取引を評価するロジックを、その取引を保存するロジックから分離する。

情報の隠蔽と抽象化

抽象化または情報隠蔽

コードに線や継ぎ目を描くことにより、外側から見たときに背後にあるものを気にしないようにすることが重要である。関数、クラス、ライブラリ、またはモジュールのクライアント側はそれがどのように機能するかについて何も知る必要はなく、気にするべきではない。

情報の隠蔽には、データを隠すだけでなく、抽象化も含まれる。
また、抽象化は抽象的な概念オブジェクトを作成することだけではなく、情報隠蔽の一部であることもある。

技術的問題と設計上の問題

多くの組織はコードの変更に恐れを抱いているが、既存のコードを変更することは良いことであり、変更ができないコードは実質的に死んでいる。

優れたソフトウェアエンジニアリングとは、間違いを犯しながら、修正できる方法で作業し、問題理解を深め、デザインに反映し、製品と技術を進化させる事である。これを実現するには、小さなステップで作業し、コードを分かりやすく保ち、影響を最小限に抑えながら変更を行い、変更が安全かどうかを素早く確認する方法が必要である。抽象化と情報隠蔽は、正しく扱うことができれば、使いやすいシステムへの明確な道筋を示すものである。

テストによる抽象化の改善

テストは単に間違いを見つけるだけでなく、コードの設計にも役立つ。

抽象化を用いて、望ましい動作を明確に表現することが肝要。コードを書く前に、仕様(テスト)を書くことで、設計が簡単になる。このアプローチは、定義上、設計を抽象化することになる。
テストケースを適切に記述できるように、アイデアを簡単に表現できるコードへのインターフェイスを定義している。
仕様書 (テスト) を書くことは、設計行為である。コード自体の動作とは別に、プログラマーがコードと対話する方法を設計している。
コードの消費者の観点から明確でシンプルな表現を目指し、実装の詳細は後回しにする。この方法で、コードは使いやすくなり、実行内容と実行方法が分離される。これは、契約による設計の実用的で軽量なアプローチである。

適切な抽象化を選ぶ

抽象化は、ソフトウェア開発や問題解決において大事な要素である。
複雑な事象や概念を簡略化して、理解しやすくするために行われる。ただ、どの抽象化が適切かは、目的や状況によって異なる。以下は、適切な抽象化を選ぶ際のポイント。

  • 目的に沿った抽象化: 問題解決のためには、目的に特化した抽象化が大事。例えば、地下鉄の地図は乗客が駅間の移動を理解しやすくすることを目的としている。

  • シンプルさとわかりやすさ: 抽象化は、複雑な概念や事象を簡単に理解できるようにする目的がある。適切な抽象化は、簡潔でわかりやすい表現を心がけることが大事。

  • 柔軟性: 抽象化は、変更に対応できるように柔軟性を持つことが望ましい。TDD(テスト駆動開発)は、抽象化の問題を考慮し、より良い抽象化を見つける方法として役立。

  • バランス: 過度に抽象化されたデザインも、不十分な抽象化も問題を引き起こすことがある。適切なバランスを見つけることが大切で、テスト可能性を利用してそれを達成できる。

経験と実践を積むことで、適切な抽象化を選ぶ能力が向上する。目的や状況に応じて最適な抽象化を選び、効果的な問題解決につなげることが肝要。

問題領域からの抽象化

問題領域をモデリングすることで、デザインにガイドラインを与え、問題を理解しやすくする。

イベントストーミングは、問題の領域を明確にするのに役立つ。
イベントストーミングは、問題領域を分析するための手法で、関連するイベントやアクションを識別し、それらを関連付けることで問題領域をマッピングする。これにより、問題領域内で自然に分離された概念や関心事のクラスターを特定することができる。

これらのクラスターは、システム内のモジュールやサービスとして実装される可能性がある概念を表している。イベントストーミングはまた、問題領域内で自然に抽象化され、他の技術的区分よりも独立性が高い領域を明確にする。これらの領域は、境界付けられたコンテキストと呼ばれる。

境界付けられたコンテキストは、システム内の独立した部分を表し、それぞれが特定の問題領域を対象としている。これにより、システム全体の設計がより疎結合になり、変更や拡張が容易になる。

イベントストーミングを用いることで、問題領域における自然な抽象化の線や、デザインにおいて独立性を保つべき領域を特定できるため、開発者はより効果的な設計を行うことができる。

カップリングの管理

カップリングのコスト

カップリングは、「モジュール間の相互依存の程度」として定義される。

モジュール性や凝集性などのシステムの属性や、抽象化や関心の分離などの手法が重要である本当の理由は、それらが結合を減らすのに役立つからである。 結合の削減は、ソフトウェア開発速度と効率、およびスケーラビリティと信頼性に直接的な影響を与える。

「疎すぎる結合」にもコストはかかるが、一般に、「きつすぎる結合」のコストよりもはるかに低コストである。したがって、一般に、密結合よりも疎結合を優先することを目指す必要があるが、その選択を行う際のトレードオフも理解する必要がある。

その他

  • ソフトウェア開発は一種の進化的プロセスである。プログラマーとしての私たちの仕事は、望ましい結果に向けた有向進化の段階的なプロセスを通じて、学習と設計を導くことである。
  • タイピングを減らすためにコードを最適化するのは間違い。その結果、判りにくくなった場合間違ったことを最適化している。コードはコミュニケーションツールである。
  • コードの主な目的は、アイデアを人間に伝えること。アイデアをできるだけ明確かつシンプルに表現するコードを書くべきである。あいまいさを犠牲にしてまで簡潔さを選ぶべきではない。
  • タイピングを減らすよりも、思考を減らすように最適化する。
  • この本のテクニックは、答えを提供することを意図したものではない。答えがわからない場合でも安全に進歩できるようにするためのアイデアとテクニックのコレクションを提供することを目的としている。実際に複雑なシステムを作成している場合は、常にそうである、完成するまで答えはわからない。

Discussion