2012/04/28

Dual Target Testingで組み込みソフトウェアの品質を早期に作りこむ

4/22(日)に「Test Driven Development for Embedded C 読書会 第3回」を開催しました。当日のやり取りはTogetterにまとめていますので、そちらもご覧ください。

この日は以下の2章をやりました。
  • 第5章「Embedded TDD Strategy」
    • 組み込みシステム開発でTDDをやる上で重要なDual Target Testingについて紹介されています。
  • 第6章「Yeah, but...」
    • TDDを実開発に導入する上でよく聞かれる反対意見とその意見に対する反論が書かれています。
本エントリでは第5章の内容をご紹介します。


ハードウェアとソフトウェアの並行開発が組み込みシステム開発を難しくしている

組み込みシステム開発では、ハードウェアとソフトウェア開発が並行で走ることがよくあります。

以前、「組み込み開発のリスクである「ソフトウェアとハードウェアの並行開発」の解決方法の一つが組み込みTDDじゃないだろうか?」というエントリでもそのことについて書きましたが、この並行開発が組み込みシステム開発が困難になる理由だと私は考えています。それはソフトウェアとハードウェアの両方にバグが混入された状態で開発が進むため、原因の切り分けが非常に分かりづらいバグが発生することがよくあるからです。

並行開発について少し例を挙げてみましょう。私が経験した組み込みシステム開発では、以下のようにいくつかイテレーション(フェーズとも言う)を区切って、イテレーションごとに機能のスコープを切り、イテレーションの最後にハードウェアとソフトウェアを結合して、品質評価をやるというスタイルを取っていました。こういうプロトタイピングを繰り返し、だんだん仕様を固めていくスタイルはよくあるのではないでしょうか?


このように繰り返しプロトタイピングしながら進めていくスタイルでは、
  • 開発序盤にそもそも次期製品のハードウェアがない。
    • たいていの場合は、チップ周りを担当する人には本当に最小限の回路が乗っているハードが提供されて、OSの立ち上がりの確認をしたりする。
    • もっとユーザ寄りの機能を担当する人は、前製品のハードウェアが提供され、全製品と重複している箇所の動作確認からはじめたりする。
  • ソフトウェアとハードウェア双方にバグがある。
という状態で進みます。

そして、イテレーションの最後に結合してリリースした後の品質評価が大量のバグが上がってくると、まずはバグの原因はハードウェアなのかソフトウェアなのかの切り分けから始まります。双方の品質が低い状態なので原因の切り分けははっきり言うと難しいです。そのため、ソフトウェア技術者にとってはソフトウェアにバグはないと確認し、自信を持って主張するためのエヴィデンスの確保に大量の時間を取られてしまいます。

はっきり言って、この時間は不毛だし、生産的ではないでしょう。この不毛な時間を少しでもなくし、より生産性の高い開発を実現するために、組み込みソフトウェアエンジニアは日夜、ハードウェアが届く前にソフトウェアの品質を作りこむことに力を注がなくてはいけません。


Dual Target Testingでソフトウェアの品質を早期に作りこむ

ハードウェアが届く前にソフトウェアの品質を作りこむにはどうしたらいいんでしょうか?書籍で推奨されているのがDual Target Testing、つまりターゲット・ハードウェアとPC上の両方でテストできるようにすることです。

書籍では、以下のプロセスでテスト駆動な開発をしろと解説しています。



特徴は以下。
  • 最初の段階で、徹底的にPC上で単体テストとリファクタリングをして、単体レベルのソフトウェアの品質を確保してから評価ボードに持っていく。
    • デバッグ環境の貧弱な評価ボード上で単体レベルのロジックのミスを見つけるような、不毛な作業がなくなります。
  • PC上でターゲット用のコンパイルを行ったり、評価ボードやターゲットハードウェア上でも単体テストを動かすことで、ホスト環境とターゲット環境の違いによるバグを早期に見つける。
  • ターゲットハードウェアに対する受け入れテストも用意する。
ここでやっているのはあくまでも単体テストなので、単体テストのスコープの検証しかできませんが、評価ボードに持っていくまでに品質を確保できているので、まず開発の立ち上がりがスムーズに行くと考えられます。

