リーダブルコード 個人的重要箇所

理解しやすいコード

コードは理解しやすくなければいけない
コードの簡潔と安心では、安心を選ぶ。

コードは最短時間で他の人が理解できるようにかかなければならない
コードは短くしたほうがいいけれど、理解するまでにかかる時間を短くするほうが大事。
他人がコードを理解できないだけでなく、半年後の自分が理解できなくなるかもしれない。

表面上の改善

名前に情報を詰め込む

名前に情報を詰め込む
sizeやgetなどの名前は一見問題なさそうに見えるが、情報量が含まれていないこともあるため気をつけるようにする。そのために

  • 明確な単語を選ぶ
  • 汎用的な名前を避ける(あるいは使う状況を選ぶ)
  • 抽象的な名前よりも具体的な名前を使う
  • 接尾辞や接頭辞を使って情報を追加する
  • 名前の長さを決める
  • 名前のフォーマットで情報を伝える

等の工夫をする。

イテレーターの名前をつける際に、配列の名前と最初の文字にするとバグに気づきやすくなる。
例) clubs[ci], members[mi], users[ui], clubs[clubs_i], members[members_i], users[users_i]など。

直行する概念は無理に一つにしようとせずに、別々に使えるようにする。そうしないと名前がややこしくなってしまう。例) 無理にコマンド一つにまとめようとせず、--use_local_databaseのようなオプションを用意する。

すべての変数名に属性を入れるのではなく、変数の意味を間違えて使ってしまったときにバグになりそうなところにunescapedなどの属性を変数名に入れるようにする。

識別子のスコープが大きいときは変数名がなにであるのか情報が近くに含まれていないため、明確にわかるような名前をつけるようにする。

誤解されない名前

名前が他の名前と間違えられることはないだろうか?と何度も自問自答する。
filterという名前を例に取った場合、それは選択するのか、除外するのかがわからないため、select、exclude等の名前を付けて明確にする。

ブール値の変数名はis、has、can、shouldのような名前をつけてわかりやすくすることが多い. 。user_is_autheticatedなど。

getなどの名前はユーザーにとって軽量アクセサという認識がある。取ってくるデータが多い場合にはcompute等の名前をつけ、大量のデータを持ってくる処理だということを明確にする。

美しさ

コードを読みやすくするため三原則

  • 読み手が慣れているパターンと一貫性のあるレイアウトを使う
  • 似ているコードは似ているように見せる
  • 関連するコードをまとめてブロックにする

リーダブルコード作者は美しさと優れた設計を独立した考えだとしている。できれば、その両方を読者には追求してほしい。

同じようなコメントが三回以上続いてしまった場合などは、関数の構造を模してコメントを付けたものをそれぞれの関数の一番上に置く。そうすることで、可読性も上がりコメントを書く量が減る。

コードの表面上の改善はコードの構造も改善できる。

HTMLのタグの順番を対応した並び順、重要度順、アルファベット順などコードの中で一連の並び順を使うべきである。

コードを段落でわけることでざっと目を通せるようになる。短い段落なのか、長い段落7日は最終的には個人の好みになるが、チームで合わせないと読みにくくなってしまうため、揃えるようにする。

一貫性のあるスタイルは「正しい」スタイルよりも大切。

コメントすべきことを知る

コメントの目的は、書き手の意図を読み手に知らせることである。

  • コメントするべきではないことを知る
  • コードを書いているときの自分の考えを記録する
  • 読み手の立場になって何が必要になるかを考える

コードからすぐにわかることをコメントに書かない。

改善が必要なときには
TODO: もっと高速なアルゴリズムを使う
のようなフォーマットのコメントを残す。

定数に、なぜその値なのかというコメントをつけることも多々ある。

質問されそうなことを想像する、ハマりそうな罠を告知する、要約コメントをつける等の対策を取ることで可読性が上がる。

ライターズブロックを乗り越えるにはとにかく書き始める。とりあえず書いたものを言葉を変えたりして修正することで、適切なコメントになる。

コメントは正確で簡潔に

コメントは領域に対する情報の比率が高くなければいけない

コメントを簡潔にしておく... 短いコードには3行分などの長いコメントを付けない

あいまいな代名詞を避ける... 代名詞に名詞を代入してみる

歯切れの悪い文章を磨く

関数の動作を正確に記述する

入出力のコーナーケースに実例を使う...
例) // 実例 Strip("ab", "a")はaを返す。
言葉の説明だけではわかりにくい場合があるので、実例を用いて説明することでわかりやすくなる。

コードの意図を書く

名前引数コメント... プログラム言語によっては引数に名前をつけて渡すことができるが、そのような機能がない場合にはインラインコメントをつけて対応する。
例) Connect(/timeout_ms = / 10);

情報密度の高い言葉を使う... データベースのキャッシュ層である、正規化する等の言葉で長い説明が短い言葉で済むことがある。

ループとロジックの単純化

制御フローを読みやすくする

文章で「もし君が18歳以上ならば」という文章にしたときに、変わる値(君の年齢)が先に来ていて、変わらない値(18歳以上という条件)が後に来ている。
文章が自然に成り立つように条件式のコードも合わせると読みやすくなる。
if (your_age >= 18) の並びで書く。

行数を短くするよりも、他の人が理解するのにかかる時間を短くする

do/whileループは書き直せることが多い場合には、積極的に避けるようにする。条件が前もって書かれていないため、可読性、エラーなどの問題が起きやすくなってしまう。

変更するときにはコードを新鮮な目で見る。一歩下がって全体を見る。
コードを作成していると、これが一番簡単な解だとコードを書いている側が思っていても、初めて見た人にとっては複雑に見えることがある。ネストを浅くするなどして、回避する。

巨大な式を分割する。

巨大な式は飲み込みやすい大きさに分割する。

説明変数
式を説明するために変数名を使う

要約変数
複雑なコードを簡単に説明するために変数を使う

ド・モルガンの法則を用いて、式を簡単にすることができる。

頭がいいコードに気をつける。あとで他の人がコードを読むときにわかりにくくなる。

複雑なロジックに取り掛かる際に、実現したいことの条件の反対を考えてみる。
例)1を含む、ではなく1を含まないと考える
そうすることで、条件式が簡潔になり、バグも少なくなるケースがある。

変数と読みやすさ

  1. 変数が多いと変数を追跡するのが難しくなる
  2. 変数のスコープが大きいとスコープを把握する時間が長くなる
  3. 変数が頻繁に変更されると現在の値を把握するのが難しくなる

説明変数や要約変数を使ってコードを読みやすくしていく。その中で読みやすくならない変数を削除する。

now = datetime.datetime.now()
root_message.last_view_time = now

の変数はあまりよくない。nowと名前をつけなくても、十分にコードから読み取りやすい程度の複雑さであるから。

中間結果を保つだけの変数は使わずに、結果を直接用いるようにしてコードを短くする。

変数のことが見えるコード行数をできるだけ減らす

最初からすべての変数のことを知る必要はないので、使用する箇所の近くで変数宣言をするようにする。

変数を操作する場所が増えると、現在地の判断が難しくなる。


まとめ

  • 邪魔な変数を削除する
  • 変数のスコープをできるだけ小さくする
  • 一度だけ書き込む変数を使う

コードの再構成

無関係の下位問題を抽出する

エンジニアリングとは大な問題を小さな問題に分割して、それぞれの解決策を組み立てること。 この原則をコードに当てはめることで堅牢かつ読みやすいコードになる。


無関係の下位問題を積極的に見つけて積極的に抽出するために

  • 関数やコードブロックを見て、このコードの高レベルの目標は何かを明らかにする
  • コードの各行に対して高レベルの目標に効果があるのか、無関係の回もなぢを解決しているのかを明らかにする
  • 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする。(無関係の下位問題 = プロジェクト固有ではない、汎用なコード)

これらのステップを踏む。


既存の関数等でわかりにくいインタフェースのものを使用する場合、自作でラッパー関数を作りわかりやすくする。また、既存のコードでわかりにくい箇所がある場合にはインターフェースを整えるようにする。

