2014年05月22日

コードを書くときに心がけていること

なんか流行っているので乗ってみます。

趣味コード

趣味とはいっても、暇つぶしだったり、流行りもののチュートリアルに触って「おれ新しい◯◯やってみたぜ」みたいなのは極力しないようにしてます。仕事で必要になった時に、仕事の時間の中で集中的に学ぶ方が学習効率が高いので、趣味時間の活用という意味ではもったいないですよね。幸い、まったく未知の基礎的な内容というのはほとんど出会わなくなってきて、新しい技術といっても、既存の知識を土台にして、軽く検索すればOKなことがほとんど。ということで、趣味といっても、将来の仕事で役に立ちそうな種となる可能性のあるものを作るように心がけています。実際に種になるかどうかは運次第なので、命中率に関しては気にしません。

とは言うものの、先月末に二人目の子供が生まれてからは趣味のコードの時間はまったく取れてません。まあしばらくは潜伏期間かな。子供が二人いれば、少し大きくなったら子供同士で遊んだりしてくれるようになるらしいので!家で勉強する父親の背中を子どもたちには見せる予定です。勉強しない大人から「勉強しろ」って言われても説得力がないし、反発されるだけですしね。Wishリストはこちらです。

仕事のコード

いろいろ書いているけど、「いきなりチームに投入されても、既存のチームメンバーの邪魔をせずに、きちんと成果を出す」というのがコアですね。

ドキュメントなんて存在しない

ドキュメントは最初の一歩を踏み出すためにコンセンサスを取る上では大事ですが、どんどん変化しつづけるコードベースの場合、その仕様をすべて網羅するようなドキュメントがきちんと整備され続けるという職場に出会ったことがありません。仕様書はファーストバージョンで止まっているでしょう。

ユーザ向けマニュアルなんかを書く場合は、そこそこ追従しているでしょう。Doxygenとかpydoc、JSDocみたいなツール向けのコメントも比較的まともなはず。むしろ、そこをメンテしないでコードとのギャップを作る人はタバスコを目薬にする刑に処するべき。それ以外のドキュメントは期待しない方が吉です。積極的にコードを読んで意図や行間を把握できるようにします。コードが完成してからドキュメント書くほうがムダがないですよ。Sphinxとかオススメです。

自分の色は出さない

「俺のコーディング標準の方がかっこいいぜ」というのは、プログラマが一度はかかる麻疹の仲間だと思うのですが、他の人とコードを書いている以上は他の人のスタイルに合わせます。他の人のヘルプで後からプロジェクトに入る、みたいなケースが多いのですが、まずは既存のコードを読んで「こういうコーディング規約なのね」というのを読み込んで、真似して書いていきます。部分的にかっこいいかもしれないけど、他の人がコードを見ていった時にスタイルが違うコードというのは、脳に7つしかないワーキングメモリのうち、確実に1つを奪ってしまうため、理解を妨げます。

このスタイルというのは、インデントとか見た目の問題だけではなく、フォルダ分け、クラスの粒度、コールバックの返し方、引数の順序などの構造化の部分も含みます。他の人から見て、読みやすさというか、理解のしやすさが変わってきます。

幸い、僕が仕事をしてきたチームの人は、インデントがバラバラとかそういうレベルの低いコードを書く人がいなかったので、既存のコードを真似るので十分でした。もしそういうレベルでバラバラということであれば、自分のところから少しずつ統一していくというのは手ですね。その際も、その言語の標準から離れないようにするのが一番摩擦が少なくて良いかも。

スタイルのチェックは自動化する

最近のgolangなんかはコーディングのフォーマットを自動で直したりできます。僕が仕事でよく使っているJavaScriptにも、スタイルチェックしてコードを実際に修正してくれるツールもありますが、そこまでいかずとも、最低限はチェックの自動化はします。命名規則以外では、チェックが自動化できないような難しいルールは適用しないようにします。JSHintを使って、記号の前後のスペースなども含めた、なるべく厳しいルールファイルを作ってコミット時のチェックに組み込みというのが今のところの成功パターンです。

仕様は決まらないものと心がける

仕様はいつ決まるんでしょうか?RFCがあって、それにしたがって実装するという開発でなければ、基本的に仕様は決まらないものとして考えます。すべての決定は「仮ぎめ」です。B2Cのウェブサービスなんかは、実際に提供してみて反応を見てみないとその機能で良かったかどうかなんてわかりません。受託開発みたいなケースだと、あらかじめ要望を聞いて、何をするか、何をしないかの線を引いて、その決定に向けて開発をしていきます。それは契約上仕方がないものですが、その仕様も所詮は仮ぎめです。決まらないリスクをユーザが取っているだけすね。ユーザの代表者に出てきてもらって意見を出してもらって仕様を考えて、それを実装して提供したとしても、他のユーザから「使いにくい」という意見が出てくることもあります。例え「インサイト顧客だ」「プロダクトオーナーだ」とロールを定めてみたところで、その人が本当に正解を知っている保証はありません。