また、PC、評価ボード、ターゲット・ハードウェアの3つに対して単体テストが自動的にできるため、結合した上で単体レベルの動作確認などが不要になります。結合した時に振る舞い、あるいはマルチタスクの微妙なタイミングによリ発生するバグなど、もっと複雑な不具合の究明に力を注ぐことができるはずです。


Dual Target Testingをやるには地道に単体テストを積み上げる必要がある

ただし、自動テストをやるには単体テストが用意されている必要があります。これは、いきなり評価ボードに持っていって実機デバッグをやるより、はっきり言って時間のかかる作業だと思います。

単体テストを作らずに評価ボードで実機テストを繰り返すスタイルは、直近で見ると一見手間がかからなくて楽な方法です。しかし、機能を追加するとテスト作業が全てが手作業になり、繰り返し単体レベルの動作を保証することはできません。また、途中で混入したデバッグ作業に時間を浪費し、生産性の向上は見込めません。

Dual Target Testing用に単体テストを積み上げていくスタイルは、直近で見ると時間がかかり、進捗は遅くなりますが、開発を進むにつれて混入したバグを即座に発見することができるため、デバッグ時間の節約にもなり、より難易度の高い不具合の究明に時間をさけるようになります。

この開発時間についてどう捉えるかは6章で扱っているので、そちらで紹介したいと思います。

どうやってDual Target Testingをやるの?

具体的なテクニックは第7章「Introducing Test Doubles」以降で紹介されています。次回の読書会で扱う予定。その際にまたブログで紹介したいと思います。

2012/04/14

#coderetreat in Tokyo 2nd に参加してきた

Coderetreat in Tokyo 2ndに参加してきました。

Coderetreatとはイベント告知のページによると以下のようなイベントです。
Coderetreat は、プログラマのための練習、学習のためのイベントです。同じ課題をペアプロで実装しながら、プログラミング、設計、テストなどの技術を学びます。
特徴を挙げると・・・
  • 全員がライフゲームという題材に取り組む。
  • ペアプログラミングをしながら実装する。
  • 開発45分で振り返り15分で1セッション。
  • セッションのたびにペアを入れ替える。
  • 毎回コードを捨てて、一から作り直すという作業を繰り返す。
  • 言語は固定せずにいろんな言語で実装する。
    • 実際、私はRuby/C/Javaで実装しました。
  • セッションのたびに追加課題が与えられる。
    • 配列を使うなとか、if文を使うなとか、セッター・ゲッターは使うなとか。
このイベントはかなり斬新で、すごい楽しかったです。

これをやることによって何が起こるかというと
  • ペア作業を繰り返すことで、いろんな人の設計アイディアが合成されて、セッションを進むごとに設計が洗練されていきます。
    • 実際、最初のライフゲームのデータ構造は地味な2次元配列でしたが、途中からクラスベースで多次元に対応できるデータ構造になっていきました。
  • 普段使わない言語で実装することで、違った視点を得られたり、違う言語ならではの体験ができます。
    • 特に、私は組み込みCでのTDDに取り組んでいるので、Rubyでのコードの記述量の少なさ、本質的なコードに集中できることの生産性はすごいうれしいと分かりました。
    • C言語の場合、言語的な制約をカバーする要素が多く、Rubyのような生産性はなかなか出せません。
    • ただし、C言語でも(Rubyには及ばないまでも)早いサイクルでTDDを回すことはできますが。
  • 実装の制約をかすことで、よりオブジェクト指向らしいきれいな設計に収束していきます。
今回のイベントに参加したおかげで、ペアプロ、TDD、オブジェクト指向設計の面白さや大切さを再発見しました。

今、組み込みCのTDDの読書会をやっていますが、区切りの良いタイミングで、今回のイベントのように演習中心でペアプロをする会をやれたらいいなという思いが強まりました。その時は、組み込みらしく実機も用意して、dual targeting testを実践すると。

最後になりましたが、場所・食事・おやつのスポンサーのグロースエクスパートナーズ株式会社、株式会社 情報システム総研-、株式会社 スペイシーズの皆様に感謝したいと思います。素晴らしいイベントをやっていただいてありがとうございました!


2012/03/25

#agilesamurai dojo gatheringでマスター・センセイのコーチングを受けた

