【プログラミングで必須】エラー処理の、大事な3つの考え方

error-handling

エラー処理。プログラミングの学習中は、なかなか関心が持たれない。だけど実際には、とても大事な必須の処理。

エラー処理は、どのような考え方で作るべきなのでしょうか?

目次

エラー処理ではなにをするのか?

想定外の事象に対処する

プログラムでは、動作の途中でしばしば、普段では起こらない事象に遭遇します。例えばお買い物サイトプログラムなら

お買い物サイトプログラムの異常事態
  • 商品購入しようとおもったら、ちょうど売り切れになっていた…
  • 購入決算のためにサーバに接続しようとしたら、つながらない
  • なぜか分からないが、突然プログラムが途中で終わった

このような、通常の動作と異なる挙動に対処するのが、エラー処理です。

エラー処理がなければ、

  • 売り切れになってるのに、買い物手続きが進んでしまう
  • 途中で処理中断したのに、金額清算されてしまう

といった事態が起こります。これを防ぐために、エラー処理が必須なわけです。

エラー処理の、大事な3つの考え方

エラー処理のプログラミングで重要なのは、以下の3つの考え方。

エラー処理の大事な3つの考え方
  • エラーが発生したら、すみやかに中断する
  • エラーが発生した時点で、リソースを確実に開放する
  • 状況分析のため、エラー情報を記録する

大事なこと(1)すみやかに中断する

プログラムは、エラーが発生したら、正常の処理を中断して、異常時の処置に移る必要があります。

エラーが発生した時点で、即時に中断する。大切です。エラー状態のまま処理を進めてはいけません。商品が売り切れになったのに決済画面に進むのはおかしいですよね。

「トラッシュよりクラッシュ」という言葉があります。エラーのまま混迷な状態に陥るよりは、その場で停止したほうがまだマシだ、という意味です。エラーを無視せず、すみやかに対処しましょう。

大事なこと(2)リソースを確実に開放する

リソースとは、プログラムが使うなにかしらの材料。メモリ、ファイル、ネットワーク、データベースなど。

リソースは、使うときに確保し、使い終わったら開放する必要があります。

正常の場合と同様に、エラーの場合でも、リソースの開放は確実に行う必要があります。

エラーの場合は処理中断するので、気を付けないとリソースを確保したまま開放されない状態になってしまいます。

大事なこと(3)エラー情報を記録する

エラーが発生したとき、その原因がすぐには分からないことがあります。

あとでエラー原因を分析するため、なにが起こったかを記録しておく必要があります

この記録のことを、ロギングといいます。たいていの場合、エラー情報をユーザーに見えないところにテキストファイルで残します。このファイルをログファイルといいます。

3つの考え方をプログラミングする

この3つの考え方、実際にどうプログラミングするのか、押さえておきましょう。

プログラミングでの、エラー処理の実装方法

エラー処理には、以下の2つが必要です。

  • エラーが発生したことを通知する
  • エラーに対して何か対処する

モジュール分割されたプログラムでは、この2つを、それぞれ別のモジュールで対処することが多いです。

実装方法は大きく分けて2通り。

プログラムにおけるエラー処理
  • 戻り値方式 戻り値リターンでエラーを伝え、リターンしたモジュールでエラー対処
  • 例外方式 例外スローでエラーを伝え、例外キャッチしたモジュールでエラー対処
  • 戻り値方式を取るのは、C、Go、Rustなど
  • 例外方式は多くの言語が採用しています。C++、Java、Python、Ruby、PHP、JavaScript、Kotlin、Swift…

プログラミング言語のエラー処理は、歴史的に、戻り値方式(C言語)から始まり例外方式(C++,Java…)が主流になり、また戻り値方式に戻る(Go,Rust)という歴史があります。ここでは、主流の例外方式で説明します。

Javaでの、例外のプログラム例です。

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        try {
            // 例外が発生する可能性があるコードを記述する
            int result = divide(10, 0);
            System.out.println("結果: " + result);
        } catch (ArithmeticException e) {
            // 発生した例外がArithmeticExceptionの場合の処理
            System.out.println("0での割り算はできません。");
        }
    }

    public static int divide(int num1, int num2) {
        // 引数の検証
        if (num2 == 0) {
            throw new ArithmeticException("ゼロでの割り算はできません。");
        }

        // 割り算の実行
        return num1 / num2;
    }
}

エラー処理実装(1)throwで、すみやかに中断する

エラーが発生したときは、throwで例外を発生させます。(例外を投げる、といった言い方もします)

throwが実行されると、次の処理には進まず、catchまで処理が飛びます。そのメソッド内にcatchがなければ、呼び出し元までさかのぼっていきます。

エラー処理実装(2)-1 finallyでリソース開放

