📚

【読書メモ】『達人プログラマー』「表明」と「爬虫類脳」がスッキリ分かった話

に公開

はじめに

達人プログラマー 第二版 熟練に向けたあなたの旅 アンドリュー・ハント(著者), デイビット・トーマス(著者), 村上雅章(訳者) オーム社

エンジニアの必読本とも言われている達人プログラマーを読んだ際に特に勉強になった、「表明」、「爬虫類脳」に焦点をおき、私の読書メモを記事にしてみました。
そのため、多くの私の主観による解釈が多分に含まれています。
そのため、この記事を読んで興味を持たれた方は原著をあたっていただけるととても助かります!

書籍購入の参考になれば幸いです。

第2章: 達人のアプローチ

ソフトウェア開発における基本的な設計原則と思考法

  • DRY (Don't Repeat Yourself): 同じことを繰り返すな
    • 知識や意図の重複をあらゆるレベル(コード、ドキュメント、プロセス)で排除します。
    • 一つの知識は、システム内において、単一、かつ明確な、そして信頼できる表現を持つべきです。
  • 直交性: コンポーネントを独立させる
    • システムの各部分が互いに影響を与えずに変更・交換できるように設計します。
    • メリット: 生産性向上(変更・テストが容易)、リスク低減(影響範囲の限定)。
  • 可逆性: 決定を変更しやすくする
    • 重要な決定(技術選定など)は、後から変更可能にするための選択肢を残します。ハードコーディングを避け、設定や抽象化レイヤーを活用します。
  • 曳光弾 (Tracer Bullets) とプロトタイピング: 早期にフィードバックを得る
    • 「曳光弾」:主要な機能をエンドツーエンドで繋いだ、荒削りだが動くコードを早期に作成し、アーキテクチャやリスクを検証します。
    • 「プロトタイプ」:特定の側面(UI、アルゴリズムなど)を探求・検証するために捨てる前提で作成します。
  • ドメイン特化言語 (DSL): 問題領域の言葉で語る
    • 可能であれば、問題領域の概念を直接表現できるミニ言語を作成・活用し、コードの意図を明確にします。
  • 現実的な見積もり:
    • 見積もりは推測であることを理解し、「~のオーダー(桁数)」で伝えます。経験に基づいて精度を上げていきます。

第4章: 妄想の達人

「自分の書いたコードでさえ、いつバグが潜むか分からない。他のシステムやライブラリだって、予期せぬ動きをするかもしれない…」 と、常に最悪のケースを想定する 『健全な被害妄想(Pragmatic Paranoia)』 を持つことが、プロフェッショナルとしてソフトウェアの堅牢性を高める上で非常に重要である。
達人なプログラマーは自分を含め、全て信用しない。
完璧な人間がいないように、完璧なコードも存在しません。だからこそ、私たちは防御的な姿勢でコードを書く必要があるのです。

契約による設計 (Design by Contract, DbC): 責務を明確にする「約束事」

★ DbCは、コンポーネント間のやり取りを、まるでビジネス契約のように、明確な 「約束事」 に基づいて行おう、という考え方です。これにより、それぞれの部品が何をすべきで、何をしてはいけないのかがハッキリします。

この「契約」には、主に以下の3つの要素があります。

  1. 事前条件 (Precondition): これは、関数やメソッドを 呼び出す側 が守るべき「約束事」です。「この機能を使いたいなら、最低限この条件は満たしてくださいね」という、いわば 入り口でのチェック項目 です。(例: divide(x, y) なら y はゼロであってはいけない)
  2. 事後条件 (Postcondition): これは、関数やメソッドを提供する側(つまり、 実装した側)が保証する「約束事」です。「事前条件を守ってくれたなら、処理が終わった後には必ずこの状態になっていることをお約束します」という、 出口での品質保証 です。(例: divide(x, y) の結果は必ず数値である)
  3. 不変条件 (Invariant): これは、特定のクラスやモジュールが、その 有効期間中ずっと守り続けなければならない 「約束事」です。メソッド呼び出しの前後などで常に真であるべき条件で、オブジェクトの一貫性を保つためのルールです。(例: 銀行口座クラスの残高は常にマイナスにならない)

★ このように「契約」を結ぶことで、各コンポーネントの責任範囲が明確になり、コードの意図が伝わりやすくなります。もし契約違反があれば、それはどちらかの責任であり、問題の特定が容易になるのです。

★ 表明 (Assertion): コードに埋め込む「私の仮定はこうだ!」宣言

★ さて、DbCの「契約」を守らせるための強力な武器が 「表明(Assertion)」 です。これは、コードのある特定の箇所で 「この条件は絶対に真のはずだ!」 という開発者の 強い仮定 をコードで表現する仕組みです。

def calculate_discount(price, percentage):
    # 事前条件の表明: 割引率は0%以上100%未満のはず
    assert 0 <= percentage < 100, f"Invalid percentage: {percentage}"
    # 事前条件の表明: 価格は正の数のはず
    assert price > 0, f"Price must be positive: {price}" 

    discount_amount = price * (percentage / 100.0)

    # 事後条件の表明: 割引額は価格以下のはず
    assert discount_amount <= price, "Discount cannot be greater than price"
    return discount_amount

★ もし、この assert に書かれた条件が実行時に 偽 (False) になったらどうなるでしょう? その場合、プログラムは 意図的にクラッシュ します。これは、「ありえないことが起こった!設計上の仮定が崩れたぞ!」という重大なサインであり、バグが存在する ことを示しています。

表明の主な目的は以下の通りです。

  1. バグの早期発見 (Fail Fast): 問題が発生したその瞬間にプログラムを止めることで、不正なデータが後続処理に影響を及ぼすのを防ぎ、原因特定を容易にします。
  2. デバッグ効率化: エラー箇所が明確になり、デバッグの手がかりとなります。
  3. ドキュメンテーション: 「このコードはこういう前提で書かれているんだな」という開発者の意図を、実行可能な形で示すコメントの役割も果たします。

重要:表明とエラーハンドリングは全くの別物!

★ ここで絶対に混同してはいけないのが、表明とエラーハンドリングの違いです。

  • 表明 (Assertion): 開発中に プログラム内部のバグ(本来ありえないはずの状況)を発見するための仕組み。表明が失敗したら、それはコードを修正すべきサインです。開発者向けのデバッグツール と言えます。
  • エラーハンドリング (try-except など): 実行時に 起こりうる外部要因のエラー(ユーザーの不正な入力、ファイルが見つからない、ネットワーク接続失敗など)に適切に対処するための仕組み。回復処理を試みたり、ユーザーにメッセージを伝えたりします。ユーザーや外部システム向けの対処メカニズム です。

★ ユーザーが入力フォームに間違った値を入れる可能性があるからといって、そこで assert を使うのは間違いです。それは表明ではなく、通常の入力値チェックとエラーハンドリングで対処すべき問題です。表明は、あくまで「プログラマーの仮定」を検証するためのものなのです。

正直に早く失敗する (Crash Early): 問題を隠蔽しない

★ 表明が失敗したとき、あるいは他の予期せぬエラーが発生したとき、それを無理に握りつぶして処理を続けようとするのは、多くの場合、状況を悪化させます。達人プログラマーは 「正直に、そして早く失敗させる」 ことを推奨します。

なぜなら、問題が発生したその場でプログラムを停止させれば、

  • エラーの原因となった箇所が特定しやすい。
  • 不正な状態が後続の処理に影響を与え、さらなる問題を引き起こすのを防げる。
  • 問題の存在が明確になり、修正を促すことができる。

もちろん、ユーザーが直接触るシステムでいきなりクラッシュさせるのは望ましくない場合もあります。しかし、開発中やテスト段階においては、問題を隠蔽せず、早期に失敗させる方が、結果的に高品質なソフトウェアにつながります。

第7章: コーディング段階

実際にコードを書く際に意識すべき、具体的なプラクティスです。

★ コーディングは論理的な思考活動ですが、時折、私たちの 「爬虫類脳」 が顔を出すことがあります。これは、特に予期せぬ問題(バグなど)に直面したときに現れやすい、非合理的で本能的な反応のことです。

「爬虫類脳」とはなんでしょうか? 以下は自分なりの解釈です。 (詳細は本書をあたってください。)

  • パニックと視野狭窄: バグを見て頭が真っ白になり、簡単な原因を見落としたり、場当たり的な修正を繰り返したりする。
  • 責任転嫁: 「これは俺のせいじゃない、あのライブラリが悪いんだ!」と、反射的に外部のせいにしたくなる。
  • 変化への抵抗: 新しいツールややり方に対して、理由もなく「なんか嫌だ」「面倒くさい」と感じてしまう。
  • 固執: 「自分の考えたこの設計が一番だ」と思い込み、他の意見を聞き入れられない。
  • 近視眼的な行動: とりあえず動くように、場当たり的なコードを書いてしまう。

★ こうした「爬虫類脳」の反応は、誰にでも起こりうることです。大切なのは、「あ、今、自分は冷静じゃないかも」「これは感情的な反応だな」と、まず自覚すること。 そして、一度深呼吸して落ち着き、客観的・論理的な思考を取り戻すよう努めることです。散歩したり、文章を扱うような脳の使い方をしていたらば、図や絵を書いて、異なる脳の使い方をするのも良いでしょう。その「なんか嫌な感じ」の正体を突き止めれば、より良い設計やコードへのヒントになることだってあります。この自己認識こそが、達人への一歩だと私は捉えました。

  • 論理的に考える、しかし、本能にも耳を傾けよう: コードを書く際は常に論理的に考えますが、何か「嫌な感じ」がする、という直感(本書では「爬虫類脳の声」とも表現)にも注意を払います。違和感の正体を突き止め、設計やコードを見直すきっかけにします。
  • 偶発的プログラミングは避けよう: なぜ動くのかを完全に理解せずに、偶然うまく動いたコードに依存してはいけません。常に意図を持ってコーディングします。
  • アルゴリズムの計算量を意識しよう: 特にデータ量が増えた場合に性能が問題にならないか、アルゴリズムの計算量(オーダー)を考慮します。
  • 継続的なリファクタリング: コードは書いた瞬間から劣化し始めます。より良くするための小さな改善(リファクタリング)を、日々のコーディングプロセスに組み込みます。
  • テストで品質を保証するようにしよう:
    • ユニットテスト: 個々のコンポーネントが期待通りに動作するかを検証します。
    • テスト駆動開発 (TDD): テストを先に書き、それをパスする最小限のコードを実装し、リファクタリングするサイクルを回します。
    • プロパティベーステスト: 個別のケースではなく、満たすべき性質(プロパティ)を定義し、ランダムなデータでテストします。
  • 外部からの入力は信用しない: ユーザー入力、ファイル、ネットワークからのデータなどは常に検証・サニタイズし、セキュリティ脆弱性を防ぎます。
  • 命名は重要: 変数、関数、クラスなどの名前は、その役割や意図を正確に反映するように慎重に選びます。良い名前はコードの可読性を大幅に向上させます。

おわりに

読んでとてもよかったです。
ふと思いついた時にランダムにページを開き、そこに書いてあることが実践できているのかを確認して、いつか私も達人、いや準達人になれるように頑張りたいと思いました。
読んでいる中で、「表明」、「爬虫類脳」という単語の意味を理解しっかりと理解せず読んでしまい、何度もページを戻り読み直し、また時間が空いたら忘れてまた思い出すという行動を何度もやってしまいました。本を読みながら読書メモを取るようにしているのですが、その内容を何度も書き直すのです。しかし、ある時から Gemini 先生を利用し、「達人プログラマーの文脈の [表明] って何?、メモした内容を元に解説をしてよ」などを投げて壁打ちを行っているうちに本を普段読まない私でも理解することができました。
何度も読み直します!

https://www.ohmsha.co.jp/book/9784274226298/

Discussion