Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

『ルールズ・オブ・プログラミング』を読んだ #iknowtherulesjp

Ghost of Tsushimaなどを作った会社の人が書いた本です。ゲーム開発におけるコードを書く際の教訓を整理し、改めて示し直したいい一冊だったと思います。大事なことですが、著者は決して「このルールを絶対使え」と言っているのではなくて、そもそもまず会社の製品の特性上、このようなルールを敷いておくと品質や生産性を高く保てたという前提があり、その前提を元に「ルールを選び取って自分たちのコーディング哲学を構築しよう」と推奨しています。

この手の本では『リーダブルコード』がよく薦められる傾向にあると思います。私にとってもリーダブルコードは確かに駆け出しの頃すごく役に立った記憶はあるのですが(もう10年くらい前に読んだので正直忘れた)、そこから知識がアップデートされておらず、私の中ではもう古い書籍になっていると言えるかもしれません。そして、なぜこの本ばかりが現場エンジニアからやたら薦められるかというと、薦める上級エンジニア側がビギナーやジュニアのエンジニアにこの手の話を手解きする際、上級エンジニア側のコーディング一般の書籍の知識がアップデートされておらず、結果『リーダブルコード』が薦められるという傾向にあるのではないかと思っています。なぜかというとコーディング一般の話はあまりに常識すぎ、駆け出しの頃に一度読んでキャッチアップできたあとは、再び自分で知識をアップデートする必要に駆られない傾向にあるためだからではないか、と思っています。

が、著者も本書の中でいうように、そもそもコーディング一般のルール、すなわちコーディング哲学は、チームを組むたび、あるいはチームに新しい参画者が増えてきたタイミングで都度議論し合われるべきです。コーディング一般に関する新しいインプットをしていないということは、すなわちこの辺りの話を日常の業務でさほどしていないことの証左になりえてしまうかもしれません。この辺りは私も反省点があり、なるほどと思わされました。正直なところ、この手のコーディング哲学の本はあまりに当たり前すぎて読んでいて退屈ですからね。でも、よくよく議論しあうとチームメンバー同士で微妙に前提が違ったりすることはよくあります。たとえば、Java出身の私がC++出身者のチームに入ると、Java出身者の私はフルネームで全部書きたがる傾向にあるが、C++出身者は短い変数名や場合によっては省略された命名を好む傾向にある、などです。[*1]

この本には21個ルールがあるので、中でとくに個人的によかったものを書き留めておきたいと思います。なお、書籍内には数多くのC++コードが掲載されており、文章以外にはそれを通じて実際の議論の進行を眺めることができます。

1. 単純さ

「単純さ」というか「シンプル」は、コードを書いているうちに議題に上がることがよくあるものの、実態として何を示しているのかいまいちよくわからない単語ではないかと思います。著者はこのことについていい言語化をしているように思っており、紹介しておきます。

単純さの計測はたとえば、次の手段を用いているかもしれません。

  • チームにいる他のチームメイトにとってどれだけ理解しやすいコードになっているか?で測る。
  • コードをどれだけ簡単に作成でき、かつ作成できたコードに不具合が存在しない状態まで持っていくかにかかる時間で測る。

単純の逆は複雑ですが、複雑さの計測にはよく使われるものとして次のような手法があるかもしれません。

  • 書かれているコードの量が少ないこと。
  • 持ち込んでいる概念の数やそのコードを読むのに必要になる新しい用語の数が少ないこと。
  • 説明するのにかかる時間量が少ないこと。

忘れがちなんですが、持ち込む概念の数が複雑さにつながることはままあるように思いました。個人的な癖としてついつい新しい概念を創造し、それを表現するためのデータ構造を追加してしまいがちなんですが、これは確かに複雑性を高めることになるんですね。できるだけ少ない概念で説明できるか、もしくは概念同士に関連があり、一つがわかると芋づる式に他の概念も理解できる関係性を持つような概念を作り出す必要があるんだなと思いました。

なお忘れてはいけないこととして著者も強調していますが、目の前のお題の考察をおざなりにしてコードの単純化ばかりに取り組んではいけません。そもそも取り組んでいる問題を単純できないかとか、問題に対する解法を単純化できないかなどを先に考えてみるべきとのことです。その通りです。

4. 一般化には3つの例が必要

どのタイミングで目の前のコードを一般化するのは悩みの種ですね。将来を見越して一般的な関数にしてしまおうか…と逡巡することは多いです。下手したら1日1回はやってるんじゃないかな。

著者は明確に、一般化には3つの例を思いつかなければならないと述べています。わかりやすい基準ですね。逆にいうと、2つ例を思いつく程度では一般化をする必要はなく、2つの例に対してそれぞれ関数なりを提供すればそれで済むのだ、ということです。

一般化はたしかに数が増えてこればメリットは多少あるのですが、正直デメリットに直面することの方が多いでしょう。一般化の仕方がきれいでなかったばかりに、別のユースケースでの利用を行うと追加でさらに修正が必要になるようなパターンです。これにより本来一般化で享受したかったはずの変更のしやすさより、変更のしにくさの方が目立つようになってしまいます。すべては、一般化するユースケースの数が足りず、考察が足りないがために起きていることと言えると思います。