3/24(土)にあったAgilesamurai Dojo Gatheringでのこと。

ワールドカフェにて

なんやようわからんけど、うちのテーブルにマスターセンセイことJonathan Rasmussonさんが来て、僕がホストをやった。

legoboku「前のグループで話してたのは、アジャイルにしろ何かを変えるためにはモチベーションが大事で、他の人のモチベーションをどうやってあげたらいいのか?を話ししてました。」

legoboku「ある人は、共通の話題とかを見つけて仲良くなって、彼らを支援したらいいんじゃないかとか。あるいは、他の人のモチベーションを上げるって難しいから話振って興味を示してくれる人を見つけて仲間を作るのが精一杯じゃないかって。」

Jonathan「そうだね。他の人のモチベーションを上げるってことは本当に難しい。それをやれって言われても、そこは責任を持つことはできない。僕らにできるのは自分で自分のモチベーションを上げることが出来る人をサポートすることくらいだ。」

Jonathan「だから、他の人を変えなくちゃいけないって思いつめなくてもいいよ。大事なのは、自分で自分の仕事に誇りを持って、信念をもってやることじゃないかな。そして、君自身が楽しむことができたら、あいつが楽しそうにやってるのはなぜだろう?って周りの人が興味を持ってもらえるかもしれない。」

そうっすね。皆、何か変化が起きて欲しいと思うけど、誰かに変えろって言われたら反発しちゃうし、自発的な変化が起きるように支援する事が大事かなと。あと、自分が楽しむこと。do it for fun.

ビアバッシュしてる時に真面目な話

legoboku「えっと、けっこーseriousなproblemがあって、質問があります。」

Jonathan「problemじゃなくてchallengeだよ!では、向こうで話そうか」

legoboku「僕は組み込みソフトウェアの開発をやっているのですが、組み込みシステムの開発ってアジャイルに向いていないんじゃないかって意見があるんです。例えば、組み込みシステム開発って、メカエンジニアとか電気回路のエンジニアとか製品企画とかとすりあわせしながら、繰り返しプロトタイプを作るって開発します。」

legoboku「で、リリース対象の機能を備えたハードウェアがないとシステムテストができないけど、物理的なものを作るのって早くはできない。それに、リコールすると回収コストがものすごくなるし、ドメインによっては人命にかかわるから、品質保証プロセスが重たいものになりがち。繰り返しはしているけど、アジャイルにはなりにくい。」

Jonathan「そうだね。僕は組み込みのことはよくわからないけれど、ハードウェアが早くつくれないってのはそうなんだろう。例えば、ハードウェアとソフトウェアの境界を早く決めることでスムーズにソフトウェアを開発することはできないかな?」

legoboku「そうですね。Test-Driven Development for Embedded C (Pragmatic Programmers)では、ソフトウェアエンジニアとハードウェアエンジニアが先にインタフェースの仕様を合意して、テスト駆動で中を作り込むってシーンを描いてました。」

Jonathan「そう。境界を早く作るってのが一つの手だね。」

legoboku「そうですね。境界を作るには技術領域が違う人達が協力する必要があるんです。でも、誰でもどっちも分かっている人ってそれほど多くなくて、お互いに相手が何を言っているのか分からなくて、コミュニケーションがうまくできなかったり。それに、なんか組み込みの分野ってハードウェアの人が偉くて、ソフトウェアの人が後回しにされているような気がしてて、最後に尻拭いされているような感じがするんですよね。」

Jonathan「うーん、じゃ、こう考えようか。君がCEOで、僕がハードウェアエンジニアだ。僕がソフトウェアエンジニアとうまくやって、プロジェクトとして開発をアジャイルにするために僕にどういう風に説明するかな?」

legoboku「そうですね。例えば、共通の目的が何かを話するかもしれないですね。目的は、お客さんが満足する製品を早く提供すること。そこは一致しているんだから、技術的なところでうまく協調できないかとか。」

Jonathan「じゃ、うまく協調するためにはどうしたらいいだろう?」

legoboku「あ、そうだ。例えば、ソフトウェアエンジニアがハードウェアエンジニアのところに行ってちょっと修行するってのはどうでしょう?彼らが何を考えているとか、何を気にしているのか分かるかもしれない。」

