Upgrade to Pro — share decks privately, control downloads, hide ads and more …

例外はスローするなリターンし ろ

例外はスローするなリターンし ろ

Nakano as a Service

October 30, 2024
Tweet

More Decks by Nakano as a Service

Other Decks in Programming

Transcript

  1. 皆さん例外処理ちゃんとやってますか? 例: トークンの中身をパースして返す ついつい例外処理をサボりがち そんなあなたに function verifyToken(token: string): UserSessionInfo {

    if (!isTokenValid(token)) { throw new Error("token is not valid") } return parseToken(token) } const session = verifyToken(cookie().token) /* 不正なトークンが渡ってきた場合が考慮できてない! */ const user = getUser(session.userId) // ここまでたどり着かない
  2. メリット1: 例外処理をしないと値を使えなくなる Error でないことを確かめないと userId に使えない! function verifyToken(token: string): UserSessionInfo

    | Error { if (!isTokenValid(token)) { // 例外をリターンするようにした return new Error("token is not valid") } return parseToken(token) }
  3. メリット1: 例外処理をしないと値を使えなくなる エラー処理をちゃんとすれば値を使える! function verifyToken(token: string): UserSessionInfo | Error {

    if (!isTokenValid(token)) { // 例外をリターンするようにした return new Error("token is not valid") } return parseToken(token) } const session = verifyToken(cookie().token) if (session instanceof Error) { // エラーを処理する redirect("/login") return } // userId が使える! const user = getUser(session.userId)
  4. メリット2: どんなエラーが起こるのかわかる 独自の例外クラスを定義しておけば、関数の返り値の型を見るだけでどんなエラーが起こるのかわかる 皆さんもエラーをリターンしてみてください! // 独自の例外クラスを定義 class InvalidTokenError extends Error

    {} class InvalidPayloadError extends Error {} // 関数の返り値の型を見ればどんなエラーが起こるかわかる function verifyToken(token: string): UserSessionInfo | InvalidTokenError | InvalidPayloadError { if (!isTokenValid(token)) { return new InvalidTokenError() } // トークンのパースでもエラーが発生する場合 const payload = parseToken(token) if (payload instanceof Error) { return new InvalidPayloadError() } }
  5. 判別可能なエラー型で例外クラスの定義をサボる 独自の例外クラスを定義せずとも code で区別できるエラーを定義できる 以下のように使う function verifyToken(token: string): UserSessionInfo |

    PubErr<"INVALID_TOKEN", string> | PubErr<"INVALID_PAYLOAD"> { if (!isTokenValid(token)) { return new PubErr({ code: "INVALID_TOKEN", cause: token }) } const payload = parseToken(token) if (payload instanceof Error) { return new PubErr({code: "INVALID_PAYLOAD" }) } return payload } const session = verifyToken(cookie().token) if (session instanceof PubErr) { if (session.code === "INVALID_TOKEN") { // PubErr<"INVALID_TOKEN"> 型として扱われる const token = session.cause // cause に原因となったtoken (string )が入っている }
  6. 判別可能なエラー型って何? 近々記事を書く予定なので詳しいことは中野に聞いてください。 とりあえず code でエラーが区別できるようになるやつと覚えていただけるとありがたいです。 定義は以下 interface PubErrProps<C extends string,

    E = never> { code: C; message: string; cause?: E; } export class PubErr<C extends string, E = never> extends Error { code: C; cause!: E; constructor({ code, message, cause }: PubErrProps<C, E>) { super(message, { cause }); this.code = code; } }
  7. え、呼び出し側でいちいち例外処理するのがだるい? function verifyToken(token: string): UserSessionInfo | PubErr<"INVALID_TOKEN", string> | PubErr<"UNEXPECTED_PAYLOAD">

    { // isValidToken がエラーをリターンするvalidateToken に変わった const validToken = verifyToken(token) if (validToken instanceof Error) { return new PubErr({ code: "INVALID_TOKEN", cause: token }) } const payload = parseToken(validToken) if (payload instanceof Error) { return new PubErr({code: "UNEXPECTED_PAYLOAD" }) } return payload }
  8. Neverthrow のsafeTry を使う if 文が不要になった(代わりに見たこともないような構文が増えた) 。かなり端折った説明なので雰囲気だけ捉 えてほしい。 他にも便利な機能があるのでNeverthrow の公式ドキュメントを見てみてください Neverthrow

    公式ドキュメント Neverthrow は新しい概念がそこそこあるのでチームでやるときはオンボーディングを丁寧にしてください。 function verifyToken(token: string) { return safeTry(function*() { const validToken = yield* verifyToken(token) // if 文は不要! const payload = yield* parseToken(validToken) // if 文は不要! return ok(payload) }) }
  9. まとめ 例外をリターンすることで以下のメリットがある 呼び出し元に例外処理を強制できる どんなエラーが起こるのか型を見ればわかる 最初からすべてを実践する必要はない 1. Error をリターンする 2. 独自例外クラスを定義する

    3. 判別可能なエラー型で独自例外クラスの定義をサボる 4. Neverthrow で呼び出し元の例外処理を端折る 補足: 返り値の型は推論されるので書かなくていい みんなも例外をリターンしてみてね!