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