Software Engineering Lecture 6/12

前回の復習

defstruct (Common LISPの構造体)

continuation を使った要求駆動型プログラム

## 戦略的なデバッグ

道具を使いなそう。

エラーメッセージを理解する。
step プログラムを一歩ごと実行する
trace プログラムを追跡する
break プログラムを途中で止める

良く出るエラーメッセージ

Error: The function TESTER is undefined. 関数が定義されていない。
たぶん、タイプミスだろう。
Error: The variable A is unbound. 変数の値がない。変数のタイプミス
か、変数の定義と使う順番が間違っている。
Error: Expected 1 args but received 2 args 引き数の数が違う。
関数の定義と、呼び出し方を見比べてみよう。
Error: Invocation history stack overflow. 関数の呼び出しが深すぎて
実行できない。たぶん、プログラムミスだろう。再帰呼び出しの終了条件を
調べよう。

エラーメッセージが出るようなエラーは実は簡単に対処できる。そこを
見れば良いのだから。プログラムの最初のころは、こういうエラーがたくさん
でる。そして、どんどん直っていく。しかし、一度も実行されないコードは
そういうチェックがされない。このようなエラーは頭が良いかどうかに
関係なく入ってくる。だから、テストはプログラムのすべての部分を一度は
通るようにしなくてはならない。

したがって、そのようなテストのための関数呼び出しをプログラムソースに
コメントあるいはドキュメントとして埋め込んでおくのが良い。

## ステップ実行

(step (sum-until 0 100 (integer-generator 10))) などとすることにより、
プログラムの実行を一段一段見ることができる。普通はコンパイルしたコード
をステップ実行することはできない。

Stepper commands:
n (or N or Newline): advances to the next form.
次のフォームに進む
s (or S): skips the form.
このフォームを飛ばす。実行するけどステップはしない。
p (or P): pretty-prints the form.
今のフォームを可愛く(pretty)表示する。
f (or F) FUNCTION: skips until the FUNCTION is called.
今呼ばれてる関数が終わる/答を返すまで飛ばす。
q (or Q): quits.
ステップを止めて一挙に実行する。
u (or U): goes up to the enclosing form.
f と同じだけど、飛ばすところが少し異なる。
e (or E) FORM: evaluates the FORM and prints the value(s).
LISPの式を評価する(eval)。変数を表示したりするのに使う。
r (or R) FORM: evaluates the FORM and returns the value(s).
今、実行している関数の返り値を指定する。
b (or B): prints backtrace.
どういう関数から呼ばれてきたか履歴を表示する。
?: prints this.
これを(英語だけだけど)表示する。

step は小さいプログラムを理解するのには向いている。初心者向き、
いや、非常に細かくプログラムを調べるのに向いている。
大きなプログラムになると、すべてをステップ実行することはできない。
たとえ、1pageのプログラムでもいやになるのが普通だ。

## トレース

trace は関数の挙動をプログラマに教えるものである。(help 'trace)と
すると英語のマニュアルがでる。

(trace sum-until integer-generator) としてから、
(sum-until 0 100 (integer-generator 0) を動かしてみよう。

さらに、trace にはいろいろなオプションを付けることができる。例えば、
(trace (sum-until :depth 5)) とすると、最初の5つの再帰呼び出しだけが
トレースされる。

コンパイルされたコードでもトレースはできることが多い。

`:DEPTH'
DEFAULT: No depth limit
FORM is simply a positive integer specifying the maximum
nesting of traced calls of FN, i.e. of calls of FN in which
the :COND form evaluated to non-NIL. For calls of FN in
which this limit is exceeded, even the :COND form is not
evaluated, and the call is not traced.

しかし、これと同じことは、例えば(print x)とかをプログラムにつけ加える
ことでも実現できる。これは特にデバッグ環境が整ってない場合に有効で
ある。新しいアーキテクチャ、あるいは、OSを作っている時、そして
お金がなくて良い環境を買えない時、そういう時に使おう。また、traceや
stepよりも簡易で高速なのも大きな利点だ。

## ブレイク

プログラムの実行を途中で止めて、何が起きているのかを調べたい。
そういう時にはbreakを使う。例えば、まだ作ってない部分や、
対応してないエラーなどの部分にbreakを置いておく。実際には
(print x)などと同じでプログラム中に埋め込んで使うことが多い。
(trace (sum-until :entry (break))) のようにも使うことができる。

あるいは、プログラムの途中からstep実行するというのも便利だ。

## こんなことをやっていて1000行のプログラムが書けるのか?

自分は、このプログラムを理解しているのか? もし、理解してないのなら、
そのプログラムは動かない。動いたとしても偶然動いているだけだ。
そのようなプログラムを自分で使いたいと思うか?

しかし、実際にはプログラムが自分の理解できる範囲に収まるとは
限らない。そういう時は、関数の持つ意味に立ち戻る。

関数の意味は入力と出力、そして副作用により決まる。
どんな入力があるのか
どんな副作用があるのか。作用を受ける変数はどれか? 作用を受ける構造は?
そしてどんな出力があるのか

これらの具体的な例を考える。特に以下のことに気を付ける。

プログラムの終了条件をまず考える。
できればプログラムの全部の部分を通るだけの例題を考える。
プログラムの中間結果を試す。

再帰などの場合は、

プログラムの中間結果から中間結果の対応を試す。

ことも重要だろう。
逆に、この例を先に作ってから、プログラムを作成することもできる。この
ような方法は実は半自動化できることが知られていて、
帰納推論プログラミング (Inductive Programming)と呼ばれている。
また、自動的に行わなくても、このような例から始めるプログラミングは、
プログラミング by example とか 冒険的プログラミングとも呼ばれている。

## 当てずっぽうにプログラミングして良いのか?

それは、当てずっぽうにプログラミングして良いということを意味するのか?
二つの意味でそれは違う。

検証できる例が十分にあること。
その例を決まった方法で拡張した例に対しても正しい答を返すこと。

この二つが重要である。これは、プログラムが実行される、正しく動く範囲を
考えることに相当する。これを決めることは実際には容易ではない。Inductive
Programming の難しさもそこにある。しかし、簡単なものなら経験と感で
カバーできる部分でもある。再帰型のプログラムはどれも同じような形を
していたことを思い出そう。

## 当てずっぽうにデバッグして良いのか?

デバッグは、戦略的に行わなくてはいけない。エラーメッセージが出ているのに、
そこが直せないのは、単なる無知か、プログラム言語やシステムコールの
バグだろう。本当に難しいエラーはエラーメッセージの出ないエラーである。

とにかく、なんかの方法で答が間違っていることは分かっているはずである。
(でなければデバッグしていない) そこで、最初の入力から明らかなエラー
までを2分探索法で探していく。そのためには、break, trace などを使う。
特に、値が異なりはじめる部分を見つけるような条件文を入れるのが良い。

(if (not (equal answer variable)) (break))

などとすれば良い。

## プログラムの難しさとプログラムの分割

5行のプログラムができなくて1000行書けるのか?

実はそれは可能あでる。何故なら、プログラムの大部分は短い部分の寄せ集め
だからである。もちろん、それは、そのようにプログラムするからだが。
そのためにはプログラムを適切な要素に分割しなければならない。その
分割パターンを調べるのがソフトウェア工学だとともいえる。
sum-until, tree-insert は、そのような分割の例である。その他、

構造化プログラミング
データフローダイアグラム
オブジェクト

などに基づいた分割が研究されている。

Prev
Next
Kono's home page