Jonathan「そうすると、ソフトウェアのプロジェクトに戻ってきても、以前よりうまく開発できるかもしれないね。」

知らないお姉さん「わー、Jonathan、サインして!!」

Jonathan「あ、呼ばれてる。もう大丈夫かな?」

legoboku「あ、はい。なんか分かったような気がします。」

いやね、簡単な回路図とか読めるんすよ。で、もっと奥深く入った方がスムーズにコミュニケーション出来るってのも分かるけど、ソフトウェアでもいろいろ考えることあるし、なんか電気回路とかにモチベーションががががが。

あるいは、こういうのは企画とか営業とエンジニアとか、他の職種の境界でも起こるのかもしれない。あいつ何言っているのか分からないし、何でそんな簡単なことすぐにやってくれないのかとか。
でも、ワールドカフェで話したのは、仲が悪いんじゃないよ、コミュニケーションが足りないだけで、お互いを知ればもっとより良くできるはずとか。

そんな風に、コーチングを受けて思った。

文化のギャップを乗り越える

日英の逐次通訳をやるのも、技術領域の境界を話し合うのも同じなのかもしれないと帰りながら思った。気持ちが大事だよ!気持ち!!
でも、もっと上位の目的で合意するためには、実は相手の言語と思考パターンを理解しないといけない。相手の言語と思考パターンが全く分からない状況では、あいつは何を考えているんだとかコミュニケーションの障害が起きてしまう。それを乗り越えるには、気持ちだけじゃなくて、言語スキルと相手の思考パターンを理解することが大事なんだ。多分、きっと。

そのためには、あれか同じ釜の飯を食うってやつか。

2012/03/07

組み込み開発のリスクである「ソフトウェアとハードウェアの並行開発」の解決方法の一つが組み込みTDDじゃないだろうか?


私はSIerの人間であって、メーカーの人間ではないですが、メーカーの製品開発の中に深く入り込んで開発した経験があります。その時感じたのは組み込み開発の最大のリスクがハードウェア・ソフトウェアの並行開発だということです。

■未完成品のハードウェアに対してコードを書くのは大変><

一般的な組み込み開発の場合、開発PC上で開発→ 評価ボードでデバッグ → 本製品上での評価と開発が進みます。ベッタベタな開発の場合、最初はターゲットがない状態でごしごしコードを書いて、しばらくしたら評価ボードと言われる半完成品のハードウェアが届いて、そいつにソフトウェアをのっけてデバッグして、ソフトウェアのバグを取り除く作業をやります。

とにかく早く実機で動かすのが腕の見せ所!な状態なので、そもそも単体テストできることを考えていない場合も。だからハードウェアと密結合を起こして、後から単体テストできなくなってしまいます。

ここで
  • 開発PC、評価ボード・製品向けのライブラリやコンパイラが微妙に違う
  • 最初は評価ボードの完成度が低い
  • ソフトウェアのロジックのミスがある
  • ハードウェアが固まった後のしわ寄せがソフトウェアにくる
などなどのリスクがある状況で開発が進むので、とにかくデバッグに時間がかかったり、バグの原因の切り分けが難しくなったりして、ソフトウェア開発がものすごい困難になります。

(組み込みでアジャイル開発をやる場合は、ここがネックになると思います。)

Dual-Target Testing

そこで、Test-Driven Development for Embedded Cでは、最初からハードウェアへ依存しない設計にし、開発PCでも評価ボードでも単体テストが動くようにすることを推奨しています。これをDual-Target Testingと呼んでいます。これで、開発PCで徹底的に単体テストをし、ソフトウェアのロジックミスを排除して、さらに評価ボード上でも単体テストが動くことを確認します。これで、テストがかなりスムーズに行えます。

Dual-Target Testingに必要な要素は?

では、Dual-Targeting Testをやるには何が必要なんでしょうか?第一にJava界隈で育ってきたテストの技術を応用して、最初から単体テスト可能に設計することが重要だと思います。

次に、低レイヤーの開発の場合、メカ・エレキなど周辺領域の技術者との緊密なコミュニケーションが必要だと感じます。Test-Driven Development for Embedded Cでは、エレキの技術者とハードウェアとのインタフェース部分の設計をつめているシーンが書かれています。最初からハードウェアへ依存しない設計にするためにも、ハードウェアとのインタフェースをつめておかないと、Test Doubleの作り方も決められないからだと思います。

