1. ホーム
  2. php

[解決済み] 例外をキャッチして再スローするためのベストプラクティスは何ですか?

2022-04-22 01:44:49

質問

キャッチした例外はそのまま再スローすべきですか、それとも新しい例外にラップすべきですか?

つまり、こうすればいいのか。

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw $e;
}

またはこれです。

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw new Exception("Exception Message", 1, $e);
}

という答えが返ってきた場合 直接投げる を使うことを提案してください。 例外の連鎖 例外チェーニングを使用する現実的なシナリオを理解することができません。

どのように解決するのですか?

例外をキャッチしてはいけない 意味のあることをするつもりがない限り .

何か意味のあること"は、この中の一つかもしれません。

例外処理

最も明白で意味のある行動は、エラーメッセージを表示し、操作を中断するなどして、例外を処理することです。

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    echo "Error while connecting to database!";
    die;
}

ロギングまたは部分クリーンアップ

時には、特定のコンテキストで例外を適切に処理する方法がわからないことがあります。おそらく、全体像に関する情報が不足しているのでしょうが、障害が発生した時点にできるだけ近いところでログを取りたいものです。このような場合、catch、log、re-throwを行いたいかもしれません。

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    logException($e); // does something
    throw $e;
}

関連するシナリオとして、失敗した操作の後始末を行うには適切な場所にいるが、失敗をトップレベルでどのように処理すべきかを決定することができない場合があります。以前のバージョンの PHP では、これは次のように実装されていました。

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
catch (Exception $e) {
    $connect->disconnect(); // we don't want to keep the connection open anymore
    throw $e; // but we also don't know how to respond to the failure
}

PHP 5.5で導入された finally キーワードを使用することで、クリーンアップのシナリオに別のアプローチで対応できるようになりました。クリーンアップのコードが、何が起こったかに関係なく (すなわち、エラー時と成功時の両方で) 実行される必要がある場合、スローされた例外が伝播するのを透過的に許容しながら、これを実行することが可能になりました。

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
finally {
    $connect->disconnect(); // no matter what
}

エラーの抽象化(例外チェインによる)

3つ目のケースは、起こりうる多くの障害を論理的に大きな傘の下にまとめたい場合である。論理的なグループ分けの例

class ComponentInitException extends Exception {
    // public constructors etc as in Exception
}

class Component {
    public function __construct() {
        try {
            $connect = new CONNECT($db, $user, $password, $driver, $host);
        }
        catch (Exception $e) {
            throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

この場合、ユーザは Component データベース接続を使用して実装されていることを知られないようにするためです (将来的にはファイルベースのストレージを使用するという選択肢も残されているかもしれません)。そのため Component 初期化に失敗した場合、次のように記述します。 ComponentInitException がスローされます。これによって Component の例外をキャッチすることができます。 の詳細(実装に依存する)にアクセスすることができます。 .

よりリッチなコンテキストの提供 (例外チェインによる)

最後に、例外に対してより多くのコンテキストを提供したい場合があります。この場合、例外を別の例外でラップして、エラーが発生したときに何をしようとしていたのか、より多くの情報を保持するのが理にかなっています。たとえば、以下のようになります。

class FileOperation {
    public static function copyFiles() {
        try {
            $copier = new FileCopier(); // the constructor may throw

            // this may throw if the files do no not exist
            $copier->ensureSourceFilesExist();

            // this may throw if the directory cannot be created
            $copier->createTargetDirectory();

            // this may throw if copying a file fails
            $copier->performCopy();
        }
        catch (Exception $e) {
            throw new Exception("Could not perform copy operation.", 0, $e);
        }
    }
}

このケースは上記と似ていますが(そして、この例はおそらく人が考えつく最高のものではありません)、より多くのコンテキストを提供することのポイントを説明しています:もし例外が投げられたら、それはファイルコピーが失敗したことを教えてくれるのです。しかし なぜ は失敗したのでしょうか?この情報はラップされた例外の中で提供されます(この例がもっと複雑なものであれば、複数のレベルが存在する可能性があります)。

を作成するシナリオを考えてみると、この方法の価値がよくわかる。 UserProfile なぜなら、ユーザープロファイルはファイルに格納されており、トランザクションセマンティックをサポートしているからです。

この場合、もしあなたが

try {
    $profile = UserProfile::getInstance();
}

という例外エラーが発生した場合、混乱するのは当然でしょう。この "core" 例外を、コンテキストを提供する他の例外の層でラップすると、エラーへの対処がはるかに容易になります("プロファイル コピーの作成に失敗しました" -> "File copy operation failed" -> "Target directory could not be created").