開発イテレーション偏重

開発イテレーションを早くすれば、かなりの問題が勝手に解決される、と信じています。なんか最近、他の要素を軽視しすぎていたり、特にイテレーション速度に影響しなさそうなことすらしている気がしていて、信仰とかのレベルかもしれない、という気がしてきたので、ちょっと書いてみようかなと。主に C++ の話です。

仕事とかしてると良い判断力が求められたりしますが、判断というのは結構難しいですよね。アプローチ A と B で悩んだ時に、手が速ければ両方できたりします。開発イテレーションを無限に速くすると、必要とされる判断力はゼロに漸近していきます。やったね。

2手で変更の正当性を高速に確認できるようにする

make (かその他のビルドコマンド)てやったらビルドができて、 make check (かその他のテストスクリプト)てやったら遅くないテストが全部走る、という体勢が好きです。試すためにはあっちのディレクトリで make してこっちで build.sh を走らせて、それで bin/test foobar とかするとできる……みたいなのはとても良くない。単純に新しくチームに入る人とかにとっても厳しいですしね……

そのためには(正直どうかと思うけど)テストの網羅性を捨てて良いとさえ考えてます。まあ理想的には CI では網羅的なテストが走るけど、手元でイテレーション回す時のテストはそのサブセット、みたいなのが良いのでしょう。 CI といえば CI の時間ちょっと削るとかも好きで、好きなんだけどその時間たぶん本来の作業に回した方が良いケースも多いと思うので、信仰だなあと思うゆえんでもあります。

イテレーションを高速化するためならどんな努力でもするので、ちゃんと複数プロセッサ使って並列にテストが走る仕組みを整えたりもします。

細かいユニットテストを避ける

ユニットテストをちゃんと書いていても、なんか結局それを使っている main の部分とか、 Python インターフェイス生やしていたらその接続部分にバグが出たりするので、なんか結局 end-to-end で起きる挙動でテストするのが一番確実だと思っています。 Ruby なんかはほとんど (全部だっけ?) のテストが Ruby で書かれてると思うんですが、そんな気持ちです。

なんなら、これもどうかとは思うのですが、細かすぎるユニットテストを書かないようにしています。細かいテスト書いてるとコケた時にその場所の特定がラクになるなどのメリットがあるのですが、テストがコケる確率ってそんなに高くなくて、コケた時にかかる時間の節約に対して、ちゃんとユニットテストを書くコストの方が高いということは結構あるように思う。 Ruby の例のように、 end-to-end のテストの方が、細かいユニットテストを書くより楽なケースは特に。 AST を自分で構築するより、テストしたい AST になってくれるであろうコードを書いた方がラク、という話。

ヘッダを最小化する

ビルド時間を最小化したくて、ヘッダサイズって結構効くという信仰を持っているので、なるべくヘッダを小さくしています。昔はヘッダ書くと最低1つはクラスがあったのだけど、最近は関数2,3個宣言されてるだけ、みたいなことが多い。

信仰なので本当に効いてるのかはわかりませんが、ヘッダオンリーライブラリとか使った時に、その部分だけ明らかにコンパイルが遅かったりするので、世の中の人もっとヘッダをシンプルに、できればヘッダで template 使わない、など気をつけてくれるとなあ……とよく思っています。

あとまあよく言われることですが、過剰な抽象化とかですね。 YAGNI!

やたらチェックする

イテレーションを最速化したいのであって、タイピングを最速化したいのではないので、割とやたら assertion 相当のコードを入れます。例えば

CHECK_LE(0, i);
CHECK_GT(x_.size(), i);
return x_[i];

のようなコードを書きます。 x_.at(i) をすればチェックした上で失敗したら throw してくれるのはもちろん知っているのですが、例外が起きた時に、どこで起きた例外かをバックトレースから特定して、 gdb で i の値がどうなってるか、などをするのが結構手間なので、呼び出し元の行数と、比較している値を表示してくれる、上記の(glog由来の)マクロがお気に入りです。

また、 CHECK_EQ(x, y) << z みたいなことができて、 z は x != y の時だけ評価されて表示されるのですが、この時 operator<< が x, y, z の全てに定義されていないといけないので、めんどくさいけど、積極的に stringify するコードを書くようにしています。

最適化オプションつきで開発

これはケースバイケースで、特に初期の頃はむしろ -O はつけずに作業しますが、テスト時間が増えてくると、基本的に常に最適化オンで作業して、たまに起きる gdb でのデバッグ作業がつらくなるのは我慢する、というスタイルにすることが多いです。上記マクロでそこらじゅうにチェックが入っているはずなので、 gdb が必要な可能性を落としている……はずです。

あと、大きいプロジェクトなら -g も切って、イテレーションを最速化して printf 入れまくった方がデバッグしやすい、とする時もあります。 LLVM で遊んでる時とかはそれが結局一番良いように思いました。

あと、リリースビルドで消える assertion 、という概念が嫌いです。ほぼ 99.9% パフォーマンスに影響しないくせに、副作用のある式の記述が面倒になり、それを解決すると参照されない変数ができることがあって嫌い。 NDEBUG 時だけ起きるコンパイルエラーとかで時間を消費させられるのが嫌い。

なるべく auto を使わない

auto 、書いてる時はラクなんですが、読んでる時にかかるコストが増えるので、損益分岐点は 20 文字くらい、かなあと思っています。 iterator なんかはめんどくさいし文脈から自明なことが多いので、 auto 使う、みたいな気持ちでいます。

auto に限らず、ループ内包や std::transform 、三項演算みたいなやつも、書くのがラクになるかわりに、単なる純朴なループや分岐の方が可読性が高いと感じることが多くて、他の人より使う頻度が低いなあ、とよく感じます。

一方で using namespace はあまり実害が無くてコスパ良いと思っている(特に std はスタイルが違うので無くても明白)のですが、コーディング規約で許されてないので諦めています……

まとめ

なんか他にもある気がするのですが、他と見比べてて、自分のスタイルが違うと感じることが多いところを並べてみました。他の人の工夫とかも気になるところです。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h