5. 最適化するな

  1. 最適化するな。
  2. 単純なコードを速くするのは簡単だ。
  3. だいたいの最適化に関する懸念は存在しない。みんな気にしすぎ。

基本的には1、2を守りつつ、たとえばHFTのトレーディングシステムをPythonで作っていて遅いと思うのであれば、ゴッソリC++に置き換えてしまえばいい、という主張です。個人的にはシンプルでおもしろいと思いましたし、そもそもシンプルなコードを書いていれば最適化が容易というのもなんとなく、経験則に合うような気がします。

6. コードレビュー

コードレビューの目的は、バグを見つけること以外には知識の共有があると言います。コードレビューの適切な実施はチーム全体に知識を広める優れた方法です。

ところで、より有意義なコードレビューをするために、筆者はさらにコードレビューを4つに分類し、それぞれ意義があるかどうかをまとめています。

シニアレビュアー ジュニアレビュアー
シニアレビュイー 有用 有用
ジュニアレビュイー 有用 絶対禁止

私もやはり同様にジュニア同士のコードレビューにはあんまり意味がないなと思っていて、この表の整理はしっくりきたなと思いました。シニアとのレビューは、不具合の発見だけでなく、たとえばコードのチーム内での規則違反を発見できたりとメリットが大きいとのことです。コードレビューは教育プロセスやオンボーディングプロセスのひとつと捉え、真剣に場を設計する必要がある行為と言えるでしょう。

コードレビューの副次的な効果として、成果物を他の人に見せる必要があるとわかっていると、誰しも良いコードを書くようにがんばるというものがあります。「仲間に喜んで見せたり誇れたりする仕事をしなきゃいけない」、つまり仲間の同調圧力(ピアプレッシャー)の健全版である、と言っています。

コードレビューは極めて有用な社会的交流の場であり、そういう側面を持っているからこそ、レビュアーとレビュイーの意見交換が積極的に行われるよう努める必要があります。レビューの場が論争になっているのは何かがおかしい証拠で、それではお互いに何も学べなくなってしまいます。あとは、コードの規則や命名について議論する場ではなく、そうした水掛け論に近い主観で決まる議論はチームで解決すべきです。

11. 2倍よくなるか?

既存のアーキテクチャをまるっと置き換える時の話です。この手の話は費用対効果を論じるのが難しく、定量的な議論をがんばろうとすると結果結論を得られず物事が進まないが、一方で現場はいつまでも限界を迎えつつあるアーキテクチャの上で苦労しながら機能開発を進めなければならない、みたいな話はまあまああるのかなと思います。

著者は、この手の話をする際には、経験則から「2倍」よくなるかで決めているそうです。2倍にはいろいろあり、単純な人月計算での工数を減らせたりとか、そもそもかかるメモリ量を減らせたりとか、そういった基準です。それを満たせなそうな場合は、一旦小さい部分の改善をするに留めておきます。シンプルでわかりやすい基準だと思います。使っていきたいですね。

なお、「作り直しは並列で行う」というルール19が後半で登場します。2倍よくなることがわかって、作り直しが決定したら、ルール19を眺めてみるのもよいかもしれません。

14. 4種類のコード

この章は次の表が印象に残っています。

「やさしい」問題 「難しい」問題
「単純」な解法 期待通り 野心的
「複雑」な解法 実に、実にひどい 容認されている

やさしい問題をわざわざ複雑な解法で解くのはやめましょう、ということでしょうか。FizzBuzzEnterpriseなんかはいい例かもしれません。

平凡なプログラマはやさしくても難しくても複雑な解法を書きます。優秀なプログラマは、やさしい問題ならば単純な解法を書きますが、複雑な問題は難しい解法で解きます。偉大なプログラマは、どちらも単純な解法でコードを書く…とのことです。

まとめ

この本はプログラマ個人がどのようにプログラミングをするべきかという議論も多少はありますが、どちらかというとプログラミングそれ自体を集団的・社会的な行為として捉え、それに対する著者の洞察をアドバイスするという書籍と捉えるのがよいのかなと思います。

大規模開発でよく起こるさまざまな事象に対して興味深いアドバイスをいくつかしてくれており、私も最近規模が大きめのチームを見ることになったので、自分としても活かせる点が多いなと思いながら読んでいました。

一方で、たとえばスタートアップやそもそも一人で開発しているといったシチュエーションでは、必ずしもこのルールが役立つとも限らないと思います。著者も前段や最後で指摘するように、これらのルールはそもそもゲームの大規模開発から得られる知見であり、自身のチームに合わせてこれらのルールをカスタマイズする必要があると思います。

網羅的なコーディング指南書というよりかは、どちらかというと私のようにスタッフエンジニアくらいの人が、自分のチームやプロダクトの開発の指針をどうしていこう?と考えたときに、一定程度の指針を与えてくれる一冊なのかなと思っています。シニア〜スタッフエンジニアくらいの目線を知りたいまだシニアでないエンジニアにも、そういう意味ではおすすめできるのかもしれません。

*1:これは昔の職場での実際の経験談です。もしかすると、C++のように言語によらない話なのかもしれません。