Meiryo’s blog

やってみて詰まったことを備忘録として残すブログ

【プログラミング】例外処理の使い所と注意事項を調べた

例外処理ってtry...catchの書き方とか
throwsの書き方とか文法的なことは
わかるけど
いざ使おうとなったときに
そもそもどこで使えばいいのか、とか
どの例外を投げればいいのか、とか
わからなくなりません?
今回は例外処理をするのにどういう考え方を
すればいいのかを調べてみました。

言語

C#のコードで説明しているのですが
多分Javaでも通用するのではないかと思います。

例外処理をどこで書けば良いのか知る方法

今まではなんとなく「ファイル開く時とか
サンプルでusing使ってるとこっしょ」
という考えでしたが ちゃんと知る方法ありました。

その方法は
各メソッドのドキュメントを読みに行く
ということです。

下記画像はDictionaryの
Addメソッドのドキュメントです。

ここにどういうことが起きたら
例外が発生するか記載されています。

試しに例外を出してみました。
keyにnullを渡すと
ArgumentNullExceptionが

既に登録されているKeyを
再度登録しようとすると
ArgumentExceptionが
それぞれ発生することを確認できました。

また、用意されているメソッドではなく
自作メソッドでもthrow命令を使えば
例外を発生させることができるので
これらの例外が発生する箇所で
例外処理を書く必要があります。

この例外処理の書き方なのですが
色々注意しなければならない点がありました。

例外処理の注意

握りつぶしをしない

下記のコードは握りつぶした例です。

namespace Test
{
    using System;
    using System.Collections.Generic;

    public class Test
    {
        public static void Main(string[] args)
        {
            var dic = new Dictionary<string, string>();
            string s = null;

            try
            {
                dic.Add("a", "aaa");
                dic.Add(s, "bbb");
            }
            catch (Exception ex)
            {
            }

            Console.WriteLine(dic["a"]);
        }
    }
}

2回目のAddメソッドでkeyにnullを渡しているので
ArgumentNullExceptionが起きていますが
catchの中で何もしていないので
例外が発生しているのに
何事もなかったかのように終了します


こうしてしまうとそのうち見えないバグと
戦うことになってしまうので
握りつぶしはしないようにしましょう。

もしなにかやむを得ない状況で、
「例外処理をせずにスルーさせたい」
という場合は
その旨をコメントで書きましょう。

Exceptionではなく、適切な型で受け取る

ArgumentNullExceptionを受け取る書き方は
下記の2つがあります。

try
{
    dic.Add(s, "bbb");
}
catch (Exception ex)
{
   Console.WriteLine(ex.Message);
   Console.WriteLine(ex.StackTrace);
}
try
{
    dic.Add(s, "bbb");
}
catch (ArgumentNullException ex)
{
 Console.WriteLine(ex.Message);
 Console.WriteLine(ex.StackTrace);
}

これはどちらがいいかと言うと
後者の書き方です。
Exceptionクラスは全ての例外クラスの
基底クラスなので

全ての例外を受け取ることが可能です。

全部受け取れるほうが
楽そうで良い感じがしますが
複数の例外処理を書くときに
処理を分けることができなくなります

適切なクラスで受け取ると
処理を分けることができます。

下記はAddメソッドが
ArgumentNullExceptionと
ArgumentExceptionを発生させる
可能性があるので、これらの処理を
分けた例です。

namespace Test
{
    using System;
    using System.Collections.Generic;

    public class Test
    {
        public static void Main(string[] args)
        {
            var dic = new Dictionary<string, string>();

            string s = null;

            try
            {
                dic.Add(s, "sss");
            }
            catch(ArgumentNullException ex)
            {
                Console.WriteLine("keyがnull");
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);

            }
            catch (ArgumentException ex)
            {
                Console.WriteLine("keyが既に存在する");
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
}

Exceptionクラスだけで受け取ると
このように分けることはできないので
適切なクラスで受け取りましょう。

(※できなくなりますって書いたけど
if文で分けれるかも。
でもその場合、追加していくにつれて
一箇所に複数のことを
書かなくてはいけなくなって
可読性が下がりそうだから
やっぱり分けたほうがいいと思う)

例外を回避して例外処理を使わないようにする

例外処理はこれまでの例のように、
どこでも書くことができるのですが
やたらめったらに使ってはいけません。
例外処理は重いので回避できるところは
回避しましょう。

回避するとはどういうことかを
下記のコードで表しました。
先程の例外処理のコードを
変えたものです。

namespace Test
{
    using System;
    using System.Collections.Generic;

    public class Test
    {
        public static void Main(string[] args)
        {
            var dic = new Dictionary<string, string>();

            string s = null;

            if (!String.IsNullOrEmpty(s))
            {
                dic.Add("a", "aaa");
            }

            if (!dic.ContainsKey("a"))
            {
                dic.Add("a", "aaa");
            }

            Console.WriteLine(dic["a"]);
        }
    }
}

それぞれ事前にチェックを入れることにより
例外が発生しないように回避しました。

これにより「例外処理と回避はどっちにすればいいのか」
わからなくなりましたが
丁度いい質問がteratailにありました。
例外処理は気楽に使っていいものなのでしょうか?

これを見た上でわからないものが出てきたら
それはもう有識者に相談しましょう。

参考にしたサイト

例外の推奨事項 - .NET | Microsoft Learn

例外処理 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

[雑記] 例外の使い方 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C