ソフトウェア開発を支援するために、やりたいことや問題を管理するシステムがあります。例えば、Bugzilla*1やRedmine*2、GitHubなどがそのような機能を持っています。システムごとにやりたいことや問題の呼び方が違います。例えば、Bugzillaでは「バグ」、Redmineでは「チケット」、GitHubでは「Issue」と呼んでいます。ここではRedmineと同じ「チケット」と呼び方を使うことにします。
今回紹介するのは、後からチケットを見たときに、チケットを書いた時に知っていた情報を思い出せるようなチケットの書き方です。
このような書き方は、プロトタイプのような「作って終わり」とか「短期間の開発」というようなソフトウェアでは必要がないでしょう。また、後述の通り、「チケット駆動開発」にも使えないでしょう。継続的に開発を続けていくソフトウェアの開発のように、やりたいことや問題はあるけどすぐにすべてに着手できない、という開発には使える場面があるはずです。
チケットの書き方というと、問題を報告するときにどのように報告すると開発者に喜ばれるか、という観点での話がありますが、今回の話はそのような話ではありません。開発者自身が開発者自身のためにチケットを書くときの話です。
後から見て思い出せるチケットを書く目的は「目の前のやりたいことや問題に集中できるようにすること」です。
継続的に開発を続けていくと、新しいアイディアが浮かんだり、新しい問題が見つかることがよくあります。そのとき、1つのアイディアを実装したら新しいアイディアが浮かんでくるというようにはならず、新しいアイディアはどんどん増えるけど実装が追いつかない、ということがほとんどです。問題についても同じです。
新しいアイディアや問題を全部覚えておくことは大変です。そこで、覚えておかなければいけないことを少なくして、目の前のことに集中できるようにしたいのです。目の前のことに集中して1つずつ解決していきたいのです。
この「目の前のやりたいことや問題に集中できるようにすること」という目的を実現するために、覚えておかなければいけないことをチケットに残して、覚えておかなければいけなかったことを忘れます。では、覚えておかなければいけないことはなんでしょうか。試行錯誤した結果、以下の項目があれば忘れても後で思い出せることがわかりました*3。
「動機」と「ゴール」は必要な情報で、「実現案」はあればうれしい情報です。
「動機」はチケットの作業をする前に必要で、「ゴール」はチケットの作業を完了するときに必要で、「実現案」はチケットの作業を開始するときに必要です。
「動機」がわからないと、後で見たときに、このチケットの必要性を判断できません。必要かどうかがわからなければ、そもそも解決しなければいけないかどうかということを判断できません。そのため、チケットの作業を始める前に、チケットがどれだけ重要かを判断するために「動機」が必要です。また、「ゴール」が適切かを検証するためにも必要です。
「ゴール」がわからないと、後で見たときに、チケット作成時はどこに向かおうとしていたか、がわかりません。ゴールに到達したかどうかでチケットが完了したかどうかを判断するため、少なくとも作業を始める前には設定する必要があります。実際にゴールに到達したらチケットを完了にします。もちろん、チケット作成時には適切だと思ったゴールが、チケットを見なおしたときに適切ではなくなっているということはあるでしょう。そのときはチケットの作業をする前に新しくゴールを設定する必要があります。
「実現案」があると作業をすぐに始めることができます。なければ、どうやってゴールに到達するかを検討する必要があります。もし、チケットを作成するときに検討したのであれば、その結果を記録しておくと、改めて同じことを検討しなくてもよいためすぐに作業に入れて便利です。
必要な情報を整理できたので、それらだけをチケットに含めるようにします。このようなときはテンプレート化をするということがよく行われます。今回の場合は以下のようなテンプレートにしました*4。
タイトル: 「動機」あるいは「ゴール」の要約 h2. 動機 なぜこれをやりたいか、なぜこれが問題かがわかる情報を書く。 問題の再現手順があるならそれもここに書く。 h2. ゴール どうなるとうれしいかを書く。 どうしてこのゴールで動機を満足できるかがわかるとよい。 h2. 実現案 ゴールに到達するための案があるならそれを書く。 案がないなら「案が思いつかないので考えないといけない」ということを書く。 ないというのも大事な情報。 書き忘れたのであれば思い出そうとしたほうがよいかもしれないが、 そもそも検討していないのであれば、 検討しなければいけないということがすぐにわかる。
例えば、「groongaのHTTPサーバー機能を強化すること」をチケットにする場合は以下のようになります。
タイトル: HTTPサーバーの機能が貧弱で不便(「動機」の要約バージョン) あるいは タイトル: ちゃんとしたHTTPサーバー機能が欲しい(「ゴール」の要約バージョン) h2. 動機 groongaのHTTPサーバーは必要最小限の機能しかもっておらず、 POSTや認証機能などHTTPで使える便利な機能が使えなくて不便である。 ただし、現在のHTTPサーバー機能はやっていることが少ないため速い。 HTTPの便利な機能は欲しいが、速さは譲れないポイントなので、 遅くなるなら便利な機能は諦めたほうがよさそう。 h2. ゴール ちゃんとしたHTTP機能を提供する。ただし、速度は落とさない。 速度面で現状のHTTPサーバーと同等かそれ以上のものを狙う。 さらにHTTPの仕様をできるだけ満たすこと。 h2. 実現案 groongaのHTTPサーバーをnginxのモジュールとして実装する。 イメージはPassengerのStandaloneモード。 作業の流れは以下の通り。 * groonga用のnginxモジュールを作る。 * nginxをgroongaのソースコードにバンドルして、 groonga用nginxモジュール付きでビルドできるようにする。 * できあがったバイナリをgroonga-httpdという名前でインストールできるようにする。 Apacheモジュールとすることもできそうだけど、 Apacheよりnginxの方が性能がでるので、nginxの方がよさそう。
この情報があれば「groongaのHTTPサーバー機能を強化すること」について忘れても後で思い出せます。また、チケットを書いた人とは別の人が作業をすることになったときもすぐに情報を把握できて作業に入りやすいです。
もし、これが「ちゃんとしたHTTPサーバー機能をサポートする」だけであれば、「どうやって実装しよう?」などを改めて検討しないといけません。もしかしたら、「速さを大事にする!」という観点を忘れてしまうかもしれません。そのようなことが続くとボロが増えることになるので、「チケットを使って覚えることを減らしてソフトウェア開発を支援する」というやり方は自分たちには向いていなかったね、ということになるでしょう。
この方法の問題点はチケットを書くのが面倒だということです。例えば、Pivotal Trackerというツールではタイトルだけでチケットを作ることができるため、ちょっとメモをとる感じでどんどんチケットを作っていくことができます。しかし、このテンプレートにあわせて書こうとすると、「どうしてこの作業は必要なんだろう」とか「どうなったらうれしいんだろう」ということを考える必要があり、ちょっとメモをとる感じでは作れません。慣れてくればチケットを作る前から「どうしてこの作業は必要なんだろう」という視点でやりたいことや問題を考えられるようになるため、チケット作成時に悩むことは少なくなりますが、まとまった文を書く必要があるため、ちょっとメモをとるよりも数倍時間がかかります。
チケット駆動開発では「チケット無しのコミットは禁止」というようにチケットを使いますが、それをやるにはこのチケットの書き方は重すぎるでしょう。忘れても思い出せるように記録を残すことが目的なので、チケットを書いているよりコードを書いたほうが早い場合は、忘れなくてもよいためチケットは作りません。これはチケット駆動開発とは方向性が違います。
やりたいことや問題を忘れても後から思い出せるようにチケットに記録する方法を紹介しました。これはやりたいことや問題をたくさん抱えている場合に有効な方法です。すべてのことを覚えておく必要がなくなるため、問題の1つずつに集中して対応することができます。もし、後からチケットを見たときに、よく「なにこのチケット?」となる人は試してみてはいかがでしょうか。
*1 Mozilla関連のプロジェクトやGNOME関連のプロジェクトなどで使われている。
*2 Ruby本体関連のプロジェクトやgroonga関連のプロジェクトなどで使われている。
*3 個人差はあるでしょう。
*4 Redmineのデフォルトのマークアップ言語がTextileなので、ここでもTextileを使っています。
「同じことは同じように書く」ことがどうして大事かを説明します。
return
の有無先日、DevLOVE運営チーム主催のリーダブルコードイベントが開催されました。イベントの前半はリーダブルコードの訳者である角さんによるリーダブルコードの紹介で、後半は参加者が「リーダブルコードとはどういうコードか」をディスカッションしました。ディスカッションでは実際に参加者が書いたコードを読みながら「ここはリーダブルだね」「ここはこうした方がもっとリーダブルじゃないか」といったことを考えました。
さて、その中で使ったコードを見ながら「同じことは同じように書く」ことがどうして大事かを説明します。ここで使うコードはdproject21/yaruo_tdd_triangleのtriangle.rbです*1。
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 31 |
class Triangle attr_accessor :a, :b, :c def is_equilteral_triangle? not_nil? && preCondition? && @a == @b && @b== @c && @c == @a end def is_isoscales_triangle? not_nil? && preCondition? && (@a == @b || @b == @c || @c == @a) end def is_triangle? not_nil? && preCondition? && (a < b + c) && (b < a + c) && ( c < a + b) end def is_scalene_triangle? return is_triangle? && !is_isoscales_triangle? end def preCondition? unless [@a,@b,@c].find{|n| n.is_a?(Numeric) && n > 0} false else true end end def not_nil? if (@a.nil? || @b.nil? || @c.nil?) false else true end end end |
ディスカッションのときは以下のreturn
に注目しました。
1 2 3 |
def is_scalene_triangle? return is_triangle? && !is_isoscales_triangle? end |
このコードはRubyのコードなので最後に評価した式がメソッドの返り値になります。そのため、この場合はreturn
を書いても書かなくても動作は変わりません。では、どのような観点でreturn
に注目したかというと、他のメソッドではreturn
を使っていないのにこのメソッドでは使っているという観点です。コード全体を見れば、特にreturn
を使うかどうかを使い分けていないだけという雰囲気を感じますが、通常はここで「何か意図があるのではないか」と考えます。それではどのように考えるかを説明します。
まず、書かなくてもよいreturn
を明示的に書いているということは、return
を強調しているのではないかと考えます。つまり、「このメソッドだけは返り値が重要」で、他のメソッドでは「Rubyなので必ず値を返すけど、その値は重要ではない」という意図があるのではないかと考えます。それでは、return
がない他のメソッドも見てみましょう。
1 2 3 |
def is_triangle? not_nil? && preCondition? && (a < b + c) && (b < a + c) && ( c < a + b) end |
メソッド名に?
がついていることに注目します。RubyではSchemeなどと同じように、真偽値を返すメソッドの名前の最後を?
にする習慣があります。これを考慮すると、このメソッドは「真偽値を返す」ということが重要だと考えられます。しかし、明示的なreturn
がないため、「return
は返り値が重要であるということを示す意図がある」という考えと両立しません。よって、明示的なreturn
は「返り値が重要」ということを示すためのものではなく、使っても使わなくてもどちらでもよいからたまたまついていただけなのだろうと考えます。
このように、周囲のコードとあわせて読めばreturn
の意図を推測できますが読む人が大変です。しかし、すべてにreturn
を書く、一切return
を書かない、一定のルールでreturn
を使う*2、など全体として統一された使い方になっていれば読む人が読みやすくなります。できるだけ読みやすいコードにしたいですね。
リーダブルコードのイベントのときに使ったコードを例にして、同じことが同じように書かれていない場合は読む人が大変という事を説明しました。自分がコードを書くときは同じことをするときは同じように書いて、どういう意図でこのコードを書いたかが読めばすぐにわかるコードを書きましょう。
なお、ここではreturn
を例にしましたが、他のコードや技術的な文書でも同じことが言えます。通常の文章では、同じことを何度か言う場合は同じ言い回しを避けます。これは読む人が飽きて読みづらくなることを避けるためです。一方、プログラミングや技術的な文書では「同じことは同じように書く」ことで、読む人がすぐに「同じこと」であると認識できるようにします。これにより、読む人が書いた人の意図を理解しやすくなります。
あわせて読みたい:
長らく更新がない状態が続いてしまっておりましたが、Firefox/Thunderbird用アドオンの開発者向けテスティングフレームワークUxU(UnitTest.XUL)の新バージョンとなるバージョン1.0.0を、本日付けでリリースしました。動作対象は、現在Mozillaによって公式にサポートが継続されているFirefox 10 ESR、Thunderbird 10 ESR、および最新のリリース版までです。Firefox 3.6、Thunderbird 3.1などの旧バージョンでの動作は保証されていませんので、ご注意下さい。
バージョン1.0.0では影響の大きな変更点として、複数のテストの並列実行に関する動作が大幅に改善され、実用的な機能になりました。
設定画面から最大の並列実行数を変更するか、コマンドライン引数を通じて並列実行数を明示的に指定することで、これまですべてシーケンシャルに実行されていたテストが、テストケース(ファイル)単位で並列に実行されるようになります。
JavaScriptを使ったテストでは、Webページの読み込みなど、同期的には結果を得られない・処理待ちが必要となる場面が多くあります。UxUでは、ユーティリティメソッドなどを通じて簡単に処理待ちを行える事が特長の1つとなっています。
しかしながら、処理待ちが発生するという事は、必然的に、全体のテスト実行時間が長くなってしまう事にも繋がります。
複数のテストを並列実行すると、1つのテストで処理待ちしている間に、別のテストの処理を進める事ができます。処理待ちしている時間を無駄にせずに済むようになるため、処理待ちが多い場合においては、全体的なテスト実行時間の短縮が期待できます。
UxUは、プロファイルを指定して別プロセスを起動する場合を除き、すべてのテストが同一プロセスの同一スレッド上で動作する設計となっています。そのため、単純なforループやwhileループなど、同期的な処理により時間がかかってしまっている場面については、他のテストを並列実行する事ができません。
このような理由でテストの実行時間が長くなっているケースについては、残念ながら、並列実行では実行時間の短縮は望めません。
テストの設計によっては、他のテストと並列に実行するとランダムに失敗するようになってしまう場合があります*1。並列実行機能は、個々のテスト同士が衝突しないようになっている事を確認した上で利用して下さい。
なお、テストケース中で「var parallel = false;
」と明示的に指定する事により、そのテストケースを並列実行の対象外にする事ができます。並列実行の対象外になっているテストケースは、同時に2つ以上実行される事はありません*2。並列実行すると失敗するテストについてはこの方法で問題を回避し、できる所から並列実行の恩恵を得るという事もできます。
UxU バージョン1.0.0で実用的な機能となった複数テストの並列実行機能について、有効な場面と有効でない場面、問題が起こる場面などの情報を簡単にご紹介しました。テスト実行時間の長さに悩まされていた方は、是非一度お試し下さい。