TypeScriptで安全なエラーハンドリングを実現する 『neverthrow』

### はじめに

TypeScriptでエラーハンドリングをするとき、 `throw` を使うことがあると思いますが、`throw` には見えない落とし穴があります。この記事では、`throw` の問題点と、それを解決してくれるライブラリ **`neverthrow`** について解説します。

---

### なぜ `throw` は危ないの？

一見すると、`throw` を使ったエラーハンドリングはシンプルで分かりやすく見えます。

```ts
function risky(): string {
  if (Math.random() > 0.5) {
    throw new Error('失敗しました');
  }
  return '成功！';
}
```

しかし、この関数の戻り値の型は `string`。**エラーになる可能性があるにも関わらず、型情報からそれが読み取れません。**
つまり、**関数名や型からは、失敗する可能性があることが分かりません。**

```ts
const result = risky(); // TypeScriptはstringだと認識
console.log(result.toUpperCase()); // 実行時にクラッシュの可能性
```

このように、**`throw` は TypeScript の型システムの外にあるため、型チェックで安全性を担保できません。**

---

### neverthrowとは？

`neverthrow` は、関数の結果を **「成功か失敗か」** を型で表現できるようにする TypeScript ライブラリです。  
Rustの `Result` 型にインスパイアされています。

```bash
npm install neverthrow
```

---

### `neverthrow` のメリット

#### 1. 型でエラーを扱える
- `Result<T, E>` 型で、成功 (`Ok`) か失敗 (`Err`) を明示できる。
- IDEで補完されるので、どちらのケースも漏れなく書ける。

#### 2. try/catch が不要で読みやすい
- 関数の戻り値で処理を分岐できるため、ネストが減る。
- 失敗が予測可能になる。

#### 3. 安全に処理をつなげられる
- `map`, `mapErr`, `andThen` などのメソッドを使って、成功時だけ処理を続けられる。

---
### 基本の使い方

```ts
import { Result, ok, err } from 'neverthrow';

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return err('ゼロで割ることはできません');
  }
  return ok(a / b);
}

const result = divide(10, 2);

if (result.isOk()) {
  console.log('成功:', result.value);
} else {
  console.log('エラー:', result.error);
}
```

## 比較しながら理解する：`throw` vs `neverthrow`


###  ・`throw` を使った場合（型システムの外）

```ts
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('ゼロで割ることはできません');
  }
  return a / b;
}
```

この関数の型は `divide(a: number, b: number): number`。

でも実際には `b === 0` のときに `throw` が発生します。  
TypeScriptの型的には `"number を返す"` とされているのに、**実行時には「返さない（= エラーになる）」可能性がある**。

### 問題点：
- 呼び出し側からは **「失敗する可能性がある」ことが分からない**。
- IDEの型情報では `number` が返るとしか見えない。
- **catch し忘れるとアプリがクラッシュ**する。

### ・`neverthrow` を使った場合（型システムの中）

```ts
import { Result, ok, err } from 'neverthrow';

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return err('ゼロで割ることはできません');
  }
  return ok(a / b);
}
```

この関数の型は `Result<number, string>`。  
これは「成功すれば number、失敗すれば string（エラーメッセージ）」ということを、**型が正確に表しています。**

### 利点：
- 呼び出し側は `.isOk()` や `.isErr()` で結果を安全に扱える。
- IDEでも「成功か失敗か」を明示的に意識できる。
- catch を忘れてクラッシュすることはない。

---

### 実践：複数の処理をつなげてみる

```ts
function parseNumber(s: string): Result<number, string> {
  const num = Number(s);
  return isNaN(num) ? err('数値ではありません') : ok(num);
}

function square(n: number): Result<number, string> {
  return ok(n * n);
}

const result = parseNumber("5").andThen(square);

result.match({
  ok: (v) => console.log("結果:", v),
  err: (e) => console.error("エラー:", e),
});
```
処理の流れとしては
1. `parseNumber` → 文字列を数値に変換（失敗したら `err`）
2. `square` → 数値を2乗して `ok` で返す
3. `andThen` → 前が成功なら次の処理へ、失敗ならスキップ
4. `match` → 成功時・失敗時の処理を分けて実行

### 実行結果の例

- `"5"` を渡す → 結果: `25`
- `"abc"` を渡す → エラー: `数値ではありません`

### ポイント
- `andThen` を使うと、**安全に処理をつなげられる**
- `match` で **成功・失敗を型で分けて処理できる**
- `neverthrow` の基本的な流れがこのコードに詰まってる！

---

### おわりに

TypeScriptで安全なコードを書くためには、**エラー処理を型の中に閉じ込めること**がとても大切です。  
`neverthrow` を使えば、**「エラーになる可能性」を明示的に型として扱える**ので、より堅牢で読みやすいコードが書けます。