Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

プログラムを書く順番とテスト駆動開発について

 下の本を読んでいた。

プログラミングの基礎 (Computer Science Library)

プログラミングの基礎 (Computer Science Library)

 この本はどういう本かといえば、OCamlという、関数型言語と呼ばれる中でも、あまり有名ではないほうの言語(というと失礼だけど)を使ってプログラミングの基礎を学ぶという本。そういうと、OCaml好きな人には怒られるかもしれないけれども。

 良いにしろ、悪いにしろ、関数型言語の特徴は、個々のパーツを作って云々という部分が非常にクリアーになっているところであるな、とは思う。というのも、下手に「代入」を使わないことによって、むしろデータ操作の流れがクリアになるし、余りに大きいパーツにしてしまうと、そもそもその流れ自体がよくわからなくなるので、必然的に小さいパーツの集まりとして書く癖がつくような気がする(メイン関数1万行とかにはなりにくいのかな、という淡い気持ち)。

 「なるほど、そういうことだよなあ」とか思いながら本を読んでいたのだけれども、本の中で気になったことが一つだけあった。

 それはデザインレシピという考え方だ。

 本書の定義によれば、「デザインレシピ」というのは、「正しいプログラムを作るための手順」ということらしい。著者のサイトを見ると、元としてはHow to Design Programsという本から来ているらしい。で、単純に「正しいプログラムを作るための手順」という話であるならば、「ふーん」という感じであるのだけれども、ふと、その作業の流れを見て、あることに気がついた。

 「あれ、これってテスト駆動開発でやっていることと一緒じゃないか?」

 どういうことか。

 例えば本書の一つのデザインレシピを取り上げてみよう。これは関数定義に対するデザインレシピだ。

目的: 作成する関数が何をするものかを考えます。何を受けとり、何を返すのかを考え、関数の型を決定します。これをもとに関数の出だし、ヘッダを作成します。

例: 関数の動きをより明確かつ具体的にするため、作成する関数に望まれる入力と出力の例を作成します。そして、それを実行可能なテストプログラムにします。

本体: 関数の本体を作成します。目的のところでは関数が「何を」するのかを考えましたが、ここではそれを「どうやって」実現するかを考えます。

テスト: 確かに望む動作をしているか、上で作ったテストプログラムを使って確認します。望動作をしていなかったら原因をこのデザインレシピに沿って考え、誤りを正します。

 テスト駆動開発の場合であるならば、先にテストを書く、という意味で「例」を書くことが順番として先に来るのだと思うけれども、このデザインレシピとテスト駆動の場合において、まず何に注目するべきかという点では一致している。どの点が一致しているかというと、「入力と出力」をまず考えるということだ。

 このことは、この関数を利用する私たちにとっても、基本的には「入力と出力」に関心を持つ、ということと一致していると思う。これは『計算機プログラムの構造と解釈』の「ブラックボックス抽象としての手続き」という項目で下のように説明している。

例えば、square(という関数)を使って、good-enough?手続きを定義するとき、われわれはsquare手続きを「ブラックボックス」と見ることが出来る。その時、その手続きがどう結果を計算するかには関心を持たない。関心があるのはそれが二乗を計算するという事実だけである。二乗を計算する方法は隠しておき、後で考える。(p.15)

 当然のことながら、二乗の計算がやたら遅かったりしたら、その手続きについて考えざるを得ないが、単に「ブラックボックス」として関数を使う時は、入力したらその関数に期待されている出力が出てくるということに着目している。

 もう一つ重要な点がある。

 それは、対象とする関数が「何を受けとり、何を返すのか」の定義をどうするかということに着目しているが、テスト駆動開発のプロセスでも、最初にそのことを実装したりした記憶がある。(いま、元になった本を持っていないので、そうだったかどうかは怪しいけれども)

 例えば、典型的なところでFizzbuzzの実装を考えてみる。Fizzbuzzとは、1〜100を順番に出力するのだけれども、3で割り切れる時はFizz、5で割り切れるときはBuzz、そして15で割り切れる時はFizzbuzzを出力するというプログラムだ。

 そこで、Pythonの一番最初のテストを考えてみる。

# -*- coding: utf-8 -*-
import unittest

class FizzBuzzTest(unittest.TestCase):

    def test_define_fizzbuzz(self):
        self.assertEqual(fizzbuzz(1), 1)

if __name__ == '__main__':
    unittest.main()

 当然、このスクリプトを実行すると、このテストは失敗する。なぜなら、fizzbuzz関数が存在していないからだ。だからfizzbuzz関数を定義してあげる必要がある。

 一番、愚直にテスト駆動開発を行なうとするならば、一番愚直な形でテストを通す方法にする。例えば、上記のケースであるならば、実は下のような関数定義でいい。

def fizzbuzz(number):
    return 1

 人を馬鹿にしている感じがあるかもしれないが、これで、入力に数字が入り、出力として数字が出てくることが明確になる。『プログラミングの基礎』には、条件分岐のテンプレートとして下のような記述が載っている。

本体を作成する前に、まず場合分けの条件を特定します。それをつかって必要な場合分けを行なうif文を作成します。この段階ではif文のthen部分、else部分は型の合った適当な値を入れておきます。

 これをもう少し横暴にした感じが上のようになる。で、テストをもう一つ追加する。

class FizzBuzzTest(unittest.TestCase):

    def test_define_fizzbuzz(self):
        self.assertEqual(fizzbuzz(1), 1)

    def test_fizzbuzz_return_number(self):
        self.assertEqual(fizzbuzz(2), 2)

 当然、二番目のテストはこけるので、fizzbuzz関数を下のように書き直してあげる。

def fizzbuzz(number):
    return number

 すれば、二番目のテストは通る。

 当然、このあとにも実装は続くのだけれども、実装自体は、検索をすればいくつも他の記事があると思うので、そちらを参考にして、とりあえずfizzbuzzの実装は未完成で中断させておく。

 もちろん、これを愚直にやると、余りにも冗長になり、なおかつテストのメンテナンス性が失われるから、たぶん一番目のテストは、現実の実装では飛ばされるのかも、とは思うのだけれども、たぶん意識しておきたいところの一つなのかも、という気がする。

 それと、基本的に動的型付言語であるPythonとかをいじっているとあまり意識しないのだけれども、「静的型付言語」が好まれる背景の一つには、恐らく一番最初の「テスト」として、「型」というものが立ちはだかるからなのかなーという印象。

 というわけで、『プログラミングの基礎』を気分転換に読んでいて「なるほどな」と思った次第でした。

参考書

テスト駆動開発入門

テスト駆動開発入門

  • 作者: ケントベック,Kent Beck,長瀬嘉秀,テクノロジックアート
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2003/09
  • メディア: 単行本
  • 購入: 45人 クリック: 1,058回
  • この商品を含むブログ (159件) を見る