错误处理
Targo 遵循 Go 风格错误处理。普通失败通过返回值传递,而不是抛出异常。
核心规则
- 可能失败的操作优先使用
[T, error | null]。 - 在调用点显式检查错误。
panic()和recover()保留给异常或不可恢复的情况,不用于普通失败。- 优先显式包装或返回错误,而不是依赖异常风格的控制流。
常见错误返回模式
typescript
const [data, err] = loadConfig(path);
if (err != null) {
return [zero<Result>(), err];
}
return [data, null];有意忽略值
当只关心元组的一侧时,保持被忽略的值显而易见:
typescript
const [_data, err] = loadConfig(path);
if (err != null) {
return err;
}
return null;typescript
const [value, _err] = maybeReadCached(path);
return value;如果项目有更强的本地约定,遵循该约定。否则优先使用 _err 或 _value 这样的显式占位名。
Comma-Ok 风格结果
不是所有元组返回都是错误返回。有些 API 返回值加存在标志:
typescript
const [value, ok] = cache.Get(key);
if (!ok) {
return [zero<Item>(), new Error("missing item")];
}
return [value, null];把这些当作 Go 风格多返回值 API,而不是 JavaScript 解构语法糖。
错误构造
typescript
import { New } from "errors";
return [zero<Result>(), New("missing config")];typescript
import { Errorf } from "fmt";
return [zero<Result>(), Errorf("invalid config %s: %v", name, err)];实践建议
- 返回值立即使用时,在调用点附近解构。
- 如果结果需要在多个分支中传递,引入命名局部变量而不是重复调用函数。
- 优先使用显式分支流,而不是把所有东西压缩到嵌套条件表达式中。
常见错误
- 把
[T, error | null]当成 Promise 结果。 - 直接翻译
try/catch而不是重新设计函数契约。 - 在尝试普通元组解构之前搜索代码库中的"特殊语法"。
- 把忽略值隐藏得太深,导致控制流不清晰。
- 在同一路径中混用错误返回元组和异常风格控制流。
- 把
panic()用于日常业务错误。
推荐模式
Ok-chain 用于顺序可失败操作
ok() 是一个内置函数,将 [T, error | null] 结果包装为可链式调用的管道,避免嵌套的 if-err 检查:
typescript
// 代替嵌套 if-err:
function loadNumber(path: string): [int, error | null] {
return ok(readLine(path))
.flatMap<int>(s => Atoi(s))
.unwrap();
}链式方法(非终结):map、flatMap、mapErr、orElse、tap、tapErr 终结方法(结束链):unwrap、must、orDefault、orZero、fold
typescript
// mapErr 包装错误并添加上下文
ok(loadConfig(path))
.mapErr(err => Errorf("config failed: %v", err))
.unwrap();
// orDefault 提供回退值
const settings = ok(loadConfig(path))
.map(config => parseSettings(config))
.orDefault(defaultSettings);错误包装与上下文
typescript
import { Errorf } from "fmt";
const [user, err] = findUser(id);
if (err != null) {
return [zero<Response>(), Errorf("findUser(%d): %v", id, err)];
}