動かしてからバグを取る?動かす前にバグを取る?
動かしてからバグを取る?動かす前にバグを取る?というテーマは、あちこちで語られていますね。今更わたしが何か付け加える必要もありません。
しかし、同じテーマでも違った人間の色眼鏡をとおして見てみると、また違った発見があるものです。私の色眼鏡を通すと、このテーマがどのように映るのか?を見ていただくのも面白いかも分かりません。ですから、思いつくままに書いてみます。
まず今回のテーマについて簡潔に定義しましょう。「動かしてからバグを取る?動かす前にバグを取る?」に関しては、(極端に分けるとするなら)2つの派閥があるように思います。
- 「一発完動派」
コードを書いても、すぐには動かさない。ひたすらコード査読を行い、バグをすべて取る。もうバグは無い!というところで初めてコードを動かし、一発で完全に動作する事を目指す - 「とりあえず動かす派」
コードを書いたら、とりあえずコード実行してみる。動かしながらバグを取っていく
私は、プログラミングをする時には、これらの派閥の中間的な事をしています。そして、それにはC++testというツールが深く関わっています。
「昔のコンピュータ云々」の話は忘れる
このテーマで必ず出てくるのは、「昔はのぅ、大変だったんじゃよ」という話です。
今でこそ、コンピュータはエンジニア1人につき1台が当たり前になっています。しかし、むかしはコンピュータというのは、それはもう高級品でした。コンピュータの台数が非常に少なかったため、プログラムを動かすためには順番待ちをしないといけなかったそうな。
そうして順番待ちをして、やっと実行させてもらえる事になりました。ワクワクしながら結果を受け取ると・・・「ブブー。プログラムエラー」。おいいいいいい!
とにかくコンピュータというのは希少リソースだったために、エンジニアの側でコンピュータの都合に合わせる必要がありました。すなわち、机上でひたすらコード査読をおこないデバッグを済ませる。そして、もう間違いが無い!というレベルになってからコンピュータ上で実行する。そうして、希少リソース(=コンピュータ)を少しでも節約しようとしていたのです。
しかしそれも今は昔。ムーアの法則に従って、コンピュータのリソースはものすごい勢いで増えています。エンジニアがムダにじゃぶじゃぶ使っても、全然構わなくなりました。すなわち、「コンピュータが希少リソースだからコード査読をする」という理由はなくなってしまったのです。
だからといって、「コード査読をするべき理由が1つもなくなってしまった」という論拠には決してなりません。ただ、コード査読をするべき理由が1つ減った、というだけの話です。
要するに、「動かしてからバグを取る?動かす前にバグを取る?」というテーマを考えるにあたって、コンピュータリソースが昔は希少だったかどうかは、どうでもいいという事です。とにかく今は、じゃぶじゃぶ使えるんですから。
この話は単純に忘れちゃう事にしましょう。
「コード理解度」が大切
コード査読もコード実行も、単なる手段です。まずはゴールをきちんと定義しましょう。
- 品質管理部のテストをパスするようなコードを書くこと
そりゃそうですよね。さらに、ビジネスでやってるのであれば次のゴールも重要です
- 納期を守ること
- なるべく正確に見積をすること
これらもすっごく重要ですね。「なるべく正確に見積をすること」には、プロジェクト開始時、プロジェクト実行中の両方を含みます。
たとえばCCPMという手法でプロジェクト管理を行う場合、「進捗で48%進みました!」というような管理はしません。そうではなく、「この工程を終えるのにあと8日かかります!」という管理をします。何%進んだとかどうでもいいんです。「んで、あと何日で終わるの?」という事が重要です。それによって、後工程の調整をしないといけませんから。
そういう意味でも、「着手したらすっごく問題がある事が分かりました。当初の予定とは違い、実際には○日かかる見込みです。」という事を、なるべく正確に報告するのが重要です。これが「なるべく正確に見積をすること」の意味です。
そのためには、コード理解度を上げるのが重要であると考えています。
図1 プログラミングとは?
プログラミングが難しいのは、「このコードがどのように振舞うかな?」という事だけではなく、「どんな入力がありうるのかな?」という事も考えないといけない点です。(ここでいう入力とは、関数の引数だけではなく、コンピュータの負荷状態などの環境条件も含みます。)ありとあらゆる入力を想定するのは、まずムリです。ですからたとえば、HAYST法のように直交表なんかをつかって、なんとか全パターンの入力を調べなくても信頼性が保てるようにするわけです。これが難しいんですよね。
さて、とりあえず動いたからいーじゃん、っていうのは、こんなかんじです。
図2 とりあえず動いちゃいる。なんでか知らないけど。
一方、しっかり理解した上で、間違いなく動くはず!ほらね、ちゃんと動いた!っていうのは、こんなかんじです。
図3 意図通り、間違いなく動いている
どっちもとりあえず動いているため、「テストをパスするようなコードを書くこと」はクリアしています。しかし、いざバグが見つかった場合はどうでしょう?
- コード理解度が低く、なにがどうなっているのかイマイチわかっていない人
- コード理解度が高く、なにがどうなっているのかしっかり理解している人
バグ修正が早いのはどちらでしょう?バグ修正にかかる日数を、より正確に見積もれる人は?
特にデータを持っているわけではないので個人的な体験で語ってみます。いままでの経験からは、コード理解度が高いほうが、圧倒的にバグ修正のスピードも、見積もり精度も上です。というか、コード理解度の低い人がひどすぎる、といった方が正しいかも。対処療法でモグラ叩き的な対応してたら、そりゃいつ終わるか?なんて分かりませんよね。
ちなみに、コード理解度が高い=独立して、やっとスケジュール管理が出来るようになってきた頃の私(=つい最近)。コード理解度が低い=独立前、某社にてムチャクチャなスケジュールで動いてた頃の私。というわけで、どっちも自分のことです。
ですから個人的には、コード理解度を上げることが、仕事でプログラミングをする人には重要な事である、と思っています。
ここでやっと今回のメインテーマに入っていけそうです。「費用効果的にコード理解度を上げるためには、コード査読とコード実行をどのように組み合わせれば良いか?」これが、今回のテーマです。
要するに、いかに効率よく作業するか?よりもまず、いかに効率よく学習するか?の方が大切という事です。
効率のよい学習法?
さらっと一般論だけ見ていきましょう。たとえば数学の勉強をしていたとしましょう。いま目の前に問題集があるとします。さぁ、どうしますか?
- 問題よんで、そのあとすぐ答えを見る
- 問題よんで、自分で解けるところまで解く。それから答えを見る
「すぐ答えを見る」派は、時間的には短くてすみますね。ステキです。一方、「自分で解いてみる」派は、時間はかかるものの着実に身につきます。
結局、(効率)=(成果)÷(所要時間) とすると、効率が高いのは「自分で解いてみる」方なんじゃないかなー、っと個人的には思います。
コードを書いたら、いきなりコード実行しちゃうんじゃなくって、まずはコード査読をしてみる。「どんな入力があり得るか?」「このコードはどんな風に振舞うか?」という事について、自分なりに考えた上でコード実行してみる。そうすることで、より深く理解できるように思います。
ただし、プログラミングが数学の問題と違うのは、コード理解度を上げるためにツールの補助を受けられるという点です。そこで次から、VisualStudioとC++testを活用して、いかに費用効果的にコード理解度を上げるかについて、私が実践している方法をご紹介します。
私なりの方法
私がプログラミングする際には、次の手順を踏んでいます。
- コードを書く
- コード査読を、そこそこする
- ユニットテストを書く
- テストが通るかな?とコード査読する
- ユニットテストをコード実行する。ステップ実行で逐一動作を確かめる
- バグが見つかったら、1に戻る。カバレッジ未達が見つかったら3に戻る。
- 静的解析にかける
これらについて1つ1つ見ていきましょう。
1.コードを書く
ふつーに書きます。私は、まずパブリックメソッドの定義だけします。設計書には、「このクラスの責務は、これとこれとこれだよ」というレベルでしか書いてありませんので、それを具体的なメソッドに落としこんでいきます。次に、それらのパブリックメソッドを実装しつつ、「これプライベートメソッドにしたほうが良くない?」というものを適宜、プライベートメソッドにしていきます。
でもまぁ、これは人それぞれなので、スキに書いたらいいんじゃないかと思います。
2.コード査読を、そこそこする
「どんな入力が来そうかな?」「これで、ちゃんと動くかな?」というのを、そこそこコード査読して確かめます。机上デバッグ派の人は印刷して確かめるみたいですが、私はそこまではしません。(だって、インク代がバカにならないんですもの。)
ここはもうゲーム感覚で、「バグを事前につぶせたら、私の勝ち~」という程度の軽い気持ちでやっています。
3.ユニットテストを書く
ユニットテストフレームワークとして、C++向けであればCppUnitがデファクトスタンダードな気がします。でも、私はC++testがお気に入りなので、これを使っています。
4.テストが通るかな?とコード査読する
ユニットテストを実行したら、ちゃんと動くかなー?っと、軽くコード査読します。ここは、もうほんとに軽くです。
5.ユニットテストを実行する
実行する際、ざっと動かして「あぁ、動いた。良かった」とは言いません。コード査読でちょっと手を抜いた分、ここのステップで補います。ユニットテストを1つ1つ実行しながら、VisualStudioでステップ実行を行います。
ただ動けばいいのではなく、コード理解度を上げるのが目的です。ですから、「コード査読で予想したとおりに、ちゃんと動くかなー?」と、答え合わせをするようなイメージです。主要な変数をウォッチしながら、とにかく1ステップ1ステップ、動作を確認していきます。こういう点では、VisualStudioはホントによく出来ています。
さらにここで、C++testのオイシイ機能をご紹介しましょう。CppUnitだと、「こう実行したら、こういう結果になるべきだ」というのを、あらかじめ書かないといけませんよね?でも、C++testでは、そこまでしなくていいんです。
「こう実行した結果、この変数がどうなるか確認しよう」と書くだけでいいんです。ASSERT文を書くようなイメージで、POST_CONDITION( “変数名”, 変数 ) と記述しておきます。
図4 POST_CONDITION記述
そうすると、ユニットテスト終了時に、この変数の値が一覧表で出てきます。
図5 ユニットテスト実行結果
この画面は、iabs(1)が1になったよ?あなたは、これをどう思う?という画面です。あとは、この結果がオッケーだと思えば承認、ダメだと思えばバグを直す、という風にすれば良いのです。
図6 結果を承認する
図7 ユニットテストのコードが書き代わり、ASSERTになる
便利すぎて堕落しそうですが・・・これは止められない!
6.カバレッジ未達が見つかったら3にもどる
C++testでは、カバレッジの測定もできます。よく、品質をはかるメトリクスの1つとしてカバレッジを採用しているケースがあります。
それももちろんそうなのですが、私はコード理解度を図るメトリクスとしてカバレッジをとらえています。たとえば、int型変数の絶対値を求める、次の関数を書きました。
int iabs( int i ) { int result = 0; if( i < 0 ) { result = -i; } else { result = i; } return( result ); }
これを、C++testでユニットテスト実行する際、i= 1 の場合のみ確認しました。すると、こんな表示があらわれます。
図8 カバレッジ表示
緑が実行済みの行、赤が未実行の行です。これは、C0カバレッジが100%になっていない。ただそれだけの話です。
私はこれを、「ねぇ、あなた iの境界値分析ちゃんとやってないでしょ?まだまだコード理解度が不足してるんじゃないのー?」っていう警告と受け止めます。
そこで、次のようなケースについても、ちゃんとテストしよう!と思うわけです。
- i= int 型のとりうる最小値(INT_MIN)
- i= -1
- i= 0
- i= 1
- i= int型のとりうる最大値(INT_MAX)
ただカバレッジを100%にしたいだけなら、2,4だけ実行すればいいですよね?それで、C0もC1も100%になるでしょう。しかし、カバレッジ不足=コード理解度不足ととらえるなら、「よし、ちゃんとiについて考えよう!」と思えるわけです。
晴れて、上記のコードに含まれているバグを見つけることができるわけです。(クイズ:さて、どんなバグでしょー?)
これはつまり、C++testが私のコード理解度不足を指摘してくれる、と考えられます。ですから、一発完動にこだわらなくっても、C++testがそれを補ってくれるわけです。コード査読の代わりにするわけにはいきませんが、コード査読のモレを補う方法としては、なかなか有効だと思います。
7.静的解析にかける
C++testには、ソースコードの静的解析機能があります。コーディング規約に反しているとか、バグの可能性のあるコードを書いているとか、そういう形式的なエラーについては、ここで検出できます。たとえば上記のコードを静的解析にかけると、MISRA Cのルールに引っかかっている事が分かります。
図9 静的解析の結果
結構いろんなチェックができますので、コーディング規約違反なんかはたいがい検出できると思います。そしたらもう、コードレビュー時にしょうもないコーディング規約違反の話をしなくても済むようになります。
ここでコーディング規約違反が見つかって修正する際は、ユニットテストを動かしながらやります。そうすると、うっかりデグレしてしまった!なんてことを、ある程度は防げます。
ちなみにC++testにもいくつかのグレードがあります。残念ながら、最低グレードの製品では静的解析ルールを編集する事はできません。そこで、私は静的解析ルールが編集できる、ちょっとだけグレードの高いバージョンを購入しました。グレードが高いといっても、正直そんなに高いものではありません。静的解析ルールの編集機能は、あったほうが良いと思います。
私なりの方法のまとめ
要するにこういう事です。実行前にコード査読はする。でもそれは軽めにすませて、かわりにコード実行の結果を詳細にみる。結果を詳細に見るために、VisualStudioや、C++testの持つ機能を利用する。
そうすることで、コード査読に時間をかけすぎる事なく、コード理解度を上げることができます。
C++testは、購入して1年半ほど経過していますが、とてもよく動作しています。お金がなくなって保守契約を切るとしたら、C++testより先にMATLABの契約切っちゃうんじゃないかな・・・とすら思います。
コード理解度を上げる事への特典
このようにコード理解度を効率的にあげながら作業していくと、次のような特典が得られます。ただし、かなり私の主観が混じっています。
- コード実行では再現しにくい、複雑なケースのバグも見つけられる(特に、マルチスレッド関係とセキュリティホール関係)
- バグ取りの時間が早くなり、所要時間の見積もり精度も高くなる
- なぜか、新規にコードを書く際も、バグを書く事が少なくなる。その結果、コード査読でもコード実行でもバグが見つからない、真の一発完動、といった事が起きやすくなる (1つのクラスが1000行くらいで、そのクラスを一気に書き上げてもバグが無かった、という程度です。システム全体の一発完動はさすがに無理だと思います)
- コントロール感があって、とにかく気分がいい
ただし、これに対する代償として、コード査読のための時間を支払わないといけません。
しかし個人的には、「所要時間の見積もり精度が高くなる」というだけでも、十分にこの時間を支払う価値があると思います。スケジュール通りに作業しているとすっごく気分がノリノリですが、スケジュール遅れで作業しているとテンションがダダ下がりですから!
ソフトウェア開発の非属人性
たまに、「シロウトでも、それなりの成果が上げられるようなソフトウェア開発プロセスを!」というお話を聞きます。そのために、見える化をしてやろう、と。ここにも、コード理解度が関係してくるように思います。
以前、TPS(TOYOTA Production System) をソフトウェア開発に適用した際のレポートを読んだことがあります。その際は、プロジェクトの状態を見える化されていました。状態をダッシュボードに表示したり、プロジェクトに問題があればすぐにアンドンの赤ランプが灯る、というようなことをされていました。この方法は、ある程度の技術力のある人たちが、いかに正しい道を踏み外さないようにするか?という目的に使うには良い方法だと思います。しかし、シロウトにソフトウェア開発をさせることは出来ないでしょう。
そもそも、シロウトにソフトウェア開発なんてムリです。「プロジェクト状態の見える化」は、あくまでマクロ的、統計的な管理指標としてなら役に立ちますが、シロウトの作業をどうこうするようなミクロレベルの話に対しては無力であると思います。
私が目指すとしたら、「シロウトでも、比較的短期間の間にエキスパートになれるようなソフトウェア開発プロセスを!」という風に考えます。要するに、いかにコード理解度を上げやすい環境を作るか?です。シロウトに良いソフトウェア開発なんてムリですが、学習によってエキスパートになれる可能性であれば、誰もがもっているもの。それを、いかにうまく引き出せる環境を作るか?が重要ではないかと思います。誰だって、最初はシロウトですからね!
そういうわけで、「シロウトでも、それなりの成果が上げられるようなソフトウェア開発プロセスを!」という目的であれば、見える化=「コード理解度が深まる化」という風に考えると良いかと思います。つまり、シロウトをなるべく早く熟練工にしてしまおう、という考え方です。
コード査読しなくてもいい人も居るかも
今回ご紹介した方法はあくまで私にあったやり方です。とにかく費用効果的にコード理解度を上げられれば、どんな手法を取ろうと構わないと思います。ですから、中にはコード査読なんかしなくっても、コード実行しさえすればコード理解度は十分高くなる!という方も見えるでしょう。
それはそれで構わないとおもいます。数学の例でいくと、自分で解かなくても答えだけ見れば全部理解できるよ!っていうような人ですね。たぶん、とんでもなく頭がいいんでしょう。(実際に、世の中にはこの手の天才が居るんですよね。超うらやましい!)
今回ご紹介したのは、私のような凡人でも、なんとかツールの補助を受けてプログラミングが出来るようになりたい!というための方法です。万人には当てはまらないでしょうから、ケースバイケースで自由にカスタマイズされたらいいんじゃないかな、と思います。
ただ、どんな方法をとるにせよコード理解度を高める事は重要!これだけは譲れません。たとえば、ユニットテストにパスしてるんだから、中身なんかどうでもいいじゃん!っていう意見には賛成できません。
ユニットテストそのものの管理コストもバカになりませんから、ユニットテストにすべての責任を押し付けるのはトータルで見て不経済だと思います。そもそも、コードのありとあらゆる面をスキなくテストできるユニットテストというのは、コード本体と同じだけの情報エントロピーを持っている事になります。要するに、実装は違うが論理的には同一、のプログラム2つを同時にメンテすることと同等です。そんなの、やってらんない!
コード実行によるテストはもちろん重要ですが、コードインスペクションなどのコード査読手法とうまく組み合わせて、トータルで品質を確保するのが費用対効果の高いやり方である、と思っています。ワインバーグさんの本に出てきたエゴレス方式(だったかな?)。あれはやってみたいですね。XPのペアプロよりも、かなりコード理解度にこだわった手法な気がします。現代のツールとからめて、新約エゴレス方式なんて開発してみたいものです。
そんなわけで、コード理解度をより効率よく高めるにはどうすれば良いか?これは永遠のテーマで、これからも研究しつづけていくつもりです。
なーんて書くとすごく立派に聞こえますが、さて何をどうしたらよいやら・・・ハッハッハ。だれか、良い方法を教えてくださいな!