最終的に仕様が決まるのは実際にコードを動かしてしばらく使った後です。事前の打ち合わせは「あらかじめ考えれば分かるはずの落とし穴」を避けるためのものぐらいの認識です。コードを書いて動かしてみて、足りないものがあれば足し、間違っていたら書き直します。打ち合わせもユーザの意見もコーディングもテストも、最終的な仕様を決めるための手段。決まったらそこで仕事が完了。ユーザが考えているよりもスムーズなワークフローを思いついたら、それを実際に実装してみて「これどうや?」って見せることもあります(もちろん話で説明したり、ドキュメントで説明することもある)。大きな流れさえきちんと抑えられれば、そこまで大きな巻き戻しは発生しないでしょう。

モックオブジェクトは死ね

TDDが死んだとか楽しいとかが少し前に話題になりました。まぁこれはマサカリを投げてみたら仲間に刺さって口論になったぐらいの案件という理解。

  • 本や三原則から考えればTDDの定義はテストファーストが前提となっているのは明確。
  • テストファーストをなくしてもTDDと言えるかというと、開発を駆動(次に行うべき行動がテスト/コンパイルエラーとして表示される)してないのでそれはKent BeckのTDDじゃない。
  • Kent BeckのTDDから変化したものはTDDじゃないか?@m_sekiさんが「どこを変えたらTDDでなくなるか?」という実験をされていたりする。
  • テスティングフレームワークを使ったテストはみんな価値を認めるところ。事前に書くのがTDD的にはベストだけど、事後でもバグを見つけるのに良いです。人間そんなに記憶力良くないので1年もコード書き続けていたら、どんどん検証に時間がかかるようになって開発速度はどんどん落ちる。半年間書き続けたコードの依存関係を完璧に覚えておける人だけが石を投げなさい。
  • 開発速度は徐々に遅くなっていくなど、時間が経つと変化してくる(1次関数では表せない)ものなので、何%早いとか、何%品質が上がるなんて数字化できるはずがないです。よく分かってないけどなぜか権限を持っている大人を説得するための方便でしかないなんて、みんな気づいているよね?
  • フレームワーク主体の開発だとTDDが使いにくいのは事実です。ただ、それはTDDが膝に矢を受けたというだけで、TDDが死んだわけではない。ここで死んだとか幻想みたいな言葉をマサカリだと思って投げると事故が起きる。
  • データベースにテストデータを流し込むヘルパーを用意したり、ルーティングテーブルの外からミドルウェアを発火しつつパラメータを渡すようなヘルパーを用意したり、フレームワーク側がサポートしてくれれば、ちょっと事前準備に手間はかかるけどテストファーストは不可能ではない。
  • イベントループが内部に完全に隠蔽されたGUIフレームワークに比べたら、オープンソースのウェブのフレームワークはまだ光明あるよね。
  • TDDを再び開発の中心に持っていくには、フレームワークがどういう機能を備えればいいか?クライアントが複数つながっているWebSocketのテストを簡単にするためにはどうすればいいか?みたいなことを議論した方が建設的。

まぁ、そんな話は重要じゃないので置いといて、「ユニットテスト」と呼ばれてた時代に言われなかったけど、TDDになって以降に話題になったものとして、モックオブジェクトがあります。モックオブジェクトにはもちろん利点はありますが、欠点もあります。個人的には欠点の方が大きい気がしています。

テストコードは「実行可能なドキュメント」と言われることもありますが、モックオブジェクトを使うとドキュメントの質がすごく下がるんですよね。ブラックボックス的なテストコードの密度が高ければドキュメントとして読みやすいのですが、テストの準備コードが幅を利かせてくる。そんでもって、内部実装が丸見えなテストになってしまう。致命的な欠点は、モックするオブジェクトの挙動が変わった時、あるいはモックの設定が間違っていた時に、その問題に気づくのが遅れるという欠点があります。コードをかけばバグが出ます。コードを書かなければバグは出ません。テストコードにだってたまにはバグが入ります。モックオブジェクトを書くとテストコードにバグが入る確率が格段にあがります(ロジック部分は赤→緑のサイクルでテストのバグはある程度潰せます)。テストコードのためのテストを書く?ますます本末転倒ですよね。

JavaScriptであれば、非同期のコールバックとかPromise周りをちょっとフックするぐらい、あるいは、外部モジュールを呼び出す時のパラメータチェック程度にモックオブジェクトの使用を抑えるべきかな、と思います。テスト高速化のためにデータベースとかネットワーク呼び出しをモック化とか、そういうのは辛いです。開発の中でテストを高速化するのは、JavaScriptのMochaであれば、onlyを付ければ特定のテストケースだけ実行ができるので、全部のテストの実行に時間がかかるというのは気にしなくてもいいはず。他のフレームワークでも似たようなことできるの多いと思います。MongoDBとかRedisならコレクションをクリアするのは高速ですしね(MongoもコレクションじゃなくてDBのdropはさすがに遅い)。実データベースを使ったテストも苦じゃないです。

テストコードはドキュメント。ノイズは減らす