「無関係の下位問題」に関する関数は広く使用可能なので(util/)のようなディレクトリを用意し、そこにファイルを入れる。

まとめ プロジェクト固有のコードから汎用的なコードを切り離す。

1度に1つのことを

コードは1つずつタスクを行うようにしなければならない

一行一行のコードをタスクに分けて、デフラグをする。関数化、クラス化、コードのブロックに分けなど。

正しい分け方かどうか、よりも分けること自体が大事。if文の中が複雑な式、構造になっている場合にはタスクを分けて考え、それをコードにする。

プログラムが行っていることを正確に説明できるようにする。

コードに思いを込める

コードは簡単な言葉で書くべきである。そのために

  1. コードの動作を簡単な言葉で誰にでもわかるように説明する
  2. その説明の中で使っているキーワードやフレーズに注目する
  3. その説明に合わせてコードを書く

の手順を追う。

コードの動作を説明の中で出てこなかった事柄、名前から不必要な変数名などが明らかになる。明らかになった変数名は汎用的な名前にすることができ、そうすることで考えなくてはならない事柄が減り、可読性が上がる、面倒なことが減るなどのメリットがある。

これらを行う際に、ラバーダッキングが有効な手段になりえる。

短いコードを書く

最も読みやすいコードは何も書かれていないコード

すべての要件を満たそうとすると、大変な作業になる。そのソフトウェアにとって必要なものはなにかを選別し、必要なものだけを実装する。

ソフトウェアエンジニアが書く一日の平均コード量は、なんと10行

できるだけコードを書かないようにするために、

  • 不必要な機能をプロダクトから削除する。過剰な機能は持たせない。
  • 最も簡単に問題を解決できるような要求を考える。
  • 定期的にすべてのAPIを読んで、標準ライブラリに慣れ親しんでおく。

選抜テーマ

テストと読みやすさ

他の人が安心してテストの追加や変更ができるように、テストコードを読みやすくする。

大切ではない詳細はユーザーから隠し、大切な詳細は目立つようにするべき。
そのために、テストの内容を示したものでないどうでもいいコードは、ヘルパー関数などを作りきれいにする。テスト内容を示しているものを目立たせるようにする。
テストのトップレベルはできるだけ簡潔にする。入出力のテストはコード1行で記述できるとよい。


エラーメッセージがよりわかりやすい関数、メソッドを使うようにする。わかりやすいエラーメッセージを吐く関数を自作するのもよし。


コードを完全にテストする最も単純な入力値の組み合わせを選択しなければいけない。

テストには最もきれいで単純な値を選ぶ。

ストレステストなどテストに大量の値を入れたいときがある。そのような場合にはプログラムを組み値を渡すようにすることで、記述量が減り可読性も上がる。

コードを検証するときには完璧な入力値を1つ作るのではなく、小さなテストを複数作るほうが簡単かつ効果的かつ読みやすくなる。


テストコードは関数になっていることが多い。その際には、

  • テストするクラス
  • テストする関数
  • テストする状況やバグ

がすぐに理解できる名前が良い。

Test_関数名_状況

の形式でテストコードの名前をつける。
ほとんどのテスティングフレームワークではテストが失敗したときにはその関数の名前が印字されるようになっている。そのため、関数名はコメントだと思ってもよい。長い名前になってしまうのは気にしなくて良い。


あとでテストを書くつもりでコードを核とテストしやすいようにコードを設計するようになる。

テスト容易性の低いコード

テスト容易性の高いコード

  • クラスが小さい。あるいは内部状態を持たない。
  • クラスや関数が1つのことをしている。
  • クラスが他のクラスにあまり依存していない。高度に疎結合されている。
  • 関数は単純でインターフェースが明確である。


やりすぎ

  • テストのために本物のコードの読みやすさを犠牲にしてしまってはいけない。両者に利点があるようにする。

  • テストのカバレッジは100%でなくてよい。そもそも現実的にカバレッジが100%になることはない。残り10%のテストはUIやどうでもいいエラーケースが含まれている。バグのコストが高くないのでテストが割に合わない。またプロダクトの種類によってテストコードにかける最適な時間は変わってくる。

  • テストがプロダクト開発の邪魔になってはいけない。

「分/時間カウンタ」を設計・実装する

一つの英単語で動詞と名詞が同じスペルものは、直感的に関数名などを理解できなくなる要因になってしまうので、取り扱いに注意する。他の英単語に差し替えるなど。

ユーザーフレンドリーなコードを書くために、コードの第一印象を同僚などに聞いてみる。

プリンシプルオブプログラミング 個人的重要箇所

前提、原則、思想、習慣、視点、法則、などのことをプリンシプルという。 プリンシプルを身につけると、なぜその技術が必要なのか、が理解することができる。 プリンシプルとは普遍的な知識であり、様々な技術に対して当てはめることができ、エンジニアの土台としての知識になる。プリンシプルは抽象的なものであり、大きな原則になる。
ときにはプリンシプル同士の主張が相反するものになる。その場合にはより良い解になるよう、状況に応じて判断する。

1章 前提

プログラミングの普遍的な事実のこと。プログラミングとはこういうもの、というコードを書くときの前提知識。

1.1 プログラミングに銀の弾丸はない。

ソフトウェアには複雑性、同調性、可変性、不可視性の4つの困難性を示す性質がある。 ソフトウェアは概念の集積であり、製品そのものやプロセス、意思決定の経緯なども見ることはできない。図面にされていても、情報は捨象されている。

本質的、偶有的
物事には本質的なものと偶有的なものがある。本質的なものとはそれがないと対象物とは言えなくなる性質のもの、偶有的なものはそれがなくても対象物が成り立つ性質のもののこと。
偶有的なものは容易に改善がしやすい。偶有的なものは自動化などで効率化し、本質的なものに多くの時間が避けるようになると好ましい。

1つのツールや技法が全てに当てはまるということはない。地道にソフトウェアの歴史や手法、考え方を学び複雑さを軽減することが大事。

1.2 コードは設計書である

ソフトウェアは基本設計、詳細設計、プログラミング、テスト、デバッグまでが設計であり、そのアウトプットが設計書であるコードになる。これらは不可分の作業であるため、それらの作業を分担のするのはうまいやり方ではない。設計をしてみないとわからないことが多い。故に、コードはできるだけ早めに書き始めるのがよい。

ロゼッタストーン
ロゼッタストーンは継続的なプログラミング活動において重要と思われるドキュメント、将来の保守担当者に対する簡潔な手引書。ソフトウェアの開発環境を理解するための情報と、ソフトウェアのアーキテクチャを理解するための情報を記述する。ソフトウェアの開発環境の情報はビルドとテストを実行する方法を記述する。ソフトウェアのアーキテクチャはコードからは読み取れない全体像などのコード全体を俯瞰して見た図を記述する。
コードから読み取れないWhy(コードにはHow, Whatが含まれている)、設計理由をドキュメントに残しておくと修正の判断材料として役立つ。

1.3 コードは必ず変更される

コードは無常であり、変更されるもの。変更に強いコードにするためには、コードが読みやすいということがもっとも大切。コードは書いている時間よりも読んでいる時間の方がはるかに長い。書くのにどれだけ時間がかかっても、読む時間を短縮できるなら十分にもとを取ることが可能。

2章 原則

プログラミングにおける基本的な決まりごと。鉄則と比べると例外を認める緩やかな規則。これらの原則どおりに考えると自然と筋のよいコードになる。

2.1 KISS

構成する各要素がシンプルで、各要素が担う債務が最小限におさえられているシンプルなコードは、コードを通じたプログラマ相互のコミュニケーションを楽にし、コミュニケーションコストを下げることができる。(修正容易性が高まる)

KISSはソフトウェア開発の中でコードだけではなく外部仕様にも適用することができる。機能ばかりが多く複雑なソフトウェアは、使われなくなる。機能やインターフェースをシンプルに保つと使いやすい、使われるソフトウェアになる。

