neverthrow is not for meを 再考する

-- Views

June 12, 26

スライド概要

TSKaigi2026〜アフターパーティー〜 のスライドです。
neverthrowを一度見送ってからなぜ採用に至ったのか、その過程と判断を記しました。
https://every.connpass.com/event/393937/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

neverthrow is not for meを 再考する TSKaigi2026〜アフターパーティー〜 株式会社スリーシェイク Rinrin Copyright © 3-shake, Inc. All Rights Reserved.

2.

自己紹介 ● ● ● ● Copyright © 3-shake, Inc. All Rights Reserved. 名前:Rinrin / 林 侑生 所属:株式会社スリーシェイク Sreake事業部 業務:決済基盤のリプレース 今回再考したきっかけ ○ 「neverthrowはnot for me」関連投稿が 散見 ○ 昨年開発時に導入を見送り、同感だった ○ 一方で、ふと今も同感なのか疑念が生じた 2

3.

会社紹介 会社名 株式会社スリーシェイク 設立日 2015/1/15 代表者 代表取締役社長 所在地 東京都中央区銀座8丁目21番1号 住友不動産汐留浜離宮ビル7F 吉田 拓真 Googleクラウド・AWSの両方のエンジニアリングに強みを持つ Mission: インフラをシンプルにして イノベーションが起こりやすい世界を作る Vision: 労苦〈Toil〉を無くすサービスを適正な価格で提供し続ける 従業員: 200名over 100 日本のSREをリードする あらゆるサービスを 連携するハブになる インフラ・アプリ・データ・セキュリティ・AI 全方位で顧客の内製化を推進する伴走支援 あらゆるSaaSをノーコードで連携する クラウド型ETL/データパイプラインSaaS Engineer 50 0 事業者が抱える セキュリティリスクをゼロに 「いいエンジニア」を あなたのチームに セキュリティ対策をワンストップで 実現する脆弱性診断SaaS ハイスキル人材の紹介とHR戦略支援の両輪で エンジニア組織の課題に併走 60% 2015 2016 2017 2018 2019 2020 2021 2022 会社・事業部説明資料(SpeakerDeck) 3

4.

Sreakeの事業 SREの思想・技術をベースに、4つの領域でプロフェッショナルサービスを提供しています ・アプリケーションモダナイゼーション ・プラットフォーム構築 / 運用高度化 API / マイクロサービス / リファクタリング Google Cloud / AWS / Kubernetes / IaC ・信頼性 / 生産性 / DevEx向上 ・開発プロセス改善・高度化 SRE / o11y / CI/CD / Platform Engineering SRE / DevOps AI駆動開発 / 開発環境 / CI/CD ・セキュリティ強化 アセスメント / SIEM / WAF / CSIRT 4 Application Modernization ・組織組成・強化 アジャイル / スクラム / チームトポロジー ・生成AI導入 / 活用 ワークショップ / RAG ・データベース構築 / 移行 / 性能改善 ・AIエージェント開発 PostgreSQL / MySQL / Spanner / TiDB MCP / A2A / 既存システム連携 AI / ML ・モデル開発環境構築 MLOps / データフライホイール ・データ基盤構築 Looker / Snowflake / BigQuery / Redshift DBRE ※記載されている会社名、製品名、サービス名等は、各社の登録商標または商標です Copyright © 3-shake, Inc. All Rights Reserved. 3

5.

目次 1. 2. 3. 4. 前提 Why neverthrow is not for me? neverthrow is not for meを再考する まとめ 5

6.

01 前提 Copyright © 3-shake, Inc. All Rights Reserved. 6

7.

try...catch文の現状 型安全性の欠如 TypeScript側は対応しない 上位モジュールから「throwされた際に何が飛ぶ か」が型に出ない TypeScript側で今後対応の予定はない ● 何でもthrowできる仕様(MDN) ● カスタムエラー / 汎用Error / プリミティブ / 値 ● ● Javaでは型でthrowを表現可能 Javaのthrows句を参考に導入する提案 ○ →Not Planned(予定なし) ○ microsoft/TypeScript #13219 解決策は言語側ではなく、ライブラリや言語の利用者に委ねられる形に... Copyright © 3-shake, Inc. All Rights Reserved. 7

8.
[beta]
neverthrowとは
コンセプト

主な特徴

"How do we encode failability into the typesystem?"
— neverthrow 開発者ブログより

訳:どのようにしてfailability:失敗可能性を型システムに組み込むか?

●

throw を使わず、エラーを値として返す

●

Result型(Ok/Err)への明示的な変換

●

エラー処理漏れを防ぐLintルールまでセット

