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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for IkedaNoritaka IkedaNoritaka
May 22, 2026
1.1k

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にも人間にも優しいエラーハンドリングを実現する