oinume journal

Scratchpad of what I learned

Chapter 2 - The Nature of Complexity / A Philosophy of Software Design

第2章は"The Nature of Complexity"というタイトルで、ソフトウェアのComplexityつまり複雑性についてじっくり説明されている。

Complexityの定義

Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.

Complexityの3つの症状

1. Change amplification

- とある機能追加・変更をしたいだけなのに、いろんなところを変更しないといけない。以下の図で言うと、背景色を変更したいだけなのに`bg = "red"`の定義が複数箇所あるため、これらを全て修正しないといけない、みたいな。

2. Cognitive load

  • 認知負荷。開発者が機能追加・変更のタスクを完了させるためにどのぐらいシステムの内部を知っている必要があるか、と言うこと。多ければ多いほど把握できなくなり、修正漏れなどのバグが発生することになる。
  • システム設計者は時々コードの行数で複雑性を表そうとするが、1行に処理が集約されてすぎていて逆に認知負荷を高めるケースもある。1行より複数行のコードの方がシンプルになっている場合もある

3. Unknown unknowns

  • 開発者が機能追加・変更のタスクを完了させるために、どの部分を変更すればいいのかが明らかではないこと。もしくはどの部分を変更すればいいのかの情報が明らかではないこと
  • 上の図だと、「背景色を変更したい」と言うタスクがある時に、bannerBgを変更しても全てのページの背景色が変わらないので、どの部分を変更すればいいのかが分かりにくい

Chapter 1 - Intruduction / A Philosophy of Software Design

A Philosophy of Software Design の第1章を読んだのでそのまとめ。

Intro

  • プログラムは機能が増えるごとに複雑さが増していく。複雑さが増えると、開発スピードが遅くなりバグが増える
  • 開発ツールは複雑性に対処するに役立つが、これには限界がある。一方、シンプルなソフトウェアのデザインはより大きくてパワフルなプログラムを導いてくれる。
  • 複雑性に対処するには2つのアプローチがある。
    • 1つ目はコードをシンプルかつ明らか(わかりやすく)すること。例えば複雑性は特殊ケースを削除することで減らすことができる。
    • 2つ目のアプローチはカプセル化でmodular design とよばれている。
      • Modular designではソフトウェアをモジュールに分割して管理して(OOPだとクラスとか)それぞれのモジュールは他のものに依存しない。
      • そのため、プログラマーはあるモジュールの開発をするときに、他のモジュールの詳細を知らなくて済む。

ウォーターフォールモデルの紹介

  • ウォーターフォールだと基本的には設計フェーズではすべてを設計し、開発のフェーズでは設計をしない。ソフトウェアは物理的なシステムより複雑で目に見えないので、特に大きなシステムであるほど全体を細部まで理解することは難しい。
  • 開発フェーズで初めて問題がわかるケースもよくあり、場合によっては設計のやり直しが発生する。ウォーターフォールモデルだとこれは大きな手戻りになってしまう。
  • この問題があるため、最近のソフトウェア開発ではアジャイルのようなインクリメンタルなアプローチが使われている。

How to use this book

ソフトウェアの設計スキルを向上させるための良い手法の一つは、"red flags"という複雑なソースコードの断片のサインを認識することである。この本ではその"red flags"を、メジャーな設計の問題を通じて説明する。

A Philosophy of Software Designを読み始めた

タイトル通りで、最近第2版が発売されたのと、いろんなところでオススメされていたのでこの本を読んでいる。やっと第6章まで読み終わったので、それぞれの章のまとめをブログにアップしていく予定。

以下は読んだ章のINDEX.

JavaScriptの記号の演算子と構文

JavaScript / TypeScript では一見すると「これなんだ?」という記号の演算子や構文がよく出てくるので、自分用の備忘録としてメモしておく。式と演算子 - JavaScript | MDNには演算子の一覧のページがあるため、わからない記号が出てきたら以下のページを見るとだいたい載っているはず。

?? - Nullish coalescing operator

  • Null 合体 (??) - JavaScript | MDN
  • Null 合体演算子 (??) は論理演算子の一種です。この演算子は左辺が null または undefined の場合に右の値を返し、それ以外の場合に左の値を返します。
  • 例えば以下のコードだと、aがnull or undefinedの場合は a is null or undefined がvalに代入される
const val = a ?? 'a is null or undefined';

!! - Double not operator

  • Logical NOT (!) - JavaScript | MDN
  • 複数の否定演算子を連続して使用することで、明示的にあらゆる値を対応する論理型プリミティブに変換することができます。
  • 否定の否定なので肯定ということになり、元の値をbooleanに変換するということらしい
const val = !!a; // val is always boolean

?. - Optional chaining operator

  • オプショナルチェーン (?.) - JavaScript | MDN
  • ?. 演算子の機能は . チェーン演算子と似ていますが、参照が nullish (null または undefined) の場合にエラーとなるのではなく、式が短絡され undefined が返されるところが異なります。関数呼び出しで使用すると、与えられた関数が存在しない場合、 undefined を返します。
const val = a?.b?.c; // aがnull or undefinedの場合 val が undefinedになる

... - Spread syntax

  • スプレッド構文 - JavaScript | MDN
  • スプレッド構文 (...) を使うと、配列式や文字列などの反復可能オブジェクトを、0 個以上の引数 (関数呼び出しの場合) や要素 (配列リテラルの場合) を期待された場所で展開したり、オブジェクト式を、0 個以上のキーと値のペア (オブジェクトリテラルの場合) を期待された場所で展開したりすることができます。
  • 一番多用されるのは配列やオブジェクトをコピーするときなのではないかと思われる
const arr = [1, 2, 3];
const arr2 = [...arr]; // arr.slice()みたいな

! - Non-null assertion operator

これはJavaScriptではなくてTypeScriptにしかない演算子。

// Compiled with --strictNullChecks
function validateEntity(e?: Entity) {
  // Throw exception if e is null or invalid entity
}

function processEntity(e?: Entity) {
  validateEntity(e);
  let s = e!.name; // Assert that e is non-null and access name
}