effect-ts-anti-patterns について
Effect-TSでリソースリークやクラッシュ、未処理エラーにつながる誤用を見つけるためのコードレビュー・デバッグ資料です。Effect-TSを使う開発者向けで、通常のTypeScriptや他の関数型ライブラリは対象外です。
主な機能
- 01Effect-TSの代表的なアンチパターンの説明
- 02安全なEffect合成の例
- 03型付きエラー処理の解説
- 04並行処理数の制限方法
- 05中断に対応したリソース解放
- 06コードレビュー時の警告項目
活用シーン
- 01Effect-TSコードのレビュー
- 02未処理のPromise拒否のデバッグ
- 03過剰な並行処理の防止
- 04リソース解放とエラー処理経路の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mrevanzak/ effect-ts-skills --skill effect-ts-anti-patternsインストール後、Claude Code で/effect-ts-anti-patternsを入力して読み込まれたか確認します。
詳細
Effect-TS のアンチパターン
概要
Effect-TS は強力な関数型フレームワークですが、よくある命令型の習慣は、その安全性を保証する仕組みを迂回することがあります。このスキルでは、リソースリーク、プロセスのクラッシュ、未処理のエラーにつながるパターンを特定し、修正します。
使用する場面
- Effect-TS の実装をコードレビューするとき。
- Effect のコードで「未処理の Promise 拒否」やプロセスのクラッシュをデバッグするとき。
- 並列処理のパフォーマンスを最適化するとき。
使用しない場面:
- シンプルな非 Effect の TypeScript コードの場合。
- 他の関数型ライブラリ(fp-ts など)を使用している場合。
アンチパターンの一覧
| アンチパターン | 悪いコード | 良いコード | 理由 |
|---|---|---|---|
| 境界の外で実行する | 関数の途中で await Effect.runPromise(fx) | yield* fx(合成する) | エラーチャネルとトレーシングを迂回します。 |
yield* の付け忘れ | const user = yield fetchUser() | const user = yield* fetchUser() | 結果ではなく Effect オブジェクトを yield します。 |
| エラーをスローする | if (!ok) throw new Error() | yield* Effect.fail(new Error()) | throw は型付きエラーではなく「Defect」(クラッシュ)を発生させます。 |
| 上限のない並列処理 | Effect.all(tasks)(デフォルト) | Effect.all(tasks, { concurrency: 10 }) | OOM とレート制限の上限到達を防ぎます。 |
| エラーを無視する | Effect.runPromise(fx)(catch なし) | fx.pipe(Effect.catchAll(...)) | 未処理の拒否につながります。 |
| 手動でクリーンアップする | try { ... } finally { cleanup() } | Effect.acquireRelease(...) | finally は中断時のクリーンアップを保証しません。 |
よくある間違い
1. yield と yield* の落とし穴
// ❌ BAD: Missing *, yields the Effect object itself
const bad = Effect.gen(function* () {
const user = yield fetchUser(id); // Returns Effect, not User!
});
// ✅ GOOD: yield* executes the Effect
const good = Effect.gen(function* () {
const user = yield* fetchUser(id); // Returns User
});
2. 上限のない並列処理
// ❌ BAD: 10,000 concurrent requests (OOM risk)
const bad = Effect.all(urls.map(fetch));
// ✅ GOOD: Bounded to 10 concurrent
const good = Effect.all(urls.map(fetch), { concurrency: 10 });
3. コードの途中で Effect を実行する
// ❌ BAD: Effect.runPromise scattered throughout
const bad = async () => {
const user = await Effect.runPromise(getUser(id));
const posts = await Effect.runPromise(getPosts(user.id));
};
// ✅ GOOD: Compose effects, run ONCE at boundary
const good = Effect.gen(function* () {
const user = yield* getUser(id);
const posts = yield* getPosts(user.id);
return { user, posts };
});
正当化の一覧
| 言い訳 | 実態 |
|---|---|
「速くするために concurrency: 'unbounded' を使います」 | 速さはクラッシュにつながります。外部リソースへの並列処理には必ず上限を設けてください。 |
「とりあえず throw します」 | throw はエラーチャネルを迂回し、コードをテスト不能にします。 |
| 「ここで実行するほうが簡単です」 | コードの途中で実行すると、コンテキスト、トレーシング、中断に対する安全性が失われます。 |
注意すべき兆候
- ループやヘルパー関数内の
Effect.runPromiseまたはEffect.runSync。 - 大きなリストに対して
concurrencyオプションなしで使用するEffect.all。 Effect.genブロック内のthrowキーワード。- Effect の呼び出しで
*を付けずに使用するyield。
関連項目
- 必須の前提知識: effect-ts-fundamentals
- 必須のサブスキル: effect-ts-concurrency
- 参考資料: effect-ts-resources
コメント
Markdown 対応