giraphme/blog

zod と neverthrow を組み合わせる

業務で zod と neverthrow を利用することが多いのですが、以下のようなコードを書くことが頻繁にあります。

この例では、型付きの API アクセスするラッパー関数を callApi として定義しており、ResultAsync 型を返すと想定しています。
この程度の分量であれば特に読みづらさはないですが、zodの safeParse の結果とneverthrowの ResultResultAsync 型の違いがあるため、記述量が増えた場合に辛さを感じる場面があります。

##プロトタイプ拡張によるインターフェースの統一

この問題を解決するために、zodのプロトタイプを拡張して、neverthrowの Result 型を直接返すメソッドを試してみました。

この拡張により、先ほどの例は以下のように書き直すことができます。

これにより Early return を気にすることなく、 neverthrow の Result 型でメソッドチェーンで表現することができるようになりました。
しかし、経験豊富な開発者であれば、プロトタイプ拡張にはいくつかの重要なデメリットがあることをご存知でしょう。

##プロトタイプ拡張のデメリット

プロトタイプ拡張には以下のような問題点があります。

  • 名前空間の汚染: プロトタイプを拡張するとそのプロトタイプを使用するすべてのコードに影響するため、意図しない副作用を引き起こす可能性があります。例えば、他のライブラリが同じ名前のメソッドを追加しようとした場合、競合が発生します。

  • バージョン互換性の問題: ライブラリのバージョンアップで内部実装が変わると、プロトタイプ拡張が動作しなくなる可能性があります。

  • テストの複雑さ: プロトタイプ拡張はグローバルな影響を持つため、テストが複雑になります。また、テストの順序に依存する問題が発生することもあります。

##ユーティリティ関数として定義する

いきなりプロトタイプ拡張を紹介しましたが、やはり一般的にはこのアプローチを取ることが多いでしょう。

この関数を適用して、先ほどの例を書き換えます。

##まとめ

目先の記述量の少なさや、importの煩雑さに惑わされてプロトタイプ拡張しそうになったので、自戒のためにこの記事を書きました。
場合によってはプロトタイプ拡張が必要となるプロダクトもあるかもしれませんが、ほとんどの場合ではライブラリのプロトタイプ拡張は行わない方が良いと考えています。
どうしてもプロトタイプ拡張を使用したい場合は package.json での zod のバージョン指定を厳格にしたり、 npm ライブラリとして実装し peerDependencies で管理する事で多少リスクヘッジすることができます。
プロジェクトの package.json で zod のバージョンを固定する場合、 「zod のバージョンを固定しているのはなぜか」というコンテキストが管理しづらいため、 npm ライブラリとして切り出す方がより安全でしょう。

© giraph.me