ソフトウェアエンジニアリングとは何か

『Google のソフトウェアエンジニアリング』第1章を読んで理解した内容を、自分の言葉で書き直したもの。 本文の引用ではない。節番号は原典を引き直すときの手がかり。

一言でいうと

プログラミングは、コードを書くというその場の行為。 ソフトウェアエンジニアリングは、そのコードを使い続けなければならない期間ずっと有用に保ち、 かつチームで共同作業できるようにするための、ポリシー・プラクティス・ツールの総体。 違いは時間軸とチーム規模にある。(§1.4)

判断の軸は3つ。時間と変化・スケールと効率・トレードオフ

時間と変化 (§1.1)

プロジェクトが長期間動くかどうかは事前には分からないことが多い。だから長期間動く前提で作る

  • 持続可能とは「どんな変更が技術的・ビジネス的理由で来ても反応できる状態」を指す。 求めているのは反応する能力であって、実際に毎回追随することではない。 価値が薄い、他に優先事項がある、という理由でアップグレードを見送る判断はあってよい
  • アップグレードを先送りするほどコストが上がり、「二度とやらない」という結論に傾く。 そこまで行くと、書き直すか永久に上げないかの二択しか残らない
  • 逆に頻繁にやるほど楽になる。何度も上げてきたコードは実装の細部ではなく、 言語や OS が約束している抽象に依存するようになるので、脆い箇所が減って自動化もできる

Hyrum の法則 (§1.1.1)

システムの観察可能な挙動には、それが仕様かどうかに関係なく必ず依存者が現れる。 ハッシュの反復順序のような「たまたまそうなっているだけ」の挙動も例外ではない。

したがって、依存先の未公開・偶発的な挙動に頼ったコードは、たとえ動いていても負債になる。 賢さが褒め言葉になる領域と、責め言葉になる領域は違う。

変化しない選択はあるか (§1.1.3)

ない。十分に長く続くプロジェクトは、基盤のどこかを必ず変えることになる。

  • 依存する技術のどれもが、後から発覚する致命的なバグやセキュリティ脆弱性を抱えうる。 Heartbleed や Spectre のような問題が出たときにパッチを当てられない状態は、かなりの賭け
  • ハードウェアの性質も変わる。CPU とメモリのレイテンシ差が開き続けているので、 「効率的なコード」の姿自体が時代とともに変わる

変化それ自体は善ではない。変化のために変化する必要はない。 ただし変化できる能力は持っておき、その能力を安く保つ投資はしておく。

スケールと効率 (§1.2)

見るべきは、ソフトウェアの実行時性能だけではなく開発そのものがスケールするか

問いの立て方の例。

全体ビルド1回にどれだけ時間がかかるか
リポジトリのまっさらなコピーをローカルに落とすのにどれだけ時間がかかるか
プログラミング言語の新バージョンに上げるコストはどれだけか

繰り返し実行するものはすべて、人的労力の面でスケーラブルであるべき。 1人あたりの消費リソースが四半期ごとに増えていくなら、その道は持続可能ではない。

スケールしないポリシー (§1.2.1)

  • 「X 月 Y 日に旧版を消すので各自移行しておくこと」 — 依存グラフが深く広くなると破綻する。 移行作業を利用者側に押し付けず、インフラ側が内部化すれば規模の経済が効く
  • 全員に保守の骨折りを求める — 専任の専門家グループにやらせる方がスケールする
  • チームや機能ごとの開発ブランチ — 他の開発者に再同期とテストのコストを課す。 組織が大きくなるほどオーバーヘッドだけが増える

スケールするポリシー (§1.2.2)

  • 壊れてほしくないものは CI に入れる。 Google のルールは 「インフラ変更で製品が壊れても、それが CI のテストで表面化していなかったなら、 インフラ変更側に落ち度はない」というもの。 これがないとインフラ側が全チームを追いかけて回る羽目になる
  • 知識は拡散する。 質問に答えるのを厭わない専門家が1人いれば、 その言語をまともに書けるエンジニアが100人生まれる。専門家は媒介者

