hachiNote

勉強したことをメモします。

TDDBCでの教えを胸に、巨大なC#レガシーコードと戦ってみた

目的

業務で現在、とても厄介なC#コードと戦っています。途方に暮れかけていましたが、TDDBC札幌で教えていただいたことから突破口が見えてきました。感謝の気持ちを表しつつ、ちょっとした現状メモです(それにしてはすごく長くてすみません)。
正確には「戦ってみた」じゃなくて、「戦い始めた」ですね。

敵のデータ

どんなアプリケーションか
  • C#で書かれた(一部C++もあるが)Windowsフォームアプリケーション。ドライバ的なところからビューアまで、かなり巨大。
  • とりあえず今自分が見ているところはビューアの改造とかのわりと表層的な部分。C#のみ
  • Visual Studio 2008 Professional Edtionで開発
コードの状態
  • コードの質が悪すぎる。今までみたコードの中で最もひどい
    • コピペコード多すぎ。とにかくところかまわずがんがんコピペ状態。
    • メソッド長過ぎ。クラスがでかすぎ。Cじゃないんだから7000行とかだめでしょ
    • クラスが密結合すぎ。Abstractという名称のクラスなのに、"if( this is SubClass )"みたいなコードがあってまじでびびった。子クラスのこと知ってんじゃん!
  • 仕様書がない。クラス間の関係を示す資料すらない
  • 当然ユニットテストなどない!

自分がしなくてはならなかったこと

ビューアに機能に新機能を乗っけることが私の作業でしたが、とにかくどこから手を付けたらいいのかわからない。

改造すべきもっとも重要なメソッドを見つけたが、400行くらいある。複雑すぎてよくわからない。

このメソッドを含むクラスを継承してメソッドをオーバーライドすれば求める機能は実現できそうだが、そうするとこの巨大なメソッドをまるまるコピペしてちょいちょいと修正を加えることになる。でもそんなことは自分がひどいと言っているコードと同じものを作り出してしまうだけ。そんなのは絶対に許せない。でもどうしたらいいんだろう……。

仕様化テストで道が開けた

途方に暮れていたとき、ちょうどいいタイミングでTDDBC札幌2.0が開催されました(id:hachiilcane:20110607:1307462022)。

そこで初めて知った、仕様化テストという言葉。既存のコードに対するユニットテストを書いて、実装仕様を把握するとともにリファクタリングを行う下地を作ることです。

不安はありましたが、自分が今出来ることはとりあえずこれしかないんじゃないか。翌日から早速ユニットテストを書き始めました。

もくもくと仕様化テストを書いていき、これは何か意味があるのかな……と不安になり始めた頃、変なところでテストがNGになりました。あれ? ここでNGになるなんておかしいな?

真のレガシーコードを侮ってはいけない

苦労しながらも事前にコードを読んで一応理解したつもりでユニットテストを書いていたので、NGになることを想定していなかったのです。よくよく見て行くと、実はわかりにくいの先に既存のバグが潜んでいました。理解していたつもりなのに、実際は自分の事前の理解は間違っていたのです。

そう、これはまさにテストを書いてみたからこそ気がついたこと。おおっ、テストを書いた意味があったよ! ここで一気に気分は急上昇。

とにかくテストを書いてみることで最初の一歩を踏み出せる

さらにテストを書いて行くと、今までぼんやりとしていた理解が次第にハッキリとしてきて、現状のコードの問題点もより正確に見えてきました。

テストの件数が増えていくと、それとともに守られている感が出てきて、かなり精神的に落ち着いてきました。これならプロフェッショナルとして、汚い重複コードを量産せずに機能追加できる。

既存の動きが把握できない、既存のコードに対してどう新規機能を設計したらいいのかわからない。漫然と既存コードを読みながらため息ついてばかりいないで、だまされたと思ってとにかくテストを書いてみる。これが大事なんだなと思いました。

手を動かせば、自ずと道が見えてくる。技術者らしい解決方法ですね、これ。

Visual Studio 2008 Professional Edtion標準の単体テスト機能について

ここからは、Visual Studio 2008での単体テストについてわかったことを少しまとめたいと思います。

Visual Studioユニットテストを書くのは初めてだったので、自分でなんとなく試行錯誤しながらやってみました。感想みたいなものなので、あまり役に立たないかもしれません。

本格的に調べたい方は、こちらの記事などがとても参考になると思います。
Visual Studio 2008単体テスト機能のすべて (1/4):特集:Visual Studio 2008単体テスト機能徹底活用(前編) - @IT
[ブログ紹介] Visual Studio で単体テスト: TDD.NET

VS標準のテスト機能とNUnit

Visual Studio 2008の場合、Professional以上であれば標準で単体テストの機能がついています。しかし、ネットを見る限りでは、NUnitというツールを使う人の方が多いのかなと思います。

