C
Linux
TDD
オブジェクト指向
embedded

組込みに近いものをTDDで開発してみる〜問題提起編〜

はじめに

下記の続きです。

組込みに近いものをTDDで開発してみる〜準備編〜

リアルタイムで内容を考えているため、ご意見・ご要望があれば、ぜひご連絡下さい。
コードはgithubで公開中

前回からの変更点と決定事項

ビルドツール

ビルドツールをAutotoolsからCMakeに変更しました。
CMake version 2.8以降が必須環境となります。

プロダクトコード

プロダクトコードはC言語にします。
あまり複雑なコードを書く予定がないため、生C言語で続ける予定です。コードが複雑になれば、glibの利用を検討します。

作るもの

キーボードの「A」ボタンを押すと、caps lockのLEDがON/OFFする。

とりあえずここから始めて、その場のノリと思いつきとフィードバックで仕様拡張していきます。
今回掲載するコードを除き、TDDで作っていきます。
最終的には、ラズパイなどの別ハードで、キーボード以外のハードウェアを使用し、ちゃんと移植できたよね、めでたしめでたし、としたいですね!
現時点で本当に具体的なハードウェアを選定していないです。ほら、その方が実際の開発っぽいでしょ?

問題提起

さて、作るものに戻って、キーボードの「A」ボタンを押すと、caps lockのLEDがON/OFFすると言われてプログラムを作るとき、どのような実装になるでしょうか?

愚直に作ると、概ね次のような疑似コードになるのではないでしょうか。

static void mainloop() {
  do {
    キーイベントを取得
    if (Aボタンが押された) {
      write(LEDをトグル);
    }
  } while (何らかの条件を満たすまで);
}

int main() {
  mainloop();
  return 0;
}

上の疑似コードを実際に作ると次のようなコードになりました。
libevdevの動作は、前回のライブラリ理解のためのテストで確認しましたね。

#define KEYBOARD_DEVICE "/dev/input/event2"
#define LED_DEVICE      "/sys/class/leds/input2::capslock/brightness"

#define KEY_RELEASED 0
#define KEY_PRESSED 1

static void mainloop() {
  struct libevdev *dev = NULL;
  int key_fd = open(KEYBOARD_DEVICE, O_RDONLY|O_NONBLOCK);
  int rc = libevdev_new_from_fd(key_fd, &dev);

  if (rc < 0) {
    fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc));
    exit(1);
  }

  int led_fd = open(LED_DEVICE, O_WRONLY|O_NONBLOCK);
  if (led_fd < 0) {
    fprintf(stderr, "Failed to init LED device.\n");
    exit(1);
  }

  bool led_on = false;
  do {
    struct input_event ev;
    rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
    if (rc == 0) {
      if (ev.type == EV_KEY && ev.code == KEY_A && ev.value == KEY_PRESSED) {
        led_on = !led_on;
        char buf[2];
        snprintf(buf, 2, "%d", led_on ? 1 : 0);
        write(led_fd, buf, 2);
      }
    }
  } while (rc == 1 || rc == 0 || rc == -EAGAIN);

  libevdev_free(dev);
  close(key_fd);
  close(led_fd);
}

int main() {
  mainloop();
  return 0;
}

Linux環境がある方は実際に動かすことができます。
前回記事を参考に、キーボードデバイスとLEDデバイスを確認し、2つのマクロ(KEYBOARD_DEVICE, LED_DEVICE)のパスを修正して下さい。
ビルドし、build/bad_example/bad_exampleを実行すると、「A」キーを押すたびにLEDがONされたりOFFされたります(環境によりroot権限が必要です)。

さて、このコードは一見、問題がないように見えますが、多くの問題を内包しています。

  • 全てのコードが意図通りに動作していることが明確でない。(エラー処理、終了時の動作確認ができない)
  • ロジックとハードウェアが密結合しており、ハードウェア変更、追加がロジックに影響する(オープン・クローズドの原則違反)。
  • mainloop関数を変更する理由が複数存在する(単一責務の原則違反)。例えば、キーイベント追加、LEDデバイス追加、双方ともにmainloopへの修正が発生する。

これらの理由のため、機能拡張を繰り返していくと、機能追加が次第に困難になり、デグレを起こさずに機能追加することが難しくなります。

次回からは、今回のコードをTDDで開発するとどうなるか、を実際にやってみて、順に機能を追加していきます。

次:
組込みに近いものをTDDで開発してみる〜file open編〜