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はフリーなのでビシバシ手元で静的解析をかける感じで使いたいなと思います。

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

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