あどけない話

インターネットに関する技術的な話など

偉大な習慣

「僕は、偉大なプログラマなんかじゃない。偉大な習慣を身につけたプログラマなんだ。」 --- Kent Beck

僕の信じた伝説

この一年間、あまりコードを書かずに、たくさんの本を読み、勉強ばかりしていました。そして、自分がかなり時代に取り残されたプログラマであることが身に染みて分りました。

僕の信じていたプログラミングの伝説は、こんな感じです。

初期工程で完全な仕様を作れ
実際問題、完全な仕様なんて作れるはずがありません。仕様は変わります。また、時代の変化やユーザの要望の変化により、要求も変わります。ですから、仕様が変わってもよいように、実装に柔軟性を持たせないといけません。
効率第一
大切なのは、コードの分りやすさです。効率はよいが分りにくい大きな関数を書くのではなく、効率はやや悪いが分りやすい小さな関数を書くべきです。関数呼び出しは遅いという伝説もありますが、最近のコンピュータは高速ですから、大きな関数は小さな関数に分割しましょう。チューニングは、必要になってからやりましょう。
テストやデバッグは最終工程である
そうやってプログラムを書くと、納期間際にテストをすることになり、結局デバッグの時間が足らなくなるでしょう。プログラムはトップダウンではなく、ボトムアップで作っていくべきです。小さな関数を書いて、テストし、自信の持てるコードを増やしながら、実装を進めていくのです。
コメントを書け
コメントが不要なぐらい、分りやすいコードを書く方が大切です。変数名も関数名も、英語の文章のように読めるよう配慮しましょう。
一から書き直せばもっとよくなる
現実的に一から書き直す時間はないでしょうし、一から書き直して失敗した事例もたくさんあります。一から書き直すのではなく、リファクタリングを繰り返して、理想に近づけるのが現実的です。

偉大なプログラミング・スタイル

プログラミングは、こういう風に進めましょう。

  • テストすることを念頭にコードを考える
  • 副作用のある部分と副作用のない部分を明確に分ける
  • 副作用のある部分は、なるべく小さく押さえるか、抽象化する
  • 副作用のない小さな関数をたくさん書き、必ずテストする
  • テスト・ケースは必ず保存しておく
  • 関数の実装を変更したら、保存したテスト・ケースを使ってテストする

僕もこの偉大な習慣を身につけようと思います。

Emacs Lisp のテスト

とういう訳で、Emacs Lisp でコードを書くときも、テスト駆動にしようと思ったのですが、検索しても適切なテスト支援ツールは見つかりませんでした。

そこで、Elite(Emacs Lisp Interactive TEst) というプログラムを書いてみました。現時点では、副作用のない関数の正常系しかテストできません。

テスト・ケースの用意

たとえば、以下のような関数を考えます。

(defun case-equal (str1 str2)
  (string= (downcase str1) (downcase str2))) C-xC-e

このコードを*scratch*バッファで書いた場合、最後の括弧の後ろで C-xC-e と押して評価するでしょう。

そして、以下のようなテストをします。

(case-equal "foo" "Foo") C-xC-e
;; => t

返り値は t になります。

この C-xC-e を押すところで、M-x elite-make-deftest を実行すると、テスト・ケースの定義を作り、*scratch* バッファに挿入してくれます。

(case-equal "foo" "Foo") M-x elite-make-deftest
(elite-deftest case-equal t "foo" "Foo")

elite-deftest というマクロがテスト・ケースを定義します。第一引数がテストしたい関数の名前、第二引数が返り値、第三引数以降が関数への引数になります。

引数を取らない関数の場合は、:none を指定します。

(defun func-no-args () (list 1 2)) C-xC-e
(func-no-args) M-x elite-make-deftest
(elite-deftest func-no-args '(1 2) :none)

テストの実行

テストを実行するに、以下の 3 つのコマンドが用意されています。

elite-test-current-buffer
現在のバッファで、テスト・ケースが定義されている関数をすべてテストします
elite-test-package
入力したパッケージ名(プレフィックス)を持ち、かつ、テスト・ケースが定義されている関数をすべてテストします
elite-test-this-function
カーソル付近の関数をテスト・ケースが定義されていればテストします。関数の定義の最後で実行しても、その関数を切り出してくれます。

前二つがバッチ的なテスト、最後が対話的なテストになります。

テストの結果は、*elite*バッファに表示されます。ただし、elite-test-this-function を実行し、テストの結果が成功なら、ミニバッファに表示されます。

対話的なテスト

関数の実装を変えたら、C-xC-e に続いて、M-x elite-test-this-function を実行すると便利でしょう。

(defun case-equal (str1 str2)
  (equal (downcase str1) (downcase str2))) M-x elite-test-this-function

インストール

elite.el を load-path の通ったディレクトリに置き、.emacs に以下を加えます。

(autoload 'elite-deftest "elite" nil t)
(autoload 'elite-make-deftest "elite" nil t)
(autoload 'elite-test-this-function "elite" nil t)
(autoload 'elite-test-current-buffer "elite" nil t)
(autoload 'elite-test-package "elite" nil t)

テスト・ケース(elite-deftest)は、適当な .el ファイルに保存しておきます。利用するときは、ファイルを開いて、M-x eval-current-buffer を実行すればいいでしょう。

おまけ

elite.el 自体もテスト・ケースが定義されています。elite-test-package を実行し、パッケージ名に "elite" を指定してみて下さい。デモを兼ねていますので、失敗するテスト・ケースも入れています。

todo

  • 副作用のある関数のテスト方法も考える
  • 異常系のテスト方法も考える