// プログラム例
const ok = <T, E>(value: T): Result<T, E> => new Ok(value)
const err = <T, E>(error: E): Result<T, E> => new Err(error)
const makeHttpRequest = async (url: string): Promise<Result<ResponseData, Error>> => {
if (!isUrl(url)) return err(new Error('Invalid URL'))
// other business logic here...
return ok({ ... })
}

関数型プログラミング由来のResult型で解決するライブラリ
8
Copyright © 3-shake, Inc. All Rights Reserved.

9.

02 Why neverthrow is not for me? Copyright © 3-shake, Inc. All Rights Reserved. 9

10.

neverthrow導入のきっかけ 01. TSKaigi 2025での認知 昨年のセッションで紹介され、Result型を用いたエラーハンド リングを知るきっかけに。 02. 堅牢な実装パターンの提示 例外を投げない「堅牢な設計」の具体例として紹介されてお り、強い関心を持つ。 03. 直感的なパターンマッチ 正常系と異常系を型システムで網羅的に扱えるわかりやすさ に惹かれた。 TSKaigi2025のセッション:fast-checkとneverthrowのPBT+Result型 で堅牢なビジネスロジックを実現する 10 Copyright © 3-shake, Inc. All Rights Reserved.

11.
[beta]
導入して見えた課題
02. 可読性

01. チーム内の認知コスト

●
●

TSらしくない記法と概念への戸惑い
●

パターンマッチの慣習がなく認知コストが
高い

●

andTee等のメソッドチェーンは直感的な
連想が難しい

fromPromise の冗長さ
try...catchに近く、可読性に変化なし

import { ResultAsync } from 'neverthrow'
import { insertIntoDb } from 'imaginary-database'
// insertIntoDb(user: User): Promise<User>
const res = ResultAsync.fromPromise(
insertIntoDb(myUser),
() => new Error('Database error')
)
// `res` has a type of ResultAsync<User, Error>

Copyright © 3-shake, Inc. All Rights Reserved.12

12.
[beta]
自前実装してみた
declare const TAG: unique symbol;
// シンボルで成功 /失敗を厳密に区別した Result型
type Success<T> = { data: T; readonly [TAG]: "success" };
type Failure<E extends Error> = { error: E; readonly [TAG]: "failure" };
export type Result<T, E extends Error> = Success<T> | Failure<E>;
// 必要最低限の APIだけ:判定関数 と async→Result 変換ラッパー
isFailure(result) / wrapAsyncCall(fn, cleanup?)

特徴
●

関数は最低限(判定 + async変換)に絞る

●
●

利用側は if (isFailure(result)) で早期return
Goに近い書き心地でWeb開発フレンドリー

デメリット
● neverthrowの堅牢性を失う
●

自前実装のため、保守コストが発生

 Symbol - JavaScript | MDN
12
Copyright © 3-shake, Inc. All Rights Reserved.

13.

03 neverthrow is not for meを再考する Copyright © 3-shake, Inc. All Rights Reserved. 13

14.

保守の目線で再考する ライブラリ(neverthrow) ● 著名なライブラリの一つである ○ ● 安易な設計判断だったかもしれない ● 求める体験に相当するAPIがあるかもしれない 今後継続的にメンテナンスされる ● 一部APIを使わないという選択肢もあったはず Maintenance status #670 ● 生成AI時代でも「全て実装する」は正解ではな い ○ ● GitHubのスター数 7.5K 自前実装 流行りのサプライチェーンリスク問題? ○ ○ npmの設定やpnpm等パッケージマネージャ 側で対応可能 自前で実装する理由にはならない ○ コア要件をどのくらい満たすか、が重要 参考:「OSSがあるなら自作するな」は AI時代も正しいか ── Build vs Adopt の新しい判断基準 14 Copyright © 3-shake, Inc. All Rights Reserved.

15.

neverthrowをもう一度見てみる 1. コア要件の充足 2. 採用のグラデーション 自作で得たかった性質をほとんど満たしてい た 独自メソッドやfromPromiseは選択次第 ● ● ● エラーを値として扱う ○ try...catch文の課題を解決する 型でデータとエラーを厳密に区別する 早期リターン可能 ○ result.isErr() ○ Goのようにハンドリング可能 ● ● 全てのAPIを無理に使う必要はない ○ メソッドチェーンをほとんど使わない など 冗長なら部分的な自作/共通化を検討 ○ fromPromiseはラッパー関数1箇所に 限定する 15 Copyright © 3-shake, Inc. All Rights Reserved.

16.

neverthrowの導入方針 1. Linterによる制限 2. ラッパー関数の実装 苦手な関数やメソッドはLinterで制限 fromPromiseの冗長さを解決 ● ● ESLint / Oxlint ○ no-restricted-imports ○ no-restricted-properties biome: GritQLを使用 ● ラッパー関数で冗長さを1箇所に閉じ込める ● 利用側はneverthrow標準のResultに近い体 験にする ○ 戻り値に少しだけ工夫 16 Copyright © 3-shake, Inc. All Rights Reserved.

