達人プログラマー 熟達に向けたあなたの旅 (第2版) のメモ

とても良い本だった。

序文

pragmatic はラテン語 pragmaticus に由来する。これは「実務上の熟達した」という意味。 それ自体はギリシャ語の「行うに適していること」を表す単語に由来する。

第1版が出てから20年が経過したので、古い技術と思想を書き換えて第2版にアップデートした。

大規模なプロジェクトでも、個人の技術とか情熱とかも大事。 自分の能力を少しずつ改善し進歩していくことが大事。

第1章 達人の哲学 A Pragmatic Philosophy

あなたの人生 「給料が低い」「テクノロジーが古い」「仕事が退屈」とか不満を抱いているプログラマーは多い。でも、それを変化させるための努力は誰もができるはず。バスの中で勉強したり、条件の合う会社を探したり・・・ここはそれができる業界なので頑張って志を持とう。

猫がソースコードを食べちゃった 達人は、プロとして責任を取る。失敗があれば、誤りや無知は認める。いい加減な言い訳をせず可能な限り対処して対策を用意する。そうしてチームメンバーの信頼関係が作られる。信頼関係がなければ良い仕事はできない。筋の通った主張になっているか自分の心と対話しよう。

ソフトウェアのエントロピー ソフトウェア開発も時間と共に無秩序になる。割れた窓(悪い設計、誤った意思決定、質の低いコード)は修復するべき。さもないとエントロピーが増大してソフトウェアは破綻する。

石のスープとゆでガエル チームメンバーが協力してプロジェクトに貢献すれば良いものができるが、今忙しいからとリソースを出し渋ることも多い。そうならないためには、理にかなった要求をすること。そして、未来を想像できるような良いものをアウトプットすること。そうすると、渋っていた人も興味を持って、参加したくなる。逆の立場なら、興味に引き摺られて大きな構想を忘れてしまうことがないように注意。

十分に良い(good-enough)ソフトウェア 必ずバグはある。しかしユーザやプログラマー自身を満足させることはできる。そこには技術的な改善とユーザ要求のトレードオフがある。途中で手を止めることも必要。完璧なコードは存在しない。

あなたの知識のポートフォリオ プログラミングの知識は新しい技術、言語、環境の登場により陳腐化していく。プログラマーが持っている知識をポートフォリオとして管理していくと良い。これは金融ポートフォリオの考え方と似ている。定期的に、分散させて、ハイリスクとローリスクバランス良く投資していくべき。また、利益を最大にするために安く買い、高く売ることに注意し、時にはコストの配分を見直しするべき。より具体的な提案は下記の通り。

  • 年に1つの言語を学習する

  • 月に1冊は技術書を読む

  • 技術書以外の本を読む

  • 勉強会や講習をうける

  • ユーザーグループに参加する

  • 環境を変えてみる(OS やエディタを変えてみる)

  • 最先端のニュースや記事に目を通す

一つ注意点として、取り入れた情報を批判的にみるということを忘れないように。メディアには偏見や間違った知識が溢れているので。

伝達しよう! プログラマーは会議をしたり、聞き取りをしたり、討論をしたり、ドキュメントを書いたり、意思を伝えることに日々時間を割いている。母国語で意思を伝える時、そこにプログラミングの技術を適用する(例えば DRY, ETC などを当てはめてみる)のは、プログラミングの能力を鍛えるユニークな方法と言える。いくつかのアイデアを列挙する。

  • 聞き手のことを知る(ニーズ、興味、能力を把握する)

  • 言いたいことを知る(自分が伝えたいことを整理してから文章にする)

  • タイミングを選ぶ(聞き手が聞きたいと感じるタイミングで話しかける)

  • スタイルを選ぶ(聞き手が事実のみを知りたいタイプか、それとも詳細を知りたいタイプかに合わせる)

  • 見栄えを良くする(デザインテンプレートや、スペルチェック機能などを使おう)

  • 聞き手を巻き込む(ドキュメントの草稿をみてもらってフィードバックを受けよう)

  • 聞き手になる(質問したり対話しよう)

  • 相手の立場になる(メール等は必ず返事をしよう)

  • ドキュメントとコードをまとめる(コードの中にトレードオフや意思決定などを書き込もう)

第2章 達人のアプローチ A Pragmatic Approach

