better-result-adopt

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

better-result Adoption

better-result 迁移指南

Migrate existing error handling (try/catch, Promise rejections, thrown exceptions) to typed Result-based error handling with better-result.
将现有的错误处理(try/catch、Promise拒绝、抛出异常)迁移至使用better-result的类型化Result式错误处理。

When to Use

适用场景

  • Adopting better-result in existing codebase
  • Converting try/catch blocks to Result types
  • Replacing thrown exceptions with typed errors
  • Migrating Promise-based code to Result.tryPromise
  • Introducing railway-oriented programming patterns
  • 在现有代码库中采用better-result
  • 将try/catch块转换为Result类型
  • 用类型化错误替代抛出的异常
  • 将基于Promise的代码迁移至Result.tryPromise
  • 引入面向铁路编程模式

Migration Strategy

迁移策略

1. Start at Boundaries

1. 从边界开始

Begin migration at I/O boundaries (API calls, DB queries, file ops) and work inward. Don't attempt full-codebase migration at once.
从I/O边界(API调用、数据库查询、文件操作)开始迁移,逐步向内推进。不要尝试一次性完成整个代码库的迁移。

2. Identify Error Categories

2. 识别错误类别

Before migrating, categorize errors in target code:
CategoryExampleMigration Target
Domain errorsNotFound, ValidationTaggedError + Result.err
InfrastructureNetwork, DB connectionResult.tryPromise + TaggedError
Bugs/defectsnull deref, type errorLet throw (becomes Panic if in Result callback)
迁移前,对目标代码中的错误进行分类:
类别示例迁移目标
领域错误NotFound、ValidationTaggedError + Result.err
基础设施错误网络问题、数据库连接失败Result.tryPromise + TaggedError
漏洞/缺陷空引用、类型错误允许抛出(在Result回调中会成为Panic)

3. Migration Order

3. 迁移顺序

  1. Define TaggedError classes for domain errors
  2. Wrap throwing functions with Result.try/tryPromise
  3. Convert imperative error checks to Result chains
  4. Refactor callbacks to generator composition
  1. 为领域错误定义TaggedError类
  2. 用Result.try/tryPromise包装抛出异常的函数
  3. 将命令式错误检查转换为Result链式调用
  4. 将回调重构为生成器组合

Pattern Transformations

模式转换

Try/Catch to Result.try

Try/Catch 转换为 Result.try

typescript
// BEFORE
function parseConfig(json: string): Config {
  try {
    return JSON.parse(json);
  } catch (e) {
    throw new ParseError(e);
  }
}

// AFTER
function parseConfig(json: string): Result<Config, ParseError> {
  return Result.try({
    try: () => JSON.parse(json) as Config,
    catch: (e) => new ParseError({ cause: e, message: `Parse failed: ${e}` }),
  });
}
typescript
// BEFORE
function parseConfig(json: string): Config {
  try {
    return JSON.parse(json);
  } catch (e) {
    throw new ParseError(e);
  }
}

// AFTER
function parseConfig(json: string): Result<Config, ParseError> {
  return Result.try({
    try: () => JSON.parse(json) as Config,
    catch: (e) => new ParseError({ cause: e, message: `Parse failed: ${e}` }),
  });
}

Async/Await to Result.tryPromise

Async/Await 转换为 Result.tryPromise

typescript
// BEFORE
async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new ApiError(res.status);
  return res.json();
}

// AFTER
async function fetchUser(id: string): Promise<Result<User, ApiError | UnhandledException>> {
  return Result.tryPromise({
    try: async () => {
      const res = await fetch(`/api/users/${id}`);
      if (!res.ok) throw new ApiError({ status: res.status, message: `API ${res.status}` });
      return res.json() as Promise<User>;
    },
    catch: (e) => (e instanceof ApiError ? e : new UnhandledException({ cause: e })),
  });
}
typescript
// BEFORE
async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new ApiError(res.status);
  return res.json();
}

// AFTER
async function fetchUser(id: string): Promise<Result<User, ApiError | UnhandledException>> {
  return Result.tryPromise({
    try: async () => {
      const res = await fetch(`/api/users/${id}`);
      if (!res.ok) throw new ApiError({ status: res.status, message: `API ${res.status}` });
      return res.json() as Promise<User>;
    },
    catch: (e) => (e instanceof ApiError ? e : new UnhandledException({ cause: e })),
  });
}

Null Checks to Result

空值检查转换为Result

typescript
// BEFORE
function findUser(id: string): User | null {
  return users.find((u) => u.id === id) ?? null;
}
// Caller must check: if (user === null) ...

// AFTER
function findUser(id: string): Result<User, NotFoundError> {
  const user = users.find((u) => u.id === id);
  return user
    ? Result.ok(user)
    : Result.err(new NotFoundError({ id, message: `User ${id} not found` }));
}
// Caller: yield* findUser(id) in Result.gen, or .match()
typescript
// BEFORE
function findUser(id: string): User | null {
  return users.find((u) => u.id === id) ?? null;
}
// 调用者必须检查: if (user === null) ...

