[NAME] ALL.dao.defer-error [TITLE] 延迟代码块和错误处理 [DESCRIPTION] 道语言里的错误处理是基于延迟代码块(defer blocks)。 延迟代码块可基于错误或例外有条件或无条 件执行。 0.1 延迟代码块(Defer Blocks) 延迟代码块是一种在函数正常退出或因错误退出时执行的代码块。 延迟代码块的有条件或无条件执行是 基于有无错误和什么错误: * defer{block} or defer(){block}: 无条件执行, 不管函数以何种形式退出; * defer(none){block}: 有条件执行,仅当函数正常退出时; * defer(any){block}: 有条件执行,当函数因任何错误退出时; * defer(type){block}: 有条件执行, 仅当导致函数退出的错误跟type匹配时; * defer(type as name){block}: 跟defer(type){block}一样, 并且在代码块里,错误对象将可以 用变量名name访问。 无条件延迟代码块的最主要用途是函数退出时的善后处理。 这种善后处理将被无条件地执行,而不管函 数是以何种方式退出。 无错误条件延迟代码块defer(none),则可用于实现原子事务处理。 其他错误条件 延迟代码块则主要用于错误处理。 延迟代码块是以函数闭包编译和执行的。也就是说这种代码块可以访问外层局部变量, 将在运行时生成 可获得这些变量当前值的闭包。只不过这些闭包将在函数退出是自动 执行。所有可执行延迟代码块将以 被创建顺序的相反顺序执行。 1 routine Test() 2 { 3 defer { 4 io.writeln( "always executed" ) 5 } 6 defer( none ){ 7 io.writeln( "defer( none )" ) 8 } 9 10 for( i = 2 : 5 ) defer { io.writeln( "deferred", i ) } 11 12 io.writeln( "returning" ); 13 return 123 14 } 15 16 io.writeln( Test() ) 0.2 错误处理 道里面错误处理的主要方式是基于有条件的延迟代码块。 一个函数里可为不同的错误类型定义不同的延 迟代码块。 每个有条件的延迟代码块只能被一个错误对象激活运行。 当有多个错误时,每个代码块将从 最新的错误到最老的错 误检查是否可被其中的一个错误激活。 为了处理某个特定的错误类型,那么一个以该类型为条件参数的 延迟代码块必须被定义。这样这个代码 块将可以被该类型的错误 激活运行。并且激活此代码块的错误将被消除,允许程序再从处理 错误的函数 的调用处继续执行。 这种处理错误的延迟代码块的返回值类型必须跟定义它的函数一致。 因为当这种延迟代码块被执行时, 该函数还没执行到正常返回, 所以返回值还没设定。这种延迟代码块是唯一可以在错误发生后 设定合适 的返回值的地方。 例子: 1 routine Test() 2 { 3 defer ( Error ){ 4 io.writeln( "Error is handled! And a new value is returned!" ) 5 return 456 6 } 7 io.writeln( "Test(): before error;" ) 8 std.error( "some error" ) 9 io.writeln( "Test(): after error;" ) 10 return 123 11 } 12 io.writeln( Test() ) 这里使用了标准方法std.error()来抛出错误。 这个方法有三个重载版本,这里用的是最简单的一个。 它 将以它的参数字符串为错误信息创建一个通用错误对象。 道定义一些标准的错误类型以报告一些标准操 作和方法所可能抛出的错误。 0.3 错误类型 上例所用Error类型是一个通用错误类型。它也是其他错误类型的基类。 Error类型本身则是从例外类型 Exception派生出来。 这个例外类型可保存所有跟它相关的信息,诸如,错误的通用名,错误消息,错误数 据, 源文件名,行号和函数调用栈等。 以下是标准的道例外类型: 1 Exception # General exception 2 Warning # General warning 3 Error # General error 4 Error::Field # Invalid field 5 Error::Field::NotExist # Field not exist 6 Error::Field::NotPermit # Field not permit 7 Error::Float # Floating point error 8 Error::Float::DivByZero # Division by zero 9 Error::Float::OverFlow # Floating point overflow 10 Error::Float::UnderFlow # Floating point underflow 11 Error::Index # Invalid index 12 Error::Index::Range # Index out of range 13 Error::Key # Invalid key 14 Error::Key::NotExist # Key not exist 15 Error::Param # Invalid parameter(s) 16 Error::Syntax # Invalid syntax 17 Error::Type # Invalid type 18 Error::Value # Invalid value 19 Error::File # File error 值得注意的是,Warning和Error也被声明在全局命名空间, 这样它们就可不用Exception::前缀而直接使 用。 在这些例外类型中,就Exception,Warning 和Error被预先定义了,其他的仅被预先声明了。 只有预 先定义的例外类型可被直接使用。其他类型则需要使用Exception::Define() 如下显示定义才能使用: 1 const IndexError = Exception::Define( "Error::Index" ) 等号右边的表达式将作为常量表达式在编译时求值,这样这行代码之后的代码将可直接使用 Error:: Index类型。 这里Exception::Define()将定义一个名为Index的错误类型。 这个新类型将Error派生,并 在Error里注册为Error 的一个成员类型。 这样定义的类型将以C数据类型的方式定义生成,并拥有一个 独一无二的标志Error::Index。 也就是在任何地方使用Exception::Define( "Error::Index" ) 定义的 Error::Index都将是同一个类型。 上面这种错误类型的定义方式不仅局限于定义那些预先声明的错误类型。 它也用来定义任意错误类型: 1 const MyErrorType = Exception::Define( "Error::MyError", "General information" ) 这里的第二个参数将作为这种错误类型的通用信息被关联到这个新错误类型上。 定义那些预先声明的错 误类型不需要这第二个参数,因为它们的标准错误信息也已经 被预先声明。 这种错误定义的方式的主要 好处是方便简单。但它也有个缺点,也就是这种方式定义的 类型没有构造方法,因此要抛出这种类型的错 误,必须使用一个特定重载版本的 std.error(),详情请看后面。 现在这个新定义的错误类型可用两种方式使用:一种是使用Exception::Define() 放回的类型;另一种是 它的标准名Error::MyError: 1 defer( MyErrorType ) { ... } 2 defer( Error::MyError ) { ... } 另一种定义用户错误类型的方式是从Error类型派生道类: 1 class MyError : Error 2 { 3 routine serialize(){ return ('MyError', self) } 4 } 0.4 抛出警告和错误 在道代码中,警告可用标准方法std.warning()抛出: std.warning(), 1 std.warn( info: string ) 这个方法将立即打印包含此方法的调用位置(文件名和行号)和函数调用栈信息的警告消息。 这些信息提 供了此错误发生的环境的基本信息。 错误也可用类似方法std.error()抛出: 1 std.error( info: string ) 道里面,抛出错误将导致当前执行函数立即退出,并开始尝试执行此函数所创建的延迟代码块。 如果此函 数定义了处理此类型错误的延迟代码块,那么这个代码块被执行之后, 程序的正常执行将从此函数的调 用处继续。否则,此函数的调用者也将退出, 并且它的延迟代码块也将被尝试执行。这个过程将被重复直 到,有一个函数 定义了处理此错误的延迟代码块,这种情况下,程序的正常执行将从这个函数的调用处继 续。 如果没有函数定义了处理此错误的延迟代码块,程序将被终止,此错误的信息 将被打印错来。跟警告 类似,此信息包括了std.error()的调用位置(文件名和行号) 和函数调用栈信息。 这个std.error()方法有三个不同的重载版本。 上面用到的是其中最简单的一个,其他两个如下: 1 std.error( errorObject: Error ) 2 std.error( errorType: class<Error>, info: string, data: any = none ) 前者可以用来抛出一个预先创建的错误对象。 后者则即时创建一个错误对象,这个错误对象将是错误类 型errorType 的一个实例,并把info作为错误消息和data数据数据保存。 值得注意的是通过Exception ::Define()定义的错误类型的错误只能 通过最后面这个方法抛出,因为这样定义的错误类型没有构造方 法。 0.5 和错误处理相关的方法 道语言的标准方法里还包括了另外三个和错误处理相关的方法。 它们就是: 1 std.exec() [=>@T] => @T 2 std.exec( defaultValue: @T ) [=>@T] => @T 3 std.try() [=>@T] => list<Error>|Error|@T 0.5.1 std.exec()[=>@T]=>@T 正如前面提到的,当错误被处理后,程序的正常运行将从定义处理该错误的延迟代码块 的函数的调用者 那里开始继续。如果要想程序在离错误发生处最近的地方处理错误 并继续正常执行,那么下面的方法将 可以用来做到这一点。 1 std.exec() [=>@T] => @T 代码块方法将在新的函数调用帧里执行,在这个方法的代码块里使用处理错误的 延迟代码块将允许错误 被处理后,程序从此方法后面继续执行。 1 var fout = io::stdio 2 3 std.exec { 4 defer { recover() } 5 fout = io.open( "NonExistentFile.txt", 'r+' ) 6 } 7 8 if( fout != io::stdio ) defer{ fout.close() } 9 10 fout.writeln( 'hello' ) 0.5.2 std.exec(defaultValue:@T)[=>@T]=>@T 有时候,有些表达式可以被预知的抛出错误,而这错误并不影响程序的执行, 因为这些表达式有合理的缺 省值可以在错误发生后替代使用。 一个简单的情形就是,程序运行的某些信息既可输出到文件, 也可输 出到标准输出。如果相关文件存在就输出到该文件, 否则输出到标准输出。这其实就是上例所作的,那些 代码可使用 本方法作如下简化: 1 var fout = std.exec( io::stdio ){ io.open( "NonExistentFile.txt", 'r+' ) } 2 if( fout != io::stdio ) defer{ fout.close() } 3 fout.writeln( 'hello' ) 0.5.3 std.try()[=>@T]=>list<Error>|Error|@T 这个方法跟上面的类似,只不过这不返回缺省值,而是将发生的错误放回。