Static Reflection
Static Reflection
发布于 2026-02-16 / 11 阅读
0

利用 Dart Callable Class 统一封装异常

设想这样一个开发场景:你需要将大量的 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抛出。