Test-Driven Development for Embedded Cではどうやっているのか?

最後に書籍のサンプルをのせていますので、そちらを参照してください。このサンプルの肝はコンストラクタでハードウェアへの依存性を注入している箇所です。変数ledsAddressとは、書き込みポートのアドレスですが、これをコンストラクタで与えることで、開発PCでも評価ボードでも単体テストできるようにしてます。

また、かなり芸が細かいですが、このハードウェアは読み込みポートがないという設定です。LEDの状態は一度ledsImageという変数に書き込んでおいて、あるタイミングでポートに書き込みます。読み込みポートがあるのかとかはエレキの人と早めに仕様を詰める必要があります。

今回の例は極めて単純なので、だからどうしたと思われるかもしれません。現場にはもっとテスト困難な例があると思います。そんな時にどうするのか?基本は同じで、ハードウェアおよび外部モジュールに対する疎結合な設計、早期のハードウェアとのインタフェースの確立だと思います。

詳しくは次回以降のTest-Driven Development for Embedded C読書会で学んでいきます。乞うご期待。

(サンプル)テストコード
#include "unity_fixture.h"
#include "LedDriver.h"
#include "RuntimeErrorStub.h"

#ifndef NULL
#define NULL @((void *) 0)
#endif

TEST_GROUP(LedDriver);

static uint16_t virtualLeds;

TEST_SETUP(LedDriver)
{
 LedDriver_Create(&virtualLeds);
}

TEST_TEAR_DOWN(LedDriver)
{
}

TEST(LedDriver, LedsOffAfterCreate)
{
 uint16_t virtualLeds = 0xffff;
 LedDriver_Create(&virtualLeds);
 TEST_ASSERT_EQUAL_HEX16(0, virtualLeds);
}

TEST(LedDriver, TurnOnLedOne)
{
 LedDriver_TurnOn(1);
 TEST_ASSERT_EQUAL_HEX16(1, virtualLeds);
}

TEST(LedDriver, TurnOffLedOne)
{
 LedDriver_TurnOn(1);
 LedDriver_TurnOff(1);
 TEST_ASSERT_EQUAL_HEX16(0, virtualLeds);
}

TEST(LedDriver, TurnOnMultipleLeds)
{
 LedDriver_TurnOn(9);
 LedDriver_TurnOn(8);
 TEST_ASSERT_EQUAL_HEX16(0x180, virtualLeds);
}

TEST(LedDriver, AllOn)
{
 LedDriver_TurnAllOn();
 TEST_ASSERT_EQUAL_HEX16(0xffff, virtualLeds);
}

TEST(LedDriver, TurnOffAnyLeds)
{
 LedDriver_TurnAllOn();
 LedDriver_TurnOff(8);
 TEST_ASSERT_EQUAL_HEX16(0xFF7F, virtualLeds);
}

TEST(LedDriver, LedMemoryISNotReadable)
{
 virtualLeds = 0xffff;
 LedDriver_TurnOn(8);
 TEST_ASSERT_EQUAL_HEX16(0x80, virtualLeds);
}

TEST(LedDriver, UpperAnbLowerBounds)
{
 LedDriver_TurnOn(1);
 LedDriver_TurnOn(16);
 TEST_ASSERT_EQUAL_HEX16(0x8001, virtualLeds);
}

TEST(LedDriver, OutOfBoundsChangesNothing)
{
 LedDriver_TurnOn(-1);
 LedDriver_TurnOn(0);
 LedDriver_TurnOn(17);
 LedDriver_TurnOn(3141);
 TEST_ASSERT_EQUAL_HEX16(0, virtualLeds);
}

TEST(LedDriver, OutOfBoundsTurnOffDoesNoHarm)
{
 LedDriver_TurnAllOn();
 LedDriver_TurnOff(-1);
 LedDriver_TurnOff(0);
 LedDriver_TurnOff(17);
 LedDriver_TurnOff(3141);
 TEST_ASSERT_EQUAL_HEX16(0xffff, virtualLeds);
}