NUnitを使おうかなと思ったんですが、お客様もコードを書いていることもあり、特殊な環境が必要になってしまうのはよくないなということであきらめました(そもそもお客様にはっきり了承を得ずに勝手にやってしまっているところもあるし)。

しかし、Visual Studio 2008 Professional Edition の場合、カバレッジがとれないのが痛いところです。Team Suiteという上位バージョンでないとついてないみたいです。

その点、NUnitカバレッジツールと組み合わせて使用する記事がWebにかなり転がっているので安心感があります。Professionalでもツールを入れればカバレッジとれるらしいんですが、PartCoverというフリーツールを使ってみたところ結局うまく動かず……。まあこれは私がヘタレなだけかもしれません。

VisualStudioの単体テストの機能はなかなか便利だよ

  • 先にメソッドのシグニチャを書いてから右クリックで「単体テストの作成」を行う。シグニチャに会わせてテストコードのひな形をかなり具体的に賢く生成してくれる。厳密にはテストファーストじゃなくなっちゃうかもしれなけいど、すごく便利なので使わないともったいない。プライベートメソッドの場合もこれでやるとうまくやってくれます
  • 実行速度もそれほど遅いとは感じない
  • カーソルがあたっている場所によって"Ctrl+R, T"によるテストの実行範囲が変わる。namespaceの下あたりで実行するとそのパッケージ全体のテストを実行してくれる
  • 話はずれるが、VSは開いているファイルのメンバ一覧を常時表示する機能(秀丸でいうところのアウトライン機能)がないです。これはほんとにひどいです。これだとまったく作業がはかどらないので、Source Code Outlinerというプラグイン的なものを入れるのが必須です。こちらを参考にしました。Source Code Outliner PowerToy for Visual Studio 2008 - kkamegawa's weblog

テストメソッド名を日本語で書く意義

わかってもらえる可能性

TDDBCでテストメソッドを日本語で書いていたので、私もとりあえずそうしてみました。すると、これは結構大きな意味があることなのかなという気がしてきました。

ユニットテストは継続して使ってもらわないと意味がない、というかもったいないです。しかし実際は社内でもお客様でもユニットテストに理解がある人の方が少ない。かろうじてユニットテストという言葉は知っているのかも、くらい。

そのようなときに、とりあえずちらっとテストコードを見て「ほんの少しでもなにかわかる気がする感」があるかないかはとても重要だと思うのです。そこでわからない、と思ってしまったら、二度とユニットテストは使ってもらえないでしょう。

なので、積極的に日本語のメソッド名をつけた方がいいでしょう。長くなってもかまわないと思うので、そのテストの内容を一行できっちり具体的に表現しましょう。「〜すると〜となること」みたいないかにもテスト仕様書的書き方でいいと思います。そのほうが取っ付きやすい感が出るし、メソッドの一覧を眺めたときに「ユニットテストは仕様書を兼ねるんだよ」ということをわかってもらいやすいのではと思います。

Description属性にテストの内容を書くのもおすすめ

テストメソッドを日本語で書く注意点としては、メソッド名には句読点、括弧などは全角文字であっても使えないようです。なので書きにくいと思ったら、Description属性(テストメソッドの上に、[Description("……")] のように書く)に書くのがいいと思います。

個人的には、///で書くメソッドコメントに書くよりも、Description属性に書いたほうがいいと思います。理由は、

  • テストの結果タブに表示させることができるから。デフォルトでは表示されないが、右クリックして設定を変えると表示されます
  • VS環境のC#カラーシンタックス的な問題で、Description属性のほうが目立って視認性がよいから

です。

テストケース内の解説の量

テストメソッド名またはDescription属性に一行できっちりテスト内容を書いたら、テストメソッドの中にはコメントはほとんど書かなくていいと思います。たとえばAssertの一行一行に説明を入れてしまったりすると、あとでテストコードをメンテナンスするのも大変だと思います。それよりは、変数名などを工夫するなどして、コードで意味をわからせることに力を注いだ方がいいかなと。expectedとactualをはっきり書くことも重要だと思います。

最後に

長くなりましたが、テスト駆動開発をやってみるにあたって、大事なのは「とにかくテストを書き始めること」だと感じました。

テスト駆動開発が本当に効果があるのか、やり方がよくわからない、コードがわからなさすぎてテストが書ける気がしない……。始める前はさまざまな不安があります。

でも実際書き出し始めると、不安なことの多くが解消していくのがわかります。苦労もありますが、少なくともテストコードを書いて損することはほとんどないと思います。

上のほうで長々と書いた私のやり方もきっといっぱい間違っているんじゃないかと思います。でもそれは徐々に改善して行けばいい話で、最初から完璧を求める必要はないし、それでも多くのリターンがあります。

やってみてだめだと思ったら止めればいいんです。そんな軽い気持ちでいいんじゃないでしょうか。