エンターテイメント!!

遊戯王好きのJavaエンジニアのブログ。バーニングソウルを会得する特訓中。

【書評】Good Code, Bad Code ~持続可能な開発のためのソフトウェアエンジニア的思考

読んだ経緯

結構話題になっていたので、読んだ。
日本国内で出版されるテクノロジー系の本は、新しい視点が薄いから、国外の視点を見てみたく読もうと思った。

本へのリンク

www.shuwasystem.co.jp

まとめも

かなり内容は、端折ってる。
自分の言葉に置き換えてるから、内容と完全に一致しているわけではないので、注意

理論編

抽象化レイヤー

抽象化レイヤーを作るメリット

  • 可読性
    • 詳細まで把握する必要がなくなる
    • 概念が理解しやすい
  • モジュール性
    • 問題範囲を分割できる
    • 実装の交換を可能にし、複数シナリオへの対応を柔軟にできる
  • 再利用性と汎化
    • 問題解決が汎化しやすく、横展開が簡単になる
  • テスタビリティ
    • テスト対象が狭くなり、モックの適用がしやすくなる

コード分割の方法

  • API
    • きれいに抽象化レイヤーを作れる
    • 入力と出力が明確になっている
  • 関数
    • 単一のことをやらせるのに適してる
    • 組み合わせて複雑な振る舞いを構成できる
    • 小さくして焦点を絞ることが重要
  • クラス
    • 行数である程度、凝縮状態を判別できる
      • 関心事の分離
      • 凝縮度合い

コードは、文章のように書けばいいと思っている人もいるかも知れないが、分割する概念の単位によっては、多くの問題を抱えることもある。
適切なクラスの大きさに分割することは、よい抽象化レイヤーを担保することになり、時間を使うだけの価値はある。
レイヤーが厚すぎるのは、レイヤーが薄すぎる問題よりも悪化するケースが多い。
レイヤーの分離範囲に迷うのであれば、薄い方に寄せた方が無難。

コードでの契約

コード書くときの注意点

  • 明確だと思っても、他の人には明確ではない
  • 他のエンジニアの不注意でコードが壊れる
  • 書いたコードのことは忘れることを考慮に入れる

上記の対策として、下記を考慮してやると軽減できる

  • 名前
  • データの型
    • コンパイルで使い方の理解を促せる
    • 強く使い方を矯正でき、誤用を避けさせる
  • ドキュメントやコメント
    • 読まれるとは限らない
    • メンテされてない可能性もある
    • 理解させるための補助くらいに考える
  • コードを読ませる
    • 長いコードは読めないので、やめる

誤用が最小限になるように務めるのがエンジニアの仕事

ルールを守らせるなら、コンパイルエラーが一番強制力が強い。
それが不可能である場合、チェック処理やアサーションを利用した契約の強制が代替案となる

エラー

エラーは隠蔽しないほうがいい。
早く失敗し、通知をしたほうが被害を抑えられる。

エラーの種類

  • システム回復可
  • システム回復不可

実践編

コードを読みやすくする

  • わかり安い名前を使う
    • 名前はコードの見え方に多大な影響を与える
    • コメントは、分かりやすい命名の不適切な代替案
  • コメントは、なぜに焦点を当てる。コードだけですべて表現するのには限界がある
    • コードの存在理由を書く
  • 冗長なコメントは避ける
    • メンテナンスコストがかかる
    • コードを乱雑にする可能性がある
    • 必ずしも読みやすいコードの代価にはならない
  • コメントには、トップレベルの要約を書く
  • 簡潔だけど理解できないコードは避ける
    • 演算子を多用してたり、ビットのマスクを使っているのは、完結でも読みにくい
    • 誤用を引き起こしやすい
    • コード量を減らすためにやっているのだとしたら、やめる
  • 一貫性のないコーディング
    • 混乱を招く可能性大
  • ネストの深いコードは避ける
    • ロジックを追うのが困難になる
    • ネストが多いのは、関心事が多すぎる結果である可能性が高い
  • 関数の呼び出しを読みやすくする
    • 名前付き引数を利用する
    • 説明的な型を使用する
  • 説明のない値の利用を避ける
    • 誤用し易い
    • 適切な名前の定数に置き換える
  • 無名関数を適切に使用する
    • 大きな処理や自明でない処理を無名関数として使用するのは避ける
    • 小さな処理に適用する
  • 新しい言語機能を適切に使用する
    • 既存の問題が、新機能によって解決できることもある
    • 必ずしも最善とは限らないので、用途の見極めを慎重にする