TEST(LedDriver, OutOfBoundsProducesRuntimeError)
{
 LedDriver_TurnOn(-1);
 TEST_ASSERT_EQUAL_STRING("LED Driver: out-of-bounds LED", RuntimeErrorStub_GetLastError());
 TEST_ASSERT_EQUAL(-1, RuntimeErrorStub_GetLastParameter());
}

TEST(LedDriver, IsOn)
{
 TEST_ASSERT_FALSE(LedDriver_IsOn(11));
 LedDriver_TurnOn(11);
 TEST_ASSERT_TRUE(LedDriver_IsOn(11));
}

TEST(LedDriver, OutOfBoundsLedsAlwaysOff)
{
 TEST_ASSERT_TRUE(LedDriver_IsOff(0));
 TEST_ASSERT_TRUE(LedDriver_IsOff(17));
 TEST_ASSERT_FALSE(LedDriver_IsOn(0));
 TEST_ASSERT_FALSE(LedDriver_IsOn(17));
}

TEST(LedDriver, IsOff)
{
 TEST_ASSERT_TRUE(LedDriver_IsOff(12));
 LedDriver_TurnOn(12);
 TEST_ASSERT_FALSE(LedDriver_IsOff(12));
}
(サンプル)プロダクトコード
#include "LedDriver.h"
#include "RuntimeError.h"

enum {ALL_LEDS_ON = ~0, ALL_LEDS_OFF = ~ALL_LEDS_ON};
enum {FIRST_LED = 1, LAST_LED =16};
static uint16_t *ledsAddress;
static uint16_t ledsImage;

void LedDriver_Create(uint16_t* address)
{
 ledsAddress = address;
 ledsImage = ALL_LEDS_OFF;
 *ledsAddress = ledsImage;
}

void LedDriver_Destroy(void)
{
}

int convertLedNumberToBit(int ledNumber)
{
 return 1 << (ledNumber - 1);
}

static void updateHardware(void)
{
 *ledsAddress = ledsImage;
}

int IsLedOutOfBounds(int ledNumber)
{
    return (ledNumber < FIRST_LED) || (ledNumber > LAST_LED);
}

void setLedImageBit(int ledNumber)
{
    ledsImage |= convertLedNumberToBit(ledNumber);
}

void clearLedImageBit(int ledNumber)
{
    ledsImage &= ~convertLedNumberToBit(ledNumber);
}

void LedDriver_TurnOn(int ledNumber)
{
 if (IsLedOutOfBounds(ledNumber)) {
  RUNTIME_ERROR("LED Driver: out-of-bounds LED", ledNumber);
  return;
 }
 setLedImageBit(ledNumber);
 updateHardware();
}

void LedDriver_TurnOff(int ledNumber)
{
 if (IsLedOutOfBounds(ledNumber)) {
  RUNTIME_ERROR("LED Driver: out-of-bounds LED", ledNumber);
  return;
 }
 clearLedImageBit(ledNumber);
 updateHardware();
}

void LedDriver_TurnAllOn()
{
 ledsImage = ALL_LEDS_ON;
 updateHardware();
}

BOOL LedDriver_IsOn(int ledNumber)
{
 if (IsLedOutOfBounds(ledNumber)) {
  return FALSE;
 } else {
  return ledsImage & (convertLedNumberToBit(ledNumber));
 }
}

BOOL LedDriver_IsOff(int ledNumber)
{
 return !(LedDriver_IsOn(ledNumber));
}

2012/03/04

Test-Driven Development for Embedded C読書会第2回をやりました











本日、「Test-Driven Development for Embedded C (Pragmatic Programmers)」読書会の第2回をやりました。

今日読んだ範囲は?

第3章「Starting a C Module」と第4章「Testing Your Way to Done」をやりました。第3章は初めてのTDDセッションでLEDドライバのコア部分をTDDで実装する章です。第4章はテストリストに書いた機能をひと通り実装しながら、リファクタリングをしていく章です。

第3〜4章の一連の流れは前半のハイライトだと思います。本書を読み進めていく場合に、最初に感じを掴みたい方はここのサンプルを一から自分で書いてみることをオススメします。

サイトにレジュメを置いているので、こちらも参照してみてください。
サンプルコードは私のgithubのリポジトリにあります。ちょっとインデントが崩れているので、後で綺麗に整形します^^;
常にコードを安定な状態に置く

