Hash λ Bye

Haskell, Clojure, MLなどの話

『リーダブルコード』を読んだ

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック

まあ、なんかみんな読んでそうだし、すぐ読めそうだしと思って読んでみた。 内容の引用はせずに感心したポイントだけ書く。

全体として

コードは理解しやすくなければならない

という基本原理に従ってそれを実現するためのコードの捉え方や書き方を伝えている。 それぞれの基準や考え方はこの本に限らず言われていることなので、この本を読まずとも自然と身につく人もいそう。

自分も過去に書いた自分のコードを見返して分かりにくいな、と感じることはこれまでたくさんあった。 その経験から少しずつ上記の原理のような「読み手に伝える」コードを意識するようになっていった。

なので自分の中にあった心がけみたいなものが、この本で改めて言語化されて思い出されたというのが雑感。 こまごまとしたポイントでは「なるほど」と思えるところもあったので、以降ではそういったポイントを拾っていく。

チームでコードを書くにあたって、本書の内容を一つの基準として持っておきたいと思うし、 チームの人達にどうシェアしていくべきなんだろうな、と考えるきっかけにはなった。(ので読んで良かった)

感心したポイント

4.4 縦の線をまっすぐにする

テストコードを書いていると同じ関数に対して実引数を変えて何度も呼び出すようなパターンに出くわす。

ここで実引数の並びが縦方向で整列するように視覚的調整を行うのは、忘れがちだけれどもやってみると幾分見やすくなったと感じるので良いと思った。 パラメータの組み合わせを一瞥して把握できるようになる。各ケースのパラメータの違いが強調されるので、読者もテストのパターンや入出力の対応に集中することができる。

コードの桁を整列して視覚的ノイズを減らすことで、強調したい部分をうまく伝えている。

GroovyのテストフレームワークであるSpockのData Driven Testではさらにデータの対応を強調した表現に特化した構文がある。 Data Tables

5.2 自分の考えを記録する

TODO, FIXMEなどを使って他のメンバーに向けて課題を共有するみたいなことは今までもあった。 しかしここで言われるように、より素直に設計の失敗や懸念をコードに残しておくことはあまりできていなかったかもしれない。

コードを書いている時に発想として「これはこういう構造を参考にできるな」と気づいた時などは、 参考ページへのリンクや考え方の説明を試みたコメントを残すこともあったものの、 もっとカジュアルに「このコードを読む未来の誰かのためにあれこれコメントで語る」まではやっていなかった。 この節を読んで、そのくらいまで気軽に書いていいのかもな、とも思った。

ただ、後々コードが消されたりするタイミングがあると、そのプログラマが「このコメント消していいのかな」と迷ったりするかもしれない。 他の人にとってのそのコメンタリーの言及スコープが明確でない、ということなのでコメンタリーとしては失敗していると言えそう。

6.5 入出力のコーナーケースに実例を使う

これは自分の知る範囲でもPythondoctestHaskelldoctest などで活用事例があった。 ある関数の振る舞いを直感的に捉える上で例が載っていると、理解がぐっと早くなるのは自分の体感としてもある。 コーナーケースの振る舞いを文章で記すだけでなく例示があれば、読み手も説明文に対する自分の理解が正しいかを確認できる。

自分の場合は規則や振る舞いについて一般的な説明を受けただけだとどうしても消化不良になりがちなので、 具体例を記してあると自分の理解の答え合わせができて安心する。

7.6 悪名高き goto

gotoはJavaでいうfinallyブロックへのジャンプのような使い方をする限りにおいてはそんなに忌避しなくても良いのでは、という話。

linuxkernel/fork.c をチラ見してみたが確かにリソース開放処理を漏れなく行うブロックにラベルを付けている。 なにか処理中断の必要性を検知するとそのラベルへと goto でジャンプしてリソース開放をしているようだった。

ちなみにいくつも goto に行き先があったり呼び出し元側へとジャンプしたりするのはスパゲティの元になるので駄目、と本書では言っている。

それならJavaで非検査例外を投げて2, 3スタック上でキャッチするような行為は普通に処刑になってしまうのではと思った。

8.7 式を簡潔にするもう1つの創造的な方法

マクロを使ってボイラープレートを消すのもいいのではないか、という話。

この例ではマクロの宣言と使用が関数の中に閉じており、フォーカスが明確なのであとで混乱を招くことは無さそうだ。 混乱を招かない限りは創造的と言ってもいいのだが、マクロを使うこと自体が創造的であるかのように勘違いしない方がいいな、という。

確かにマクロ導入前のコードでは色々なシンボルにまぎれてしまい、どのデータに注目しているのか分かりにくくなっている。 それに対してC++のコードでのアプローチがたまたまマクロであっただけで、他のプログラミング言語ならまた別のアプローチ (HaskellだとLens) で解けるのだろう。

9.3 変数は一度だけ書き込む

自分がイミュータブルなデータを使ったプログラミングが好きなのでこの節に反応しただけ。 Effective Javaのように長く愛されている書籍でも確か似たようなこと言及されていた気がする。

変数が長命で書き換わる回数が増えれば増えるほど変数の現在の状態を推測するのが難しくなるみたい。

と言いつつ僕たちはレジスタを長時間に渡り書き換えまくっているのに、それに対してはそんなに拒絶反応を示さないのは、なんでなんだっけ、と思ったりする。

10.4 汎用コードをたくさん作る

Lispの本とか読んでいるとよく言われるボトムアッププログラミングのアプローチの一端がこれなのかなと思っている。

コードの進化の方向がボトムアップなのかトップダウンなのかということが問題でもない。 どちらかというとドメイン固有関数とデータ一般を操作する関数を分けよう、というコンセプトが大事なのではないかと思う。

このビジネスロジックとライブラリ的な分割に基づいてアプリケーションのレイヤを捉える話は以前に プログラミングClojureにおける「データ」とは何か で書いた。

14.3 テストと読みやすさ

独自の「ミニ言語」を実装する

というのはテストに必要な入出力などの定義を独自の記法で表せるようにして、 テストコード内にその解釈系を実装する、というアプローチ。

個人的にはこういうローカルなDSLを各テストの都合に応じて拵えることを許すと各テストでミニ言語が散らばって、 かえって認知負荷が増大するのではないかなと危惧しているのであまり勧めたくない。

できるだけそのプラットフォームやライブラリでサポートされているデータ構造と関数を素直に使ってテストを書いた方が良いと思う。 その上にもう1つ解釈系のレイヤをわざわざテストコードに差し挟むことが繰り返された後の混乱の方が怖い。

コードの中に独自の拡張した構文を入れる、みたいなのLispでもあるけどやはり濫用は避けた方が良いとされるし、 使う前に熟考して欲しい感はある。

と、まあだいたいそんなところ。