想定外の事態をなくす

  • マジックバリューの戻り地をなくす
    • 誤用される可能性大
    • 異常値であることが判別されないことがある
    • nullやオプショナル、エラー等で代価できないか考える
  • nulオブジェクトを適切に使用する
    • 空のコレクションで解決できることもある
    • 想定外の事態を起こす可能性もある
  • 入力パラメータの変更に注意する
    • 入力パラメータの変更は、バグを発生させる可能性大
  • 将来性も加味した列挙型の処理
    • 想定外の値が来たときのケースも考慮する(値が追加になったけど、ロジック入れ忘れ防止のため)

誤用しにくいコードを書く

  • 不変にすることを検討する
    • 可変なコードは推測しにくい
    • 可変なコードは、マルチスレッドで問題を起こしやすい
    • 生成時のみ値を設定する
    • 普遍性を実現するデザインパターンを適用する
  • 汎用的なデータ型を避ける
    • 誤用し易い
    • 制限がゆるすぎる
    • 専用の型を用意するなどで回避する
  • 時間の扱い
    • 単位を揃えないと計算が間違ったりする
    • タイムゾーンの考慮をする
    • 統一したデータ構造を使うようにする

コードをモジュール化する

  • モジュール化するとテストしやすくなる傾向がある
  • DIの使用を検討する
    • ハードコーディングした依存関係は問題になる可能性が高い
    • DIすることでモックによるテストが容易になる
  • インタフェースに依存する
    • 抽象化レイヤーを維持しやすい
    • 高いモジュール性を維持できる
  • クラスの継承に注意
    • 想定外の継承は、抽象化レイヤーを壊す
    • コンポジションを利用して抽象化レイヤーの破壊を防止する
  • クラスは自分自身に関心を持つべき
    • 一つの変更が他クラスへ影響が発生しないようにする
  • 関連データをまとめてカプセル化する
    • 関連データはクラスにグループ化する
  • 戻り値から実装の詳細が漏れることに注意する
    • 内部の実装が漏れていると、内部の処理の変更の影響が、多くの場所に発生してしまう
    • レイヤーに適した戻り値を返すようにする
  • 例外から実装の詳細が漏洩することにも注意する
    • 非検査か検査例外を発生させるのかは、十分に検討する

コードを再利用、汎用化しやすくする

  • 再利用を促すことで、労力を節約できる
  • グローバルな変数の利用は避ける
    • 管理コストが想定より高い
    • 再利用すると危険になる
  • 抽象化レイヤーを作ることを心がける
    • 再利用が促進される

ユニットテスト

ユニットテストの原則

  • よいユニットテスト
    • 破損を正確に検出できる
      • コードへの信頼が得られる
      • 将来のバグからコードを保護できる
    • 実装の詳細に影響されない
    • 失敗原因が分かりやすい
    • テストコードが読みやすい
    • 簡単かつ迅速に実行できる
  • モックの利用
    • テストを簡単にする
    • 外部の実装に依存せずにテストできる
    • 実態からかけ離れたテストになる可能性がある
  • フェイク
    • インタフェースの代替実装のこと

ユニットテストの実践

  • 関数のテストではなく、動作のテストをする
    • 1関数1テストケースは、不十分
  • プライベートな関数のテストは避ける
    • テストが実装の詳細に依存する
    • リファクタリングを困難にする
    • 後悔されてるAPIのインタフェースを利用するか、コードを小さい単位に分割できないか考える
  • 一度に1つの動作のみテストする
    • 複数の動作をテストさせると、何が原因で失敗しているのか調査するのが難しい
    • シナリオごとにテストケースを作る
  • 共通のテストのセットアップを行う
    • テストケース間の依存を排除できる
  • 状態の共有はさける
    • 結果に与える影響が大きいことが多い
    • 意図しない状態になったときにテストにならない
  • 適切なアサーションを利用する
    • 失敗原因が明確になる
  • DIの利用を促す

感想

書いてある内容については、おおよそ合意できる。
そこまで新しい発見はなかったが、内容を理解するのにだいぶ苦戦した。
なお、ちゃんと理解できているかは怪しい。。。

問題とそれに対する解決策を掲示しているので、類似した問題がないか探して、その章を見るというのが、この本の使い方なのかもしれない。
"なぜ?"を理解するのに苦労した。
なぜそうするのかに注目してると読むのに苦労しそう。

個人的な使い方としては、大まかな概念の理解には使えるが、そこからさらに詳細を詰めるのには適さないと感じた。
より詳細な理解を促すには、別の本に頼ることになりそう。
テストに関しては、別な本で理解した内容が羅列されてる印象を持った。本のタイトル忘れてしまったが、あれは良書だったなって、改めて感じた。

結構、内容が難しかったから、読む場合は、覚悟が必要ということだけは伝えたい