今日の勉強会で一番勉強になった点を2つご紹介します。

まず、1つめは「常にコードを安定な状態に置く」ということ。これは以前、ブログの
C/C++でTDDする時の手順をステートマシン図で描いてみた」に書いたTDDの作業を表現したステートマシン図に通じる話です。

今日、勉強会をやっていてふと思ったコードには2つの安定した状態があって、TDDでは必ずその2つのうちのどっちかの状態に置いて安定させるという意図があるんじゃないかということです(持論です)。



1つ目はテストが通って安定した状態。これにはテストが通っただけでリファクタリングされていない状態と、リファクタリングされている状態があります。これはTDDを少しかじっていれば知っている内容ですね。

2つは、(勉強会を初めて知りましたが)テストが通っていなくて安定している状態があります。コンパイルエラー、リンクエラー、テスト失敗ときてプロダクトコードを書くための過程の状態です。それまでの間に、ちゃんとテストが通っていない実装があることが目に見えている状況を作り出しています。これで全体としてはテストが通っているけど、失敗もエラーも起きない隠れた実装ミスや不要なものがあるという不安定な状況を意図的になくそうとしています。

ここまで厳密にやる必要があるのか?と思う人がいるでしょう。もちろん、人によっては、こんなことやらなくても、ぱぱっと1度で完全なコードを実装出来る人もいると思います。私のように、そうでない人は何かしらの手段で実装モレや不要な実装を残さない手段は取っておくと良いのかなと思いました。

実装する時は最初からパフォーマンス重視にせず、基本は可読性を意識する

もう1つは、リファクタリングを通じて重複を排除していく箇所でこのような記述がありました。

don't let the performance factor outweigh improved design and readability unless there is proof the code is contributing to a specific performance problem.

今リファクタリングしようとしているコードがパフォーマンス上の問題箇所であるという証明がないなら、可読性を重視しろということです。

これは組み込みで大事な性能を無視しろということを言っているわけではありません。ディスカッションでも意見が挙がりましたが、パフォーマンス上の問題はちゃんとプロファイラなどのツールで計測して、ボトルネックを明らかにしてから対処するべきです。どこにボトルネックがあるかを明らかにしないまま闇雲にパフォーマンス重視なコードを書いていては、保守性が下がり、開発コストに悪影響を与えます。

なので以下がいいのかなと思いました。
  • アーキテクチャ設計の段階でちゃん性能要求を満たすよう設計しておく。
  • アーキテクチャを固める段階、プロジェクト序盤でプロファイリングなどを使って性能のボトルネックを明らかにして対処する。
  • 個々の実装では可読性を重視した実装へリファクタリングする。
今日はオフトピとして各自の工夫についても披露してもらいました

1つ目はEclipseを使い、ファイル保存時にビルド・テストが走らせること(これは私が話しました)。Eclipseには、ファイル保存時にビルドコマンドを実行する設定があります。これを有効にしておくと、Makefaileを修正して静的解析・コードフォーマット・カバレッジのツールを自動実行でき、非常に楽チンで良いかなと思っています。

2つ目は静的解析ツールAdLintをビルド時に実行すること(うちの先輩が開発してるんで宣伝させてもらいました^^;)。まだ「Mac OS X LionにC言語用静的解析ツールAdLintをインストールした」に書いたとおり調べている途中で、案を話した程度です。次回くらいに披露したいですね。

3つ目はコード整形ツールを使ってコード書式を自動で統一するという内容。今日、紹介されたのはArtistic Styleというツール。今日の話では、ビルド時に毎回Artistic Styleを走らせてコード書式の統一を強制するやり方はどうか?とい話でした。意図しない成形が起こる場合があるので注意が必要ですが、コード書式の統一をツールで自動化するのは有用だと思いました。

4つ目はカバレッジ計測ツールの利用です。皆さんgccを使っているのでgcovというツールが良さそうだというアドバイスがありました。

次回の予定は?

次回は4月にやります。そのうち案内を出します。

読む範囲は第5章「Embedded TDD Strategy」と第6章「Yeah, but...」になる予定です。前者は組み込み開発にTDDを適用する際に、後者は周囲の人にTDDを導入を勧める際に役立ちそうな内容だと期待しています。

