質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

2764閲覧

JavaScriptの例外処理の実用について

hojo

総合スコア195

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

1グッド

7クリップ

投稿2017/03/22 23:42

編集2017/03/22 23:49

async/awaitが利用できるようになって非同期処理の例外もキャッチできるようになりましたが、それなりの規模のアプリケーションを作成するにあたり、どのように例外処理を行えばよいか迷っています。

例えば以下はKoaを利用したウェブアプリケーションの例になりますが、catchで受け取ったエラーオブジェクトを判定してエラーの種類によって適切な処理に振り分けるようなことをするのが一般的だと認識しています。

const Koa = require('koa') const app = new Koa() app.use(async (ctx, next) => { try { await next() } catch(e) { // エラーインスタンスの種類を判定して適切に処理 if (e instanceof TypeError || e instanceof RangeError || e instanceof EvalError) { ctx.state = 500 } } })

上記のように標準のエラーを振り分ける場合はこれでよいと思っているのですが「アクセス権限が無い」といったエラーを独自に作成する場合には以下のようにする必要があると思います。

const Koa = require('koa') const app = new Koa() app.use(async (ctx, next) => { try { await next() } catch(e) { // 標準エラーを適切に処理 if (e instanceof TypeError || e instanceof RangeError || e instanceof EvalError) { ctx.state = 500 // 認証エラーを適切に処理 } else if (e instanceof AuthError) { ctx.state = 403 } } }) // -------------------------------- const AuthError = require('AuthError') app.use((ctx, next) => { ... if( !isAdmin ) throw new AuthError() })

しかし私はこのようにエラー処理を行っている例を見たことが無いので、本当にこれでよいのか不安です。

調べてみたところほとんどのサンプルではエラーを以下のように投げていました。

throw new Error('auth error')

しかしこうしてしまうとキャッチしたエラーを種類別に判定することができなくなってしまいますよね?

もちろんe.messageの内容を利用することでエラー別に適切な処理を行うことは不可能では無いと思っていますが、エラーメッセージは同じエラーでも内容が変わってしまうことがあると思うんです。

// 1234の値が状況によって変わるのでe.massegeをエラー種類判定に使えない console.log(e.message) // "user 1234 is not found"

そのため、エラーの種類ごとに新しいエラークラスを作成してそのインスタンスを投げる方法しかないのだろうと思っているのですが、正直なところ大量のエラークラスを作成するのは面倒な作業だなと思っています。そこで、以下のように処理してしまえばいいのでは?と思いました。

const Koa = require('koa') const app = new Koa() app.use(async (ctx, next) => { try { await next() } catch(e) { // 標準エラーを適切に処理 if (e.name == 'TypeError' || e.name == 'RangeError' || e.name == 'EvalError') { ctx.state = 500 // 認証エラーを適切に処理 } else if (e.name == 'AuthError') { ctx.state = 403 } } }) // -------------------------------- app.use((ctx, next) => { ... if( !isAdmin ) { let e = new Error('auth error') e.name = 'AuthError' throw e } })

上記の方法ではe.nameの内容を書き換える必要があるのでカスマムエラークラスを作成していた時と比較して1行でエラーをthrowすることができていた部分が4行に膨れ上がっているため若干不満ですが、それ以外は満足のいくものになりました。

よし、これでいこう!と思ったのですが、本当にこれでいいのだろうか?皆様はどのように例外処理を行なっているのだろうということが気になって前に進むことができません。

本当にこのような方法で例外処理をしてもよいのでしょうか?

例えば、このようなe.nameを書き換える方法を使って例外を投げるモジュールを作成した場合、そのモジュールが、他のプロジェクトで利用することになった場合に問題は発生しないのだろうか?という新たな疑問が発生しました。

そもそも、一般的に例外処理はどのように行えばよいのでしょうか?
検索したのですが、あまりにも記事が出てこないので質問させていただきました。

よろしくお願いいたします。

kong👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Errorクラスを継承するのが一般的なやり方だと考えます。

そこら辺に転がってるコードですと、実用レベルの複雑なケースを考えてないので、単純にErrorクラスにメッセージを載せて使っているというだけだと思います。

js

1// モジュール側 2module.exports = { 3 AuthError: class AuthError extends Error {}, 4};

js

1// 呼び出し側 2const { AuthError } = require("hoge/fuga/Error"); 3 4try{ 5 // 処理() 6} 7catch(err){ 8 if(err instanceof AuthError){ 9 // AuthErrorに関する例外処理 10 } 11}

ただし、babelを使ってES5に対応させる場合、Errorクラスからの継承をサポートしていないため、このコードを使う場合はES2015が使える環境でお願いします。

具体的にはNode.js v4以上ですね。
(async-awaitを使ってるようなので、恐らく環境はNode.js v7.6だと考えていますが。)


で、エラークラスをいちいち作るのが面倒とのことですが、これは甘んじて受け入れるしかないです。

一応、evalを用いた手抜き生成方法だけ紹介します。
あるいは、プリプロセス処理を挟んで、コンパイル時に似たような方法でファイルを生成しても良いかもしれません。ここらへんは工夫して手を抜きましょう。

js

1// モジュール側 2const errorList = ["Auth", "Unknown", "Fatal"]; 3 4module.exports = errorList.reduce((obj, name)=> { 5 obj[name+"Error"] = createErrorClass(name); 6 return obj; 7}, {}); 8 9function createErrorClass(name){ 10 return eval(`class ${name}Error extends Error {}`); 11}

以下、参考程度に一般的ではない解決方法を紹介します。Symbol.forを用いた解決方法です。

メリットは

  • いちいちエラークラスを作る必要が無いのでとにかく楽な点
  • Babelをせざるを得ない場面に遭遇した時、Errorクラス継承とは異なり比較的導入しやすい

であり、デメリットは

  • 継承が出来ないため、instanceofを使って賢く処理する時につらい
  • e.messageに相当するものを添えることが出来ない
  • 一般的な解決方法ではない

です。

js

1try{ 2 throw Symbol.for("hoge.fuga.AuthError"); 3} 4catch(err){ 5 console.log(err instanceof Error); // false 6 console.log(err === Symbol("hoge.fuga.AuthError")); // false 7 console.log(err === Symbol.for("hoge.fuga.AuthError")); // true 8}

そもそも論ですが、今回やりたいことは例外を作ることではなく、例外を投げて種類に応じて処理を分類することですよね。
そういう場合には、わざわざErrorを投げる必要はなくて、種類さえわかればSymbolでもなんでも良いんですよね。そういう時はSymbolは他の言語のEnum相当の存在であり、相性が良かったりします。

今回はSymbol.forを用いたグローバルシンボルを利用しましたが、もちろんローカルシンボルを用いても可能です。好みで使い分ければ良いと思います。

投稿2017/03/23 16:19

編集2017/03/23 16:21
gaogao_9

総合スコア103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hojo

2017/03/23 23:23

なるほど、丁寧な回答ありがとうございました。 追加質問で申し訳ないのですが、このようなモジュールを見つけたのが、これを使うことについてはどう思われますでしょうか?https://github.com/yanickrochon/error-factory
gaogao_9

2017/03/24 06:34

初めて知りましたけど、ソースコードをざっと読む感じだと普通に良いモジュールなのかな、という印象を受けました。 一方で、数年単位でリポジトリが放置されているのが気になります。 この場合、何らかの不具合が判明した時にも、メンテナが不在でPRを飛ばしても無視される可能性があります。 リスクを承知の上で採用する分には特に言うところはないので、後はご自身で判断されてみたら良いと考えます。
hojo

2017/03/24 06:56

何から何までありがとうございましたm(_ _)m
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問