きれいなコードを書けという話について
前回のブログから90日以上経ってしまったので広告が載ってしまったから短文でもアウトプットしておく。
プログラマとして仕事をしているとコードと向き合っている時間の9割以上は既存のコードを読んでいる、だから読みやすさは重要である、という言説は耳にタコができるほど誰もが言っている。
仕事で書かれるコードが誰のレビューも通ること無くマージされている現場は凄惨だが、自分より明らかに経験を積んだ人たちが何度もレビューを重ねたコードが読みやすいかというとそうとは限らない。良いコードが守るべきルールをすべて守っていても不可解なコードはあるし、どんなに読みやすいコードでも数千行の規模になってくるとやはり脳内からこぼれて一度に覚えておける範囲からはみ出る。
変数名や関数名をわかりやすくするとか不必要な技巧を凝らさないとかわかりやすい設計にするとか主観的な事を偉そうに語る本は山ほどあり、それらの本を崇める事は悪い事ではないが実際の現場に出て長くプロダクションのコードと向き合うとみんな自然と現実が本のようには行かないという諦観のようなものが出てくる。それは同僚の書くコードが汚いから、なんて単純な話ではない。
巨大なコードを読むときに真に必要なのはありきたりな教条ではなく、書いた人の気持ちを脳内で再現することである。
書き手と読み手の心が一致した時にようやく読みやすいコードになるのであって、変数名だの諸々はその前では些細な問題である。読み手と書き手が同一人物であってもここの不一致は依然として起こりうるので、そこを一致させるまでの時間を短くするよう心がけるべきだ。
これを解決する絶対的な方法は未だ発明されていないが、気持ちをエミュレーションするのに助ける方法という観点ではいくつかのtipsがある。
1. デザインドキュメントをたくさん書くこと
何か特別なフォーマットに従って書く魔法のような文書として一部の界隈で扱われている気配のある「デザインドキュメント」だが何のことはなく、チームメイトや将来の自分がこのコードを読むときに首をかしげそうな気配を感じたらとりあえず書くブログ記事のようなものである。文量もフォーマットも決まった物はないし、必ずしも書いた時点で正確でなくていい。どういう現状認識をしていてどういうメンタルモデル(もしくは誤解)に基づいてどんな意思決定をしたかをありのままに書き記しておけばレビューのときにそこを含めて議論ができるし後から考古学をするハメになったときにも大いに助けになる。例えばChromiumのコードなんかは検索してみるとデザインドキュメントへのリンクらしいものが大量に見つかる。go linkと呼ばれるこの仕組みはGoogle社外からはアクセス不能な短縮URLなのだがコード中のコメントに入れておくと読解の際に大いに役に立つ魅力には勝てず、社外から見えるコードの中にもバンバンおかれている。
go linkなんか無くても同一リポジトリ内のそこら中にmarkdown形式でカジュアルにデザインドキュメントを書いてgithub上で辿れる程度のリンクの形でコメントから参照していくのは大いに有益である。コードの質だけではなくリポジトリ内のドキュメントの充実度合いがプロダクトの成熟度の指標として語られても良いぐらいだと思っている、古かったり重複したり冗長だったりするデザインドキュメントは躊躇いなく直したり消してしまえば良いし、人間なのだから自然言語の方がコードより正確に読めるはずである。
2. blameをわかりやすい粒度にすること
眼の前にあるコードは何らかの意味のある事をしているがどこかチグハグで意味が破綻しており「頭のいい人ならこんなコードを書くはずがない…」と混乱することはよくある。厳重なレビューをくぐり抜けてなお、既存コードとの思想レベルの一貫性はどこかで綻ぶものであり、幾多の改変と拡張を重ねた果てに何が目的だったのか読むだけではよくわからなくなっているコードというのはいずれ発生するものである。
そういう時はコミットログの方からここに加えた変更を見て、どういう意図で何を変えたのかという単位で物を見たほうが書いた人の気持ちになりやすい。一つのパッチはたいてい一人の人間が書いているのだからエミュレーションする難度は格段に低い。もっというとパッチのPull-Request上の議論中にもデザインドキュメントなどへのリンクがどんどんあって然るべきである。コードは結果であり単品で意味を汲めなくてもパッチは行動であって意味を汲みやすいし、日記ぐらいのつもりで多くの情報が籠もっている方が考古学的に読む方はありがたい。単一の変更をレビューや変更規模のために複数のパッチに分けるなら、全部のパッチのコメントの中に「これは同一の目的のために作られた一連のパッチであり直前のパッチのリンクはhttps://...である」とか書いてあると嬉しい。
3. デザインドキュメント以外の文章も残すこと
プロダクト全体の方向性がどこを向いているかというのは四半期ごとでも半年ごとでも見直してビジョンとしてドキュメントの形で示されていると一連のパッチがどこを目指して作られていたか推測しやすくなる。チャットログも日記もあるならあるに越したことはなく、当時どこにハマっていたかとかどういう些末な問題に頭を抱えていたか等も後から見た人が追えているとわかりやすい。わかりやすくなるなら開発者個人のブログ記事にすらコードのコメントからガンガンリンクを貼っていい。
作った当時の人間が誰一人生きていない石碑のように、それがどういう文脈の上でどんな目的の物かわからないので気軽に破壊していいものかどうかすらわからない遺物がゴロゴロしているのが長命なプロジェクトであって、うまく行っているプロジェクトは何人かの長老のように詳しい人がその辺を全部担当していくとか時々エイヤで吹き飛ばすといった何らかの方法で対処している。あらゆる物はいずれ古くなるし常時さまざまな情報と仕事が吹き荒れるせいで石碑以上の速さで情報が風化するのがソフトウェア開発の現場であり石碑以上の冗長度で文脈を記録しておく方が好ましい。
4. 不要なコードとテストをどんどん消すこと
コードの認知負荷を高める要因は多岐に渡り、誰もが口にする変数・関数名に始まり対処法としてオブジェクト指向の特定の技法を用いる事でコードが見やすくなるとか様々な対処法が編み出されているが、どんなに完璧な方法で整理されていても1000万行のコードを認知するのは常人には無理だし、行数が増える事自体に対する絶対的な解決策は発明されそうな気配がない。なので現実的な対策としてコードをとにかく消す事が好ましい。何ならプロダクト全体を見て恩恵の薄い機能を仕様ごと消し去るぐらいが出来たらいい。テストを常に書けという流派の方々は怒るかも知れないが実装をほぼ鏡写しにしたような粒度のテストなどは仕様変更時の作業量が増えるし、TDD的にテストを先に書く事自体は場合によって有用だがTDDの過程で例えば作業用の足場のように書いた三角測量用のユニットテストなんかは最終的に似たような機能を繰り返し測定することになりがちなので消せるものなら消したほうがいい(異論は認める)。より少ないテストで機能の充足を確認できる方が認知負荷も減っていい。もっと言うと各パッチに対してコードの美しさという評価軸を加えるなら、消去行数が多いパッチはもうその時点で美しいと言って良いかも知れない。
このコードはどういう条件が満たされたときに消して構わないか、ステークホルダーと常に認識を合せていられるぐらいが好ましく、最悪のケースは何やってるかよくわからないコードだけど消すと知らない人が怒るかも知れないので永遠に消せないというパターンである。できる限りリポジトリの多くの部分が前者になるよう努力し、後者になる部分が少なくなるようコード全体には常に消去への圧力をかけるぐらいでいたい。
こうして書き並べてみるとコードの読みやすさというのは書き方の小手先や技法でどうにかなるものではなく、時系列方向への弛まぬ努力の果てに達成しえる物であると言える。
「ソフトウェアエンジニアリング」とは単にコードを書く行為のみならず、組織が時間の経過に応じてコードを構築し保守するために用いるツールとプロセス全てをも包含するということである。
とGoogleのソフトウェアエンジニアリング本にも書いてある。この本は本当にいいことが書いてあるので万人におすすめしたい(唐突な宣伝とアフィリンク)