第2回 品質の高いソフトウェアをつくるには
皆さん、第1回の要件定義はいかがだったでしょうか?今回はいよいよ、お客様の要件にあったシステムを実際に作っていきます。
紙面の都合上、この連載でシステム開発のすべての工程をお見せすることはできません。今回はその中でも、ソフトウェアの品質を向上させるにはどうしたらよいかということにフォーカスしていきたいと思います。
システム全体の品質や、お客様の要件にマッチしているかということは第3回で説明します。第2回では、ソフトウェアの品質向上(プログラミング工程で品質を向上させるためのポイント)について学んでいきます。
皆さんは、ソフトウェアの品質を向上させるためにどのような工夫をしていますか?
ソフトウェアの品質には、信頼性、拡張性、保守性など様々な観点があり、JIS(日本工業規格)では、ソフトウェアの品質特性を以下のように定義しています。
特性 | 副特性 | 説明 |
---|---|---|
機能性 | 合目的性 正確性 相互運用性 セキュリティ 機能性標準適合性 |
ソフトウェアが、指定された条件の下で利用されるときに、明示的及び暗示的必要性に合致する機能を提供するソフトウェア製品の能力 |
信頼性 | 成熟性 障害許容性 回復性 信頼性標準適合性 |
指定された条件の下で利用するとき、指定された達成水準を維持するソフトウェア製品の能力 |
使用性 | 理解性 習得性 運用性 魅力性 使用性標準適合性 |
指定された条件の下で利用するとき、理解、習得、利用でき、利用者にとって魅力的であるソフトウェア製品の能力 |
効率性 | 時間効率性 資源効率性 効率性標準適合性 |
明示的な条件の下で、使用する資源の量に対比して適切な性能を提供するソフトウェア製品の能力 |
保守性 | 解析性 変更性 安定性 試験性 保守性標準適合性 |
修正のしやすさに関するソフトウェア製品の能力。修正は、是正若しくは向上、又は環境の変化、要求仕様の変更及び機能仕様の変更にソフトウェアを適応させることを含めてもよい。 |
移植性 | 環境適応性 設置性 共存性 置換性 移植性標準適合性 |
ある環境から他の環境に移すためのソフトウェア製品の能力。 備考:環境には組織、ハードウェア又はソフトウェアの環境を含めてもよい。 |
JISのソフトウェア品質基準
ソフトウェア開発では、ソースコードの品質をさまざまな視点から定量的に評価することや、その際の評価手法や基準などの体系のことを「ソフトウェアメトリクス(メトリクス)」といいます。メトリクスを測定するツールを使って、一定の目標を定め改善していくことができます。
Microsoft Visual Studio 2013のコードメトリクス機能
どのようなメトリクスを採用するとしても、プログラムを改善し動作を保証する必要があります。今回は、この『プログラムの改善』→『動作の保証』という一連の流れを、「リファクタリング」「ユニットテスト」という手法を用いておこなっていくことを学びます。
システムを要件に合わせて変更してみよう
第一回で要件定義を完成させたので、次はいよいよシステムを構築していきます。
今回はWebサイトのリニューアル案件ということで既存のWebシステムに変更を加えていきます。
購入履歴を活用したいという要望がお客様から上がっていましたので、リコメンド機能を追加することになりました。
サーバーの機能は実装されあなたはフロントエンドの機能を担当することになりました。
『トップページ』には『最近閲覧した商品』が並んでいます。
『商品ページ』には商品とお客様の属性に紐付けられた『おすすめの商品』を表示するように機能を追加します。 ここではすこし端折って、『商品ページ』に『おすすめの商品』を追加したものをダウンロード出来るようにしてありますので、そちらを参照してください。サイトサンプル
js/typesports.jsというファイルの中に『最近閲覧した商品』と『おすすめの商品』を表示するコードがJavaScriptで記述されています。
それぞれ、showPastBowseItems()関数とshowRecommendedItems()という名前の関数として、実装されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function showPastBrowseItems() { getPastBrowseItems(function(json) { for (var i = 0; i < json.length ; i++) { var item = json [i]; $("#contents").append( $("<article>", { "class" : "items" }) .append($("< img >", { "src" : item.imageUrl, "class" : "itemImg", "alt" : item.name }) .append($("< h1 >").text(item.name)))); } }); } function showRecommendedItems() { getRecommendedItems(function(json) { for (var i = 0; i < json.length ; i++) { var item = json [i]; $("#recommend").append( $("<article>", { "class" : "recommendedItems" }) .append($("< img >", { "src" : item.imageUrl, "class" : "recommendedItemImg", "alt" : item.name }) .append($("< h1 >").text(item.name)))); } }); } |
この2つの関数を見ると、よく似ていることに気が付きますね。
どちらも同じような表示をしているにも関わらず、それぞれに実装してしまっているため、コード量が増える原因になります。 このような場合、DRY原則に従って、同じ機能を一箇所にまとめましょう。
DRY原則とは? Don't repeat yourself の頭文字をとったもので、これは『コードの重複をしないようにしましょう』という考え方です。
皆さんが使用しているエディタにはコピー&ペーストという便利な機能があると思います。 コピー&ペーストは非常に便利な機能ですが、多用することによってプログラム中に沢山の重複したコードを作ることになります。重複したコードが存在すると、コード量が増えたり、修正の際にミスが発生したりする原因になります。
例えば、あなたがコードを変更する際、リファクタリングされていれば1か所の修正ですみますが、コードが重複している場合は複数箇所の修正が必要になり、修正漏れが発生するリスクが高くなります。
コードの重複はメンテナンス性を著しく損ないます。メンテナンス性の悪いコードは負の遺産となってしまいます。 負の遺産を量産してしまわないように常にDRY原則ということを頭の片隅に置いておくようにしましょう。
このようにプログラムの動作を変えずに重複したコードをなくしたり、ソースコードを整理することを『リファクタリング』といいます。
ツールを利用して重複コードを抽出することもできます。例えば、Visual Studio 2013の『コードクローン分析』を利用すると、重複コードを抽出することができます。
Microsoft Visual Studio 2013のコードクローン分析機能
それでは、重複コードをなくすためのリファクタリングをしていきましょう。
getPastBrowseItems()関数に渡されるコールバック関数の中身が表示部分になりますので、ここをshowItems()関数として切り出します。
1 2 3 4 5 6 7 8 9 | function showItems(json) { for (var i = 0; i < json.length ; i++) { var item = json [i]; $("#contents").append( $("<article>", { "class" : "items" }) .append($("< img >", { "src" : item.imageUrl, "class" : "itemImg", "alt" : item.name }) .append($("< h1 >").text(item.name)))); } } |
この関数を、『おすすめの商品』を表示するときにも使いたいのですが、このままでは汎用的に使うことが出来ません。
例えば、このshowItems()関数ではidがcontentsの要素に対して商品を表示する要素を追加していますが、 追加しようとする先が必ずしもcontentsというidを持っているとは限りません。
『商品ページ』ではrecommendというidを持つ要素に『おすすめの商品』を追加しています。
ここでshowItems()関数に改良を加えていくのですが、ちょっと待って下さい!
showItems()を変更することによって、『最近閲覧した商品』の表示ができなくなってしまうのでは、コードをまとめた意味がありません。 このような変更によって他の箇所に影響を与えてしまい、『デグレード』してしまうことはよくあることです。
また、そのようなデグレードは新しく追加した機能に注力するあまり、なかなか確認することがなく、後で見つかってしまうということもよくあります。
そうしたことが起こらないように、『ユニットテスト』を書きましょう。
ユニットテストはtestcase.htmlの中に入っています。一例としてはつぎのような部分です。
1 2 3 4 5 6 7 | suite('#showPastBrowseItems()', function() { test("最初の要素は< article class = 'items' >", function() { showPastBrowseItems(); $articles = $("#contents > article.items"); assert.equal($articles.length, 2); }); }); |
ここではshowPastBrowseItems()を呼び出した結果、<article class='items'>という要素が2つ挿入されることを検証しています。 ブラウザでtestcase.htmlを開いてください。ユニットテストが実行されて結果が表示されます。
ユニットテストにはmocha.jsというライブラリを使用しています。mocha.jsは、JavaScriptのテスト用フレームワークです。詳細については、下記サイトをご参照ください。
mocha.js http://visionmedia.github.io/mocha/
それでは、showItems()関数を修正してshowRecommendedItems()にも適用していきましょう。
まず、showItems()に追加先の要素を引数として追加します。
1 2 3 4 5 6 7 8 9 10 | function showItems(json, $appendTo) { for (var i = 0; i < json.length ; i++) { var item = json [i]; $appendTo.append( $("<article>", { "class" : "items" }) .append($("< img >", { "src" : item.imageUrl, "class" : "itemImg", "alt" : item.name })) .append($("< h1 >").text(item.name)) .append($("< p >").text(item.description))); } } |
呼び出し側でそれぞれ引数を渡して完成です。テストケースを実行してみましょう。
テストケースが失敗しました。原因は何でしょう? showRecommendedItems()では、article要素のclassがrecommendedItems、img要素のclass が recommendedItemImgとなっていますね。
それでは、showItems()にimg要素のclassに何を入れるかも引数に追加して行きましょう。
1 2 3 4 5 6 7 8 9 10 | function showItems(json, $appendTo, articleClass, imgClass) { for (var i = 0; i < json.length ; i++) { var item = json [i]; $appendTo.append( $("<article>", { "class" : articleClass }) .append($("< img >", { "src" : item.imageUrl, "class" : imgClass, "alt" : item.name })) .append($("< h1 >").text(item.name)) .append($("< p >").text(item.description))); } } |
呼び出し元も変更して、もう一度テストケースを実行します。
どうですか?テストケースは成功しましたか?
継続的インテグレーション
さて、ここまでユニットテスト作成→リファクタリング→ユニットテスト実行という流れを見てきました。
プログラムは最初の状態から動作は変わりませんが、重複のないメンテナンスのし易いコードになりました。実際のプロダクトでは、更に不具合を修正したり、機能を追加したりといったいろいろな変更が発生します。そのたびに修正によって、他に動かなくなってしまうところはないかユニットテストを実行して確認していきましょう。
ユニットテストの利点は『修正するたびに毎回実行する』ということが気軽にできる点です。気軽にユニットテストが実行できるということは、逆にいうと修正を素早く行えるということです。
もちろんユニットテストだけではテストが十分というわけではありませんが、ユニットテストによって検出できる問題を素早くフィードバックすることができます。
そしてもう一つの利点は自動化することができるということです。毎日の最新のソースコードでビルド(JavaScriptではビルドは必要ありませんが)とユニットテストの実行を自動化することができます。
このように毎日(プロジェクトによっては何日に1回、または日に数回かもしれません)定期的にビルド&テストを自動的に行うことを継続的インテグレーション(=CI)といいます。
継続的インテグレーションはソフトウェアの品質のための(特に開発者として実践できる)とてもよい習慣です。継続的インテグレーションをサポートするツールもさまざまありますので、ぜひ取り入れてください。
ユニットテストについて
ここまでユニットテストを使って、プログラムの動作を保証していくということを行なってきました。
これで本当にシステムの品質は良くなるのでしょうか?
答えはYesでもありNoでもあります。
ユニットテストを書いてみるとわかると思いますが、ユニットテストですべてをテストすることは出来ません。ユニットテストはプログラムのあるモジュールに対して、期待する動作とはなにか?を検証するだけです。今回の記事でリファクタリングとセットで説明したのは、ユニットテストだけではその真価を発揮することが難しいからです。
ユニットテストやリファクタリングという手法はプログラムの変更に柔軟に対応できるように、プログラムに柔軟性を与えます。
プログラムを変更して他の箇所で不具合が起こったらどうしよう、とか思い悩むことはありませんか?
ユニットテストによって『プログラムを変更して他のモジュールに影響がないか?』『期待通りに動作するか?』ということを検証することが出来ます。これは、プログラムを変更するということに対して大きな勇気を与えます。積極的にプログラムを修正することが出来れば、さらに一歩進んでリファクタリングすることによってコードをより整理整頓されたものにすることが出来ます。
これが品質の向上につながるのです。ユニットテストは強力なツールですが、システム全体の品質を保証するのは統合テストや結合テストです。
第三回では、このようなテストについて説明します。