less is more
より少ないことはより豊かなことである。余分なコードを書かないことによってコードをシンプルに保ち、様々な問題を回避することができる。もとは建築分野の言葉で、表層的要素や内的な要素を減らし、単純化すること、様々な外的要因耐えうる建築物が計画でき、より豊かな空間が生まれるという思想。

2.2 DRY(Don't repeat yourself)

コードに重複があるとコードを読む作業が難しくなる、コードを修正する作業が難しくなる、テストがない等の困難が発生する。重複のあるようなコードはたいていがレガシーコードであり、テストがない。

コードを抽象化することによって重複を排除する。コードの抽象化とは具体的に処理のまとまりに名前をつけて関数化、モジュール化する、データに名前をつけて定数を定義するなど。コードの量が減り読む量が減る、名前がつくのでコードが読みやすい、修正箇所が少なくなるので品質が担保される、新しい機能を追加する際に抽象化した部分は再利用しやすい、等のメリットがある。

コードを抽象化するのは大きな壁がある。ロジックを関数にするのは時間がかかる、元の動いていたコードを変えるのでデグレードのリスクがある、面倒など。 しかし、重複を避けることは長い目で見ると有利になるため、大きな壁があっても重複を排除することが大事。

インピーダンスミスマッチ
オブジェクト指向RDBでは抽象化スタイルが異なる。このような場合にコードを作成するとやむをえないDRY違反になるこのようなものをインピーダンスミスマッチという。情報は一箇所にまとめて他の情報を自動生成する仕組みを作ると情報を一元化することもできる。

WET
DRYに対して同じことを繰り返しているコードのことをWETという。

レガシーコード
レガシーコードというと昔のコードという意味になるが、テストによる品質保護の観点からテストのないコードをレガシーコードと言うようにもなった。テストのないコードは悪いコードになるという観点から、レガシーコードと相対することになったときにはエレガントでなくてもまずはテストコードを用意し、修正を行うことが品質を守る上で必須になる。

2.3 YAGNI(You aren't going to need it)

必要になるかもしれない、でコードを書くのではなく、必要になったときに必要なコードを書くという方針で臨む。汎用性よりも単純性を考える。

いくつかに設計案があるときは汎用性ではなく、具体的なニーズに答えている単純性を選ぶ。単純な解の方が汎用性が高いということもよくある。

YAGNIはソフトウェアの機能にも当てはまる話である。

先のことを考えてプログラミングしてしまうとコードの複雑性がまし、変更のコストが急上昇してしまう。

2.4 PIE(Program Intently and Expressively)

コードはソフトウェアの動作を正確に知るための手がかり。設計書などもあるけれども、結局ソフトウェアの動作を正確に把握するにはコードを読むしかなくなってくる。

コードは書かれることよりも読まれることのほうがずっと多い。よって、書く効率よりも読む効率が優先されることになる。読みやすければ、書くときの効率が多少落ちても、それに見合うだけの価値がある。読みやすさが最優先。

コードの品質を落とすことによってバグが現れたときに都度修正するようなもぐらたたきになってしまう。時間がかかっても長期的な利益のあるコードとテストを書くことが大事。

コードにはHowやWhatは書かれているが、Whyは書かれていない。コードで表現できることはコードで表現し、それでも説明がつかない箇所にはコメントをつけるというバランスをつけて記述することが大事。

2.5 SLAP(Single Level of Abstraction Principle 抽象化レベルの統一)

コードを書くときに高いレベルの抽象化概念と低いレベルの抽象化概念を分離するようにする。複雑さに応じて多階層に分離する。すると、コードに要約性閲覧性が生まれる。関数の一覧が目次のようになり要約性を持ち、分割された関数は小さなコードの塊になり閲覧性がよくなる。

複合関数
構造化された関数は自身より一段レベルの低い関数を呼び出す処理が中心となる。このような関数を複合関数という。 コードを読んでいて抽象度が急に変わると理解がしにくくなる。

意図を伝えるためであれば、一行の処理を関数にしてもかまわない。様々なレベルの抽象度の処理を呼ばない

SLAPは文章を書くときの手順が参考になり、内容を書くこと内容をわかりやすく伝えるための構成を考えることを別の作業にする。これをコードを書く際にも当てはめる。

優れた書籍の構成要素をコードに当てはめることで、よいコードになる。

  • 序盤...ファイルの先頭コメント。コードの記述内容を説明し、どのプロジェクトに属するものであるかの説明をする。
  • 目次... 関数の一覧。目次であるがゆえに、先頭に全ての関数リストを記述すべきという考え方もあるが、エディタの機能が発達しているため、遵守する必要はない。
  • セクション... ファイル内の関数を論理的にいくつかのグループに分類できる場合などは、複数の大きなセクションに分割する。セクションの区切りの防波堤コメントを区切りに使うと効果的。コードの量が多くなるときはファイル単位で分割する。
  • 章... 書籍の章は人ひとまとまりの内容に適切なタイトルをつけたもの。コードでは関数に値する。
  • 段落... コードブロックに値する。改行や、空白の区切り、インデントなどにあたる。
  • 文 コード1つ1つのステートメントにあたる。できるだけ短く簡潔に。
  • 相互参照・索引... コード上で相当するものはないので、エディタの機能が担う領域。

2.6 OCP(open-closed principle)

コードは、ソフトウェアを生き続けさせるために、コードの振る舞いを拡張できるという拡張に対して開いている という性質とコードの振る舞いを拡張してもその他コードは影響を受けないという修正に対して閉じているという2つの属性を同時に満たすように設計する。これらを満たしているコードを柔らかい設計という。変更に対して柔軟に対応できない設計を硬い設計といい、脆い設計になってしまう。

サーバーを設置する際に、クライアントインターフェースを用意することでサーバー側に変更があった場合でも、クライアントのコードを変更せずに済む。

OCPの過度の適用はよいものではなく、ある程度変更が発生するのを待ち、一度受け入れてから変更するという戦略で防ぐことができる。
変更の内容を予測するというより、変化しそうな部分を予測することが大事。

多くのデザインパターンではOCPを実現するための手段として活用することができる。

GRASPという責任開発駆動の開発手法がある。この手法にはバリエーション防護壁という設計パターンがあり、不安定箇所の周りに安定したインターフェースを作り、変更の波及を守る防護壁を作ることができる。

2.7 名前重要

名前をつける行為と名前そのものに価値がある。要素を正しく理解し、正しく設計することで、その設計されたコードを読む人とのコードを通じての会話が円滑になる。また関数などを使用する際に目的がはっきりしているため、内部コードを読む手間が省ける。

コードにはまず名前をつけることから始める。

  • 多くの情報を入れる
  • 誤解されることのないようにする
  • 手段ではなく効果と目的を説明する
  • 名前を自分自信でチェックしたい場合には処理を書く前にそのテストを書くようにする(コードの使用者側の視点で考えられるようになる)
  • 日常会話で使いやすいよう発音可能なものにする
  • 検索可能なものにする

の点に注意することが大事。

メンタルマッピング
ある情報から記憶の中のあるべき姿のイメージに変換することをメンタルマッピングという。メンタルマッピングが発生すると読み側の負担になってしまうため、一目でわかるような命名をする。

ループバックチェック
説明→名前→説明の順番にチェックし、説明から名前、名前から説明の説明、推測ができるかをチェックする。

3章 思想

成功したソフトウェアには「文化」「哲学」「価値観」と言った思想がある。

3.1 プログラミングセオリー

プログラミングにはセオリーがあり

  1. コミュニケーション
  2. シンプル
  3. 柔軟性

の3つの価値によって支えられている。 これらの価値をプログラミングに当てはめるための橋渡しとなる6つの原則

  1. 結果の局所化
  2. 対称性
  3. 繰り返しの最小化
  4. 宣言型の表現
  5. ロジックとデータの一体化
  6. 変更頻度

がある。これらを活用することによって適切な技術選定ができるようになる。

ある技術を選定するときに考慮すべき観点のことをフォースという。フォースには要件、制約、特性があり、課題を様々な側面から検討でき、課題そのものを詳細に把握できるようになる。相補的、相反的なフォース間のバランスが必要。

3.2 コミュニケーション プログラミングセオリーを支える3つの価値①

ソフトウェアの開発コストの大半は最初に開発された後に発生する。保守のコスト。そのコストを節約するために、コードを読みやすく書かなければならない。人に見せる文章であることを意識する。

3.3 シンプル プログラミングセオリーを支える3つの価値②

コードの玉石を見分け、複雑性をなくしていく。本質的な玉の部分に余分な石の要素を紛れ込まないようにする。基本的にはシンプルとコミュニケーションは好影響を与え合うが、ときとして情報量が少なすぎる、シンプルすぎるが故にぶつかる場合がある。そのような場合には冗長になってもコミュニケーションを優先する。

3.4 柔軟性 プログラミングセオリーを支える3つの価値③

コードに柔軟性を持たせるために拡張しやすく、かつその拡張が他に波及しないように心がける。ただし、柔軟性は諸刃の剣であり、複雑なコードや設計になりがちになってしまう。即効果のあるコード以外は書かないようにする。凝った設計のトップダウンで得られる柔軟性よりも、シンプルにスタートしてユニットテストからボトムアップ的に得られる柔軟性のほうが効果的。

3.5 結果の局所化 プログラミングセオリーを実現する6つの原則①

結果の局所化とは変更の影響が局所にとどまるようにコードを構成するということ。局所化されていないとある部分の変更が全く別の部分に影響を与えてしまう。また、局所化することによって修正箇所が最低限で済む。

関係性の高いコードをまとめることが大事。

3.6 繰り返しの最小化 プログラミングセオリーを実現する6つの原則②

繰り返しの最小化とは関数などによって重複を排除すること。
繰り返しのコードは結果の局所化を原則侵害し、変更コストを増大させる。

大きなコードの塊を同じところ、似ているところ、異なるところを伝えながらコードをたくさんの小さい部分に分割することが大事。

3.7 ロジックとデータの一体化 プログラミングセオリーを実現する6つの原則③

ロジックとそのロジックが操作するデータはお互いに近くに置くようにする。
これらはコードを修正する際に同じタイミングで変更されるため、コードを読む量が減り変更コストが抑えられる。
ただし、どのロジックとデータを近づけたほうがいいのかのベストな解は最初からはわからないため、仮配置して後から適切な場所に移すのが効率的。コードを書いて動かしてから徐々にわかってくること。試行錯誤の中で明らかになる。

3.8 対称性 プログラミングセオリーを実現する6つの原則④

コードに対称性を持たせると、概念的にデコボコしたコードにならなくなり、読みやすくなり、また重複も排除しやすくなる。

3.9 宣言型の表現 プログラミングセオリーを実現する6つの原則⑤

宣言型のコードには解くべき問題の性質や、その際に満たすべき制約を記述する。命令形のコードはデータ構造とアルゴリズムを記述するので、状態と制御のデータフロー常に頭に思い浮かべないといけない。
パラダイムが命令形の言語でも一部宣言型の表現を取り入れることで可読性が上がる。

3.10 変更頻度 プログラミングセオリーを実現する6つの原則⑥

同じタイミングで変更されるものは同じ場所に置き、異なるタイミングで変更されるものは別の場所に分けておくことが大事。これは対称性を時間に対して当てはめたもの。一年ごとに変更されるロジックと頻繁に変更されるロジック等の変更理由で所属を決めると、変更頻度の原則を保つことができる。

単一責任の原則
変更理由が複数あるということは責任が複数あるということ。他の箇所に影響を与える脆いものになってしまうので、そのようなモジュールは作ってはならない。

3.11 アーキテクチャ根底技法

よいソフトウェアを作るには基礎原理、根底技法があり、モジュールの設計方針とされている。

  • 抽象化
  • カプセル化
  • 情報隠蔽
  • パッケージ化 
  • 関数の分離
  • 充足性、完全性、プリミティブ性
  • ポリシーと実装の分離
  • インターフェースと実装の分離
  • 参照の一点性
  • 分割統治

の10の技法がある。

よいコードには型があり、根底技法をコードに適用することでよいものになる。

3.12 抽象 アーキテクチャ根底技法①

抽象とは概念的に線引きを行うこと。抽象は捨象一般化の観点からまとめられる。

捨象
複雑な対称からいくつかの性質を捨て去り特定の性質に目を向けること。

一般化
具体的な対称から共通の性質を抽出し、汎用的な概念に定式化すること。

抽象は複雑なものに取り組むときに大きな武器になる。

3.13 カプセル化 アーキテクチャ根底技法②

カプセルに入れるように、関連性の高いデータをロジックを1つのモジュールに定義する。

  • コードが見やすい
  • 変更時の影響がモジュール内で済む
  • コードの変更が容易
  • 再利用性が高い
  • 小さい単位で分割されるので複雑な問題に対処できる

等のメリットが生まれる。

3.14 情報隠蔽 アーキテクチャ根底技法③

クライアントが知る情報を少なくすることでインターフェースが小さくなり、やりとりがシンプルになる。公開されている部分が少ないとモジュール内部で変更を留められる可能性が高まるため、コード変更の波及を抑えることができる。モジュール内部でしか使用していない関数は外部からアクセスできないようにするなど。

パルナスの規則
モジュールの利用者は、モジュールを利用するために必要な全ての情報を与え、それ以外の情報は一切見せないこと
モジュールの作成者は、そのモジュールを実装するために必要な情報を与え、それ以外の情報は一切見せないこと。

3.15 パッケージ化 アーキテクチャ根底技法④

モジュールを意味のある単位にまとめグループ化したものをパッケージと呼ぶ。ソフトウェアが大規模になってくるとモジュールが大量になりそれ自体が複雑になってしまう。そのため、パッケージ化が必要

パッケージはソフトウェアのビルド方法を示すマップという位置づけのため、設計はボトムアップで行う。

3.16 関心の分離 アーキテクチャ根底技法⑤

関心ごとに独立して修正できる、変更時の品質が安定する、分業して開発することができる等のメリットがあるため、関心ごとにモジュールをわけることが大事。

アスペクト指向プログラミング
横断的関心をうまく分離する技術のこと。結合ルールによって自動的に後から組み込むことで関心の分離を実現している。

3.17 充足性、完全性、プリミティブ性 アーキテクチャ根底技法⑥

モジュールの意図を使う人に伝え、有用であるためにモジュールの担っている抽象の表現は充足性、完全性、プリミティブ性を備えるべきである。

3.18 ポリシーと実装の分離 アーキテクチャ根底技法⑦

ソフトウェアの前提に依存するポリシーモジュール、ソフトウェアの全体に依存しない独立したロジック部分の実装モジュールがあり、ポリシーと実装はわけるようにする。分離が不可能である場合には明確にわかるよう表現する。

3.19 インターフェースと実装の分離 アーキテクチャ根底技法⑧

モジュールはインターフェースと実装のパートを分ける。インターフェースパートはクライアントが使用するパート、実装パートはモジュールがもつ機能を実現しているパート。

「インターフェースに対してプログラミングをするのであって、実装に対してプログラミングをするのではない」という設計原理に関する格言がある。

インターフェースの実装についてはインターフェースの背後に隠れるようにして、直接呼び出すことを許可しないようにする。

3.20 参照の一点 アーキテクチャ根底技法⑨

副作用のないプログラミングをするために、定義は一度きりにする、変数に対して再代入をしない単一代入を行う、極力定数を使い変数に対しては極力アクセスするロジックやスコープを減らす、関数は渡された引数のみに作用する等のことをする。

参照透過性

  • 呼び出しの結果が引数のみに依存すること
  • 呼び出しが他の機能の動作に影響を与えない(その後の処理の結果に影響を与える副作用をがない)

この2つの特性を関数が持っていることを参照透過性という。参照透過性を満たしている動作が外部の状態と独立していて、自己完結的である。

3.21 分割統治 アーキテクチャ根底技法⑩

大きな問題を大きな問題のまま解決するようにすると難しいため、小さく割って対処することが望ましい。

3.22 アーキテクチャ非機能要件

アーキテクチャの設計において、非機能要件は機能要件と同程度に重視する。
非機能要件の観点には

  • 変更容易性
  • 相互運用性
  • 効率性
  • 信頼性
  • 再利用性

がある。

リリース後の運用の大きなトラブルのほとんどが非機能要件的な特性に起因する。開発の最初、アーキテクチャの設計時点から考慮されるべき観点。

非機能要件で設計
要件定義においてそれぞれの観点についてどの程度必要とされるのかを確認、アーキテクチャの設計の時点で要件を考慮に入れた構造を考え、テストで要件を満たしている確認する工程を踏みアーキテクチャ設計を非機能要件を考慮したものにする。

非機能のテスト
機能のテストではwhatに注目し、非機能のテストではHowに注目する。非機能テストは機能テストと同程度に重要。非機能テストにも合格基準を設けることが必要。

セキュリティ非機能要件 重要な非機能要件の1つにセキュリティがある。

  • 機密性
  • 完全性
  • 可用性

英語の頭文字を取って、情報セキュリティのCIAと呼ばれているこの3つを維持することが、情報セキュリティの正式な定義。

セキュリティの検証をする際にはツールの使用、アウトソーシング等で検証をする。また、セキュリティ事故の発生時の追跡可能性、監査能力の評価も求められる。

セキュリティ要件と利便性のバランスが大事。

3.23 変更容易性 アーキテクチャ非機能要件①

ソフトウェアの寿命は長いため、コードの変更が容易であるアーキテクチャが必要になる。

保守性、拡張性、再構築、移植性の側面からアーキテクチャを設計するようにすることが大事。

変更に対して強い柔軟なアーキテクチャを作ることは大事だが、柔軟にしすぎるとモジュールがシンプルでなくなり、拡張性を持たないソフトウェアになってしまう。シンプルとのバランスを大事にすることが大事。

ソフトウェアエージング
ソフトウェアは経年劣化する、年を取るという考え方をソフトウェアエージングという。 正確なドキュメント化、変更時にアーキテクチャを壊さないこと、真摯なレビュー、変更箇所を予測した柔軟な設計などの対抗手段を駆使しソフトウェアエージングの速度を落とす。

3.24 相互作用性 アーキテクチャ非機能要件②

他のソフトウェアとやりとりができる能力のこと。

他のソフトウェアとやりとりをするためには、外部の機能やデータ構造へアクセスが明確に定義されたアーキテクチャを設計する、プロトコルやデータを標準的な規格を選定することを意識する。

既存のソフトウェア側の要求に合わせなければいけない場合、既存のソフトウェア同士の接続の仲介をするソフトウェアを作る場合などもある。

3.25 効率性 アーキテクチャ非機能要件③

時間効率性
時間という観点からリソースの使用効率を定義したもの。

資源効率性
コンピューター資源という観点からリソースの使用効率を定義したもの。

リソースを積極的に活用し、ソフトウェアを効率よく利用することが大事。

3.26 信頼性 アーキテクチャ非機能要件④

信頼性とはソフトウェアが例外的な場面、予期しない方法や不正な方法で使用されても機能を維持する能力のこと。信頼性にはフォールトトレランスロバストネスという2つの側面がある。

フォールトトレランス
ソフトウェアに障害が発生したときに正常な動作を保ち続ける能力。

ロバストネス
不正な使用方法や入力ミスから、ソフトウェアを保護する能力。フォールトトレランスと違い、内部的な修復は必ずしも要求しない。ソフトウェアを定義された状態に移行することを保証する。

フォールトトレランス観点の対策として、アーキテクチャに冗長性をもたせる、障害時に提供する機能を絞り込み、大事な機能のみを提供して処理継続を優先させる設計のフェールソフト等を考慮する。

ロバストネス観点の対策として、障害時にその部分を切り離す設計のフォールセーフ、ユーザーが誤った操作を行っても安全に稼働させる設計のフールプルーフ等を考慮する。

3.27 テスト容易性 アーキテクチャ非機能要件⑤

テスト容易性とは、ソフトウェアに対して効果的かつ効率的にテストを行う能力のこと。

ソフトウェアは複雑になってくるとテストも難しくなってくるため、テストを容易にするアーキテクチャが求められてくる。
テストコードは本番コードに従属するイメージだが、テストコードが本番にあってもよい。テストをしやすくするための構造が本番コードにあってもという価値観の転換が必要。
テスト容易性のための設計ではモジュール間の依存関係の排除がポイントになる。

3.28 再利用性 アーキテクチャ非機能要件⑤

再利用性とは、全体でも一部でも別のソフトウェアの開発に再利用する能力のこと。
再利用するソフトウェア開発再利用のためのソフトウェア開発がある。

  • 再利用するソフトウェア開発の場合はソフトウェアを既存のモジュールから組み立てるソフトウェア・コンポジションを支援するため、アーキテクチャの構成を既存の構造やモジュールのプラグインできるようにする。

  • 再利用のためのソフトウェア開発では開発中のソフトウェアから自己充足的な部分を取り出すことができるアーキテクチャにする。

再利用の3の法則

難易度3倍の法則...再利用可能なモジュールを作るのは単一のソフトウェアで使うモジュールを開発する場合に比べ、3倍難しい。

テスト3種類の法則...再利用可能なモジュールは共有化する前に3つの異なるソフトウェアでテストする必要がある。

3.29 7つの設計原理

障害を作り込まないために考慮すべきコード構造上の7つの核心観点のこと。適切なコードレビューをするために必要になる一定の価値観や観点になる。

3.30 単純原理 7つの設計原理①

可読性を上げる、複雑なところにバグが出るのを防ぐためにシンプルにこだわるということ。

3.31 同型原理 7つの設計原理②

同じことが同じように表現されていると、違うものが目立つ。よって、障害やバグに気づきやすくなる

3.32 対称原理 7つの設計原理③

コードに対称性があると予測しやすく読みやすいコードになる。条件があるときに、反条件の整合性も考慮する。

例外的状況が多すぎる場合にはそもそも要求が整理されていない場合がある。例外的状況は対称性を崩し、障害の温床となる。

同質的なものは同じレベルで表現し、でこぼこしていないコードにすることでコードが読みやすくなる。

set/get, start/end などのような命名規則も守ることにより、対称性を保つことができる。

3.33 階層原理 7つの設計原理④

コードに明確に表現された階層構造があると、読む人が全体を抽象化して理解することができるため、抽象階層構造のあるコードを書くように心がける。

3.34 線形原理 7つの設計原理⑤

処理の流れが複雑であると、それだけで障害の温床になる。一直線の処理にすると可読性も上がるため、処理が階層の上位から下位に向かって流れるように意識をする。また、分岐の少ないコードにすることも大事。

3.35 明証原理 7つの設計原理⑥

コードは何度も読まれるため、コードの不確実性を取り除き、一見して明らかに正しいと言えるようにコードを書くこと。

3.36 安全原理 7つの設計原理⑦

必然性のないところや曖昧なところは安全サイドで設計、プログラミングをしておくこと。

3.37 UNIX思想

UNIX思想とは優れたプログラミングを行うための、経験に基づいた技の集合。
UNIXは1969年に生まれてなお現在も使われている。この優れたUNIX思想はソフトウェアを開発する際にも役に立つ。

3.38 モジュール化の原則 UNIX思想①

ソフトウェアは複雑なため、シンプルなモジュールを保つようにする。

3.39 明確化の原則 UNIX思想②

コードを読む人のため、保守性を高めるためにコードは巧妙にではなく明確に。少しのパフォーマンスのために複雑さを大幅に引き上げるトレードオフはしないように。

3.40 組み立て部品の原則 UNIX思想③

ソフトウェアは他のソフトウェアと組み合わせることができるように、できる限りシンプルなフィルタとして作る。データストリームの形式はテキスト形式にするのが良い設計。多少オーバーヘッドがあっても、自由に読み書きできるメリットを享受するため。

3.41 分離の原則 UNIX思想④

ポリシーカニズムを分ける。

  • サービス系アプリケーション....フロントエンドがポリシー、バックエンドがメカニズム。
  • エディタアプリケーション...ユーザーへのインターフェースがポリシー、エディタのエンジンがメカニズム。

3.42 単純性の原則 UNIX思想⑤

コードはシンプルにする。カタログスペックが上がるように機能を増やす考え方をやめる。シンプルを美しいとする文化

3.43 倹約の原則 UNIX思想⑥

大きなコードは書かないようにする。ここでいう大きいとは単純にコード量が多いこと、コードが複雑であることを指す。

3.44 透明性の原則 UNIX思想⑦

ソフトウェアの動作を外からわかりやすくなるように設計する。
透明性...ソフトウェアに動作が一見してわかるようにすること。
開示性...ソフトウェアの内部状態を監視または表示すること。

この2つを満たすことは直接ユーザーに貢献することではなく、デバッグや障害調査が容易になるのでプロジェクト全体によい影響を与えることになる。
デバッグ機能は設計の最初の段階から考えるようにする。デバッグを簡単にする仕組みを本番コードに組み込むことを躊躇しない。

3.45 安定性の原則 UNIX思想⑧

通常の条件下だけでなく、予想外の条件下でもソフトウェアが適切に動作すること。堅牢であること。ただし、複雑になると安定性が落ちてしまうため、透明性と単純性を保つようにする。

3.46 表現性の原則 UNIX思想⑨

コードに置ける情報の表現方法はロジックではなく、データに寄せる。コードの改善をするときはコードの複雑さをデータの複雑さに切り替える。

3.47 驚き最小の原則 UNIX思想⑩

インターフェースは使う人の驚きを減らすようにする。

3.48 沈黙の原則 UNIX思想⑪

表示を最小限に抑える。重要な情報のみを表示させ、他の情報などはスイッチで切り替える等で表示させる。

3.49 修復の原則 UNIX思想⑫

修復失敗時には処理を継続するのではなく、処理を停止させ、そのエラーをできるだけ目立つようにする。

ソフトウェアの入力と出力に関して、受け入れるものについては自由主義、送るものについては保守主義の考え方がある。もともとはネットワーク用のソフトウェアにおける箴言

3.50 経済性の原則 UNIX思想⑬

ハードウェアやソフトウェアの購入を制限するのは経費削減のためだが、プログラマの時間の方がよっぽど単価が高いので、開発環境のためのハードウェアやソフトウェアに投資する。結果生産性と品質が格段に上がる。
ルールや制約は施行後にバランスを調整する。開発に支障が出るのであれば、これだけは最低限譲れないというところまで譲歩する。

3.51 生成の原則 UNIX思想⑭

可能は範囲で、適材適所で、部分的にでもコードを生成するコードを書く。生成されたコードは安上がりで高品質。

3.52 最適化の原則 UNIX思想⑮

プログラミングにおいて、コードを最適化する前に正しく動作するコードを書くようにする。透明性や単純性を犠牲にして微々たる高速化をする必要はない。半端に最適化すると全体に渡る大きな効果が得られる最適化ができなくなる。

正しく動かしてから速くするようにする。まずは遅くても正しく動作する実装をする。まず動くプロトタイプを作ることは、コードを書かなくても済む機能がわかるため最適化の観点からも効果的。

3.53 多様性の原則 UNIX思想⑯

ソフトウェアに関わる全ての場面において「唯一の正しい方法」はない。多様性を認め、よりよいやり方を模索し続けることが大事。一回のリリースでユーザーの全ての要望には答えられないため、後から変更できるように柔軟な設計にする。

3.54 拡張性の原則 UNIX思想⑰

自分の設計が唯一の正しい方法と考えるのは危険。最初の設計がもれなく完璧ということはないため、コードに成長の余地を残すようプラガブルな設計にする。その際に、予測で機能を追加するのではなく、後で必要になったように簡単に機能を追加できるような状態にするということを履き違えないようにする。

3.55 UNIX哲学

UNIXは古くから存在し、設計哲学が優れており普遍的であるため今もなお利用されている。

3.56 小は美なり UNIX哲学①

理解が容易、保守が容易、リソースに負担をかけない、相互性がある等のメリットがあるため、ソフトウェアは小さく保つ。

3.57 1つ1仕事 UNIX哲学②

1つのソフトウェアには1つの仕事をさせる。例えばディレクトリの内容を表示するコマンドがあるとして、この際に表示整形の機能を追加してはいけない。あくまで1つの仕事をし、表示整形に関しては別途整形を専門とするコマンドを作成し、それを組み合わせればよい。他のソフトウェアでも再利用できるメリットがある。

3.58 即行プロトタイプ UNIX哲学③

前提の誤りを早期に発見できる、要件不備による手戻りを減らせる、早いうちから誤りを取り除く作業を始められる等のメリットがあるため、できるだけ早くプロトタイプを作る。最初から完全なソフトウェアを書くことは不可能なため、継続的な改善作業が必要。

第3のシステム
人間は全てのシステムにおいて第1のシステム、第2のシステム、第3のシステムをリリースしていくことになる。

第1のシステム...性能は高いが、必要な機能が欠けている。
第2のシステム...機能は多いが、性能が犠牲になっている。
第3のシステム...両者の最適なバランスの取れた必要な機能だけが備わっている。

この第3のシステムを目指すためには最初に他の2つのシステムを作ること。この順序を変えようとしても、手間が増えてしまうことになる。この第3のシステムへとできるだけ進めるための方法がプロトタイプである。プロトタイプの目的は第3のシステム。

3.59 効率性より移植性 UNIX哲学④

ソフトウェアの設計は選択の連続である。移植性と開発効率性という二律背反の選択に迫られたとき、優先すべきは移植性

ソフトウェアの成功を測るものさしの1つに「いくつのプラットフォームで稼働するか」というものがある。ハードウェアに依存しないコードを書くようにする

3.60 データはテキスト UNIX哲学⑤

コードの移植性だけでなく、データの移植性も重要。テキストファイルはほとんどの場合でバイナリファイルよりも優れている。 テキストファイルは冗長かもしれないが、パフォーマンスは無視できるほどの差。コードよりも移植性が高い、人間がただちにデータを確認できる、ツールやコマンドで扱いやすいなどのメリットがある。

ソフトウェアは各々テキストファイルに入出力するように設計してソフトウェア同士の連携を容易にする。その際は、標準的なテキストファイルの形式を選ぶようにする。

3.61 レバレッジ・ソフトウェア UNIX哲学⑥

よいプログラマはコードを書く。偉大なプログラマはよいコードを借りてくる
自分の仕事に他人に成果を取り込むことで、投資を少なく大きな収入を生み出す。レバレッジがかかる。

3.62 シェルスクリプト活用 UNIX哲学⑦

シェルスクリプトによっててこの効果と移植性の効果を高める。

てこの効果
シェルスクリプトを使用して他のソフトウェアやコマンドをつなげる。

移植性
シェルスクリプトはインタプリンタなので、コンパイル言語に比べ移植性が高くなる。
シェルスクリプトはグルー言語として使用し、小さなソフトウェアをつなげて大きなものにする。

シェルスクリプトで使用するコマンドは他人の成果、努力の結果でありシェルスクリプトに組み込むことで、自分の目的のてことして使用することができる。レバレッジがかかる。

3.63 対話インターフェース回避 UNIX哲学⑧

対話的インターフェースは拘束的ユーザーインターフェースとも言われるため、過度の対話的インターフェースは避けるようにする。

3.64 フィルタ化 UNIX哲学⑧

すべてのソフトウェアをフィルタとして設計する。データ入力には標準入力を用い、データ出力には標準出力を用いる。このように設計するとソフトウェア同士を組み合わせられるコネクタブルな設計になる。

90%の解(UNIX小定理)
どんなことであれ100%で物事をこなすのは困難。そのため、ユーザーが90%で満足することを目指し、残りの10%は自分でどうにかしてもらおうという考え方をするとバランスがよくなる。

4章 視点

将来に渡って改善しやすい息の長いコードにするための「視点」「見方」のこと。山のように迫られる選択の判断基準になる。

4.1 凝集度

凝集度とはモジュールに含まれている機能の純粋さを表す尺度。モジュールの強さを測るもの。レベルが高いほど純粋である。

レベル1 暗号的強度
暗号とは偶然に物事が一致するという意味。たまたまモジュール内に重複している命令群のパターンがあったので、統合して1つのモジュールにするなどのケース。

レベル2 論理的強度
ある機能を抽象的に捉えてまとめたもの。関連したいくつかの機能を含み、そのうちの1つだけがモジュールによって識別され(論理)、実行されるモジュールのこと。内包される命令群の関連性は弱いため、モジュール強度は下がる。

特定のデータ処理を局所化できるメリットなどがある。

レベル3 時間的強度
特定の時点に連続して実行する複数の機能を1つのモジュールにまとめたもの。代表的なものは初期処理モジュール。

レベル4手順的強度
問題を処理するために関係している複数個の機能のうちいくつかを実行。複数機能な順番(手順)に実行される。複数機能の1つだけといった使い方ができなくなる。

レベル5連絡的強度
基本的には手順的強度の特性を持つ。モジュール愛機能間でデータの受け渡し(連絡)をしたり、同じデータを参照する点が異なる。

レベル6: 情報的強度
特定のデータ構造を扱う複数の機能を1つのモジュールにまとめたもの。論理的モジュールと違い一口点が複数あり、各々が固有のパラメーターをもつためパラメーターの扱いにくさを解消できる。

レベル7: 機能的強度
モジュール内の全ての命令が1つの役割(機能)を実行するために関連しあっているモジュール。変更理由が他のモジュールとも共通の事情による変更の可能性が高くなる。

モジュールの独立性を高めるためにはモジュール内での関連性を高くし、モジュール間の関連性を最小にするようにすることが大事。

4.2 結合度

モジュール同士の関係の密接さを表す尺度。結合度の判定はモジュール間でデータがどのように上渡しされているかに注目して行う。

レベル1 内部結合
モジュールとモジュールが一部を共有するようなモジュール結合。

レベル2 共通結合
共通域に定義したデータをいくつかのモジュールが共同使用するような結合形式。

レベル3 外部結合
外部宣言したデータを共有したモジュール間の結合形式。public宣言された変数など。

レベル4 制御結合
呼び出し側のモジュールが呼び出されるモジュールの制御を指示するデータをパラメータとして渡す結合形式。相手をブラックボックス化しにくいので結合度が強くなる。

レベル5スタンプ結合
共通域にないデータを2つのモジュールで受け渡しする結合形態。データ構造はパラメーターを介す。受け渡すデータの一部を使用しないこともあり、不必要なデータが生まれてしまう点が結合度を強くしている。

レベル6 データ結合
モジュール感のインターフェースとしてスカラ型のデータ要素をパラメーターとして受け渡す結合形式。相手モジュールをブラックボックス化できるので結合度は一番弱くなる。

ハイブリッド結合
ハイブリッドに戻り値が変わってしまう関数などは、使用者側が意識して使用しなければならないため、結合度がやや高い

結合度の着眼点は強さ、太さであるが、本数や方向にも気を配る必要がある。

モジュールや関数の持つ性質として、何度も同じ結果が返ってくるべき等性、操作対象に影響を与えない安全性の考え方がある。

4.3 直交性

コード同士に独立性、分離性をもたせ、片方を変更しても他方に影響を与えない直行している変更に強いコードにするようにする。

レイヤー化のアプローチが有効的な手段。レイヤー化にはレイヤー間で変更が連鎖する場合がある、データ変換のオーバーヘッドが発生する等のデメリットがある。

リレーションの直行性
リレーショナル・データベースで正規化と同じぐらい重要なリレーションの直交性という概念がある。複数のリレーションに間の重複に関する概念。データベース全体から重複をなくしていくこと。

4.4 可逆性

可逆とは、ある変化が起こってもある条件を加えると元の状態に戻る性質。

ある方針を決めて突き進んでも問題が合った時にやり直しができるようにする。 複雑になりすぎないよう、コードに柔軟性を持たせることで対応する。

4.5 コードのにおい

コードのにおいとは、コードが修正しにくい、理解しにくい、拡張しにくい部分のこと。

よく見る、長過ぎる、大きすぎる、多すぎる(モジュールが)、名前が合わない等の箇所に注意する。

4.6 技術的負債

ソフトウェアとして動いていたとしても、アーキテクチャが整っていない、依存度が高すぎるモジュールが多数あるなどの技術的負債があると、変更に弱い、拡張できないなどの問題が発生してしまう。状況に応じて汚くても作らなければいけないこともあるが、しっかりと後で負債を返済することをしなければいけない。

時間がないときにその場しのぎで作られた暫定ソリューションには慣性の法則が働いてしまう。暫定といいながらも、問題ないように動いてしまっているため、放置されてしまう。しっかりと技術的負債のリスクを考え、管理することが大事。

5章 習慣

よいコードを書くための行動指針のこと。

5.1 プログラマの3大美徳

怠慢
何度も繰り返し行っている手動の作業などを自動化してしまい、楽に行おうという態度。

短気
コンピューターが効率的に動いていない、意図通りに動いていないときにコードを書き直すこと。起こりうる問題を想定して仕事をする。

傲慢
高いプライドを持ち人様に対して恥ずかしくないコードを書くこと。

5.2 ボーイスカウトの規則

ボーイスカウトにはシンプルな規則「自分のいた場所は、そこを出ていくとき、来たときよりもきれいにしなければいけない」がある。これをコードにも当てはめ、コードは自分が見たときよりもきれいにする。

5.3 パフォーマンスチューニングの箴言

パフォーマンスチューニングによる最適化には2つのルールがある。

  1. 行ってはならない。
  2. まだ行ってはならない。

まずは正確に動くコードを書く。最適化を行ったがゆえにコードが複雑になってしまったり可読性が落ちてしまうことはよいことではない。また、環境に特化した最適化を行うとコードの移植性が落ちてしまう、時間が多くかかってしまうのにあまり効果のない最適化を行うと時間のムダが増えてしまう等の問題も発生する。

アーキテクチャの最適化は後から取り返していくのが困難になるため、早期からパフォーマンステストを行うべき。パフォーマンステストは継続的に行い、どのような変更したときにパフォーマンスが落ちるのかをわかるようにし、変更の局所化を行いコストを抑える。

パフォーマンスに問題があり、ボトルネックを探す際に、処理時間の大半を締めている箇所をホットスポットという。

5.4 エゴレスプログラミング

自分の書きたいようにプログラムを書いてしまうと、可読性が落ちたり複雑性が落ちてしまう。チームに貢献するためにはエゴを抑える。
ただし、自分を全て捨ててチームに貢献するという考え方はよくないため、エゴ(個性)とエゴレスのバランスを取るようにする。個性を全て捨て去ることは逆にチームに貢献できなくなる。

5.5 一歩ずつ、少しずつ

プログラミングは一度に少しずつ作業を進める。一度の複数のことをすると作業が混戦して失敗する確率が高まる、後戻りする項目が複雑になり困難になる、等の問題が発生してしまう。
古いものを新しいものにする際には、全て壊して全て作り直すのではなく、動いている箇所は残しつつ新しいコードを追加していき、古いものが全て新しくなったところで古いものを壊すという手順を踏むようにする。

思考もこの考え方をするのがよい。頭の良い人は瞬時に飛躍して答えを導き出しているようにみえるが、問題を解決するさいに一歩ずつ少しずつの一つ一つの作業が速いため、全体として速く正確になっている。わからないことがあっても、手順をしっかり踏んでコツコツと考えることが大事。

5.6 TMTOWTDI(やり方は一つではない)

対象が複雑なときは、ツール側も複雑にする。そうすることで、使用者側は覚えることが多くなってしまうが、結果的にシンプルな使い方ができるようになる。簡単なことは簡単に、難しいことは可能にを目指す。

ツールは作成する時間よりも使われる時間のほうが多くなるため、ツールをよりよいものにしようと投資することにはレバレッジがかかる。

ソフトウェアに置いてシンプルであることについては優先度が高く置かれるべき。そのシンプルさをどこに配置するかということを考える。TMTOWTDIではツール的なソフトウェアの場合ツール側が複雑さの面倒を見てクライアント側にシンプルさを配している。

6章 手法

プログラミングにおける手法、技法のこと。よい道具を知り、上手く使いこなすことが大事。

6.1 曳光弾

ソフトウェア開発で、ソフトウェアの最終形態に対する決定を提示できるようにしなければならない。そのために、一部ながらでも本物のソフトウェアが必要になる。まずは土台を作り、そこに肉付けをしていき、開発をしていく。

曳光弾とプロトタイプは似ているように見えるが、アプローチが異なる。プロトタイプは理解を検証するためのものであって、情報を得られたあとは捨てる。プロトタイプとはすなわち経験学習であり、プロトタイプの本質は学びである。よってプロトタイプは曳光弾を発射する前の偵察、諜報活動という位置づけになる。

プロトタイプは試供品と試作品
試供品としてのプロトタイプはユーザーのニーズを把握するためのもの。
試作品としてのプロトタイプは開発方法を検討するためのもの。

6.2 契約による設計

関数を呼び出す側は「想定(事前条件)」の契約を、関数側は「確約(事後条件)」の契約を守ることで契約による設計が成り立つ。正確さの保証、コードのシンプルさ、問題の早期発見等のメリットがある。

コメントとアサーションを用いて契約による設計を実現する。リリース版のビルド時にはパフォーマンスの観点から、アサーションコンパイルの対象から外すようにする。

トラッシュとクラッシュ
コードの実行中にありえないことが起きた場合には、クラッシュをするのがセオリー。トラッシュしてしまうとどんどん無茶苦茶な状態になってしまう。

6.3 防御的プログラミング

こうなるはずだ、と決めつけないでプログラミングをする。想定内のエラー、想定外のエラーの観点に気をつけてプログラミングをする。そうすることで開発中の安全運転、運用中の安全運転が得られる。

バリケード戦略
バリケードを構築し、被害を封じ込める。安全地帯の内側にあるモジュール、外側にあるモジュールの間で消毒を行うモジュールの役割配置をする。
想定内のエラーにはエラー処理を、想定外のエラーにはアサーションで対応する。

エラー処理に置ける正当性と堅牢性
エラーに処理に対してそのまま続けるのか、中断するのかということを見極める必要がある。

言語の中へプログラミング
言語の中でプログラミングをするのではなく、言語の中へプログラミングするようにする。自分の考えを言語の中へ実現しようとする。実現したいことに対して言語がサポートしていない場合には諦めるのではなく、他のライブラリを探す等の対策をし、実現する。

6.4 ドッグフーディング

自分で開発したソフトウェアを自ら使用するようにする。開発チームは無意識に悪気なく障害を避けてしまう傾向にある。なので、自らがユーザーになり問題点を発見し、改善することが大事。リリース後も自らのソフトウェア使い続ける。

6.5 ラバーダッキング

発生している問題を説明することで頭の中の整理、理解、問題解決を促す手法。説明しようとするだけで気づくことがあり、自己解決につながることも多い。

6.6 コンテキスト

コンテキストとは周りの状況や背景のこと。プログラミングのときにこのコンテキストをコードの読み書き、思考のツールとして利用する。

コードの読み書きに関しては可読性の向上、思考のツールとしてはあるものが周りのとの関係と照らし合わせてどういう状態把握することができる、等の有用性がある。

特に、現場に入ってきた新しい人は何も知らない状態のため、ローコンテキストを意識するようにする。

7章 法則

ソフトウェア開発において陥りやすい罠、アンチパターン

7.1 ブルックスの法則

開発が間に合わないと言って、後半に人を追加することは返って効率が悪くなる。あとから入ってきた人にプロジェクト特有の事柄を説明し理解させる作業、コミュニケーション量の増加、タスクの分割や諸々の確認作業の増加、などにより時間を取られることになる。
このような場合にはリスケジュールで対応する。

ソフトウェア開発において陥りやすい罠のこと。アンチパターンともいう。

7.2 コンウェイの法則

自然に任せていると組織構造がアーキテクチャに反映される。
実際にはそうではなく、アーキテクチャに組織を寄せるべき。アーキテクチャは早期に作成されたものは不安定で変更されることもあるため、十分なアーキテクチャの検証を行ったあとで組織を編成すること。

組織、アーキテクチャ、プロセスは密接に関係しているため、単独の良し悪しだけで決めないことが重要。

7.3 割れた窓の法則

ソフトウェアに少しでも割れた窓、悪いコードがあると人がそれを受け入れ真似てしまい、全体的にきれいなコードでなくなる。きれいではないコードを見つけたら即座に直す。その場で直せない場合はコメントを残す等のアクションを起こす。

7.4 エントロピーの法則

コードは自然に任せると無秩序に向かい、腐ったコードになってしまう。コードの腐敗の兆候をつかみ、見過ごさないようにして迅速に対応することが大事。

アジャイルやチーム文化でコードが腐敗していくことを防ぐ。

7.5 80-10-10の法則

プログラミングには万能薬ない
ソフトウェアを開発する際、ユーザーの求めるものを80%を実現することは短時間で可能。残りの10%はとても手間と時間がかかり、もう残りの10%は実現不可能。

万能なツールは存在しないため、万能薬を目指すのではなく、専門薬を目指すようにする。適材適所でツールを選ぶことが大事。

7.6 ジョシュアツリーの法則

まずは物事や概念の名前を知ることが大事。名前を知ることで、見えなかったものが見えてくるようになる。

ユビキタス言語
チームの共通言語のユビキタス言語を用いて(作って)、コミュニケーションを円滑にする。統一的な言語を用いることで、問題が起きにくくなる。普段の会話だけでなく、コードにも適用する。

7.7 セカンドシステム症候群

ファーストバージョンをリリースした後のセカンドバージョンのソフトウェアは機能を盛りすぎてしまい使い勝手の悪いものになりがち。自制心を聞かせて、多機能主義にならないようにする。

他のソフトウェアと組み合わせて可能なものに関しては、追加しないようにする。

フィーチャークリープ
機能が多くなりすぎて収集のつかなくなる道をたどるソフトウェアをフィーチャークリープという。他のもので実現できることに関しては要望があってもNoということが大事。大きな要望がある場合には、プラグインで実現する等の本体を大きくしない道を取る。

7.8 車輪の再発明

基本的には世の中にあるものは利用していく。車輪の再発明をしてしまうパターンは、車輪を知らなかった車輪を作りたいというパターン。

コードを書く前に同じ機能が標準ライブラリにないか、オープンソースライブラリにないか、標準のプロトコルがないかを必ずチェックするようにする。また、チームミーティングなどで相談して、他の人がすでにその機能を作っていることが確認できたら、車輪の再発明を防げる。

車輪を再発明すべきときもあり、ビジネス上で核となる箇所は車輪を再発明すべき。自分たちが作りたいようにに工夫をしてつくることができる(差別化することができる)、致命的な問題が潜んでいた場合に既存のものだと自ら修正することができないが自分たちの作ったものだと自分たちで改善することができる等のメリットがある。また、学習目的で再発明をすることもあり、質の高い経験を積むことができる。

7.9 ヤクの毛刈り

やってもやっても問題が多く、終わりそうにない状態。芋づる式に問題が次々と出てくる。
このような状態になったときに「時間に見合わない」「本来の目的からずれている」という状態になったら、早々に切り上げるという判断が重要。
ただし、価値ある目的があるとき、緊急度が高い等の場合にはヤクの毛刈りを乗り越えなければいけない場合がある。

メモを取る
ヤクの毛刈りに取り組んでいる最中の問題点として、問題が脳内でオーバーフローする、コードを読んでいるうちに目的や読んでいる場所を見失ってしまうことがあるため、メモを取りながら作業する。