最初に全体的な総評
非常に響く格言が散りばめられていて納得と共感が多かった
特に
コンピューターが理解できるコードは馬鹿でも書けるが、良いプログラマーは人が理解できるコードを書く
は本当にそのとおりだなと
AIが進化してきた今、コードを生み出すことは誰でもできるようになった。
しかしAIに仕様を伝えきってAIの力のみで開発することは今後も数年は不可能だろうと思われる。
そのため、人間が理解しやすいコードを書く能力というのは今後より求められるかなと個人的には感じている。
持続可能な開発を行うこと
これこそが今後の課題であり、エンジニアがプロとして取り組むべき課題なのかなと
0から新規開発することは今のAIはかなりの精度で行えるが、いろいろな事情によって複雑になり、既存の仕様を理解していないと開発が行えないコードに対して適切な修正を加えることは現状のAIでは満足な精度が出ない
→もしかしたら今後はよりAIがinputできるプロンプトのトークン数が増えたり、簡単にそのプロジェクト用にfine tuningできるようになって解決するかもしれないが
特に気に入ったのは複雑さに対処するの章
この章だけでも読む価値があると思う
自分も勧められてこの書籍を購入したのだが、とても学びになった
この後の文章は自分が心に残った内容と自分なりの解釈のみ記載。
アートかサイエンスか
- プログラミングはアートのようなもの
- 正解はない
- しかし、デザインと同じようにガイドラインはある
- 一定のガイドラインに沿ってコードを書くことで一貫したコードを書くことができる
- すべての作業は設計段階で発生する
- 設計を詳細に書くこととコードを書くことはほとんど同じ
チェックリスト
- 必ずチェックリストを作成し、適切なタイミングで見返す
- 自分の場合はnotionかPRかissueに書く
- 最初にCI/CDを構築する
- コンパイラの警告はエラーとして扱い、0に保つ
- エラーメッセージを可視化するため→これをしていないとエラーが発生しても問題を特定しづらくなってしまう
- バラバラなコードはlinterにルールを追加することで一貫したコードを保つ
- アロー関数を使うか、関数宣言するか
- コンポーネントの記述方式、パスカルケースかキャメルケースかなど
- 迷いを減らすようにする→迷う部分はすべてlinterのルールで定義したい
複雑さに対処する
- 持続可能な開発を目指す
- 可読性のために最適化する
- コードを書くのは一回だが、読むのは複数回
- 関連するコードは近いところに置く。必要となる全ての依存関係、変数、意思決定は同時に見えるようにすべき
- コロケーションを意識
- 人間の脳のメモリは7+-2
- 目に見えるものが全て
- グローバル変数や副作用がバグを生むのはこのため
- 関連するコードは近いところにおく
- 「どんな無能でもコンピューターが理解できるコードを書ける。良いプログラマーは人間が理解できるコードを書く」
バーティカルスライス
- ソフトウェアが動くかどうかをなるべく早く確かめる
- レイヤードアーキテクチャを使用することでバーティカルスライスを実現できる
- パーティカルスライスとは水平レイヤーをまたぐ一つの動く機能としての実装のこと
- 動くスケルトン、つまりウォーキングスケルトンを早期に実装する
- TDDとかDDDとか
- ドキュメントには判断の結果よりもなぜそう判断したかを記述すべき
- ARDとか使うといいかもしれない
- バーティカルスライスの目的はシステムが動くことをデモすること
- 最小の機能を作成し、システムが動く状態にすること
- ゴールは早くコードを書くことではなく、ソフトウェアを持続可能にすること
- データベースの振る舞いをするフェイクオブジェクト(モック的なもの)を作る
カプセル化
- 言語によってはnull許容参照型がある
- 重要なのはオブジェクトが決して無効な状態にならないことを保証できるようにすべき
- 呼び出し側で防御的なコードを書くことをなるべく避ける
三角測量
-
メンバーが貢献できるようになるまでに最低三ヶ月かかる
-
レガシーコードの問題
- コードベースを学ぶのに時間がかかる
- 変更が困難
- 依存関係が多すぎるため理解に時間がかる
-
「ソフトウェア設計のゴールは人の心に納められる塊やスライスを作ること」
-
数値表現は昇順に書く
- min ≤ max
-
どこまでテストを書けば十分かは難しいので、とりあえずここは意識しなくて良い
分解
- 欠陥を修正し機能を追加するにつれて複雑度が上がり、コードが腐敗化していく→いわゆる「茹でガエル」
- しきい値を7と定める
- コード行数が一つのメトリクスになるので、lintでwarningを出すのも一つの手か
- 長いメソッドを書かずに、コードブロックは小さくする
- メソッドは24行以内にする
- 一つのコードの中で7を超える処理をしてはいけない
凝縮
「同じ速度で変化するものは一緒にする。違う速度で変化するものは分ける」
-
ガード節をうまく使う
- ガード節を用いて早期リターンをすることでコードの見通しが良くなる
-
検証せずにパースする
-
validateメソッドは条件を満たさないときはnullを返す
-
条件をすべて満たしたときだけオブジェクトを返す
-
今はzodを使えば簡単に書けるかも
-
フラクタルアーキテクチャという考え
-
目に見えるものが全て
-
コードベースはいきなり悪化するものではなく、徐々に悪化するもの
API設計
アフォーダンス
とは、APIによってカプセル化されたコードとやり取りできることアフォード=支える、持ち上げる型をつけてオブジェクトに対してどのような操作が可能化を伝える開発手法をドット駆動開発と呼ぶ
- apiが神クラスにならないようにやるべきでないことを伝えるようにする
- apiでは不正な状態をそもそも表せないようにしましょう
- コメントを書くよりも分かりやすい名前のコードを書く
コマンドクエリ分離
副作用のあるメソッドでデータを返すようにしてはいけない例えばcreateメソッドがidを返してはいけないこれをコマンドクエリ分離(CQSと呼ぶ)voidを返すなら副作用がある戻り値の型がある場合はクエリと分離したほうがコードは読みやすい
- メソッド名で伝えられることはコメントに書くな
- 型で伝えられることをメソッド名に書くな
- APIに明確な型を与えること
- メソッドにわかり易い名前をつけること
- 良いコメントを書くこと
- 自動テストとしてわかり易い例を提供すること
- Gitで分かりやすいコミットメッセージを書くこと
- 分かりやすいドキュメントを書くこと
特定の実装方法を選択した理由などをコメントに書く
コンピューターが理解できるコードは馬鹿でも書けるが、良いプログラマーは人が理解できるコードを書く
チームワーク
- 4時間毎にmergeすることを推奨している
- そうしないとコンフリクト地獄になる
コードレビュー
- コードレビューが答えるべき一番根本的な質問は
- 「自分はこれのメンテナンスを問題なく行えるか?」
- いちばん重要な基準はコードが読みやすいかどうか
観点
- コードは意図したとおりに動くか
- 意図は明確か
- 不要な重複はないか
- 既存のコードでこの問題を解決できないか
- もっとシンプルにできないか
- テストは包括的で明確か
プルリクエストの作り方
- プルリクエストをできる限り小さくする。あなたが思う以上に小さくする
- 1つのプルリクエストでは1つのことだけをする。複数のことをしたければ別のプルリクエストに分ける
- フォーマットを修正するだけのプルリクエストでなければ、フォーマットを修正しない
- 新しいふるまいにはテストを追加する
コードの増大
- フィーチャーフラグを用いることで未完成の機能でも本番に載せる
- 機能が完成したらフラグを削除する
ストラングラーパターン
- 変更をしたいなら、まずはその変更を簡単にし、それから簡単な変更を行うこと
- これをストラングラーパターンと呼ぶ
- 絞め殺し(ストラングラー)
- 絞め殺しイチジクから取られている
- 絞め殺し(ストラングラー)
- メソッドレベルからの小さな変更から始め、クラスレベルへと徐々に大きくしていく
- フィーチャーブランチを使うのではなく、フィーチャーフラグを使っていつでもmainにマージ可能にする
- そうしないとマージ地獄に陥ってしまう
ユニットテストを編集する
- リファクタリングには事前条件としてしっかりとしたテストが必要である
トラブルシューティング
-
何が起こっているかの理解に努めよ
-
いきなりバグフィックスに取り掛かるのではなく、まずは何が起こっているかを理解する
科学的手法
- 予測する
- 実験する
- 予測と結果を比較する
- 何が起こっているかを理解できるまで繰り返す
問題の理解がゴールとなる
典型的な実験はユニットテストを書くこと
とにかくコードをシンプルにすること
以下はやめたほうが良いこと
- コード内でオブジェクトグラフを生成する代わりに複雑なDIコンテナを開発する
- 純粋関数を書こうとせずに、複雑な「モックオブジェクトライブラリ」を開発する
- 頻繁にマージせずに高度な差分ツールを使う
ラバーダッキング
- 時間を管理する
- 時間を管理しないと問題にずっとハマり続ける
- 休憩中はコンピューターから離れる
- 新しい観点が見つかる
- 問題を説明しようとすると解決策が見つかることもある
実際にアヒルに話しかけることをラバーダッキングという
欠陥数の理想値は0である
-
品質を作り込む
-
**「あとで」**はやってこない
欠陥をテストとして再現する
一度起きたバグはまた起きる可能性が高いのでテストを追加する
- 直接DBに接続するテストを書いてしまうとCIがめちゃくちゃ遅くなってしまう
- テストケースごとに新しいデータベースを作成して、テスト実行後はデータベースが消えるようにする
テストでバグを再現する
二分法を使う
- 問題を発見する
- 再現する方法を見つける
- コードの半分を削除する
- 問題がそのままなら手順2から繰り返す。問題が消えたら削除したコードを戻して逆のコードを削除する
- 問題を再現するコードが理解できる小さなサイズまで絞り込めたら完了する
最小の問題の例を作ること
Gitを使うのも有効
関心ごとの分離
- ネストされた合成とやり取りしているコードは良くない
例えばコマンドクエリ分離の原則に反してクエリの中に副作用が含まれているようなコードを書いてしまうと、複雑になる
参照透過性
同じ入力の場合は必ず同じ出力が返ってこなければならない
- 関数の中身は見なくても良い。その結果だけを追跡すれば良いようにする
横断的関心事
- ロギング
- パフォーマンス監視
- 監査
- 計測
- 実装
- キャッシング
- フォールトトレランス
- セキュリティ
ロギング
純粋でない操作を記録し、それ以上は記録しない
パフォーマンス
- 今やパフォーマンスは最大の関心事ではない
- 正確であることが何よりも重要
セキュリティ
脅威分析モデルのSTRIDEを使う
STRIDEは頭文字
- なりすまし(Spoofing)
- 改ざん(Tampering)
- SQLインジェクションなど
- 否認(Repudiation)
- 支払いの済んだ商品の受領を拒むなど
- 情報漏洩(Information disclosure)
- 中間者攻撃やSQLインジェクションなど
- サービス妨害(Denial of service)
- Dos攻撃
- 権限昇格(Elevation of privilege)
用語
- Arrange-Act-Assert
このパターンに従ってテストを書く
最後に
本は読んで学習した気になって終わりということが多いので、こうやって記事を書いて自分の中に落とし込み、さらに仕事という実践で使用して、応用して使うことで初めて理解という境地まで達成することができると感じる。
そのため、この本の内容をこれから実践して実際に使用していくことを特に意識していきたい。