コンテンツにスキップ

幸水を使ったデバッグの指針

幸水を用いたデバッグは、大きく3段階に分かれます。必ずしもこの手順に従う必要はありませんが、合理的手順だと考えます。参考にしてください

  1. 文法エラーデバッグの段階
  2. 実行時エラーデバッグの段階
  3. アルゴリズムデバッグの段階

それでは、各段階でのデバッグ手順を紹介します。

文法エラーのデバッグ

まず、文法的エラーの検出を行います。例として、次のようなファイルのデバッグを行います。仮に、ファイル名は「test.kis」として話を進めます。

# test.kis

日本語挨拶 : こんにちは
英語挨拶 : Hello.
npw : 黒衣鯖人,閑馬永空,川上新夜

# 存在しないコマンドを誤って使った例
test1 : $(copu npw npw.backup)

# 辞書モードが違う例
get npw;

# 括弧の対応がおかしい例
test2 : 英語では、「${日本語挨拶}」は「${英語挨拶)」です。

=kis
# 括弧の対応がずれて、おかしな所でエラーが出る例
function test3 $(
    if $[ ${today} != ${birthday} ] $(
        if $[ ${bootcount} >= 10 ] $(
            echo \0\s[5]非誕生日、おめでとうございます!;
        #)
    ) else $(
        echo \0\s[4]…誕生日かい、たまにはついてない日もあるよ。\e;
    )
);
=end

このファイルを幸水で読み込みます。この際、ログレベルを「error」と「warning」に設定します。OSがWin9x系でログが流れて困る場合は、適当なログファイルに出力します。

コマンドラインでの入力:
command-mode > loglevel error warning
command-mode > logfile debug.log
command-mode > load test.kis

すると、ログファイルdebug.logに、次のような内容が出力されていると思います。

.\test.kis 9: error: ':' not found.
.\test.kis 9: error: ':' not found.
.\test.kis 9: error: could not find EntryId at start of entry definition.
.\test.kis 9: error: no entry names.
.\test.kis 12: error: '}' not found. runaway entry call?
.\test.kis 12: error: could not find statement list separator ','.
.\test.kis 25: error: ')' not found. runaway script?

エラーの内容を見てみましょう。エラーメッセージ一覧を参照しながら見て下さい。まず、9行目で「":"が無い」、「エントリ名が無い」というエラーが出ています。9行目を見ると、本来ならスクリプト記述ゾーンで書くべきコマンドを、辞書記述ゾーンで書いています。このため、コマンドをエントリ名と誤解して、「":"が無い」というエラーを出しています。ですから、9行目を

=kis
get npw;
=end

このように書き換えると、9行目のエラーは解消します。

次に、12行目のエラーメッセージを見ます。「"}"が無い」が主なエラーです。よく見ると、12行目は「${英語挨拶}」とあるべき部分が、「${英語挨拶)」と、間違った括弧が書いてあります。これを修正すれば12行目のエラーは解決です。

最後に、25行目のエラーを見ます。「")"を見失った」というエラーです。しかし、25行目を見ると括弧はあります。しかしよく見ると、21行目でわざと括弧をコメントアウトしています。このため、括弧の対応がとれず、一番最後の括弧でエラーが出ていた訳です。

このように、括弧の対応エラーは、記述間違いのあった所とは違う部分でエラーが出ることがあります。括弧の対応を、エラーの出た箇所より前に遡って確かめてください。こうした作業は、メモ帳ではなくエディタを使うことをお薦めします。大抵、プログラムを書くモードがあり、括弧の対応をエディタが確かめてくれます。

以上までで文法デバッグの終わったスクリプトを掲載します。しかし、このスクリプトは、まだ実行時エラーが残っています。実行時エラーのデバッグは、次の節で解説します。

# test.kis(文法エラー修正後)

日本語挨拶 : こんにちは
英語挨拶 : Hello.
npw : 黒衣鯖人,閑馬永空,川上新夜

# 存在しないコマンドを誤って使った例
test1 : $(copu npw npw.backup)

# 辞書モードが違う例
=kis
get npw;
=end

# 括弧の対応がおかしい例
test2 : 英語では、「${日本語挨拶}」は「${英語挨拶}」です。

=kis
# 括弧の対応がずれて、おかしな所でエラーが出る例
function test3 $(
    if $[ ${today} != ${birthday} ] $(
        if $[ ${bootcount} >= 10 ] $(
            echo \0\s[5]非誕生日、おめでとうございます!;
        )
    ) else $(
        echo \0\s[4]…誕生日かい、たまにはついてない日もあるよ。\e;
    )
);
=end

文法エラーデバッグの節の終わりに、一つアドバイスがあります。エラー箇所を一つ修正したら、その都度ファイルに保存して幸水でチェックして下さい。一つエラー箇所を直すと、そのあとのエラー表示が劇的に消えることがよくあります。

これは、華和梨がファイルを読み込む際、ファイルの先頭から読み込むため、一箇所文章の解釈でつまづくと、そのあとの解釈はすべてその誤りを引きずる為です。これは、英語の翻訳で代名詞が指す人物を間違えると、そのあとの文章が滅茶苦茶な解釈になるのと同じことです。

また、疑わしい部分全体を一度コメントアウトし、徐々にコメントアウトの範囲を変えて、どこでエラーが発生するかを絞り込む方法も有効です。

華和梨は人間と比べて非常に愚直で、「文章を良きに計らう」ことが出来ません。根気よくエラーを直してください。

実行時エラーのデバッグ

続いて、実行時エラーのデバッグに移ります。エコーモードで、次のように入力して下さい。

echo-mode > ${test1}

これは、test1エントリを呼び出しています。すると、次のようなエラーメッセージが出る筈です。

undefined function "copu" called.

「"copu"という定義していない関数が呼ばれた」と言っています。確かにtest1エントリを見ると、copuというコマンドを呼んでいます。自分で定義しない限り、このコマンドは存在しません。

このように、コマンド名のタイプミス等は、実際に動かしてみないと、エラーを発見できない場合があります。文法デバッグが完了したら、主要なエントリ・ユーザ定義コマンドを実際に呼んでみて、実行時エラーが起きないか確かめます。

さらに、エントリ名のタイプミスで、実際には存在しないエントリを呼んでいる場合もよくあります。これを検知する為には、

loglevel error warning decl

このように、ログレベル設定に「decl」を追加します。この状態で、定義されていないエントリ「hoge」をエントリ呼び出しすると、

read access to empty entry 'hoge'

このように、「空のエントリを呼び出した」と通知してきます。なお、entryコマンドで空のエントリを呼んだ場合、通知しないので注意してください。

以上の作業を繰り返してエラー表示が出なくなれば、実行時エラーを追放できたことになります。

アルゴリズムのデバッグ

実は、これからが本当の意味でのデバッグです。普通の言葉でも、文法的誤りは無いのに、筋の通らない文章は幾らでもあります。また、内容は正しくても、実際に起こらない状態を前提としているため、実質的に意味をなさない文章も良くあります。スクリプトにもまったく同じことが言えます。

プログラムの世界では、プログラムの流れの骨格を「アルゴリズム」と呼びます。ゴーストのデバッグでも、最後はこうしたアルゴリズムのデバッグが必要です。例えば、Referenceが必ず3個あることを想定したコマンドは、4個目のReferenceがあった場合、情報を取りこぼすかもしれません。また、数字の大小比較で「<」と「>」を書き間違えても、文法的にはまったく正しいスクリプトです。

こうしたアルゴリズムのデバッグで有効な方法を、2つ紹介します。どちらも普通のプログラム言語のデバッグで、一般的な方法です。

printfデバッグ

この方法は、スクリプトの随所にlogprintを挿入し、スクリプトの流れのキーとなるエントリの内容を、ログに記録する方法です。C言語でprintf関数を用いてよく行うことから、この名前があります。

例えば、

reply.OnMinuteChange : $(
    if $[ $(date %M) == ${timer} ] $(
        entry sentence.timer;
    );
  )

このようなスクリプトを考えます。このスクリプトは指定時刻になったら発話することを意図していますが、ある時刻を指定した場合、発話しなかったとします。これをprintfデバッグするため、次のように書き換えます。値を「#」で挟んでいますが、これはログ中で強調するためで、特に記号に意味はありません。

reply.OnMinuteChange : $(
    logprint #$(date %M)#;
    logprint #${timer}#;
    logprint #$[ $(date %M) == ${timer} ]#;
    if $[ $(date %M) == ${timer} ] $(
        #entry sentence.timer;
        setstr @temp $(entry sentence.timer);
        logprint #${@temp}#;
        echo ${@temp};
    );
  )

このスクリプトの場合、

  • 条件式がおかしい
  • 発話する内容がおかしい

の2通りの原因が考えられます。そこで、条件式とエントリの両方をログに出力しています。また、sentence.timerエントリを複数回呼んで状態を変えないよう、一度@tempエントリにsetstrで代入し、それからログに出力、戻り値として出力を行っています。

printfデバッグは比較的煩雑ですが、エントリの内部情報を確実に得られます。しかし、内部状態を得ることで状態を変えてしまわないよう、注意する必要があります。また、デバッグコードが残っていると、ファイルサイズが増加します。ゴーストのリリースの際、ネットワーク更新を素早くする為、確実にデバッグコードを消すよう、注意を払う必要があります。

デバッガによるデバッグ

このデバッグ方法は、幸水をデバッガモードで起動して、実際にゴーストが動いている状態で行います。先ほどのスクリプトをデバッグすることを考えると、echoモードで

echo-mode > $[ $(date %M) == ${timer} ]

など、先ほどのprintfデバッグでログに出力した内容を、バルーンに出力させる方法です。

この方法は、スクリプトを書き換える必要が無い点が大きなメリットです。反面、時間に依存する情報のデバッグでは、ちょうどいい瞬間にエントリの内容を調べることが、やや難しいのが難点です。