设想这样一个开发场景:你需要将大量的 HTML 网页解析为本地的数据模型(Model)。在这个过程中,你会发现每次编写解析逻辑时,都不得不重复编写繁琐的异常捕获(try-catch)代码。这不仅增加了工作量,还让代码显得臃肿且难以维护。
为了解决这个问题,我们可以利用 Dart 的一个特性——Callable Class(可调用类),来实现异常处理的全局统一封装。
定义统一异常
首先,我们构建一套标准的异常类。定义基础异常EhException,并在此基础上扩展出专用的EhParseException。这个子类能够携带关键的调试信息,包括堆栈追踪、解析器类型以及导致错误的原始数据。
class EhException implements Exception {
final Object message;
EhException(this.message);
@override
String toString() => 'EhException: $message';
}
class EhParseException extends EhException {
final StackTrace stackTrace;
final Type parser; // 解析器类名
final String rawData; // 原始数据
EhParseException(
super.message, {
required this.stackTrace,
required this.parser,
required this.rawData,
});
@override
String toString() =>
'EhParseException[$parser]: $message\n'
'StackTrace: $stackTrace\n'
'$rawData';
}
构建解析基类
接下来,定义核心解析基类 EhBaseParser。
这里使用了泛型<I, T>来分别约束输入数据类型和返回结果类型。
关键点在于我们复写了call方法,并让它作为parser抽象方法的“代理层”。所有的异常捕获逻辑都被收敛在这个基类的call方法中。
/// [I] 原始数据类型
/// [T] 返回类型
abstract class EhBaseParser<I, T> {
T call(I input) {
try {
return parser(input);
} catch (e, stackTrace) {
if (e is EhParseException) rethrow;
throw EhParseException(
e,
stackTrace: stackTrace,
parser: runtimeType,
rawData: _formatRawData(input),
);
}
}
@protected
T parser(I input);
String _formatRawData(I input) {
return switch (input) {
Document document => document.outerHtml,
Element element => element.outerHtml,
_ => input.toString(),
};
}
}简化具体的解析器实现
有了上述基类,我们在编写具体的业务解析器时,就可以完全摆脱重复的异常处理代码,仅需专注于解析逻辑本身。
例如,实现一个用于提取画廊 ID 和 Token 的解析器:
class EhGalleryUrlParser extends EhBaseParser<String, EhGalleryIdModel> {
@override
EhGalleryIdModel parser(String input) {
final m = RegExp(r'/g/(\d+)/([a-z0-9]+)').firstMatch(input);
return EhGalleryIdModel(gid: int.parse(m!.group(1)!), token: m.group(2)!);
}
}当我们实例化并像函数一样调用EhGalleryUrlParser时,实际上触发的是基类的call方法。
由于call方法包裹了具体的parser实现,这意味着:任何在解析过程中抛出的运行时错误(比如正则匹配失败),都会在call层级被自动捕获,并统一封装为EhParseException抛出。