Rのつく財団入り口

ITエンジニア関連の様々な話題を書いているはずのブログです。

【感想】『Clean Code アジャイルソフトウェア達人の技』:Uncle Bob流のクリーンなコード道場

クリーンなコードを書いていこう

 以前に上げた『Clean Architecture』の感想記事にブクマなどなどアクセスありがとうございました。読了して洞察が増してクリーンを追求したいな気分になったので、同じくボブおじさんによるCleanシリーズ、『Clean Code』も読むことにしました。
 こちらはアーキテクチャ本よりも前に刊行。アーキテクチャのレベルよりも細かな、クラスや関数、プログラミングの習慣レベルでのクリーンで良いプログラムのコードの書き方に注目した本となっています。

Clean Code - アスキードワンゴ

前書き

 名著らしく何やらインテリジェントなことを言ったり日本の1950年代の工場の品質向上の取り組みの話題が出たり。そして細部まで気を配った、整理されたクリーンなコードや習慣の重要性を述べています。

序論

 Bad Codeのコードレビューが「なんじゃこりゃ」の嵐になっている章の挿絵スケッチが面白い。
これを避けられるのは「職人技」、(Craftmanship?)知識作業に分けられる。知識には原則、パターン、実践、経験則がある。作業は実際の作業だがすぐできることではない。努力が必要だが咀嚼して自分のものにしていこう……という、達人魂を説明しながら本文が始まります。

f:id:iwasiman:20200918235515p:plain
Clean Code

第1章 クリーンコード

 「コードは書かなくていい時代がくるはずだからプログラマはいらなくなるなんて話はばかばかしい!」という話が最初にあり、NoCode論はいつの時代も沸いてくるんだなあと思います。
クリーンコードを書いていく心構え等の話の後、そもそもクリーンコード、良いコードとはなんなのだろうと人々にインタビューし、人それぞれの定義が載っています。似顔絵も載っています。
 ここで出てくるのがもうC++の生みの親とか有名な古典本の作者さんとか、業界のレジェンド級の人ばかりでヤバイ。人それぞれのクリーンコードの定義が微妙に違うのも面白いですね。
 そして本書はクリーンコード道場としてひとつの道を示すものだ、ボーイスカウト・ルールを守りながら進んでいこう……とボブ師匠の教えが始まります。

第2章 意味のある名前

 変数名や関数名を分かりやすく、未来の自分や他人が見ても理解できるものにしようという章。以下、本エントリでは「関数」と「メソッド」をほぼ同じ意味で使っています。

  • aとかbでなく意図が分かる名前にする。
  • 間違った情報を与えない。間違えやすい似た綴りを使わない。
  • 対比は明確にし、読み手に違いが分かる名前に。
  • 発音できない変な略語を使わない。出てくる genymdhms gって何?が面白い例。
  • マジックナンバーは定数で定義するなどして、検索しやすい名前にする。
  • エンコードされた発音不可能な名前を使わない。(このケースは今どきはないような...?)
  • 型を変数名に埋めこむハンガリアン記法は不要。
  • メンバ変数の先頭に m_ を付けたりするのも不要。
  • 実装クラス名に Impl をつけるなら、インターフェイスの先頭に I は不要。
  • ループ以外では、1文字の変数名を脳内で変換しなければならないような状況は避ける。
  • クラス名には具体的な名詞を使う。実体の分からない言葉や動詞は使わない。
  • メソッド名は動詞。
  • 気取った名前やジョーク、スラングは使わない。ここでモンティパイソンネタが出てきます。

ja.wikipedia.org

  • 1つのコンセプトに2つ以上の単語を使うと混乱の元。
  • 違う意味を持つメソッドを、既存メソッドと同じ名前に語呂合わせしない。
  • プログラマが読むプログラマチックなところなら、技術的な単語を使うのは推奨。
  • プログラマが見るプログラマチックでないところなら、業務用語から名前をもってきて分かりやすくしてよい。
  • 住所の構造に関係する値だったら先頭に addr など、意味のある文脈を加える。
  • クラスの名前に間違った情報を与えない。先頭にシステム名の略称を加えるなど。

 うん、あるあるだよね~という所。よくないコード例が載っているのですが、これがうまく作ってあります。