良い設計の本質 「良い設計は、悪い設計よりも変更しやすい」ということを意味する ETC原則 (Easier To Change) が大原則。ETC 原則は、意思決定を助けてくれる。バグ修正、機能追加などで自分がプログラムを書いた時「これによって、システムが変更しやすくなっただろうか?」と自問すると良い。「変更しやすい」という言葉は、どのような変更が行われるか想像できるという前提のもとに立っている。もし、全く想像ができない場合には「書いたコードを簡単に交換できるようにする」という方針をとると良い。git のコミットや pull request を作るときに ETC? みたいなメッセージを出すのも良いだろう。

DRY 原則 - 二重化の過ち コードは変化することを迫られる。ビジネスロジックの変更、チューニングによるアルゴリズムの差し替えなど。プログラマーは、リリースされる前であっても、知識を再編成し、コードにフィードバックする。変更が多いからこそ、知識を二重化してしまった時、手間が増えやすい。もしも片方の知識だけ更新して、もう一方がそのままになっていると矛盾を生じる。これを避けるための原則が DRY原則 (Don't Repeat Yourself) である。これは、同じ知識を2箇所以上に記述するなということを主張している。単にソースコードをコピー&ペーストするなと言っているのではない。知識の二重化を回避することが重要なので、偶然コードが一致した箇所に対して DRY 原則を適用するべきではない。

DRY はコードに限定した話ではない。例えばプログラムコメントにも DRY は適用するべきである。コードに書いてあることをコメントするのは、知識の二重化に他ならない。他の例としては、データ構造にも DRY を適用するべきである。計算によって求めることができる属性をインスタンス変数に入れるのは、知識の二重化である。パフォーマンスチューニングのために、やむなく違反する場合、その影響が局所的になるように注意深く実装しなければならない。アクセサを使えば将来の変更しやすさを維持することができる。

内部 API を提供するプログラムを書いた時、それを利用するチームは、内部 API のインターフェースを知る必要がある。多くの場合はツールを使ってドキュメントを自動生成し、二重化の手間を和らげる。API を利用するためのクライアントはセントラルリポジトリに格納するのが理想。さもないと API を使いたいサービスごとにクライアントを実装することになり、そこでサービスを横断した知識の二重化、三重化が発生するため。それが外部にも公開している API なら OpenAPI (昔は swagger という名前だったらしい) のようなフォーマットに従って文書化すると良い。

データスキーマとコードの間に存在する二重化も避けることができないが、イントロスペクションと呼ばれる機能を使ってそれらのコード生成の大部分を自動化できる。他のシンプルな手法としては、データスキーマを気にせず、キー/バリュー形式のデータ構造に格納する方式がある。これにはセキュリティ上の問題があるので、必要なデータが必要な形式で保持されていることを検証するデータ駆動型の検証レイヤーを設けるのが良い。

最も取り扱いが難しい二重化は、プロジェクト内のさまざまな開発者の間で発生する二重化。機能がうっかり二重化されて検出されず、のちのメンテナンス時に問題を引き起こした、という事例は多い。開発者間の頻繁なコミュニケーションが効果的。slack チャンネルを活用する。プロジェクトの「司書」を定めておくと良い。仲間のコードを盗み見流のではなく、互いに学び合うのが良い。

直交性 平面状の2つの線分について、それらが垂直に交わることを直交していると言う。プログラミングにおいては、2つ以上のものことが、一方を変更しても他方に影響を与えない場合、それらは直交していると言う。例えば、データベースとUIが直交しているプログラムは良いプログラムで変更しやすい。直交していないものの例としては、ヘリコプターの操縦桿がある。これらのハンドルやペダルはお互い直交していないため、ただ高度を下げたいだけだったとしても複雑な操作が必要となる。このように直交していないシステムは本質的に制御や変更が難しくなる。関係のないもの同士の影響を排除することは極めて重要と言える。自己完結したコンポーネント(Yourdon, Constantine が Structured Design で提唱した凝集度の高いコンポーネント)を設計するべき。

直交性を重視したシステムを作ることで多くのメリットがある。変更の影響が減るので開発期間とテスト期間が短縮できる。作り終えたら忘れてしまって良い。コードの再利用も促進できる。直交しているコンポーネントは他の影響を考えずに自由に組み合わせることができるので単純に計算すると M 機能のコンポーネントと N 機能のコンポーネントを組み合わせれば M * N の機能を提供できる。コンポーネントに問題があったとしても、システムの他の部分へ影響しないので切り離し、取り替えができる。特定のベンダーに強く依存することを避けられる。

直交性のあるシステムは、独立したモジュールの組み合わせで作られることが多い。またモジュールには階層を定める。上の階層は、下の階層の機能だけを使ってプログラムを実現するようにすれば、依存関係を整理することができる。設計に直交性があるかどうか確認するには、コンポーネントの要求が大きく変わった時に、どれだけ多くのモジュールに影響が及ぶのかを考える。システムに直交性があるなら、その答えは1つになるはずだ。もちろん、現実的にはそうでないことが多いが。ツールキットやライブラリを導入する時にも、システムの直交性が維持できるかと言うことに注意しなければならない。

  • 恥ずかしがりなコードを書く(不必要な情報を公開しない。他のモジュールの実装をあてにしない)

  • グローバル変数を使わない

  • 類似機能を避ける

と言うことに注意してコードを書いていくと良い。ユニットテストを書くとき、そこで直交性を推し量ることもできる。なぜならテストを動かすためにどのプログラムをインポートする必要があるのか、と言うことがわかるからだ。バグを修正する時にも直交性に対するある種のテストとなる。バグを修正するのにたくさんのコードを書き換えなければならないとしたら、それは直交性を持っていないと言うことだ。そういったものを月次で分析するのも興味深い。

可逆性 可逆性のない決定をするのは、なるべく避けたい。例えばデータベースやアーキテクチャを決定して話を進めた場合に起きることを考えよう。そうしてプロジェクトの8割が完成した時に、データベースの性能が問題になったとする。しかしデータベースを固定するという決定をしたために、コードの中にデータベースに依存した実装があり、乗り換えられない。解決策の選択肢が狭まる。

そうならないように本書で紹介する技術を使用していけば後戻りできない意思決定から解放される。たとえば、ウェブアプリケーションをモバイルアプリに置き換えたいという要求が出てきたときであっても、正しく分離がなされていれば本質的にはビューを取り替えるだけで良いはずだ。

アーキテクチャにおけるベストプラクティスはたくさん発生している。いろいろな流行があって、変化が激しい。それに備えて準備しておくことなどできない。できるのは「変更しやすくする」ということだけ。

曳光弾 曳光弾(tracer bullets)とは、銃の軌跡が見えるようにした弾丸のこと。着弾した場所を観察して、照準を調整するのに使う。プログラミングにも同じ考え方が適用できる。今までに作ったことがないものを作る時、動き回る目標に対するフィードバックを得るのに使う。ユーザの曖昧な要求や、不慣れなアルゴリズム、開発手法・言語・ライブラリ等、わからないことが多い時にこの考えは役立つ。

曳光弾は使い捨てではない。エラーチェック、構造化、ドキュメンテーション、自動生成したコードなど全て残す。これらは完全に動作するものではないかもしれないが、最終的には肉付けしていって使えるものにする。曳光弾を使った開発では、古典的な開発手法と比べると下記の利点がある。

  • 早いうちからユーザーに成果物を提示できる

  • 作っているもののビジョンが、開発者に見えやすくなる

  • テスト用のプラットフォームができる

  • デモンストレーションができる

  • 進捗がわかりやすくなる

曳光弾の着弾点は目標点ではない。そのため、何度も発射して狙い直す必要があるということに注意。プロトタイピングと似ているが、プロトタイピングは成果物を一回捨て去って再構築するという点が違う。プロトタイピングの目的は、アプリケーションのある側面を探究するためのものである。一方、曳光弾の目的は、ユーザに早い段階でアプリケーションの振る舞いを提示することと、アーキテクチャの骨格を開発者に提示することの二つである。

プロトタイピングが力を発揮する例として、さまざまな大きさの積荷をコンテナに詰め込む問題がある時、その最適な答えを探すアプリケーションを考えてみよう。このアプリケーションがどのような GUI を持つべきかわからないので、内部的なアルゴリズムは一旦無視して、UI だけのサンプルを作る必要があるだろう。また、コンテナに積荷を詰め込むアルゴリズムについても、考えやすい高級言語を使って試作し、最適なアルゴリズムを見つけ出す必要があるだろう。そうして、GUIとアルゴリズムに対する知見が十分に貯まったときに、本番環境へ向けてコードを作り直す。これはプロトタイピングが適している。

プロトタイプとポストイット 自動車メーカーでは、デザインのことなる新車をプロトタイプとして作成している。見た目のテストはもちろん、空気抵抗や構造的特性をテストするためにも使われる。ソフトウェアのプロトタイプも同じようなもので、大きなコストをかけずに、リスクを分析するために作る。プロトタイプは必ずしもプログラミングによって作られる必要はなく、ポストイットを使ったプロトタイプもあり得る。ホワイトボードに絵を描いたり、ペイントプログラムを使っても良い。

プロトタイプによる調査が適しているのは、過去に試されたことのないケースや、実証されてないケースなど、リスクの伴うケース。プロトタイプの核心は得られたコードではなく学び。プロトタイプはさまざまな詳細を無視することができる。すべての機能を実装する必要はなく、データはダミーデータでもよい。また、エラーチェックなどもする必要はない。プロトタイプには高水準で使いやすいスクリプティング言語 Python や Ruby が適している。

アーキテクチャのプロトタイピングではホワイトボードにポストイットを貼り付けるだけでも良い。コンポーネントの責務が適切に分割されていて、協調できるようになっているか、二重化されてないか、などコードを書かなくても検討できることはたくさんある。

プロトタイプは破棄するということは関係者間で共通認識である必要がある。どんなに見栄えがよくても、模型をそのまま製品に使うことはできない。曳光弾によるアプローチなら、コードをそのまま使うことができる。

専用の言語 プログラミング言語はそれぞれが特徴を持っていて、それ自体が問題解決の方向性を決めたりする。なので問題領域に DSL があるなら、それを使うと問題解決が進めやすくなることもある。RSpec, Cucumber, Phoenix(のrouter), Ansible など。内部ドメイン言語は RSpec のような言語で、これはホスト言語(ruby) の機能をそのまま利用できるというのがメリット。ただしあくまで ruby の文法に従う必要はある。外部ドメイン言語は Ansible のような言語で、これは何かのホストに依存しないので文法もそれ自体が決定する(実際には完全に独自文法をもっていることはまれで、何かの言語や文法を下敷きにしていることが多い)。

見積もり 一番簡単な見積もり方法は、似たような仕事を経験した人に聞いてみること。そうでない場合は問題を把握してモデルを作ること。具体的には、時間 = 何かのパラメータによって決定する数式、のような形を作る。そしてパラメータを与えると見積もりができる。闇雲に推定せずに思考のプロセスを残しておくと、見積もりが外れた時も反省して次の見積もり精度向上につなげることができる。例:100TB のデータは 1Gbps でダウンロードした時、何時間かかるか?

米軍で使われる見積もり手法として Program Evaluation and Review Technique(PERT) というものがある。これは、タスクごとに「楽観的時間」「標準時間」「悲観的時間」の3つを見積もりする。そしてそれらを集計して見積もりを出す。この方法が最善というわけではないが、不確実さを盛り込んだ見積もりとして役に立つかもしれない。

他の見積もり方法としては、ちいさなイテレーションを実際に動いてみて、イテレーションが何周したら良いのか、というのを考える手法もある。

第3章 基本的なツール

プレインテキストの威力 知識を記録するのに適しているのがプレインテキスト。データが自己完結している。対照的にバイナリデータはアプリケーション依存なので知識を記録するのにふさわしくない。XML みたいに属性に言及しているプレインテキストは、アプリケーションが失われたとしても意味を持っているので陳腐化しない。unix もシステム管理に使うほとんどのデータはプレインテキストで保存している。この方針のおかげでプレインテキストの操作さえ知っていれば、検索したり、比較したりすることが簡単になっている。

貝殻(シェル)遊び GUI はわかりやすく、見た目通りのものが得られるが、CUI では目に見えないそれ以上のことができる。シェルの設定をカスタマイズして良い環境を作ろう。

パワーエディット エディターの操作に気を取られずに思考できるくらいエディターに熟達しよう。繰り返し作業を効率化できないか探して、答えを見つけよう。そして新たな手法を見つけた後はそれを体に覚えさせよう。拡張機能を使おう。自分で作ってみよう。

バージョン管理

デバッグ バグがあってもパニックにならないこと。最初にやるべきことはバグの再現。次に原因探し。検討がつかない場合二分探索が役に立つこともある。たとえばスタックトレースの二分探索をしてみる。入力データが大量にある場合にも二分探索が使える。リリースバージョンが複数あってバグが混入したバージョンがわからないときも二分探索が使える。プリントデバッグ。アヒルに話しかける。先入観は禁物。

テキスト操作言語 awk, sed, python, ruby など使っていこう。

エンジニアリング日誌 作業内容、学んだこと、アイデアの概略、計測器のデータ、ミーティング内容、ときには落書き(集中力を高める効果があるかもしれない)などを記録する。参照可能な記録として残すだけでも価値がある。アイデアを追い出すことで目の前の問題に集中できる。そして、アヒルに話しかけるのと同じ効果も得られる。思い出。できれば紙で記録するとよい。

第4章 妄想の達人 pragmatic paranoia

誰も信頼してはいけない。自分自身さえも。

契約による設計 Design by Contract(DbC) Eiffelという言語では、関数やメソッドが機能だという風に考えて、その機能の呼び出し条件(precondition)や、呼び出した後満たされている条件(postcondition)、そしてオブジェクトが常に満たすべき条件(invariant)を記述するという考え方を持っている。機能の呼び出し側と呼び出される機能は「それぞれの前提を満たすならば役割を果たす」契約を結ぶ。

この考え方が言語仕様として適用できる言語に Closure と Elixir がある。Close は :pre, :post のようなキーワードとともに事前条件(引数が満たすべき条件)と、事後条件(戻り値が満たすべき条件)を宣言できる。もしこの条件を満たしていないならエラーとともにプログラムは停止する。DbC の文脈で言うなら契約違反となる。Elixir はガード節という機能があり、関数名を宣言した直後に when (...) で引数が条件をみたしている時の振る舞いを宣言できる。これを満たしていないときはエラーとなる。

条件を満たしていない時はエラーになるので後述する「早めのクラッシュ」という別の考え方と同じ結果を得られる。言い換えると、不正状態のままプログラムが動作して、気付かぬうちにプログラムを破壊してしまうような状況を避けられる。NaN のような不正値を返すよりも簡単。

DbC をサポートしている言語は少ないが、考え方をコードに反映して、単体テストに組み込んだりはできる。

死んだプログラムは嘘をつかない ありえないことが発生して、プログラムが失敗することはよくある。防衛的なコーディングをしよう。エラーが情報を与えてくれる。例外処理で不用意にエラーを捕まえずに、上流でエラーを捕まえた方が良い。

トラッシュになるくらいなら、早めにクラッシュさせた方が良い。この考え方は Erlang や Elixir に反映されている。これらの言語ではエラーはスーパーバイザーが引き受けることになっている。スーパーバイザーはエラーに応じて、後始末をしたり、再起動したりといった事後処理をどうすべきか管理している。

表明を用いたプログラミング 起こり得ないことに関しては「表明(assertion)」を使おう。ほとんどの言語では assert が true にならなかった場合、プログラムが終了する。起こりえないことの検査によって生じるオーバーヘッドを減らすため assert は本番環境では無効化されることが多い。しかしながら現実世界ではディスク容量がいっぱいになって書き込みが失敗したり、ネットワークケーブルをネズミが齧ってしまい断線したり、想定されていない危険なことが起こりうる。こうした事態に陥った時のために assert 全体を無効化するべきでない。パフォーマンス上の問題があるなら、問題がある assert だけを無効化するべきである。

余談:デバッグのために差し込んだコードがシステムの振る舞いを変えてしまうことをハイゼンバグという。観測しようとすることで、観測対象に影響を与えてしまうことから、不確定性原理を提唱したハイゼンベルグの名前に由来している。

リソースのバランス方法 ファイルやメモリについては、そのリソースを割り当てた関数やオブジェクト自身が解放するようにしよう。当然に思えるかもしれないが、そうなっていないプログラムは多い。ruby ではファイルを開く時にブロックを使うべき。この形式を使えばファイルストリームを閉じるのを忘れることがなくなるし、他のメソッドに分解して、分かりにくくしてしまうことも避けられる。コンストラクタ・デストラクタを持っている言語はそのタイミングでリソースの取得と解放をしてもよい。例外が発生した時のリソース解放にも注意。

ヘッドライトを追い越そうとしない 少しずつ進んでいこう。予想できる範囲は狭い。遠い未来を予想するよりも、いつでも変更可能にしておくことで対処していこう。

第5章 柳に雪折れ無し Bend, or Break

分離 分離されたコードは変更しやすい。結合は弱い方がよい。関係のないモジュールやライブラリに依存するべきでない。分離のためには、参照せずに依頼する(TDA: Tell, Don't Ask) が必要。抽象階層を横断するメソッドチェーンは極力利用しないようにするべき。好ましくない例

def apply_discount(customer, order_id, discount)
  totals = customer.orders.find(order_id).get_totals()
  totals.grand_total = totals.grand_total - discount
  total.discount = discount
end

このメソッドだけで、顧客 - 注文 - 集計の3つに依存している。機能を維持しつつこれを避けるなら下記のような実装にする。

def apply_discount(customer, order_id, discount)
  customer.find_order(order_id).apply_discount(discount)
end

これの利点は、メソッドの依存が顧客 - 注文だけになること。この依存を残した理由は、顧客 - 注文の関係は現実世界で十分あり得る構造だから変更がないという考え。見方を変えると、直接関係を知っている子オブジェクトにはアクセスして良いが、子が関係している孫オブジェクトにアクセスしてはならない、という考え方。これはデメテルの法則の言い換えである。デメテルの法則を守るには、用途の見えにくいメソッドが大量に発生することになる。それが良いかどうかは微妙なところである。大量の delegate があるとしたらそれは依存しているのと対して変わらない。グローバル変数はなるべく使うべきでない。シングルトンオブジェクトはグローバル変数と同じなので使うべきでないが、グローバル変数をそのまま使うよりはましである。

実世界を扱う イベントを表現する4つの手法がある。

  1. 有限状態機械: 状態と入力による状態変化で表現するモデル

  2. Observer パターン: 観測可能なイベントにあらかじめコールバックを登録しておき、イベントが発生した時にコールバックを実行するというしくみ。

  3. Pub/Sub プロトコル: チャンネルがあり、チャンネルごとに受け取るイベントが違う。チャンネルに対してコールバックを登録するのは Observer と同じ。

  4. リアクティブプログラミング: イベントに対して react するという考え方。

変換のプログラミング プログラムは入力を受け取り、出力を返すものだということを念頭においたプログラミングがうまくいくこともある。これはオブジェクト指向プログラミングではないが、インターフェースが常にデータであるから、それぞれのメソッドは独立に動かすことができる。結果、依存性が少なく良いプログラムになる。データ指向のプログラミングはエラー処理がやや面倒というデメリットもある。

相続税 継承を使うのは避けたほうがよい。コードの再利用の観点でも、型の関係性定義の観点でもデメリットがある。コード再利用のために継承を使うと、サブクラスとスーパークラスが密結合する。継承ツリーすべてが依存性を持ってしまう点もよくない。型定義の上でも、二面性を持ったオブジェクトを表現すると多重継承が必要になってしまうため複雑化しやすい。継承の代わりになる手法はいくつかある。

Java で言うインターフェースは良い方法である。これは振る舞いを規定するもので実装を規定しない。これによって依存性を持たせることなく、互換性をもったオブジェクトを作ることができる。ポリモーフィズムを実現することが目的ならインターフェース(別の言語ではプロトコル)をつかうだけで良い。

次の方法は委譲。委譲を使えば継承と違って、必要な機能にだけアクセスすることが明確になる。継承を使っていると必要のないメソッドまで実装されてしまうが、委譲ではそういうことがない。

最後の方法はミックスイン。状況に特化したクラスを作っていくことができる。下記のような感じ。必要に応じてどちらのクラスを使うかを変えていく。

class AccountForCustomer < Account
  include AccountValidations
  include AccountCustomerValidations
end

class AccountForAdmin < Account
  include AccountValidations
  include AccountAdminValidations
end

設定 外部設定を持たせることでアプリケーションの振る舞いを変えられるようにしよう。よくあるのは静的ファイルやDBに格納すると言う方法。他には CaaS(Configuration-as-a-Service) と言う考え方もある。ファイルやデーターベースではなく API に保存するやり方。このメリットは API に認証を入れられること、UIを使って管理できること、動的に変更できることが違う。Vault とかのサービスがそれ。

第6章 並行性 concurrency

並行処理とは、複数のコードが同時に実行されているように振る舞うこと(マルチスレッド)。並列処理とは、複数のコードが同時に実行されること(マルチプロセス)。それなりの規模のシステムを作るなら並行処理は必要。

時間的な結合を破壊する 料理みたいに、一つの作業をしている間に他の作業を済ませることができる、という処理は多い。一個ずつ手順書として書き出してみると、並行性は見えてこないが、アクティビティ図を使うと並行性が見えてくる。プログラムの世界では、タスクを分解して並行処理し、結果を組み合わせるということをする。実際 Elixir コンパイラはそういう実装になっている。

共有状態は間違った状態 並行処理で状態にアクセスするときはセマフォが必要。セマフォとは、状態アクセスの前にロックして、アクセス終了時にアンロックすること。ロックしたままエラーで停止した場合にアンロックすることを忘れないように。カレントディレクトリでさえも共有状態だったりする。このように、並行処理ではおもいがけないエラーが発生しやすい。

アクターとプロセス アクターという概念を使うとメモリ共有の苦しみから解放される。アクターは仮想プロセッサー。アクターはメールボックスを持っていて、メールボックスにメッセージが届いたら、それを処理する。処理が終わったら待機状態になる。アクターは他のアクターを生成したり、メッセージを送ったりできる。Javascript では Nact というライブラリがある。Erlang ではアクターのことをプロセスと呼ぶがアクターの機能を実現している。

ホワイトボード ホワイトボードという状態共有アプローチがある。アクターやプロセスは、ホワイトボードに好きな情報を書き込む。そしてホワイトボードを自由に参照してそれぞれのタスクを行う。これが活躍する例として、住宅ローン受付サービスを考える。これは、さまざまな法律情報をホワイトボードに持っている。信用調査や土地所有権のデータなどはいつ届くかわからない。このような場面でうまく動くだろう。全体的に並列処理は思いがけないエラーを発生することが多いのでトレースIDをつけることをおすすめする。

第7章 コーディング段階 While You Are Coding

爬虫類脳からの声に耳を傾ける プログラミングしている時にも本能から何かを掴み取ろう。たとえば手が進まないとき。これは不安があるとか、何かの問題がある可能性を暗示している。たとえばぬかるみを進むように手が遅いとき。コードを書いては消しているようなとき。これも設計や構造の不適切さを暗示している。そういう時にやるべきことは作業の手を止めること。そしてしばらく休憩した後で、問題を具体化すること。アヒルに話しかけたり、紙に書き出してみたりする。それでも話が進まないときはとりあえず行動してみるのも良い。プロトタイピングするのがおすすめ。プロトタイピングは失敗するためにやるものだということを忘れないように。また逆に、プロトタイプはうまくできても捨てるべきだということを忘れないように。

偶発的プログラミング なぜ動いているかわからないままコードを拡張するのは危ない。どこでバグが発生したか全くわからなくなる。今動いてるから触らない方が良いな、という考え方もしない方が良い。実装に不備があるのに、たまたま動いているだけかもしれないから。1時間ずれているからといって安易に +1 するとか、そういう実装をすると破綻する。ある特定のコンテキストを暗黙の前提にしていたりとか、そういうコードは危ない。馴染みのない技法を使うべきでない。十分理解してから。最悪の仮定のもとでも動作するコードを書かなければいけない。

アルゴリズムのスピード 計算量を意識しよう。オーダーを使おう。興味があれば、クヌースの本を読んでみよう。

リファクタリング ソフトウェアは建築よりもガーデニングみたいなもので継続的な手入れが必要。振る舞いを変えずに洗練させるということが重要。洗練させるというのは、二重化を取り除いたり。直交性を高めたり。パフォーマンスをよくしたりといったこと。納期が近いからといってそれを延期すると、ダメージが大きくなることがある。それは人間の病気と似ている。リファクタリングと機能の追加を同時にやるべきでない。必ず事前にテストを書いて振る舞いが変わってないことを検証する。

コードのためのテスト テストはバグを見つけるために書いているわけではない。テストはコードのユーザの第一号に等しい。テストについて考えることでメソッドのより良いインターフェースを考えるきっかけになったりする。これを発展させるとテスト駆動開発と呼ばれる手法になる。ただしこれをやり過ぎてしまうと、一つの機能に常に焦点が集まるためボトムアップな設計になりがち。ボトムアップやトップダウンな設計はどちらも問題がある。ソフトウェアの全体像が決まっている前提での設計になってしまう。しかし現実はソフトウェアは変化していくものなので、ボトムアップもトップダウンも適切でない。エンドツーエンドで全体を作りながらインクリメンタルに変化させていくような設計が望ましい。テスト駆動開発は意外とうまくいかないこともある。テストをパスさせることだけに執着してしまって本質からそれてしまったりとか。例 https://ronjeffries.com/categories/sudoku/ つらい。ユニットテストで Design by Contract の考え方が当てはまる。うまくテストできるはず。irb とかで試すようなアドホックな(即席な)テストがユニットテストにフィードバックされてないなら、それもやっていくべき。隠し機能でデバッグできるとかも役立つことはあり得る。

プロパティベースのテスト 別の人にテストを書いてもらって、そのテストがパスするまでコードを書くという手法もあるが、これはあまりおすすめできない。なぜならテストを書くことによってインターフェースが洗練されるという利点を失ってしまうからだ。だから、テストはコードを書いた人が自ら書くのが良い。design by contract の考え方では契約と不変性という2つが登場した。これを合わせてプロパティと呼び、テストしてみよう。たとえばソートのテストで、全体の件数が変わらないこと。そして先頭から順に要素を取り出した時、要素 a と次の要素 b は a < b を満たしていること。これをテストする。プロパティベースのテストは、具体的なテストに使われる値を暗黙的に何パターンも与えてランダムにテストする。なのでテストが落ちた時にどういう場合に落ちたのかがわかりにくい。一度失敗したケースを別のテストケースとして書き出すと再現性のあるテストとなってデバッグしやすくなる。

実世界の外敵から身を守る セキュリティを気にせずにいることはできない。今の時代は常にネットワークに接続しているので誰から攻撃されるかわからない。原則は下のようなもの。暗号化については自分で実装しようと考えずにライブラリやサードパーティのサービスを使うべき。

  • アタックサーフェースを最小化する

  • 最小権限の原則を守る

  • デフォルトをセキュアにする

  • 機密データは暗号化する:

  • セキュリティアップデートを適用する

ものの名前 人間はものを認知するとき、その名前から情報を得ることに脳が最適化されている。たとえばストループ効果をみてみると、色や見かけ上の情報よりも名前が真っ先に飛び込んでくるのがわかる。いくつかの例をみる。

# より具体的な単語を使う
@user = authneticate(credentials)
@buyer = authneticate(credentials)
@customer = authneticate(credentials)

deductPercent(amount)
applyDiscount(discount)

# 名前の重複を避ける
Fib.fib(0)
Fib.of(0)
Fib.nth(0)

C 言語で i,j,k などのループカウンタを使うのは文化として定着しているので、あえてこれを変える必要はない。単語はコンテキストによって変わるので、チーム独自の意味を持っているのが普通。たとえば order はオンラインストアのチームでは注文を意味するが、宗教団体のソフトを作っているチームでは階級を表す。誤解のないようにプロジェクトの用語集を作っていけば、パターンランゲージとなる。物事は変わっていくので、一度決めた名前を使い続ける必要はない。より適切な名前にリネームすることも重要。

第8章 プロジェクトを始める前に Before the Project

要求の落とし穴 ユーザの要求というものがあらかじめ決まっていてそれを集めたらプログラミングが始められるというわけではない。ユーザ自身は何が必要かわかってない。一緒に仕事させてもらうとか話し合って少しずつ解き明かしていくもの。達人は、ちょっとした要求から想像を膨らませて、望ましくない使われ方をするのではないか、というのを考えていってフィードバックする。要求が洗練されていく。現実には要求を解決するものを作ったとしても受け入れられないこともある。そのため、プロトタイプを作ったりしてフィードバックを得ていくことは重要。文書化はプログラマー間で知識を共有するためのもので、ユーザにお伺いを立てるには使えない。

不可解なパズルを解決する 恐ろしい難題にぶつかった時、色々試しながら制約を知る必要がある。本当に覆すことのできない制約なのか、それとも単なる先入観なのか。制約が分析できた後、枠を取り払って考えることができるようになる。それでも解けない場合は犬の散歩に出かけたり、別のことをしてみよう。そうした時に良い考えが浮かぶということは研究で証明されている。それができない時は誰かに問題について説明して相談することで、答えが見えてくるかもしれない。

共に働く ペアプログラミングは重要。ミーティングよりも効果が高い。

アジリティーの本質 アジャイルは形容詞であってプロセスのことではない。物事に対して対処できる・変化できるということが大事。フィードバックループしていこう。

第9章 達人のプロジェクト Pragmatic Project

達人のチーム 10人くらいがちょうど良い。小さな問題をそのままにしないという文化が大事。開発だけでなく投資もする(プロセスの改善・新技術を試す・古いシステムの改善など)。チームで会話していくことは大事。コードネームつけるのも愛着が出てきて良い。チームが違うとどうしても二重化は起きやすい。素早くコミュニケーションできることが大事。一気通貫してできることが大事。役割で分割するのは望ましくない。

ココナツでは解決できない スクラムの形だけ真似しても問題を解決できない。カーゴカルトのように。試してみて効果のある物を取り入れていくしかない。現実の変化を受けて正しい方向に向けて舵を切り続けるしかない。

達人のスターターキット バージョン管理・回帰テスト・完全な自動化が大事。

ユーザを喜ばせる コードを提供することや締め切りに間に合わせることが目的ではなくて、ユーザの期待に応えることが一番大事。

自負と偏見 誇りを持って、署名しよう。責任を持とう。

あとがき Postface