多くのプログラミング言語では、エラー発生時でもリソース開放を確実に行うための手段が2種類あります。

ひとつめは、finally で確実にリソースを開放する方法です。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FinallyExample {

    public static void main(String[] args) {
        FileInputStream file = null;
        try {
            file = new FileInputStream("example.txt");
            // ファイルを読み込む処理などを記述
        } catch (FileNotFoundException e) {
            System.out.println("ファイルが見つかりませんでした。");
        } finally {
            // ファイルのクローズ処理など、リソースの解放を行う
            if (file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                    System.out.println("ファイルのクローズ中にエラーが発生しました。");
                }
            }
        }
    }
}

catchブロックは、throwが発生した場合のみ動きます。ここでエラー処理を行います。

finallyブロックは、throwが発生してもしなくても必ず動きます。ここにリソース開放処理を入れることで、正常の場合でもエラーの場合でも、必ずリソースを開放できるようになります。

エラー処理実装(2)-2 try-with-resourceでリソース開放

もうひとつのリソース開放方法は、try-with-resourceと呼ばれる方法です。

tryブロックで、そのブロック中で確保するリソースを宣言します

tryブロックが終わるのは、ブロック内の最後の処理が終わった時、もしくはthrowでブロックを抜けるときです。

このどちらの場合であっても、tryブロックを抜けたタイミングで必ずリソースを開放します

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class TryWithResourcesExample {

    public static void main(String[] args) {
        try (FileInputStream file = new FileInputStream("example.txt")) {
            // ファイルを読み込む処理などを記述
        } catch (FileNotFoundException e) {
            System.out.println("ファイルが見つかりませんでした。");
        } catch (IOException e) {
            System.out.println("ファイルの読み込み中にエラーが発生しました。");
        }
    }
}

プログラミング言語ごとのリソース開放方法

リソース自動開放の仕掛けは、プログラミング言語によって、実装方法にかなりの違いがあります。

基本的なプログラミング構文は、どの言語もけっこう似てくるものですが、このリソース自動開放はほんとうにバリエーション豊か。

以下に一例をあげます。

リソース自動開放のバリエーション
  • C++ RAII
  • Java try-with-resource
  • C# using
  • Python with
  • Swift defer
  • Kotlin usr
  • Go defer
  • Rust Dropトレイト

エラー処理実装(3)ロギングライブラリによるエラー情報を記録

ログの出力は、一般的にはテキストファイルへのエラー情報の書き込みによって実現します。

ロギング処理(ログファイルへの出力)を、自前で実装することは避けましょう。

自前での実装は、以下のような問題があります。

  • エラー発生時の確実な書き込みができないときがある
  • 並列処理(マルチスレッドなど)のときに競合書き込みでクラッシュする恐れがある

必ずロギングライブラリを使いましょう。Javaであれば、log4j が有名です。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ExceptionHandlingSample {
    private static final Logger logger = LogManager.getLogger(ExceptionHandlingSample.class);

    public static void main(String[] args) {
        try {
            // 例外が発生する可能性がある処理
            divide(10, 0);
        } catch (ArithmeticException e) {
            // 例外が発生した場合の処理
            logger.error("Divide by zero error", e);
        }
    }

    public static int divide(int dividend, int divisor) {
        if (divisor == 0) {
            throw new ArithmeticException("Divide by zero");
        }
        return dividend / divisor;
    }
}

ロギング内容で、なによりまず大事なのは「エラーが発生した箇所を特定できること」、次に「エラー発生までの過程を追えること」です。

プログラミング言語ごとのスタックトレース取得方法

エラー発生時の過程を追うには、スタックトレースが便利です。スタックトレースで、エラー発生時に以下のことが分かります。

  • エラーが発生した場所(ファイル名、行番号、関数名やメソッド名など)
  • そのエラーを引き起こした関数やメソッドの呼び出し履歴
  • 各関数やメソッドの呼び出しに関連する情報(引数の値など)

多くのプログラミング言語で、エラー発生時にスタックトレースが自動的に収集されているので、それを利用できます。

スタックトレースのバリエーション
  • Java ExceptionクラスのprintStackTraceやgetStackTrace
  • C# ExceptionクラスのStackTraceプロパティ
  • Python tracebackモジュール
  • JavaScript Errorオブジェクトのstack
  • Swift Thread.callStackSymbols
  • Kotlin ExceptionクラスのprintStackTraceやgetStackTrace
  • Go runtime.stack、debug.PrintStack
  • Rust std::backtrace::Backtrace

意識的にログ出力処理を入れましょう。

まとめ

エラー処理は、どうしても面白味には欠けるので、軽視しがちなところがあります。

しかし、プログラムの健全性を保つために、なくてはならないものです。エラー処理の基本を押さえて、あなたのプログラムを頑健なものにしましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次