3章 関数

 最初に筋の良くないコードの例として長い長い長いコードが例示されており、おおお……これ3分じゃ絶対わからん……となります。プログラムの最小単位である関数を掘り下げる章。

  • 小さいこと。何よりも小さいこと。最大20行ぐらい。
  • ブロックとインデント:インデントは2つぐらい、つまりif文の中で別の関数を呼ぶぐらいのネストで。
  • 1つのことを行う:関数の仕事はひとつに。
  • 関数内のセクション:やることが分離できるなら、それぞれ別の内部関数に分割する。
  • 1つの関数に1つの抽象レベル:抽象度の高低が混在すると混乱する。
  • コード通読:逓減規則:抽象レベルや整合性を合わせていると、上から下へ物語のように読める。
  • switch文:各case文の中でポリモーフィズムを使った派生クラスを生成するぐらいに抑える。
  • 内容をよく表す名前を使う:長くてもよく、整合性を持った分かりやすい名前を。
  • 関数の引数:3つ以上は避ける。
  • 共通モナディック形式:イベントは引数1つ、戻り値なし。同じように変換する関数で引数1つ、戻り値なしにすると混乱する。戻り値で表現する。
  • フラグ引数:よくない習慣であり、関数を2つに分割する。
  • 引数2つの関数:引数1つより理解が難しくなる。
  • 引数3つの関数:さらに理解が難しくなる。
  • 引数オブジェクト:x,y座標が引数なら2つをまとめたオブジェクトにするなど、よく使われるテクニック。
  • 引数リスト:可変引数でも数を取りすぎないように。
  • 動詞とキーワード:関数の名前に含めて理解しやすく。
  • 副作用を避ける:照会しつつ実はセッションもクリアするなどの、隠れた仕事をさせない。
  • 出力引数:appendHoge(obj) のように、引数のオブジェクトに対して状態を変更させて戻り値なしの関数は混乱のもと。オブジェクト指向言語では obj.appendHoge() のように引数なしで自分自身を変更する。
  • コマンド - 照会の分離原則:1つの関数の仕事は聞きに行くか何か処理をするか、どちらかにする。
  • 戻りコードよりも例外を好む:これはJava等のお作法。(Go言語などだとまた違いますね)
  • try/catchブロックの分離:try{ の中の処理が長かったら別関数に切り出し。
  • エラー処理も1つの処理:ロギングだったら別関数に切り出しなど。
  • Error.java依存性磁石:システム全体でエラーコードを定義したクラスがひとつだけあると、だんだん追加が億劫になってくる。それより例外の派生クラスを増やしていく。(これもJava文化圏の話で、言語によって違います)
  • DRY原則:重複は諸悪の根源。
  • 構造化プログラミング:入口と出口は1つという厳しい制約だが、これは古い時代の制約。
  • なぜ関数をこのように書くのでしょう?:プログラミングは文章作成のようなもので、最初から完璧なものは作れない。あとから直して洗練させていく。

 自分的には守れていないルールもあり、さすが達人クラスになると厳しい制約を自分に課しているな~と思います。最後に冒頭の筋の悪いコードをリファクタリングした綺麗なコードが載っており、1メソッドが数行レベルに分割されて徹底的に綺麗になり、このコードが実は、JavaのテストフレームワークJUnit関連の画面表示関連の関数だったと分かります。

 EclipseIntelliJを「最新のIDE」と表現していたり、過去に感動した逸話の美しいコードがJava Swing(HTML/JSPでない、Javaの画面描画周りの技術。Webが出てきた後はあまり流行らなかった) だったり、このへんはやはり2008年の本なので若干の古さはありますね。

ja.wikipedia.org

第4章 コメント

コメントの書き方に1章を設けて深掘りしています。

  • コメントで、ダメなコードを取り繕うことはできない:あくまでコードの方を改善する。
  • 自分自身をコードの中で説明する:例えばメソッド名で判別できた方が、コメントのみの補足よりは良い。
Goodなコメント
  • まっとうなコメント:著作権表示など。
  • 情報を与えるコメント:実装の補足情報を示す。
  • 意図の説明:普段と違うコードで、何をしようとしているのかを示すなど。
  • 明確化:コードの意図を示す。
  • 結果に対する警告:テスト用メソッドを説明するなど。
  • TODOコメント:後でやるところ。なるべく潰していく。
  • 強調:一見筋の通らない実装を強調するなど。
  • 公開APIにおけるJavadoc:これは必要。不誠実なことを書かないこと。
Badなコメント
  • ぶつぶつ言う:独り言や自分用メモでコードと矛盾したことは書かない。
  • 冗長なコメント:コードを上回る情報を提供しないコメントは不要。
  • 誤解を招くコメント:コードのロジックと違うことを書かない。
  • 日誌コメント:先頭に変更履歴を書く昔の風習。バージョン管理システムがあれば不要。
  • ノイズコメント:「デフォルトコンストラクタ」とか見れば分かることは不要。
  • 恐るべきノイズ:メンバ変数コメントのコピペミスに注意。
  • 関数や変数が使用できるのであれば、コメントを使用しない:その通り。
  • 道標: //Actions /////////////////のような区切りバナーは使いすぎない
  • 閉じカッココメント:({の始まりと終わりの対応を補足するコメント。これが必要なぐらいなら、関数を短くすることを検討する。
  • 属性と署名:誰が直したか等は不要。バージョン管理システムがやるべき。
  • コメントアウトされたコード:絶対にやめる。
  • HTMLコメント:関数やクラスの説明コメントがなぜかHTMLになっているもの。こういうのはツールに任せる。
  • 非局所的な情報:コードの違う場所にある事柄については書かない。
  • 多すぎる情報:長すぎる議論の履歴なんかを書かない。
  • 不明確なつながり:コメントとコードが繋がらないことは書かない。
  • 関数ヘッダ:短い関数に長い解説は不要。
  • 非公開コードのJavaDoc:内部のクラスや関数についてはいらない。

 サンプルはすべてJava、コード例に出てくるのが更新日付が2000年代初めだったり、クラス先頭に更新履歴を書く古い風習の話が出てきたり、ちょうどその頃Javaを学んだ身としては懐かしさを感じてしまいます。
 良くないコード例にApache CommonsJavaの文化圏では今も有名な定番ライブラリの内部コードが出てきたりして、おおーあのライブラリの中でもこんな悪い例のコードがあるのか~と逆に思ったり。
クラス名やメソッド/関数名は基本英語で我々日本人は読む際に脳内で変換する手間があるので、僕は多少冗長でも日本語のコメントはちゃんと書く派です。上げられている「デフォルトコンストラクタ」とか道標の区切りコメントぐらいは別に使っていいんじゃない?と思ったりしました。

ja.wikipedia.org

第5章 書式化

 自分の書いたコードが人から見て分かりやすいよう、書式を整える話を深掘りする章。

  • 書式化の目的:動くコードより読みやすいコード。将来の保守容易性と拡張性の為。
  • 縦方向の書式化:1ファイルは200~多くて500行程度。
  • 新聞に例える:見出し→本文のように詳細化。
  • 垂直概念分離性:空行をうまく使うと塊を分けて見やすくなる。
  • 垂直密度:互いに関連の深いメンバ関数、メソッドは近くに。コメントが長いと1画面に収まらず邪魔になることも。
  • 垂直距離:メソッド内でもローカル変数は使われる場所と近い場所で宣言する。インスタンス変数はあちこちで使うので先頭で良い。メソッドの並び順も上から public→内部→そのまた内部 の順。
  • 垂直方向の並び順:メソッドの 高いレベルの概念→低いレベルの概念 で上から順。
  • 横方向の書式化:グラフで出した。1行は80文字は厳しすぎだが、100~120行MAX。
  • 水平分離性と密度:演算子の優先順位をスペースで示したりするのも良い。
  • 水平方向の位置合わせ:スコープ、型、変数名で開始桁数を揃える昔の風習はもう不要。
  • インデント:人間が見やすいコードには必須。短いif文やwhile文をインデントなしで1行に書いてしまうのはやらない。(これは、JavaScript文化圏などだとやる場合もありますね。)
  • ダミーのスコープ:中身のないダミーのwhile文などもインデントして見やすくする。
  • チームの規則:チームのスタイルがあったら、個人のスタイルより優先して従う。

 関連のある変数やメソッドを1画面に収めるために、コメントもなくして行数を節約するのはちょっとやりすぎかなと思ったり。
実際のプロジェクト例が載っているのですが、AntTomcatで行数が大きすぎるクラスが多い結果が出ていて、あれほど世話になったOSSでもこんなことあるのだなあと思いました。

ja.wikipedia.org

ja.wikipedia.org

第6章 オブジェクトとデータ構造

 口絵に『スタートレック』のキャラが書いてあって最初誰だか分からなかったのですが……これは乗員メンバーのアンドロイドの「データ」氏かな?

memory-alpha.fandom.com

  • データ抽象化:publicなメンバ変数を直接公開して詳細を見せるより、メソッドの後ろに隠して抽象化する方がオブジェクトとしては望ましい。
  • データ/オブジェクトの非対称性:手続き型に近いデータ構造だと、影響なく新たな関数を加えるのは簡単。しかし新たなデータ構造を加えるのは影響が大きく難しい。オブジェクト指向だと完全に逆で、新たな関数を加えるのは難しい。だが新たなクラスを加えるのは簡単。
     複雑なシステムは新たなデータ型の追加が多いのでオブジェクト指向がうまく適合する。しかし常にOOが優れているわけではない。
  • デメテルの法則:あるオブジェクトを使う場合、そのオブジェクトの内部について知りすぎるべきではない。
    String outputDir = ctx.getOptios().getScratchDir().etAbsolutePath();
    がまずい例。

ja.wikipedia.org

  • 電車の衝突:あるオブジェクトからgetして、結果からまたgetして、結果から...は分かりにくいしデメテルの法則に違反する。適切なオブジェクトからメソッドコール1回で返ってくるのがよい。
  • 混血児:getter/setterを用いてデータ構造にアクセスかつ、処理をする関数もあったり、データ構造とオブジェクトが混ざったようなクラスは避けるべき。設計が上手く行ってないサインである。
  • 隠蔽構造:いろんなオブジェクトのメソッドを何回もコールして、隠蔽されている内部構造を尋ねる形になるのはまずい。最低限の引数を渡して単純なメソッドコールで結果が返ってくるのがシンプル。
  • データ転送オブジェクト(DTO:Data Transfer Object):関数を持たずpublicな変数を持ったクラス。結果を渡していくのに便利。これをprivate変数とgetter/setterでやるのはカプセル化しているようにも見えるが、特に利点はない。

  • アクティブレコード:DTOクラスなのだが、savefindなどのメソッドを持ったもの。DBのテーブルやデータの写像となる。ここにビジネスルールのメソッドを追加する手法は混血児になるので勧められない。解決策は、ビジネスルールを持ったオブジェクトは別に用意すること。

  • 結論:ふるまいとデータを隠蔽するオブジェクトがうまく活用できる時もあれば、データ構造の方が良い場合もある。優秀な開発者は偏見を持たずにうまく使い分ける。

 僕はJavaが注目された時代に、privateな変数+getter/setterをつけたBeanクラスのオブジェクトを使うべきだ、という文化の中で最初は学びました。その後他言語での文化……PHPRuby連想配列JavaScriptJavaScriptオブジェクトなんかを知ると、あれ、データを渡すだけだったらこのデータ構造で十分じゃんと理解が進むようになりました。本書でもオブジェクト/データ構造の両方を使うのがよいとあり、なるほどなあと思います。
 アクティブレコードにビジネスルールを注入するのはまずく、別にビジネス処理をするクラスを作っていくのがよい……という考え方は、本格的にやると『Clean Architecture』や『ドメイン駆動設計』の考え方に繋がっていきますね。

Clean Architecture 達人に学ぶソフトウェアの構造と設計【委託】 - 達人出版会 ja.wikipedia.org

第7章 エラー処理

 エラー処理は重要だが、本来のロジックを不明瞭にしてはいけないという章。

  • リターンコードではなく、例外を使用する:呼び出し側が雑然としてしまう。try-catchで例外で囲み、tryの中のメイン処理は別メソッドに切り出すようにすると綺麗になる。
  • 最初にtry-catch-finally文を書く:あたかもDBのトランザクションのように、必要十分な短いスコープの中に try-catch-finally を書くとテストもしやすくなる。
  • 非チェック例外を使用する:コンパイル時にエラーが出る各種チェック例外は、その外側、その外側……と永遠にチェックが必要で、カプセル化の破壊になる。Javaプログラマが数年間議論した結果、非チェック例外を使った方がよいとなりつつある。
  • 例外で状況を伝える:十分な情報やエラーメッセージを例外の中にくるんでスローする。
  • 呼び出し元が必要とする例外クラスを定義する:ある処理で、APIの呼び出しが多数の例外を返すのなら呼び出し元は混乱する。そのある処理の中でラップして、共通の例外をスローするようにすると分かりやすい。
  • 正常ケースのフローを定義する:あるメソッドで処理を呼び出すとcatch節の中にもロジックを書かないといけないような、ロジックを例外が分断するケースはまずい。そのあるメソッドの中を「スペシャルケース」として扱い、例外を出さずにふるまうようにすればよい。
  • nullを返さない:nullチェックが多すぎると見にくくなる。例えばnullでなく長さゼロの配列/リストを返すようにする。
  • nullを渡さない:メソッドにnullを渡すのを原則禁止とした方がすっきりする。

 最後の「nullを渡さない」は、近年の言語にあるような引数の初期値を指定できたり引数の型でnull可能かを指定できる機能が使えば不要になりますね。
 リターンコードよりも例外を利用するというのも、2000年代のJava全盛期の話です。もともと例外を使わないようデザインされたGo言語などもあります。このへんは原著の出版が2008年ということを踏まえて、適宜読み替えるべきでしょう。

golang.org

第8章 境界

  • サードパーティーのコードを使用する:例に懐かしの java.util.Map が登場。APIはたくさん用意してあり混乱しがち。であれば例えば自分たちの使うセンサーの集合を使うSensorsクラスの中にメンバ変数でMapを持って、必要なメソッドだけpublicにし、インターフェース境界を隠すとよい。インターフェース境界をシステムのあちこちに持ち回る形式はよくない。
  • 境界の調査と学習:システムに入れる前に、ライブラリの動かし方を調べて実際に試すと良い。「学習テスト」と呼んでいる。
  • log4jを学習する:Javaで有名なロギングライブラリをJUnitで学習テストしてみた例。
  • 学習テストは、タダ以上のものである:統合する前に期待通り動くか検証するのは大きな効果がある。
  • まだ存在しないコードを使用する:これから開発されるAPIをインターフェースだけで定義、デザインパターンのAdapterパターンを使ってAPIとのやりとりをカプセル化すると良い。テストが可能になる。
  • きれいな境界:システムの広い範囲のコードが、サードパーティの詳細な処理を呼び出しするのは避けるべき。(自分たちが制御できるものに依存する) サードパーティとやりとりする小さな領域を設けて境界をそこだけにする。

 アーキテクチャ的な、コンポーネント間の境界(バウンダリー)をはっきりさせることの重要性は『Clean Architecture』本でたびたび述べられていますが、同じような話を本章ではライブラリを使う時をテーマに論じています。
 ライブラリ呼び出し部分を一旦中にくるんでラップしたクラスを作り、システム内の他のコードからはそのラップしたクラス経由でしか使わない……というのはよく使われるテクニックですね。

第9章 単体テスト

 最初の昔話が1997年で、まだ単体テストフレームワークがなかった頃にC言語で手動でやった話が出てきて、おおお……となります。

TDD三原則:

  1. テストコードの前に製品コードを書いてはならない。
  2. コンパイルが通ってそのテストが適切に失敗する前に、次のテストコードを書いてはならない。
  3. いま失敗しているテストコードが通るまで、次の製品コードを書いてはならない。

これに厳密に従うと製品コードをすべて網羅するテストコードができるが、あまりに膨大になる。

  • テストを綺麗に保つ:品質標準に従わないテストコードを放置すると、リリースごとに保守のコストがかさみ、結局破棄したりすることになる。テストコードも製品コードと同様に綺麗に保つべき。
  • テストは、xxx性を可能とする:テストコードがあれば恐れず変更ができる。
  • クリーンテスト:FitNesseプロジェクトから引用した長い長い分かりにくいテストコード。リファクタリングするとずっと短く分かりやすくなり、「構築-操作-検査パターン」の構造になる。テストデータを構築、それを操作し、最後にassert文で比較するような構造。
  • ドメイン特化テスト言語:テストコード内で別の関数やユーティリティを使って工夫する、テスト言語を作ることもできる。
  • 二重規範:環境制御システムのわかりにくいテストコードの例。リファクタリングして、ヒーター/送風機/クーラーなどのスイッチON/OFFを頭文字の大文字1文字で示し、assertEquals("HBchL", hwgetState()); のようにパターンを読みやすいように改良した。本番ではありえないパターンもテストできる。
  • 1つのテストに1つのアサート:これは良い規則だが厳しめなので、なるべく少なめ、ぐらいで良い。
  • 1つのテストでは1つの概念を扱う:1つのテストメソッドにアサート文が複数あるのはよいが、複数の概念を含まないようにする。月の加算のテストで 1ヶ月追加/2ヶ月追加/月の最終日が30日 のパターンが含まれているなら別のテストメソッドにするなど。
  • F.I.R.S.T.:クリーンテストの規則。高速である、独立である、再現性がある、自己検証可能、適時性があるの頭文字。

 テストコードをどう書いていくかの概要の話でした。
ドメイン特化テスト言語を構築する」、と「言語」という難しい表現を使っていますが、ここはテストコードの中で共通なデータを作ったり判定したり、様々なユーティリティ関数を作ったりと、製品コードと同じようにいろいろ工夫できるよという話だと理解しました。
 自分もJUnitは部分的には使ったりしてきたけど、徹底的なテスト駆動開発というのはやっていないので、読んでいてむむむむ……となります。

テスト駆動開発

テスト駆動開発

第10章 クラス

 言語をJavaに限定して、クリーンなクラス実装を深掘りしていきます。

  • クラスの構成:最初は変数から。staticな変数、通常のprivate変数の順。publicな変数は避ける。メソッドも public->protected->private な順、上から下へ詳細化する。
  • カプセル化:内部的なユーティリティメソッドや変数はまずprivateでカプセル化は緩めない。テストで必要ならprotectedにする。
  • クラスは小さくしなければならない!:関数をより小さくするのは行数で計れるが、クラスをより小さくするには責務の数で計算する。publicメソッドが多すぎる神クラスはやめる。
    クラス名が Processor, Manager, Super などあいまいだと神クラスのサイン。
    クラスの簡単な解説が約25文字、日本語だと100文字以内程度でできるのが適切。説明に「もし」「そして」「あるいは」「しかし」などが出てくると責務が多すぎるサイン。
  • 単一責務の原則:(Single Responsibility Principle: SRP)。変更の理由は1つでなければならない。もっとも理解しやすく守りやすい概念だが、破られやすい。人間はプログラムが動くようになると組織化と洗練を忘れてしまうから。
     大きなシステムは大量のロジックと複雑さを持っているので、少数の何でもできるクラスより、多数の小さなクラスで作った方が、常に理解しやすい。

en.wikipedia.org

  • 凝縮性:そのクラスにあるインスタンス変数を、メソッドが操作する数が多いほど凝縮性が高いと言える。
  • 凝縮性に気を配ると、大量の小さなクラスが生まれる:大きなメソッドを分割して短くいき、引数の数を減らそうとするなら、その引数をインスタンス変数に昇格すればできる。しかしこうするとクラスの凝縮性が下がるので、クラス分割のサインとなる。責務が分かれて理解しやすく、個別にテストできる。
  • 変更のために最適化する:クラスを変更すると他の部分が動かなくなる危険性がある。解決策はクラスを細分化すること。変更しても他のクラスが影響をうけなくなり、機能追加もサブクラスを追加していけばよい。オープン/クローズドの原則 (Open Closed Principle: OCP) を実現できる。

ja.wikipedia.org

  • 変更から切り離す:例はPortfolioクラスが、内部でメンバ変数で持っている株価APIクラスに依存している場合。メンバ変数で持つのは株価APIの具象クラスでなくインターフェイスにする。そうするとテスト時に別のクラスを注入でき、テストが可能になり、柔軟性が高まって再利用可能になる。構成要素がお互いに切り離されて変更から分離される。これが依存性逆転の原則 (Dependency Inversion Principle: DIP)

ja.wikipedia.org

 最後のDIP原則の実装例は、テストしやすいクラス設計の例によく出てくきそうなものですが、テストコードがマイクロソフトの株価(一株100ドル)になっているという高度なギャグが隠れています。現実ではぐぐったら読んだ日は217ドルでした。(笑)

www.microsoft.com

第11章 システム

冒頭に「複雑さは死である」マイクロソフトCTOの言葉を引用し、システムを掘り下げる章。

  • あなたは、街をどうやって作りますか?:全体について責任を取る人と詳細をやっていく人がいるはず。クリーンコードを低いレベルの抽象化としたら、より高い抽象化レベル、システムのレべルでもクリーンにしていける。
  • システムを使う事と、構築することとを分離する:アプリケーションのオブジェクトが生成されたら、開始処理はその後のロジックとは分離する。関心事の分離、テストしやすさに繋がる。
  • mainの分離:最初の構築に関する処理はmainというモジュールにまとめると良い。依存の矢印は常に main→その先のapplication
  • ファクトリ:デザインパターンのAbstract Factoryを使い、mainの横に生成用のファクトリクラスを配置できる。この時も依存の矢印は常に main→その先のapplication
  • 依存性注入:(Dependency Injection: DI)。制御の反転を適用する。Java言語の技術であるJNDIでは、サービス名の文字列を与えるとルックアップして対象のサービスを取り出してくれる機能があり、部分的なDIを実現している。真のDIはもっと先をゆく。

  • スケールアップ:システムは後から大きくなるもので、最初から完璧な正しい設計は行えない。関心事を分離して保守していけば、アーキテクチャもだんだんと成長させられる。
    Javaの技術であるEJBの初期のバージョン1,2では関心事の分離が正しくできていなかった。
    銀行のBankを表すインタフェースとBeanクラスのコード例で示しているが、コンテナと結びついた大量のメソッドを用意の必要があったりとても面倒くさい。分離したテストができず、Beanクラスも継承ができなかったり、問題が多かった。

  • 横断的関心事:多くのオブジェクトで同じコードを繰り返し実装しなければならないようなところを横断的関心事と呼ぶ。これを、ある作業を委譲すればそちらでよろしくやってくれる「アスペクト指向プログラミング」の活用で解決できそうだった。

ja.wikipedia.org

  • Javaプロキシ:アスペクトの仕組みだが、単純な例でもコード量が多く複雑、難解。
  • Pure JavaAOPフレームワークSpring AOPJBoss AOPがある。XMLの設定ファイルに対応を書いておくと、実装側は単純なPOJOクラスでBankクラスを使っているだけだが、仕組みの側でそれをくるんだデータアクセスオブジェクトでDBに接続したり……が実現できる。Springに関係したコードは少ないのでアプリケーションはほぼ切り離されており、強い結びつきがない。これを採用したのでEJBバージョン3はコードがだいぶシンプルになった。

Core Technologies

  • AspectJ:8-9割は前述のSpring AOPJBoss AOPで解決できるが、それでも足りない場合に使える本格的ツール。
  • システムアーキテクチャのテスト実行:ドメインロジックをPOJOで記述できれば、テストが可能になる。「事前の大規模設計」(Big Design Up Front: BDUF) も不要になる。良いシステムアーキテクチャはモジュール化されたドメインの関心事で構築され、POJOで実装されている。
  • 意思決定を最適化する:こうして機敏さを手に入れたシステムは、意思決定を適切なタイミングまで遅らせることができ、決断の複雑さも減少する。
  • 論証可能な価値を追加する際には、標準を賢く使用する:業界標準は大事だが策定に時間がかかりすぎたり、実際のプロジェクトで必要な分より重すぎたりすることがある。(EJB2がそうだった) うまく使っていく。
  • システムはドメイン特化言語を必要とする:うまく使うとあらゆる抽象レベル、あらゆるドメインPOJOで表現できる。
  • 結論:クリーンなシステムでは、すべての抽象化レベルで意図を明確にし、単純な仕組みを使い、機敏さを保つ。

 EJB(Enterprise Java Beans) は2000年代にJavaの大規模エンタープライズ開発向けに考えられた仕組みなのですが、難しすぎて結局あまり受け入れられなかったという背景があります。自分もJavaを学んでいる最中に理解しきれずにそっとしておいたら、その間に廃れてしまいました。
このへん、2020年の今からすると古い話なので昔はそんなこともあった、ぐらいで流してよいと思います。

ja.wikipedia.org

 Java全盛期の本なので「POJO(Plain Old Java Object)、(読み方は「ポジョ」)という表現を使っていますが、これはメンバ変数にデータを持っただけの簡単なデータの入れ物クラス、DTOのようなものです。Ruby言語ならPORO(Plain Old Ruby Object)C#ならPOCO(Plain Old CLR Object)になります。要は単純なデータ構造を使いましょうということですね。
なおPythonyやPHPならPOPOでポポ、GoだとPOGOでポゴ、ScalaならPOSOのポソ、KotlinならPOKOでポコになってくれそうですが、そういう文化はないようです。JavaScriptも、関数を持たなければJavaScriptオブジェクト自体がもうPOJOとイコールなのでこの呼び方は使わないですね。

e-words.jp

 難しいことを語っているのですが要は、

  • 「クラスは関心事を分けるのと同様、モジュールやコンポーネント単位でも関心事を分け、互いに依存せず独立させる」
  • コンポーネント間のやりとりにはPO*Oのような単純なデータ構造を使う」
  • 「なるべくフレームワークに依存しない、難しすぎる仕組みを使わない」
  • 「テストしやすさを保つ」
  • 「単純にする」

ということだと読み取りました。このへんは別の本『Clean Architecture』でもたびたび語られています。

第12章 創発

テスト駆動開発で有名なケント・ベック氏による単純な設計のための4つの規則があり、これに従った創発的設計をするとコードがより洗練されていくよという話。

  • 規則1:全テストを実行する:テストをするためにはクラスをより小さく、単一機能を実装、結びつきを減らすようにしていくので、より優れた設計へ繋がる。
  • リファクタリング:テストがあれば、間違って壊してしまうかもしれないという恐怖がなくなるので、リファクタリングで改善していける。
  • 規則2:重複の排除:たった数行でもコードの重複は排除していく。単一責務の原則(SRP)違反に気付くきっかけになったり、チームメンバが再利用できることに気付いて複雑さを大きく減らしたりできる。デザインパターンTempalte Methodパターンが該当。
  • 規則3:表現に富む:プロジェクトのコストの大半は保守であり、その時は自分でなく他人がコードを読む。他人に理解させるコードに大事なのは何より名前付け。実装後もよりよい名づけを試していく。
  • 規則4:クラスとメソッドを最小限に:重複の排除やSRPの概念に従いすぎると、必要以上に小さなクラスとメソッドができてしまうこともある。例えば全クラスにインターフェイスを強要するとか、全クラスで常にデータと振る舞いを分けることを強要するのはやりすぎ。とはいえこの規則の優先度は一番低い。
  • 結論:これらの規則は筆者らの経験から生まれており、だからこそ価値がある。

本章は11章に比べると分かりやすかったです。

ja.wikipedia.org

第13章 同時並行性

  • なぜ同時並行性が必要なのか?:「何」を「いつ」から分離することで、アプリケーションの構造とスループットを顕著に改善できる。多数のユーザが処理するシステム、大量のデータセットを処理するシステムなどで威力。
  • 神話と誤解:同時並行性は常にパフォーマンスが良くなるわけではない。設計の変更は発生する。WebやEJBコンテナのような仕組みを使っていても、同時並行性は知っておいた方がよい。複雑になったり、再現性のないバグが起こったり、厄介である。
  • 難問:例に上がっているのはメンバ変数で数値を持っている単純なXクラス。しかしひとつでは多数のスレッドが同時にアクセスしてインクリメントしていくと、問題の起こる経路がある。
同時並行性防御原則
  • 単一責務の原則:SRP原則に従って、同時並行性に関するコードは他から分離する。
  • 帰結:データのスコープを限定せよ:Java言語では、synchronized を使って共有オブジェクトを操作するコードを保護できる。こうしたクリティカルな場所は少なくするのが大事。カプセル化を徹底し、データへのアクセスを制限する。
  • 帰結:データのコピーを使用せよ:共有しなくてもよいなら、コピーして使って最後にマージするやり方も取れる。
  • 帰結:スレッドはできる限り独立させよ:お互いに依存しないよう分割する。
使用しているライブラリを知る
  • スレッドセーフなコレクション:HashMapの代わりにConcurrentHashMapがある。
実行モデルを見分ける
  • プロデューサー - コンシューマー:プロデューサーのスレッドが作業をしてキューに格納、シグナルを出す。コンシューマーのスレッドがキューから取り出して作業、シグナルを出す。
  • リーダー - ライター:ライターが長時間ロックしてスループットが下がることがよくある。互いの要件の妥協点を見出す。
  • 哲学者の食事:哲学者が並んでいて、目の前の2つのフォークが空かないとスパゲッティが取れないというメタファー。アルゴリズムを学ぶ必要がある。

  • 同期化メソッドの依存関係に注意:Javaではsynchronizedというキーワードで保護できるが、共有されるオブジェクトのメソッドを2つ以上使ってはいけない。

  • 同期化セクションを小さくする:synchronized文で保護したクリティカルセクションはなるべく小さく。コードの中にばらまくような状況は避ける。
  • 正常な処理終了コードを書くのは難しい:あるスレッドが終わらなかったりで綺麗に終わらないことが多い。早い時期に終了処理に考慮を行い動作するようにする。
スレッド化されたコードのテスト
  • テストで正確性を担保することはできないが、優れたテストを実行することで問題をあぶりだせる。スレッドが2,3つに増えていくと問題が起こったりする。
  • 怪しい失敗を、スレッド問題の容疑者として扱う:簡単には起きないエラーが発生したりする。一過性の問題として片付けず着目する。
  • 最初にスレッド化されていないコードを完成させる:POJOを使ったマルチスレッド前提でないコードで動くようにしておく。
  • スレッド化されたコードは差し替え可能とする:スレッド数を変えたり高速/低速で動いたり繰り返したり、様々な構成で実行可能にしておくと問題解決に役立つ。
  • スレッド化されたコードをチューニング可能にしておく:スレッドの数は変えられるように。
  • プロセッサの数よりスレッドの数を多くする:システムがタスクスイッチする時に問題が起きるので、これでクリティカルセクションの不備やデッドロックが見つかる。
  • 異なるプラットフォームで実行する:Mac/Windowsの一方で起こる問題など。
  • コードに対していろいろなことを試し、強制的にエラーを発生させる:手作業なら wait(), yield(), priority() を加えたり。自動的に行うならアスペクト指向フレームワークの仕組みを使うなど。
  • 結論:同時並行処理コードを正しく作るのは難しい。まずSRP原則に従いPOJOを使って、スレッドが必要なコードを分ける。問題の原因やライブラリ、アルゴリズムを学ぶ。ロックは必要な場所にのみ行い、共有オブジェクトのスコープも絞る。テスト容易性を保って様々なテストで問題を追う。ここでもコードをクリーンにしておけば、正しいものができる可能性は上がる。

 この章はだいぶ難しく感じました。Java言語で synchronized は学んだけど、スレッド回りは結局あまり使う機会なかったような……基本的にはHTTPリクエストが飛んできたら順次処理すればよいWebアプリケーションだとあまり出てきませんが、デスクトップアプリケーションなどだと出てくるかもしれません。

第14章 継続的改良

 ケーススタディとしてJavaの実際のコード例が載っています。お題は、コマンドライン引数を分解して文字列/数値/真偽値などに分解してくれる Args クラス。 最初に最終例が載っていますが、インターフェース ArgumentMarshaler を継承- 実装して真偽値型や文字列型などそれぞれの型ごとにMarshalerに分割しているのがポイント。
それでもコードはけっこう長いです。動的型付け言語のRubyでも書いてみたらコード量が1/7になったとあってなるほど……と思います。

 そして「大雑把な下書き」クラスから始まるのですが、これがもう1クラスがめっちゃ長くてぱっと見でもBad Codeな匂いが漂って、なんとなくまずそうなのが分かります。
これをテストで保護し、インクリメンタル主義で少しずつ直してはコンパイルしてテスト、間違ったら少し戻って、よいやり方を見つけたら試して……を繰り返していきます。名著の『リファクタリング』で語られている通りの手順ですね。
 最後の方、コードが綺麗になって完成に近づいていくと文章にも「!」が増えて作者さんのテンションが上がっていきます。かなり詳しく書いてあるので、リファクタリング力を増したい方は精読して写経すると学びになりそうです。
 コードは動くだけは不十分。だめなまま出来上がってしまたコードを後からクリーンにするのは大変。実装の過程で改善を繰り返し、できる限りクリーンで単純な状態を保って腐敗を防止する……という章でした。

iwasiman.hatenablog.com

第15章 JUnitの内部

 著名なケント- ベック氏とエリック- ガンマ氏が飛行機の中で最初のJUnitを作り始めた……という逸話の後、JUnitの内部構造の話をするのかと思ったら違いました。
 JUnitのテストコードで保護された ComparsionCompactor というクラスを最終版と最初の版を提示し、リファクタリングの過程を見せていきます。

  • メンバ変数の不要なprefixを削除
  • 複数個所で出てくる条件をメソッド化、メソッド名 shouldNotCompact() のように分かりやすく
  • ローカル変数をメンバ変数と違う名前にして曖昧さを排する
  • 条件判定で否定の表現は肯定より分かりにくいので、肯定ベースに
  • 関数名を実体に即した名前に改良、中を分割して別関数に
  • 処理の過程で必要な変数は内部関数の戻り値からメンバ変数に昇格
  • ~indexが0でなく1から始まっているのを改善
  • 内部処理の条件に隠れていたバグ修正、不要なif文排除

……と、「ボーイスカウト・ルール」を適用して、クラスをよりよい状態にお掃除した例を示しています。

xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com

第16章 SeriaDateのリファクタリング

今度はJavaのJCommonライブラリにある SerialDate クラスのリファクタリング例。java.util.Date クラス周りが昔はイマイチ使いづらかったことから生まれたクラスですが、作者さんを優秀なプログラマだと認めたうえで、改善をしていきます。

www.jfree.org

  • 動かないテストコードや間違ったテストを修正、まずはすべての単体テストをグリーンで通るように
  • 更新履歴コメントはバージョン管理システムに任せて削除
  • JavaDoc内のHTMLタグは削除して pre で囲むだけに
  • SerialDate というクラス名が誤解を招くので DayDate
  • 月の定数は別クラスからenumに変えて保持
  • serialVersionUID は今回は削除
  • 使われていない定数を別クラスに移動
  • 継承クラスのインスタンス生成をAbstract Factoryパターンでファクトリクラスに任せる
  • 誤解を招いたり冗長なコメントを修正、削除
  • グルーピングできる定数はenum構造体に移動
  • 変数や引数宣言のfinalは全削除(つけた方が良いという文化も昔ありました)
  • 重複したメソッドは統合、名前変更、排除
  • フラグをメソッド引数で持ち回るのはよくない兆候なのでメソッド名で区別
  • メソッドや変数を適切なクラスに移動して分割
  • addDays() ->plusDays() のように誤解されそうな名前を修正
  • ベースクラス側にあるべきメソッドは移動(=プッシュアップ、プルアップ)
  • 継承クラスに側にあるすべきメソッドは移動(=プッシュダウン、プルダウン)
  • 間違えて直して進んでしまったところは戻って修正

……と、こちらもボーイスカウト・ルールの理念のもとに直していきます。本には「何行目の何とかを直した~」まで書いてあって、読み手はそんなところまで分からんわ!となるのですが、そこは作業メモということで。
プッシュアップ/ダウンやプルアップ/ダウンという言い回しはリファクタリングの文脈ではたまに出てきますが、日本では実際にはあまり使わない言葉だと思います。「プルダウン」はHTMLやExcelのリストボックスの話と混合しそうです。

17章 においと経験則

 名著の『リファクタリング』を引き合いに出しながら、コードのよくない匂いを本書なりにまとめたリファレンス。圧巻です。ここだけ読んだり後で見返すのも価値があると思います。

コメント編
  • 不適切な情報:バージョン管理システムに任せるべき情報やバグの番号やチケットは書かない。あくまでコードと設計に関する技術的な注意書きに。
  • 退化コメント:陳腐化した古いコメントは削除する。
  • 冗長なコメント:コメントはコードで表現できないことを書く。
  • 記述不足のコメント:時間をかけて、正しいことを簡潔な文章で書く。
  • コメントアウトされたコード:即削除すること!
環境編
  • ビルドに複数のステップを要する:秘密の手順があったり幾つも手順があるのは避ける。簡単なコマンドで実行できるように。
  • テストに複数のステップを要する:コマンド1つで全テストが実行できるように。
関数編
  • 多すぎる引数:3つを超えるのはたいてい多すぎ。
  • 出力引数:直感的でないので避ける。呼び出しているオブジェクトの状態を変えるようにする。
  • フラグ引数:関数の仕事が2つ以上あることが多いので、分割するサイン。
  • 死んだ関数:どこからも呼ばれていないものは消す。
一般
  • 1つのソースファイルに複数の言語を使用する:これが理想。2つ以上書くこともあるが、最小化すべき。
  • あって当然のふるまいが実装されていない:驚き最小限の法則に従い、コードの詳細を読まなくても期待されるふるまいをするように。
  • 境界値に対する不正確な振る舞い:開発者は正しく動くと直感を信じてしまいがち。境界条件など様々なケースで隅々までテストする。
  • 安全軽視:コンパイラの警告や失敗するテストケースを無視すると、ビルドは通っても後で困ったりする。
  • 重複:Don't Repeat Yourself(DRY)、あるいはOnce, and only once。重複を抽象化する。もっとも大切なルール。長いif文やswitch文はポリモーフィズムに置き換えられるし、似たような処理はTempalte Method, Strategyパターンで置き換えられる。

xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com

  • 抽象化レベルが正しくないコード:高いレベルの概念は親のベースクラス、低いレベルの概念は継承クラスへ。クラスより大きな概念のモジュール、コンポーネントにもこの考えは適用される。
  • 継承クラスに依存したベースクラス:一般にベースクラスは継承クラスの知識を持たない。
  • 情報過多:クラスの持つインスタンス変数の数、メソッドの数、関数が持つ引数の数は少ないほど良い。密度の高くて狭いインターフェイスを心掛けて、凝縮度を上げつつ結合度を下げる。
  • デッドコード:しばらくすると悪臭を放ち始めるので、立派な葬式を上げてシステムから消えてもらう。
  • 垂直分離:あるメソッドの内部処理のprivateなメソッドは呼び出し元のすぐ下など、縦方向の距離もなるべく最小限に。
  • 不整合:HttpServletResponse型の変数をresponseとしたら、全体で同じ変数名を使う。メソッド名も同じ。コードリーディングや理解が容易になる。
  • 雑然:何もしないデフォルトコンストラクタや未使用変数、メソッド、無意味なコメントなどは常に消す。
  • 人為的な結合:汎用的な定数を定義したenumクラスをあるクラスの中に入れてしまうなど、依存関係のないものを結合させてはいけない。手近で便利なところに投げ込んでそのままにしないでよく考える。
  • 機能の羨望:『リファクタリング』1版にある言葉。あるメソッド内で引数eのオブジェクトの中を e.getAAA().getBBB()... のように中に踏み込んでデータを取得していたら、別オブジェクトのスコープを羨望している。メソッドの定義場所を再度考える。
     ただし引数を受け取ってレポート出力するようなクラスでは必要悪となる。
  • セレクタ引数:引数の最後にboolean値があったら、大きすぎる関数を分割すべきサイン。
  • 不明瞭な意図:ハンガリアン記法や分かりにくい省略をした変数名、マジックワードは避ける。
  • 責務を持たせる場所の間違い:ここも驚き最小限の法則に従う。自分たちに便利な場所より、読み手にとって直感的な場所へ。関数名も判断基準になる。

ja.wikipedia.org

  • 不適切なstatic:一般的には非staticメソッド優先。似たようなアルゴリズムで別メソッドを増やしていく見込みがある(=多態的なふるまい)時は非static、それがない時にのみstatic。
  • 説明的変数:計算の途中結果は説明的な名前の付いた変数に格納していくと分かりやすくなる。『ケント- ベックのSmalltalkベストプラクティス- パターン』にある手法。

  • 関数名は体を表すべき:機能が分からない days.add() よりはインスタンスを操作するなら addDaysTo(), increaseByDays()。戻り値で新しいインスタンスを返すなら daysLater(), daysSince() など。
  • アルゴリズムを理解する:単に動くのでなく中を理解し、正しく動いているか見分ける。大抵は関数をリファクタリングして表現豊かなものにしていくと理解できる。
  • 論理的な依存性を物理的なものとする:依存先のどの情報に依存しているかを明らかに。例えばレポート出力するだけのクラスが、定数でページサイズを持っているのはおかしい。メソッドで外部から取得すべき。
  • if/elseやswitch/caseより多態を好む:ポリモーフィズムを活用する。

  • 標準の規約に従う:チームが合意していれば、その決まりに従う。

  • マジックナンバーを名前付けした定数に置き換える:ソフトウェア開発の最も古いルールの一つ。それ自身では意味を表現できない値であれば、数字だけでなく文字列なども対象になる。
  • 正確であれ:コードのあいまいさ、不正確さは取り除く。
  • 規約より構造:設計を強制するにはルールよりも構造が強い。abstractメソッドを持ったabstractクラス(やインターフェース)をインプリメントした具象クラスを書かせるなど。
  • 条件をカプセル化せよ:分かりにくい判定は適切な名前を持ったメソッドにする。if (shoudBeDeleted(hoge)) {...} など。
  • 条件の否定形を避ける:肯定形の方が分かりやすい。
  • 関数では1つのことを行うべき:幾つかの処理を連続して行う関数は、分解する。
  • 隠れた時間軸上の結合:メソッド内で処理をある順番に行わないといけないなら、後続の処理の引数で渡したりして間違った順番で呼べなくする手がある。
  • いいかげんにならないこと:コードを構成する時は根拠を持って行う。
  • 境界条件カプセル化する:境界条件判定の level + 1level - 1 がコードに散らばっていたら、変数 nextLevelカプセル化する。
  • 関数は1つの抽象レベルを担うべき:たとえばHTMLのタグ描画とその中のsize属性の計算がひとつの関数で混在していたら、内部関数に分割して抽象レベルを分ける。
  • 設定可能なデータは高いレベルに置く:高い抽象レベルで使われるデフォルト値のような定数は、高い抽象レベルのクラスに置く。
  • 推移的なナビゲーションを避ける:a.getB().getC().doSomething() は他のモジュールの中を知りすぎている。(=デメテルの法則) myCollaborator.doSomeThing() のようにする。

ja.wikipedia.org

Java
  • ワイルドカードを使って、長いimportのリストを避ける: import pkg.* とするとリストも短くなるし、モジュールの結合度が弱まる。今はもうIDE任せでよい。
  • 定数を継承しない:継承階層トップレベルのインターフェースに定数を定義、実装したクラスで使うのは悪しき習慣。定数クラスをstatic importする。
  • 定数とenum:Java5から追加されたenumを活用する。
名前編
  • 記述的な名前を選ぶ:関数名や変数名は注意深く、理解を助けるものに。
  • 抽象レベルに適切な名前を選ぶ:たとえばinterface Modem のメソッド名は dial()getPhoneNumber() より、connect()getConnectedLocator() の方が抽象レベルが高い。電話をダイヤルするのは具象のひとつだから。
  • 可能な限り標準の用語を使用する:デザインパターンの名前を使ったり、その言語の文化に従う。プロジェクト内で通用するユビキタス用語にするのもよい。
  • はっきりした名前:関数や変数は働きを示すはっきりした名前を使う。関数名にAndが入っても良い。
  • 広いスコープには長い名前を:小さいスコープの変数名は短くてよい(iとかj)。広くなったら長く正確な名前を付ける。
  • エンコーディングを避ける:型やスコープ、プロジェクト名やサブプロジェクト名は名前付けには不要。ハンガリアン汚染から防ぐ。
  • 名前で副作用を示すべき:関数がやることを名前と一致させる。getXX() が取得以上のことをやるなら、createOrReturnXX() にするなど。

 重複していたり互いに矛盾する経験則もありますが、改めて気づきの多いリストです。
Javaのimport文はクラス名を明確にしクラスローダーの処理時間軽減のために import pkg.* より import pkg.SomeClazz の方が良いとむかし教わってきたのですが、本書の import pkg.* の方がよいという話は若干こじづけ感があるなあと思ったりします。

テスト編
  • 不十分なテスト:思い込みに頼らずケースを網羅する。
  • カバレッジツールを使用する!:IDEでテストされていない部分を発見できる。
  • ささいなテストを省略しない:書くことのコストを上回るドキュメントとしての価値がある。
  • 無視することを指定されたテストは、あいまいさへの問いかけである:ふるまいがよく分からなかったら、要件への疑問点になる。
  • 境界条件テスト:特に重要。
  • バグの周辺は徹底的にテストを:ある関数にバグがあったら他にもあるかもしれない。バグは一定部分に集中しやすい。
  • 失敗パターンは何かを語る:整然としたテスト群の中で特定のものが失敗したら、そこからパターンを読み取れることがある。
  • テストカバレッジのパターンは何かを語る:失敗したテストのヒントになることがある。
  • テストは高速に実行できるべき:遅いテストは結局実行されない。
  • 結論:このリストは価値体系であり、この本のテーマ。プロ意識と職人魂は専門分野をつき動かす価値から生まれる。

本章の参考文献には『リファクタリング』『達人プログラマー』『オブジェクト指向における再利用のためのデザインパターン』『ケント・ベックSmalltalkベストプラクティス・パターン』『実装パターン』『アジャイルソフトウェア開発の奥義』とその筋の有名な本が並んでいました。

実装パターン

実装パターン

その後

 付録A 同時並行性IIではJavaのソケット通信を使ったクライアント-サーバーのプログラミングを例にとり、並行性の章を深掘りする章。スレッドに関する処理は一か所にまとめる、synchronizedする範囲は小さく、メソッド間の依存性は問題になる、クライアントでなくサーバーでロックする……など。かなり難しいし、このへん正直もう忘れてしまったな……と思いました。
 その後は題材に取り上げられているJCommonライブラリのSerialDateクラスとテストコードがどどんとそのまま掲載。1034行あったものがリファクタリング後は分割され、だいぶ短くなっています。後書きはTDDの緑色のリストバンドの話で終了です。

f:id:iwasiman:20200918235515p:plain
Clean Code

まとめ:洗練されたクリーンなコードを書いていくテクニックが詰まった本

 少しづつ読んだので負担はなかったのですが、紙の本はなんと528ページの大ボリューム。クリーンなコードを書ける職人魂を持ったプログラマーであるための気づきが、改めて沢山得られる本でした。
 まあ自分も開発経験はいろいろありますので、「そうなのか!」よりは、「うんうん、あるあるだよね」「これは必須!」「この決まりは知ってはいるんだけど徹底できていないんだよね」「このルールは使わないなぁ」「これはちょっと意識高すぎじゃない...?」などなどの様々な感想を持ちました。
 読んでいる間にもちょうど開発途中だったプロジェクトのコードで、あるクラスが大きいのに気づいてリファクタリングを実行して、ボーイスカウト・ルールを実行したクリーンな気持ちをちょっと実感したりもしました。

 本書で語られているクリーンなコードを追求するテクニックやあり方はプログラミング言語を問わず使えるものですが、とはいえコード例はほぼすべてJavaで書かれています。他の言語をお使いの方は適宜読み替える必要があります。
 またあえて難点を上げるとすると原著は2008年。2004年リリースのJ2SE5.0~2006年のJava SE 6の頃の話です。本エントリでも触れていますが、今となってはもう古い時代の話があちこちに混じっています。(EJB回りとか。)

このへんは、昔はそういう時代もあったんだなぐらいで適宜スキップする形で構わないかと思います。
 また、悪いコードのにほひがするBadな例として挙げられているライブラリがJava界隈では有名なApache CommonsJCommon内のコードであるのに対し、Goodな例や改善例によく出てくるのが作者さんたちが作った受け入れテスト用フレームワークFitNesse
このへんは、「へへーんオレたちの作ったOSSプロダクトの方が優秀なんだぜ~」的な若干のドヤ感も感じます(笑)。まあ本なので主張が入るものですしこのへんはそういうものなのでしょう。   docs.fitnesse.org en.wikipedia.org

 ちなみに本ブログでも前に感想を書いた『Clean Architecture』はもっとドヤ感とエモみが強くてそこが読んでいて楽しいのですが(おい)、本書は翻訳もですます調で静かに淡々と進行し、エモーショナルな波はかなり抑えめです。

iwasiman.hatenablog.com

 ブログなどで頻繁に紹介されたので日本でも読んでいる人の多い『リーダブル・コード』と、前半の名付け回りはなどは被っているところもあります。しかし本書はより深いところまで踏み込んでいます。(同様の感想を書評でよく見かけます)
 またこれまた名著の『Code Complete』で語られている一部とも若干似通ったところもあります。

iwasiman.hatenablog.com

 こうした、言語や技術の差を超えて通用する本、時代を超えても色褪せない抽象化の高い事柄を扱った名著や良著の類はいくら読んでも役に立つというのが僕の意見です。経験のあるエンジニアの方も多くがそう言われることでしょう。
若干古い本ですが、CleanなCode道場に一部だけでも入門するのは大いに意義があることと思います。  

Cleanシリーズの最新刊

本エントリ公開の頃は最新刊『Clean Agile 基本に立ち戻れ』も出ました!こちらも読むのが楽しみです。

Uncle Bobご本人+訳者陣の皆さんが揃っているインタビュー動画もあります。(字幕付き)

youtu.be