その前に情報処理技術者試験がんばれ俺orz

2012/03/03

自発性・熟達・目的、現代を生きる人々のモチベーション

2月に読んだ本を紹介します。

一番良かったのは「モチベーション3.0 持続する「やる気!」をいかに引き出すか」。
現代を生きる人々のためのモチベーションについて書かれています。昔から人々のモチベーションを上げるやり方として「飴と鞭」がありましたが、それは目標と解決策が明確な場合にしか機能しないのだそうです。現代では目標と解決策が明確でない問題の方が多いため、飴と鞭のやり方をそのまま適用できることが少ないとか。

代わって現代を生きる我々に必要なのは「自発性・熟達・目的」。社員が自発的にこうしたいという思いと、会社として利益を上げる方法を交差させるポイントを見つけることができた組織が今後活躍するのだなと思いました。

アジャイル開発でも似た点があると思っています。先日、アジャイル開発のセミナーを受けた時に、上から命令をするやり方ではなく、自己組織化したチームが自発的に自分達のやり方を決めていくことを推奨していました。おそらくアジャイル開発をやる組織は、内発的動機に突き動かされている、自己組織化されたチームではないと、短期間に高いパフォーマンスを出すのは難しいのでしょう。

「自発性・熟達・目的」。自分個人としても、自分が所属する組織としても、この言葉を実現できるやり方を模索したいなと思います。

legobokuの本棚
2012年02月
アイテム数:6
パブリック―開かれたネットの価値を最大化せよ
ジェフ・ジャービス
読了日:02月18日
{book['rank']

河北新報のいちばん長い日 震災下の地元紙
河北新報社
読了日:02月18日
{book['rank']

データ分析できない社員はいらない
平井 明夫
読了日:02月18日
{book['rank']

「週4時間」だけ働く。
ティモシー・フェリス
読了日:02月19日
{book['rank']

powered by ブクログ

2012/03/02

Mac OS X LionにC言語用静的解析ツールAdLintをインストールした

オープンソースのC言語用静的解析ツールAdLintを自宅のMacbook(OSはLion)にインストールしました。手順を残しておきますね。
(インストール方法は公式サイトにもあるので、そちらも参照してください。)

Ruby1.9.3のインストール

まず、Ruby1.9.3が必要なのでインストールします。私はMacport使ってますが、homebrewでもソースビルドでもいいでしょう。

% sudo port install ruby19 +nosuffix
% sudo port -f activate ruby19

macportで入れる場合はオプションに "+nosuffix" が必要です。Ruby1.9.Xを普通にインストールすると、Rubyを実行する時に「ruby19」とバージョン番号をつける必要があります。これだと他のツールから呼び出してもらえないので、バージョン番号なしで呼び出せるようにするためです。

AdLint1.0.0のインストール

こっちはgemで一発です。なんかマークが出たら成功です。

% sudo gem install adlint
(中略)
-------------------------------------------------------------------------------
     ___    ____  __    ___   _________
    /   |  / _  |/ /   / / | / /__  __/            Source Code Static Analyzer
   / /| | / / / / /   / /  |/ /  / /                    AdLint - Advanced Lint
  / __  |/ /_/ / /___/ / /|  /  / /
 /_/  |_|_____/_____/_/_/ |_/  /_/    Copyright (C) 2010-2012, OGIS-RI Co.,Ltd.

                         Thanks for installing AdLint!
     Please visit our project homepage at .

-------------------------------------------------------------------------------
Successfully installed adlint-1.0.0


AdLintを何に使うの?

今、「Test-Driven Development for Embedded C (Pragmatic Programmers)」の読書会をやっていて、C言語でTDDをする一連のプロセスの中にちょちょいと静的解析を入れられないか検討してます。

高いライセンスのツールと違い、AdLintはフリーなのでビシバシ手元で静的解析をかける感じで使いたいなと思います。

タイミングとしては、テストがパスしている安定な状態で、一度リファクタリングした後くらいで静的解析をかけて、問題のあるコードを取り除いたりする作業が気軽に導入できればいいかなと。あくまでもぼんやりとした案レベルですが。

次回の読書会まで時間がないので、次の次くらいに読書会参加者の皆さんに披露したいと思ってます。