達人プログラマー
哲学
達人プログラマーは、眼の前の問題を考えるだけでなく、常にその問題をより大きなコンテキストで捉え、ものごとの大局を見据えようとする。
あなたの人生
あなたは自分自身の人生を生きている。あなたには現状を打開する力がある。 この業界には驚くほど多くの機会が横たわっている。積極的な行動に出て、機会をつかみ取る。
気質
開発者には主体性が必要。
- アーリーアダプター
- 試すことが生きがい
- 研究好き
- 疑問を感じやすい
- 批判的
- 額面どおりに受け取らない
- 現実的
- 本質の理解に務める
- 何でも屋
謙虚である
自らの無知や過ちを率直に認める。 すべての問題をベンダー、プログラミング言語、管理、同僚のせいにしてはいけない。 弁解の代わりに対策を提案する。
自負と偏見
他人のコードに対して敬意を払う。
我々は所有することによって誇りを持つべき。 「私はこれを記述した。そして、この仕事についてのすべての責任は私にある」と。 成果物には、品質の証明としてあなたの署名が入っているべき。
ユーザを喜ばせる
開発者としての我々の目標は、「ユーザを喜ばせる」こと。
ポートフォリオ
知識と経験は、最も重要な資産。
- 定期的に投資する
- 継続によって大きなものとなる
- 多様化する
- まず現在使っている技術の詳細を知る
- 世界はどんどん変化する
- リスクを管理する
- 技術という卵をすべて1つのかごの中に入れない
- 安値で買い、高値で売る
- 見直しと再配分をする
ポートフォリを充実させる活動
- 毎年少なくとも言語を1つ学習する
- 月に1冊のペースで技術書を読む
- 技術書以外の書籍を読む
- 講習を受講する
- 近場のユーザグループに参加する
- 孤独はあなたの経歴によって避けるべき
- 異なった環境に慣れ親しんでみる
- Winのみで作業してきたのであれば、Linux環境に親しんでみる
- 最先端にとどまり続ける
- ニュースやオンライン投稿に目を通す
継続は力なり
数多くの小さな進歩を継続して積み重ねる。 スキルを日々磨き、新たなツールとレパートリーを増やしていく。
開発手法
チーム
達人のチームは、10〜12人の小規模な構成になっている。 コードをエンドツーエンドで構築でき、インクリメンタルかつイテレーティブに開発できるチームを構成する。 チームメンバーに質問を投げかけると、すぐに答えが返ってくるようになっているべき。 品質は、チームメンバー全員が個々に貢献することによってのみ達成できる。
コンウェイの法則
開発しているプロダクトは、チームや組織の社会構造とコミュニケーション経路によって影響を受ける。
ソフトウェアのエントロピー
ソフトウェアも時間とともに無秩序になっていく。特に、プロジェクトの文化がソフトウェアの腐敗に大きな影響を与える。 ネガティブな考えには伝染性がある。放置は他のどのような要因よりも腐敗を「加速」させる。
十分によいソフトウェア
不可能なスケジュールを約束したり、納期に間に合わせるために基本的な技術面をおろそかにするのは、プロフェッショナルとしてあり得ない。
見積もり
直感的にものごとの大きさを判断する。 見積もりを始める前に必ず問題領域について考える。 タスク(作業)ごとに「楽観的時間」、「最確時間」、「悲観的時間」の3つの時間を見積もる。
万能な方策は存在しない
あらゆる方法論の良いところを抜き出し、利用できるように適合させる。 どんな状況にも適用できる万能の方策など存在しないため、普及している手法を1つ導入するだけでは済まない。
あるべき方法
ソフトウェアを構築する唯一の方法はインクリメンタルに進めていくこと。 エンドツーエンドの小さな機能を構築し、そこから作業を進めながら問題について学習していく。
アジャイルソフトウェア開発宣言
- プロセスやツールよりも個人と対話を
- 包括的なドキュメントよりも動くソフトウェアを
- 契約交渉よりも顧客との協調を
- 計画に従うことよりも変化への対応を
要件定義
落とし穴
要求とは通常、さまざまな仮定、誤解、政策の奥深くに埋められている。 自らが欲しているものを正確に認識している人などいない。 境界条件を探し出し、相談者に尋ねる。 要求はフィードバックループの中で学んでいく。 ユーザとともに働き、ユーザのように考える。 優れた要求は抽象的な記述からできあがっている。 要求とは「ニーズ」。 失敗プロジェクトの多くは、スコープの膨張が原因。 用語集を管理する。 プロジェクトの参加者は、全員、整合性を保証するためにこの用語集を使うべき。
不可能なパズルを解決する
ある制約は「絶対的なもの」であり、他のものは「単なる先入観」。
曳光弾
要求の段階で、システムの最終形態となるイメージを目に見えるかたちで、何度も提示する。 最大のリスクを洗い出し、開発の優先順位を付ける。 コードは最小限だが、最終的には商用で利用する。
目的
- アプリケーション全体がどのように連携するのかを知る
- アーキテクチャ上の骨格を開発者に提示する
メリット
- テスト用プラットフォームになる
- 大々的な統合テストを実施するというビッグバン型の開発ではなく、毎日テストを実施できるようになる
- 進捗が明確になる
- ユースケース単体で作業に取り組める
プロトタイプ
曳光弾の前に行う偵察、諜報活動。コードは破棄する。 コードではなく、学んだ教訓に勝ちがある。
設計
よい設計の本質
ETC原則 … Easier To Change(変更をしやすくする)
世の中の設計原則は、ETC原則を特殊化したもの。 ETCは「ルール」ではなく「価値」。 常に「簡単に変更できる」という究極の選択肢を採用する。 鍵は「疎結合」と「高凝集」。
DRY原則
DRY原則 … Don’t Repeat Yourself(繰り返しを避ける)
DRY原則は知恵や意図の二重化についての原則であり、 コードの二重化すべてが知識の二重化というわけではない。 あなたが行うべきことは、既にあるものを簡単に見つけ出して再利用できるようにして、 同じものを何度も作成しないような環境を構築すること。 コードの二重化は、構造に問題がある証拠。
開発者間で発生する二重化
さまざまなところで必要となり、明確に責務を分類できない機能やデータは、何度も実装されてしまう。 開発者間での活発かつ頻繁なコミュニケーションが奨励される。
直交性
直交性とは、独立性であり、結合度を表す。 片方を変更しても他方に影響を与えない場合、それらは直行している。 直行していないシステムは本質的に変更や制御が難しくなる。 自己完結したコンポーネント(「凝集度」の高いコンポーネント)を設計するべき。
メリット
- 変更が局所化され、開発期間とテスト期間が短縮される
- 再利用が促進される
- コンポーネントの組み合わせが容易になり、最小努力で多くの機能を手に入れられる
結合度の最小化
不必要な情報は他のモジュールに公開せず、また、他のモジュールの実装を当てにしない記述を心がける。 オブジェクトの状態を変更する必要があるのであれば、それをオブジェクト自身に行わせる。
あるモジュールがデータ構造を露出させる時、そのデータ構造を使用するすべてのコードが該当モジュールの実装と結合する。 そのため、オブジェクトの属性を読み書きする際には、可能な限りアクセッサー関数を使用する。
グローバル変数を避ける
グローバル変数の使用は、データを共有しているコンポーネント同士を結びつける。
テスト
直交性を念頭に置いた設計、実装されたシステムは、テストが簡単になる。 ユニットテストの記述自体が、直交性の興味深いテストになる。
並行性
- 並行処理
- 同時に実行されているように振る舞うこと
- ソフトウェアのメカニズム
- 並列処理
- 本当に同時に実行されていること
- ハードウェアの関心事
時間的な結合を破壊する
アーキテクチャの設計に取りかかったり、プログラムを書いたりする際には、ものごとは線形になりがち。 こういったアプローチでは柔軟性、現実性ともに乏しいものとなる。
アーキテクチャ
アーキテクチャーは移り変わる。流行を追い求めないようにする。 できることは変更を容易にすることだけ。抽象化レイヤーで隠蔽する。
コーディング
プログラムを長期間にわかって正確かつ生産的なものに保つには、熟考や熟慮を要する意思決定が常に必要。 達人プログラマーは自分自身のコードを含めたすべてのコードに対して常に批判的な目を向ける。 テストの主な利点は、テストについて考え、テストを作り出している時に生み出される。
契約による設計(DbC)
「コンポーネント間で”契約”を交わし、その契約を守る限りバグは起きにくい」という考え方。
用語 | 役割 | 例(sqrt(x) ) |
責務 |
---|---|---|---|
前提条件 | 呼び出し側が守るべき条件 | x >= 0 |
呼び出し側の責務 |
後続条件 | 実装側が保証する結果 | result^2 == x |
実装側の責務 |
不変条件 | オブジェクトやデータ構造が常に満たすべき性質 | balance >= 0 |
実装側の責務 |
function assert(cond: unknown, msg: string): asserts cond {
if (!cond) throw new Error(`Contract violated: ${msg}`);
}
class BankAccount {
private _bal: number;
constructor(initial = 0) {
// 前提条件チェック
assert(initial >= 0, "initial ≥ 0");
this._bal = initial;
this.inv();
}
get balance() { return this._bal; }
withdraw(amount: number) {
// 前提条件チェック
assert(amount > 0, "withdraw > 0");
assert(this._bal >= amount, "sufficient funds");
const before = this._bal;
this._bal -= amount;
// 後続条件チェック
assert(this._bal === before - amount, "post: bal decreased");
this.inv();
}
// 不変条件チェック
private inv() { assert(this._bal >= 0, "balance non-negative"); }
}
インヘリタンス(相続)税
継承を使わない。
問題点
- コード共有のために使う
- 結合を生む
- 型構築のために使う
- 複雑さが増す
優れた代替
以下の代替によって、継承を使わなくて済むようになる。
- 委譲
- 機能の追加ができる
- has-a は is-a に勝る
- has-a … A has a B AはBをプロパティとして持つ
- is-a … AはBを継承する
- インターフェースとプロトコル
- 型情報を共有できる
- ポリモーフィズムを実現できる
- mixin と trait
- 手段の共有ができる
変換のプログラミング
ある形式のデータや構造を別の形式に変換する処理を軸としたプログラミング手法。
const names = users.map(user => user.name);
const upper = text.toUpperCase();
const ids = data.filter(d => d.active).map(d => d.id);
メリット
- 副作用が少ない(元データを破壊しない)
- テストしやすい(入力と出力が明確)
- 可読性が高い(意図が明確)
- 再利用性・合成がしやすい(小さな関数の組み合わせで大きな処理)
- 非同期処理などとの相性が良い
分離
結合は変更の敵。 結合によって、離れた場所にある2つのものごとの整合性を常に取る必要が生み出され、 一方を変更した際に必ずもう一方も変更しなければならなくなる。
TDA原則(Tell, Don’t Ask)
「教えるな、命令せよ」という設計原則。
悪い例
if (account.getBalance() >= amount) {
account.withdraw(amount);
}
良い例
account.withdrawIfPossible(amount);
キャッチ&リリースは魚釣りだけに
早めにクラッシュさせる。多くの場合、プログラムのクラッシュは最も正しい行いとなる。
達人のコード利点
- エラーハンドリングがコードに埋もれない
- コードの結合度を下げる
凡人のコード
try {
addScoreToBoard(score);
} catch (error) {
if (error instanceof InvalidScoreError) {
console.error("Can't add invalid score. Exiting");
throw error;
} else if (error instanceof BoardServerDownError) {
console.error("Can't add score: board is down. Exiting");
throw error;
} else if (error instanceof StaleTransactionError) {
console.error("Can't add score: stale transaction. Exiting");
throw error;
} else {
throw error;
}
}
達人のコード
addScoreToBoard(score);
アルゴリズムのスピード
単純なループを記述している場合であっても、それがO(n)
アルゴリズムであるということを意識する。
アルゴリズムのオーダーを見積もること。
O 記法 |
概要 |
---|---|
O(1) |
定数(配列の要素へのアクセス、単純なステートメント) |
O(log n) |
対数(バイナリサーチ)対数の底はあまり関係ない |
O(n) |
線形(シーケンシャルサーチ) |
O(n log n) |
線形対数(クイックソート、ヒープソート)線形よりも悪いが、最悪というわけでもない |
O(n^2) |
2乗(選択ソートや挿入ソート) |
O(n^3) |
3乗(2つの行列 n x n の積) |
O(C^n) |
指数(巡回セールスマン問題、集合分割) |
最善は常に最善ではない
アルゴリズムの改良は、それが本当にボトルネックになっていることを確認してから行うべき。
コードのためのテスト
テストは、コーディングの指針を与えてくれる極めて重要なフィードバック。 メソッドのテスト記述を考えることで、ユーザという観点に立ち、外部からメソッドを客観的に捉えられるようになる。
テスト文化
テストコードは本番のコードと同じくらい慎重に扱う。 結合を低く抑え、クリーンかつ堅牢にしておく。 テストがプログラミングの一部であることを忘れない。
達人のスターターキット
早めにテスト、何度もテスト、自動でテスト。 優れたプロジェクトでは「成果物のコードよりも多くのテストコード」が存在する。 コードのカバレッジではなく、状態のカバレッジをテストする。 バグが既存のテストの網の目をくぐり抜けたのであれば、次回はそれを捕まえることのできる新たなテストを追加する。
テスト駆動コーディング
とにかくTDDを実践する。
テストについて考えることで、
- コードの結合度が低下する(データベース接続を引き渡す等)
- 柔軟性が向上する(テスト対象のフィールド名をパラメータにする等)
契約に対するテスト
契約が遵守されていることを確認するテストケースを記述する。
プロパティベースのテスト
このテストでは、テスト時にランダムな値を生成する。 このテストでは、不変性と契約という観点からコードについて考えさせてくれる。
リファクタリング
いつリファクタリングを行うべきか?
「おかしなもの」に遭遇した時。
リファクタリングを避ける言い訳として、納期というプレッシャーがよく用いられる。 そういった理由でリファクタリングをやめてはいけない。 今リファクタリングすることをやめれば、将来問題が発生した際、 今以上に多くの依存関係を考慮しながら問題修復をするために大量の時間投資が必要になる。
早めにリファクタリングすること、そしてこまめにリファクタリングすること。
どのようにリファクタリングするか?
- リファクタリングと機能の追加を同時に行わない
- テストを用意する
- 小さな単位に作業をまとめ、慎重に進める
偶発的プログラミング
その場しのぎでコードが構築されてしまうこと。または、本来意図しない形で「たまたま動いてしまうコード」を書いてしまうこと。 「とにかく今動いているんだから触らないほうがいい」と、簡単にこういった考え方に陥ってしまう。
暗黙の仮定
すべての段階で、人々は心の中に多くの仮定を置いて作業を進めている。 しっかりとした事実に基づいていない仮定は、すべてのプロジェクトにとって有害なものとなる。
ストループ効果
単語は最優先で処理される。 何かに名前を付ける場合、常に自らの意図を明確にする方法を探し求める。
リソースのバランス方法
ファイル参照をどこかに保持しておくのではなく、パラメータとして引き渡すようにする。
設定
変更する必要があると分かっているデータは、コードの外に出す。 外部に出した設定は、APIの裏側などに隠す。 設定の表現という詳細とコードを分離する。
セキュリティの基本原則
- 攻撃界面を最小化する
- 複雑なコードは攻撃ベクターにつながる
- シンプルで小さなコードが優れている
- 出力データは攻撃ベクターとなる
- むやみに情報を提供してはいけない
- デバッグ情報は攻撃ベクターとなる
- デバッギングを容易にするための情報は、ハッキングも容易にする
- 複雑なコードは攻撃ベクターにつながる
- 最小権限にする
- デフォルトをセキュアなものにする
- 機密データを暗号化する
- セキュリティアップデートを適用する
ツール
常に道具を増やすことを心がける。何をするにも常によりよい方法を探すように心がける。
- シェル
- 慣れ親しむことで生産性が向上する
- パワーディット
- 繰り返し作業に「もっとよい方法があるはずた」と考える
- オートリピートを使わない
- マウス・トラックパッドを使わない
- バージョン管理
- すべてのものがバージョン管理されているようにする
- デバッグ
- 常に問題の原因の根を見つけるように努力する
- 緻密な観察を心がける
- デバッグの戦略
- バグを再現する
- テストを失敗される
- テスト作成によってソリューションが見えてくる
- アプリがライブラリ・APIを正しく呼び出していないと考えたほうが一般的に正しい
- バグがOSやサードパーティ製品内に内在する可能性もあるが、 そういった考えを最初に持つべきではない
- テキスト操作言語
- データをプレーンテキストで保存し、これらの言語を使用して操作する