何がコードベースの柔軟性を決めるか (§1.2.3)

コンパイラのアップグレードを例にした要因。

要因内容
専門知識アップグレードのやり方を知っている人がいる
安定性頻繁に取り込んでいるのでリリース間の差分が小さい
準拠性定期的に上げているので、未対応のコードがほとんど残っていない
馴染み深さ回数をこなして手順の冗長性が見え、自動化できる
ポリシーインフラ側は CI で見える利用法だけ気にすればよい

左への移動 (§1.2.4)

開発ワークフローの早い段階で問題を見つけるほど安い。 品質・信頼性・セキュリティのためのツールとプラクティスを開発の前段に置き、 できるだけ左側で欠陥を捕まえる多重防御のアプローチを取る。

トレードオフとコスト (§1.3)

どちらかに決めたときは、何をどう比較したのか常に説明できる状態にする。 比較する対象は6種類。

財務コスト  資金
資源コスト  CPU時間
人件コスト  エンジニアリング労力
処理コスト  行動を取る場合にかかるコスト
機会コスト  行動を取らない場合に失うもの
社会コスト  その選択が社会全般に及ぼす影響

社会コストは無視されやすい。そこは意識的に見る必要がある。

さらにコスト見積もり自体にバイアスが乗る。現状維持バイアス、損失嫌悪など。

そして組織の健全性は、資金があるかどうかだけでは測れない。 メンバーが尊重されていると感じ、生産的でいられるかどうかでも測る。 ソフトウェアエンジニアリングのような創造的な分野では、制限要因になるのはたいてい財務コストではなく人件コスト。 エンジニアの幸福・集中・関与を保つことで得られる効率は、他の要素を簡単に上回りうる。

決定の根拠 (§1.3.1)

決定の理由は突き詰めると2つしかない。

  • 法的要件や顧客要件なので、やらなければならない
  • 現時点で分かっているエビデンスに基づけば、これが最良の選択だから

「私がそう言うから」は理由にならない。 ただしこれは全員一致や広い合意を要求する話ではない。最後は誰かが決定者にならなければならない

計測できないものをどう扱うか (§1.3.2)

数量が全部測れる/推定できるケースと、微妙で測り方すら分からないケースがある。 後者に簡単な答えはなく、経験・リーダーシップ・前例に頼ることになる。

大事なのは、測れないものがあると認識したうえで、測れるものと同じ優先度で、より慎重に扱うこと。

時間とスケールが競合するとき (§1.3.4)

依存関係を足すか、フォーク/再実装して自分たちの要求に合わせるか。

  • 狭い問題に特化した解が、汎用の解より性能で勝ることはよくある
  • フォークすれば制御が得られ、基盤の変更に振り回されなくなる
  • ただし時間やプロジェクト境界をまたぐインターフェイス (データ構造、シリアライゼーションフォーマット、ネットワークプロトコル)のフォークは避ける
  • 一貫性には大きな価値があるが、一般性にもそれ自体のコストがある

依存関係を減らせば、外的要因による変化そのものを減らせる。

決定を覆せること (§1.3.5)

新しいデータが入れば、前提が崩れて決定が間違いだったと分かることがある。 決定した時点では筋が通っていた、というだけのこともある。

長く続くプロジェクトでは、最初の決定の後に方針を変えられることが不可欠。 それは決定者が間違いを認める権利を持っているということでもある。 間違いを認めるリーダーは尊敬を失わない。

エビデンス駆動であるべきだが、測れないものにも価値がありうると認識し、 判断力を使って「これは重要だ」と断言する。それがリーダーに求められること。

関連

  • 原則 — ここから導かれる日々の判断
  • 廃止 — 「時間と変化」を運用に落としたもの
  • バージョン管理 — 単一バージョン原則はスケールへの答え
  • 生産性の計測 — 効率を測る価値があるかの判断
  • 設計 — トレードオフを設計で引き受ける側