// AFTER
function findUser(id: string): Result<User, NotFoundError> {
  const user = users.find((u) => u.id === id);
  return user
    ? Result.ok(user)
    : Result.err(new NotFoundError({ id, message: `User ${id} not found` }));
}
// 调用者: 在Result.gen中使用yield* findUser(id),或使用.match()

Callback Hell to Generator

回调地狱转换为生成器

typescript
// BEFORE
async function processOrder(orderId: string) {
  try {
    const order = await fetchOrder(orderId);
    if (!order) throw new NotFoundError(orderId);
    const validated = validateOrder(order);
    if (!validated.ok) throw new ValidationError(validated.errors);
    const result = await submitOrder(validated.data);
    return result;
  } catch (e) {
    if (e instanceof NotFoundError) return { error: "not_found" };
    if (e instanceof ValidationError) return { error: "invalid" };
    throw e;
  }
}

// AFTER
async function processOrder(orderId: string): Promise<Result<OrderResult, OrderError>> {
  return Result.gen(async function* () {
    const order = yield* Result.await(fetchOrder(orderId));
    const validated = yield* validateOrder(order);
    const result = yield* Result.await(submitOrder(validated));
    return Result.ok(result);
  });
}
// Error type is union of all yielded errors
typescript
// BEFORE
async function processOrder(orderId: string) {
  try {
    const order = await fetchOrder(orderId);
    if (!order) throw new NotFoundError(orderId);
    const validated = validateOrder(order);
    if (!validated.ok) throw new ValidationError(validated.errors);
    const result = await submitOrder(validated.data);
    return result;
  } catch (e) {
    if (e instanceof NotFoundError) return { error: "not_found" };
    if (e instanceof ValidationError) return { error: "invalid" };
    throw e;
  }
}

// AFTER
async function processOrder(orderId: string): Promise<Result<OrderResult, OrderError>> {
  return Result.gen(async function* () {
    const order = yield* Result.await(fetchOrder(orderId));
    const validated = yield* validateOrder(order);
    const result = yield* Result.await(submitOrder(validated));
    return Result.ok(result);
  });
}
// 错误类型是所有yield错误的联合类型

Defining TaggedErrors

定义TaggedErrors

See references/tagged-errors.md for TaggedError patterns.
有关TaggedError模式,请参阅references/tagged-errors.md

Workflow

工作流程

  1. Check for source reference: Look for
    opensrc/
    directory - if present, read the better-result source code for implementation details and patterns
  2. Audit: Find try/catch, Promise.catch, thrown errors in target module
  3. Define errors: Create TaggedError classes for domain errors
  4. Wrap boundaries: Use Result.try/tryPromise at I/O points
  5. Chain operations: Convert if/else error checks to .andThen or Result.gen
  6. Update signatures: Change return types to Result<T, E>
  7. Update callers: Propagate Result handling up call stack
  8. Test: Verify error paths with .match or type narrowing
  1. 检查源引用:查看是否存在
    opensrc/
    目录——如果存在,阅读better-result的源代码以了解实现细节和模式
  2. 审计:在目标模块中查找try/catch、Promise.catch、抛出的错误
  3. 定义错误:为领域错误创建TaggedError类
  4. 包装边界:在I/O点使用Result.try/tryPromise
  5. 链式操作:将if/else错误检查转换为.andThen或Result.gen
  6. 更新签名:将返回类型更改为Result<T, E>
  7. 更新调用者:在调用栈中向上传播Result处理逻辑
  8. 测试:使用.match或类型收窄验证错误路径

Common Pitfalls

常见陷阱

  • Over-wrapping: Don't wrap every function. Start at boundaries, propagate inward.
  • Losing error info: Always include cause/context in TaggedError constructors.
  • Mixing paradigms: Once a module returns Result, callers should too (or explicitly .unwrap).
  • Ignoring Panic: Callbacks that throw become Panic. Fix the bug, don't catch Panic.
  • 过度包装:不要包装每个函数。从边界开始,逐步向内传播。
  • 丢失错误信息:始终在TaggedError构造函数中包含原因/上下文。
  • 混合范式:一旦模块返回Result,调用者也应返回Result(或显式调用.unwrap)。
  • 忽略Panic:抛出异常的回调会成为Panic。修复漏洞,不要捕获Panic。

References

参考资料

  • TaggedError Patterns - Defining and matching typed errors
  • opensrc/
    directory (if present) - Full better-result source code for deeper context
  • TaggedError Patterns - 定义和匹配类型化错误
  • opensrc/
    目录(如果存在)- 完整的better-result源代码,提供更深入的上下文