-- Views
June 12, 26
スライド概要
TSKaigi2026〜アフターパーティー〜 のスライドです。
neverthrowを一度見送ってからなぜ採用に至ったのか、その過程と判断を記しました。
https://every.connpass.com/event/393937/
neverthrow is not for meを 再考する TSKaigi2026〜アフターパーティー〜 株式会社スリーシェイク Rinrin Copyright © 3-shake, Inc. All Rights Reserved.
自己紹介 ● ● ● ● Copyright © 3-shake, Inc. All Rights Reserved. 名前:Rinrin / 林 侑生 所属:株式会社スリーシェイク Sreake事業部 業務:決済基盤のリプレース 今回再考したきっかけ ○ 「neverthrowはnot for me」関連投稿が 散見 ○ 昨年開発時に導入を見送り、同感だった ○ 一方で、ふと今も同感なのか疑念が生じた 2
会社紹介 会社名 株式会社スリーシェイク 設立日 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
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
目次 1. 2. 3. 4. 前提 Why neverthrow is not for me? neverthrow is not for meを再考する まとめ 5
01 前提 Copyright © 3-shake, Inc. All Rights Reserved. 6
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
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.
02 Why neverthrow is not for me? Copyright © 3-shake, Inc. All Rights Reserved. 9
neverthrow導入のきっかけ 01. TSKaigi 2025での認知 昨年のセッションで紹介され、Result型を用いたエラーハンド リングを知るきっかけに。 02. 堅牢な実装パターンの提示 例外を投げない「堅牢な設計」の具体例として紹介されてお り、強い関心を持つ。 03. 直感的なパターンマッチ 正常系と異常系を型システムで網羅的に扱えるわかりやすさ に惹かれた。 TSKaigi2025のセッション:fast-checkとneverthrowのPBT+Result型 で堅牢なビジネスロジックを実現する 10 Copyright © 3-shake, Inc. All Rights Reserved.
導入して見えた課題
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
自前実装してみた
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.
03 neverthrow is not for meを再考する Copyright © 3-shake, Inc. All Rights Reserved. 13
保守の目線で再考する ライブラリ(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.
neverthrowをもう一度見てみる 1. コア要件の充足 2. 採用のグラデーション 自作で得たかった性質をほとんど満たしてい た 独自メソッドやfromPromiseは選択次第 ● ● ● エラーを値として扱う ○ try...catch文の課題を解決する 型でデータとエラーを厳密に区別する 早期リターン可能 ○ result.isErr() ○ Goのようにハンドリング可能 ● ● 全てのAPIを無理に使う必要はない ○ メソッドチェーンをほとんど使わない など 冗長なら部分的な自作/共通化を検討 ○ fromPromiseはラッパー関数1箇所に 限定する 15 Copyright © 3-shake, Inc. All Rights Reserved.
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.
共通ラッパー関数の定義
方針
• 苦手な 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.
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.
04 まとめ Copyright © 3-shake, Inc. All Rights Reserved. 19
まとめ コア要件の大半を満たすため、苦手な部分は制限・補完しつつ採用する 1. “not for me”は使い方の先入観 2. コア要件とグラデーション ● ● ● 先入観を疑ってみることが大事 コア要件の大半を満たすのであれば採用 ○ Match中心の例だけを見て判断 ○ 自作すると保守コストが発生 ○ パターンマッチや andTee などへの慣 れがコストに見えていた ○ ライブラリは叡智の結晶であり、自 前の実装より良いことが多い とはいえ、“not for us”はあるかもしれない ● 提供されるAPIは使う・使わないを選択する ○ 苦手なものは利用を制限してもいい 20 Copyright © 3-shake, Inc. All Rights Reserved.
おしまい ご清聴いただき、ありがとうございました スリーシェイク公式Note Sreake事業部 採用情報 21 Copyright © 3-shake, Inc. All Rights Reserved.
付録:neverthrowはどの領域とマッチするか ● Frontend:微妙 Backend:向いている Reactであればデータ フェッチを行うライブラ リ(Tanstack Query, SWR…)が状態管理込み でエラーハンドリングす る ● ● 副作用が比較的少ない ● Fatalなエラーが起こりづ らい 副作用が多い ● 比較的あり DBや外部サービス など エラーハンドリングのミ スは避けたい ● 副作用を扱う場合やエ ラーをthrow以外で扱い たい場合 ● denoで開発しやすくなっ ているので、意外とあり かもしれない ○ ● CLI:ちょっと向いている ○ Fatalなエラーが起 こりうるため 22 Copyright © 3-shake, Inc. All Rights Reserved.
付録関連:TSに安易にResult型を導入しない方が良い 1. 境界でのトレードオフ 2. デバッグ体験の考慮 ● トレードオフはあるが、ラッパー等で実装コスト は吸収可能 ● ブレークポイント派 vs プリントデバッグ派で許容 度が変わる ● 開発の本質は「境界」を作り安全な領域を確保す ること ● 環境により避けた方が良いケースも存在する Result型の導入を推奨できるケース バックエンド ● バックエンド(APIや基盤など)は堅牢である方が望ましい ● 事業によっては堅牢さがより大事になってくるので、良いかも ● ただし、両面TypeScriptなどTypeScriptを使いたい理由がある場合に限る 23 Copyright © 3-shake, Inc. All Rights Reserved.
参考文献 ■ 仕様・公式ドキュメント ● 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.