17.
[beta]
共通ラッパー関数の定義
方針
• 苦手な fromPromise の利用を1箇所に留める
• 戻り値 Promise<Result> で標準に寄せ、直感的に利用可能にする

import { ResultAsync } from "neverthrow";
export function wrapAsyncCall<T>(
fn: () => Promise<T>,
cleanup?: () => void,
): ResultAsync<T, Error> {
return ResultAsync.fromPromise(
cleanup ? fn().finally(cleanup) : fn(),
(err) => (err instanceof Error ? err : new Error(String(err))),
);
}

17
Copyright © 3-shake, Inc. All Rights Reserved.

18.
[beta]
wrapAsyncCallの利用側
ポイント
• fromPromiseと違い、コールバックを渡すだけで簡潔に記述できる
• 例外を意識せず、戻り値をResult型として関数型スタイルで扱える

const result = await wrapAsyncCall (() =>
this.db.article. findUnique ({
where: { articleId: dbArticleId },
})
)
if (result.isErr()) {
return err(new ServerError (result.error))
}

18
Copyright © 3-shake, Inc. All Rights Reserved.

19.

04 まとめ Copyright © 3-shake, Inc. All Rights Reserved. 19

20.

まとめ コア要件の大半を満たすため、苦手な部分は制限・補完しつつ採用する 1. “not for me”は使い方の先入観 2. コア要件とグラデーション ● ● ● 先入観を疑ってみることが大事 コア要件の大半を満たすのであれば採用 ○ Match中心の例だけを見て判断 ○ 自作すると保守コストが発生 ○ パターンマッチや andTee などへの慣 れがコストに見えていた ○ ライブラリは叡智の結晶であり、自 前の実装より良いことが多い とはいえ、“not for us”はあるかもしれない ● 提供されるAPIは使う・使わないを選択する ○ 苦手なものは利用を制限してもいい 20 Copyright © 3-shake, Inc. All Rights Reserved.

21.

おしまい ご清聴いただき、ありがとうございました スリーシェイク公式Note Sreake事業部 採用情報 21 Copyright © 3-shake, Inc. All Rights Reserved.

22.

付録:neverthrowはどの領域とマッチするか ● Frontend:微妙 Backend:向いている Reactであればデータ フェッチを行うライブラ リ(Tanstack Query, SWR…)が状態管理込み でエラーハンドリングす る ● ● 副作用が比較的少ない ● Fatalなエラーが起こりづ らい 副作用が多い ● 比較的あり DBや外部サービス など エラーハンドリングのミ スは避けたい ● 副作用を扱う場合やエ ラーをthrow以外で扱い たい場合 ● denoで開発しやすくなっ ているので、意外とあり かもしれない ○ ● CLI:ちょっと向いている ○ Fatalなエラーが起 こりうるため 22 Copyright © 3-shake, Inc. All Rights Reserved.

23.

付録関連:TSに安易にResult型を導入しない方が良い 1. 境界でのトレードオフ 2. デバッグ体験の考慮 ● トレードオフはあるが、ラッパー等で実装コスト は吸収可能 ● ブレークポイント派 vs プリントデバッグ派で許容 度が変わる ● 開発の本質は「境界」を作り安全な領域を確保す ること ● 環境により避けた方が良いケースも存在する Result型の導入を推奨できるケース バックエンド ● バックエンド(APIや基盤など)は堅牢である方が望ましい ● 事業によっては堅牢さがより大事になってくるので、良いかも ● ただし、両面TypeScriptなどTypeScriptを使いたい理由がある場合に限る 23 Copyright © 3-shake, Inc. All Rights Reserved.

24.

参考文献 ■ 仕様・公式ドキュメント ● try...catch - JavaScript | MDN ● Proposal: throws clause and throws type (microsoft/TypeScript#13219) ● neverthrow (GitHub) ○ Maintenance status #670 ● Symbol - JavaScript | MDN ● no-restricted-imports - ESLint ● Biome Linter Plugins (GritQL) ■ 実践・コミュニティ知見 ● ● 例外処理(とほほのJava入門) Type-safe error handling in TypeScript (neverthrow開発者ブログ) ● PBT+Result型で堅牢なビジネスロジックを 実現する (TSKaigi 2025) ● 「OSSがあるなら自作するな」は AI時代も正 しいか ── Build vs Adopt の新しい判断基 準 ● TypeScriptに安易にResult型を導入しない方 がいい - Qiita 24 Copyright © 3-shake, Inc. All Rights Reserved.