Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OSSのコードベースにneverthrowを漸進的に 導入して、AIにも人間にも優しい エラー...

Avatar for IkedaNoritaka IkedaNoritaka
May 22, 2026
260

OSSのコードベースにneverthrowを漸進的に 導入して、AIにも人間にも優しい エラーハンドリングを実現する

Avatar for IkedaNoritaka

IkedaNoritaka

May 22, 2026

Transcript

  1. ・NoritakaIkeda ・X(Twitter): @omotidaisukijp ・ROUTE06 Acsim事業部 ・フロントエンド寄りのフルスタック ・TSKaigi2024ではLTで登壇、TSKaigi2025 では機能的凝集度の話をしました 自己紹介 2

    はじめに OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  2. クイズ — どの関数がエラーになる可能性がある ? 以下のシグネチャを見て、エラーになりうる関数はどれ ? 3 1. neverthrowとは OSSのコードベースに

    neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する ① fetchUser ② calculateTax ③ saveOrder ④ わからない ⑤全ての箇所
  3. 答え 4 1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する ④シグネチャからは わからない TypeScript

    では、失敗の可能性は型に出ない - fetchUser / calculateTax / saveOrder の3つすべて throw する可能性がある - 戻り値の型 (User / Money / OrderId) からは失敗の可能性が読めない - → 今日はこの「シグネチャだけではわからない」状態を直す話
  4. 今日話すこと neverthrowを用いて、 AIにとっても人間にとっても読みやすいエラー設計を目指す - Result型は、失敗した型を定義できるので、エラーを握り潰さない - .andThen() チェーンによって、逐次処理のコードの可読性が上がる - チームでよく議論し、学習コストを乗り越える

    - AIのモデルが進化し、 AIに過剰なガードレールをしかずに neverthrowを使える neverthrowを導入したことで、 - AIが安易にエラーを握りつぶさないため、デバッグが用意になり、開発が楽になった 5 はじめに OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  5. 目次 - neverthrowとは: - Result型で失敗の型をシグネチャに出す - なぜResult型が必要な場面があるのか - 実際のプロジェクトに導入した際の Tips:

    - AIによる漸進的な導入を行う - チームで理解を深める - .andThen() チェーンが使える場所を見極める 6 はじめに OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  6. neverthrowとは 関数の失敗を throw ではなく戻り値として返すことで、エラーの可能性を型に出すライブラリ。 - throw する代わりに Result<T, E> を返す

    - Result 型は Rust など多くの言語で採用されているエラー表現パターン 8 1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  7. Result型とは Result<T, E> は、成功なら T、失敗なら E のどちらか一方が必ず入っている値 - ok(value) で成功、err(error)

    で失敗 を作る - 値を取り出すには必ず match や andThen を呼ぶ - 非同期処理 (Promise) を Result として扱える ResultAsync<T, E> 9 1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  8. なぜResult型が必要な場面があるのか try/catch では「失敗の可能性」「失敗の 種類」が型に出ない - throw は goto 的、catch の引数の型は

    unknown - 握りつぶしが書きやすい (catch {} / return null) - 呼び出し側、 AI、半年後の自分が困る 10 1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  9. 我々のプロジェクトで必要だった場面 プロジェクト : Liam - ERD可視化ツール https://liambx.com/ - DB設計エージェント ←

    本日はこっちのプロダクト対象 12 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  10. 我々のプロジェクトで必要だった場面 liam にも、握りつぶしによる苦しみが実際にあった - 実例① 5種類の失敗が全部 null で返る関数 - 実例②

    JSON.parse と バリデーション失敗が同じデフォルト 13 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  11. 実例① — 5種類の失敗が全部 null で返る downloadFileContent: 戻り値が Promise<string | null>

    - 失敗パターンは 5種類あるのに、全部 null で返している - console.error は出すが、呼び出し側からは 原因が見えない - 「動くけど、不具合が出た時に原因不明」 14 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する 📎 github.com/liam-hq/liam/.../github/src/api.server.ts
  12. 実例① — Result化後の変化 戻り値を Promise<Result<string, Error>> に 変える - すべての失敗が

    Error として返る - 呼び出し側は match で失敗の理由が読め る - スタックトレース・原因が保持される - 例だと省略しているが、エラーごとに型を変 えられる 15 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する 📎 github.com/liam-hq/liam/.../github/src/api.server.ts
  13. 実例② — JSON parse と検証失敗が同じ空デフォルト JSON.parse 失敗 と スキーマ検証失敗 を同じ空

    デフォルトで返していた - 呼び出し側は「 JSONが壊れている」か 「Liam形式じゃない」か区別できない - 16 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する 📎 github.com/liam-hq/liam/.../schema/src/parser/liam/index.ts
  14. 実例② — Result化後の変化 JSON.parse 失敗 と スキーマ検証失敗 を同じ空 デフォルトで返していた -

    parseJson.andThen(parseSchema) で段 階を分離 17 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する 📎 github.com/liam-hq/liam/.../github/src/api.server.ts
  15. AIによる漸進的な導入を行う 全置換は失敗する。 4ステップで段階的に進めた - ① 自前ラッパー @liam-hq/neverthrow を作る - ②

    1〜2パッケージで人間が代表例を書く - ③ AI (Devin) に他パッケージへ横展開 - ④ ESLint で後戻り禁止 18 2. 実際のプロジェクトに導入した際の Tips OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する // configs/eslint/no-throw-error-plugin.js ThrowStatement(node) { if (node.argument?.callee?.name === 'Error') { context.report({ message: 'Use neverthrow Result types instead.', }) } } // configs/eslint/base.js 'no-restricted-imports': ['error', { paths: [{ name: 'neverthrow', message: 'Use @liam-hq/neverthrow instead' }], }] 📎 github.com/liam-hq/liam/.../configs/eslint/no-throw-error-plugin.js
  16. エラー型を Errorに統一した薄いラッパーパッケージの設計 @liam-hq/neverthrow は本家を薄く包んだ自前パッケージ - 主目的は OSS 公開時の依存パッケージ集約 - defaultErrorFn

    を自動で当てて unknown 漏れを防ぐ - import 先が一意になり、 AI に指示が出しやすい 19 OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する // @liam-hq/neverthrow/src/fromPromise.ts export function fromPromise<T>( promise: Promise<T>, errorFn?: (error: unknown) => Error, ) { return ResultAsync.fromPromise( promise, errorFn ?? defaultErrorFn, // ← unknown 漏れを防ぐ ) } // defaultErrorFn.ts export const defaultErrorFn = (error: unknown): Error => error instanceof Error ? error : new Error(String(error)) 📎 github.com/liam-hq/liam/.../neverthrow/src/fromPromise.ts 2. 実際のプロジェクトに導入した際の Tips
  17. 実験 — シグネチャと Skill が AI 生成コードに与える影響 同じ機能を Claude Code

    に書かせて 2 パターン比較した - 共通: countDependencies (package.json の依存数を数える関数 ) - A: throw/null 返しの API (getFileContent) を使わせる - B: Result 返しの API (downloadFileContent) + チーム作法 Skill - プロンプトは同一 / 検証目的は AI に伏せる / PR #4096 #4097 20 1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  18. 結果 — AI 生成コードの 4 つの質的変化 シグネチャ + Skill で

    AI が書くコードの「形」が変わる 21 1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する 観点 A (throw/null) B (Result + Skill) ① .andThen チェーン なし (手続き型 if文) 5 ステップ連鎖 (動詞が並ぶ) ② 手動 narrowing (typeof/null/'in') 4 段 0 段 (valibot で代替) ③ JSON.parse の扱い try/catch fromThrowable で包む ④ 失敗の原因情報 固定文字列に縮約 cause で元エラー保存 ⑤実際のPR https://github.com/liam-hq/liam/pull/4096 https://github.com/liam-hq/liam/pull/4097
  19. コードの劇的な変化 — 動詞が並ぶ宣言型へ A: 早期 return と手動 narrowing の連続 22

    1. neverthrowとは OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  20. コードの劇的な変化 — 動詞が並ぶ宣言型へ B: .andThen で動詞が並ぶ 23 1. neverthrowとは OSSのコードベースに

    neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する
  21. チームで理解を深める Lint をすり抜ける誤用は必ず起きる。ペアプロで一 個ずつ直す - isErr() で if 文の山にしてしまう -

    Result を返しているのに中で throw を呼んで しまう 24 OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する // Liamの実際のコード async function readTokenFrom(name: string): Promise<TokenPayload | null> { const raw = (await cookies()).get(name)?.value if (!raw) return null const plaintext = unpackCookieValue(raw) if (plaintext.isErr()) return null // ← 陥りがち const payloadJson = parsePayload(plaintext.value) if (payloadJson.isErr()) return null // ← 陥りがち // ... if 文の山 } 📎 github.com/liam-hq/liam/.../app/libs/github/cookie.ts 2. 実際のプロジェクトに導入した際の Tips // 修正後: andThen でチェーンに return unpackCookieValue(raw) .andThen((plain) => parsePayload(plain)) .andThen((json) => fromValibotSafeParse(TokenPayloadSchema, json))
  22. 誤用例 — Result を受け取って throw に戻してしまう Result の世界は境界まで運ぶ。途中で throw に戻すと型が

    嘘になる - AIがやりがち : `if (result.isErr()) throw result.error` で Result を破壊 - 正しい: そのまま Result で返すか、 `.andThen` でつなぐ - 境界で throw が必要なら eslint-disable + 理由コメントを書 く 25 OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する // ❌ やりがちな誤用 function processOrder(id: string): Result<Order, Error> { const orderResult = fetchOrder(id) if (orderResult.isErr()) { throw orderResult.error // Result を返すと宣言してるのに throw } return ok(orderResult.value) } // ✅ 正しい: Result をそのまま流す (境界まで開かない) function processOrder(id: string): Result<Order, Error> { return fetchOrder(id) } // ✅ 境界で開く時だけ throw に変換 (eslint-disableで意図明示) // eslint-disable-next-line no-throw-error/no-throw-error -- LangGraph retry needs throw if (result.isErr()) throw result.error 2. 実際のプロジェクトに導入した際の Tips
  23. .andThen() チェーンが使える場所を見極める Result は全ての場所に向いてはいない。刺さる場所を見極め る - 【刺さる】複数ステップが順に並ぶビジネスロジック - 【刺さらない】 DOM操作・リソース管理・フレームワーク

    制御フロー - 「内側 Result / 外側 throw / 境界でラップ」が基本 26 OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する // ✅ 刺さる: ビジネスロジックの連鎖 return validateInput(raw) .andThen(checkUserExists) .andThen(hashPassword) .andThen(saveToDb) .andThen(sendWelcomeEmail) // → 成功パスが上から読み下せる、失敗の種類で分岐できる // ❌ 刺さらない : DOM副作用の連鎖 return updateHeader(data) .andThen(() => updateBody(data)) // header失敗で body も スキップ? .andThen(() => updateFooter(data)) // 本来は部分成功 OKなはず // → 副作用は andThen で繋ぐ意味がない、 try/catchで個別に 2. 実際のプロジェクトに導入した際の Tips
  24. .andThen() と isErr() early return の使い分け どちらも Result の短絡だが、向き不向きがある -

    チェーンが効く : 純粋な変換が並ぶ、中間値を使わない - early return が効く: 中間値を使う、 async混在、副作 用を挟む 27 OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する // ✅ チェーンが効く : 純粋な変換が並ぶ return validateInput(raw) .andThen(checkExists) .andThen(save) // ✅ early return が効く: 中間値を両方使いた い const r1 = await fetchOrder(id) if (r1.isErr()) return r1 const r2 = await fetchUser(r1.value.userId) if (r2.isErr()) return r2 return ok({ order: r1.value, user: r2.value }) // ← 両方の値を返す 2. 実際のプロジェクトに導入した際の Tips
  25. 今日話すこと neverthrowを用いて、 AIにとっても人間にとっても読みやすいエラー設計を目指す - Result型は、失敗した型を定義できるので、エラーを握り潰さない - .andThen() チェーンによって、逐次処理のコードの可読性が上がる - チームでよく議論し、学習コストを乗り越える

    - AIのモデルが進化し、 AIに過剰なガードレールをしかずに neverthrowを使える neverthrowを導入したことで、 - AIが安易にエラーを握りつぶさないため、デバッグが用意になり、開発が楽になった 28 はじめに OSSのコードベースに neverthrowを漸進的に導入して、 AIにも人間にも優しいエラーハンドリングを実現する