エラー処理
エラーには、いくつかの種類があります。
構文エラー #
最初に遭遇するのは、たいてい構文エラーでしょう。これは、コードが言語の文法に従っていない、次のような単純な不具合を含みます。
1 + * 2
Wrenは、コードを読み取ろうとした時点で、これらのエラーを検出します。エラーがあると、次のような分かりやすいメッセージを表示します。
[main line 1] Error on '*': Unexpected token for expression.
もう少し「意味的」なエラーも、この種類に含まれます。未定義の変数を使うことや、同じスコープで同じ名前の変数を二つ宣言することなどです。例えば、次のように書くと、
var a = "once"
var a = "twice"
Wrenは、次のように知らせます。
[main line 2] Error on 'a': Top-level variable is already defined.
これは、コードを一つでも実行する前に行う点に注意してください。ほかの一部のスクリプト言語とは異なり、Wrenは、可能な限り早くエラーを見つけられるようにします。
コードの実行を開始したなら、構文や変数のスコープに関するエラーがないことは確かです。
実行時エラー #
残念ながら、「コンパイル時」のエラーをすべて直しても、コードが意図どおりに動くとは限りません。プログラムには、静的には検出できないエラーが残っているかもしれません。コードを実行するまで見つけられないので、「実行時」エラーと呼びます。
実行時エラーの大半は、VM自身から発生します。VMができない操作を、コードが行おうとするためです。最も一般的なのは、「メソッドが見つからない」というエラーです。オブジェクトのメソッドを呼び出しても、そのクラスにも、すべてのスーパークラスにもそのメソッドが定義されていなければ、Wrenには何もできません。
class Foo {
construct new() {}
}
var foo = Foo.new()
foo.someRandomMethod
これを実行すると、Wrenは次のように表示します。
Foo does not implement method 'someRandomMethod'.
その後、コードの実行を停止します。ほかの一部の言語とは異なり、Wrenは実行時エラーが起きた後に実行を続けません。実行時エラーは、コードに不具合があることを意味するため、それに気付いてほしいのです。助けとなるよう、エラーが起きたコードの位置と、そこへ至るすべてのメソッド呼び出しを示すスタックトレースを表示します。
ほかによくある実行時エラーは、メソッドに型の違う引数を渡すことです。例えば、リストのインデックスには数値を使います。ほかの型を渡そうとすると、エラーになります。
var list = ["a", "b", "c"]
list["1"]
これは、次のように表示して終了します。
Subscript must be a number or a range.
[main line 2] in (script)
この二つが、最も一般的な実行時エラーですが、ほかにもあります。リストの範囲外へのアクセス、引数の個数を間違えた関数呼び出しなどです。
実行時エラーを処理する #
ほとんどの場合、実行時エラーはコードの不具合を示し、最善の解決策は、不具合を直すことです。しかし、実行時にエラーを処理できると便利な場合もあります。
言語を単純にするため、Wrenには例外処理がありません。代わりに、エラーの処理にファイバーを使います。実行時エラーが起きると、現在のファイバーは中止します。通常は、そのファイバーを呼び出したファイバーも、メインファイバーまで順に中止し、VMを終了します。
ただし、tryメソッドを使ってファイバーを実行することもできます。呼び出したファイバーで実行時エラーが起きると、エラーを捕捉し、tryメソッドはエラーメッセージを文字列として返します。
例えば、次のプログラムを実行すると、
var fiber = Fiber.new {
123.badMethod
}
var error = fiber.try()
System.print("Caught error: " + error)
次のように表示します。
Caught error: Num does not implement method 'badMethod'.
呼び出したファイバーは、もう使えませんが、ほかのファイバーは通常どおり続行できます。実行時エラーによってファイバーが中止された場合、そのファイバーオブジェクトからエラーを取得することもできます。上の例を続けると、
System.print(fiber.error)
これも、次のように表示します。
Num does not implement method 'badMethod'.
ファイバー呼び出しの連鎖の中で実行時エラーが起きると、その連鎖をたどってtry呼び出しを探します。したがって、tryを呼び出したファイバーが、さらに呼び出したファイバーで発生した実行時エラーも捕捉できます。
実行時エラーを発生させる #
実行時エラーの大半はWren VMの内部から発生しますが、自分で実行時エラーを発生させたい場合もあります。Fiberの静的abort()メソッドを呼び出すと、それができます。
Fiber.abort("Something bad happened")
エラーメッセージを渡す必要があり、そのメッセージは文字列でなければなりません。
渡したメッセージがnullなら、実行時エラーは発生しません。
処理の失敗 #
最後の種類のエラーは、最も高水準のものです。これまでのエラーは、すべて不具合、つまりコード自体が正しくない箇所を示します。しかし、予測できない理由により、単にコードが処理を達成できなかったことを示すエラーもあります。これを「失敗」と呼びます。
ユーザーから入力文字列を読み取り、数値へ解析するプログラムを考えてください。多くの文字列は有効な数値ではないので、この解析は失敗することがあります。プログラムが失敗を防ぐには、解析の前に文字列を検証するしかありませんが、文字列が数値かどうかの検証は、解析とほぼ同じ処理です。
このように失敗が起こり得て、プログラムがそれを処理したい場合、ファイバーとtry()は、粒度が粗すぎます。代わりに、こうした操作は、何らかのエラーの印を返すことで、失敗を示します。
例えば、数値を解析するメソッドは、成功したら数値を、解析が失敗したことを示すためにはnullを返せます。Wrenは動的型付けなので、メソッドから型の異なる値を返すことは、簡単で自然です。
モジュール化 →← 並行処理