コードは人間が読むもの。テストコードももちろんコードです。モック派は単体テスト(ユニットテストとは別の言葉ですよね)重視だと思います。もちろん、テストの理論をきちんと知った上でモックをきちんと設定して、C0/C1カバレッジ、境界テストあたりまで網羅できるなら、それはひとつの理想な気がします。とはいえ、コードが増えればメンテの手間も増えるし、引き継ぎとかで読むのも大変になります。個人的には単体テストと結合テストの間ぐらいを狙っています。ロジックが本番環境でデータベースにアクセスするなら、ユニットテストでもデータベースを実際に使います。もちろん、ユニットテスト同士を独立させ、順序が違ってもきちんとテストが通るように、ローカルのDBをクリアするようなヘルパーは用意する必要がありますが、余計なコードはその程度に抑えます。外部サービスに依存している部分はその機能をエミュレートしたダミーのモジュールにまるごと入れ替えられるようにしておけばいいかな、と思います。

テストコードをドキュメントとして読む時は、上から下までそのまま読めるのが理想だと考えています。フレームワーク固有のヘルパーとしてまとめられる部分は外部ファイルにまとめますが、初期化その他のプロセスを細かく関数に分けたりといった構造化は極力しません。before/afterなどのテスティングフレームワークで提供されている程度にとどめます。テストコードの構造を追いかけるのに脳のスタックメモリを使うようなテストは、対象となるコードの理解を妨げます。コードジャンプしながら読むとかなるべく避けたいところ。ゲームブックを速読できる人はいませんよね?テストは主役じゃないので、なるべく省エネルギーで読めるのが望ましいです。初期化→入力値の設定→返り値の取得という流れが見える、かつ一画面に収まっているテストが最高ですね。

時にはカバレッジを気にする

ライブラリとして他のコードから利用されるコードなら常に気にすべきだと思いますが、アプリを作るみたいなケースだったらあまり気にしません。計測も積極的にはしません。ただし、ロジックとして切り出せる部分であれば、すべての選択肢を網羅するようなテストを書いたり、境界条件の前後できちんと動作するかどうかのテストを書きます。

本来なら、コントローラ周りとか、ビュー周りのロジックがきれいに切り出せない部分のコードでも、正常系、異常系のテストも、可能なら全部網羅すべきですが、まずは正常系は最低限。異常系も、対象コード内で発生する異常系なら入れるのがベターかなと。その代わり、きちんと境界値テストできるモジュール側にコードをなるべく寄せるようにはします。依存しているモジュールによって発生する異常系は、まあ時間があれば。

共通部品化の注意点

「共通部品を増やして速度改善」のように速度を基準に考えるのは間違いです。共通化はリスクの低減のための手段と割り切るべきです。副次的にもしかしたら速度アップが得られるかもしれませんが、自販機の当たりのようなものと考えるべきです。

業務でコードを書いていて、共通部品(ライブラリやフレームワーク)が充実すればするほど、組織の開発力が上がっていきます。そういう意味で、決められた機能を満たす動くアプリを開発する以外にも、「共通部品を残す」というのも組織の大事なミッションと言えます。開発力が上がるということは開発速度が上がるということが多くの人の想像するところですが、ここに落とし穴があります。今年のGDCのソニーのATFのセッションで「共通で使えるようなコード」を書くと、3〜4倍の時間がかかると説明されていました。共通コードの内部側だけではなく、共通コードを使う側のコードの平準化などの作業が必要になるからです。複数のチームがいればチーム間の折衝も必要になるかもしれません。ドキュメントも必要になりますし、きちんとしたテストも必要でしょう。コードを修正したら、他のチームに影響がないか、あった場合には他のチームのコードも直す必要があるかもしれません。

3〜4倍の時間がかかるということは、3〜4チームがその共通部品を使って初めて元が取れますが、いきなり3〜4チームが使うなんてのはレアケースかと思います。「速度重視」なチームの場合、共通部品のリポジトリがあったとしても「折衝が面倒だから、とりあえずフォークしてカスタム版を作ってしのごう」となりがちです。各チームがフォークしだすと、それをまた一つにまとめるのは難しくなります。もちろん、共通部品が枯れてくれば開発の速度も上がるでしょうが、それは副次的な作用でしかないです。共通化は各チーム間の暗黙知の「表出化と連結化」のために行うのを再優先にした方がうまくいくはずです。前提の知識がそろうと、チーム間の人やコードの移動などがやりやすくなります。そのようなリスクの低減を共通化の目標とすべきです。例え使うチームが少なくとも、技術的に難しい部分を部品化しておく、というのは一つの手です。これも技術的リスクの低減ですね。

共通化では他のチームのコードに踏み込むことがありえるので、死んだかどうかにかかわらず、テスティングフレームワークを使ったテストは用意してメンテしておくべきですね。テストの仕方もチーム間で共通化が大事です。

posted by @shibukawa at 17:19 | TrackBack(0) | 日記 はてなブックマーク - コードを書くときに心がけていること
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/97749572
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック
検索ボックス

Twitter

www.flickr.com
This is a Flickr badge showing public photos and videos from shibukawa.yoshiki. Make your own badge here.
<< 2016年09月 >>
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  
最近の記事
カテゴリ
過去ログ
Powered by さくらのブログ