panicと(PHPの)exception
panic
組み込みの関数
panicが起こるとそれ以降の処理は行わず、スタックを巻き戻し、アプリケーションをクラッシュさせる
この際、deferがあればLIFOで実行していく
defer内にrecoverがある場合はpanicを捕捉し、アプリケーションを回復させる
recoverした時点でパニック状態は解除されるが、その関数の残りのdefer(スタックに残っているもの)は通常通り実行されてから、呼び出し元に戻る
code:go
func main() {
recovery()
fmt.Println("Hello, 世界")
}
// panicを起こして回復させるまでの関数
func recovery() {
defer fmt.Println("1番")
defer func() {
// recover()が組み込み関数
if r := recover(); r != nil {
fmt.Println("2番 - panic捕捉:", r)
}
}()
defer fmt.Println("3番")
panic("panic!!")
// 下は処理されない
fmt.Println("4番")
}
// output
// 3番
// 2番 - panic捕捉: panic!!
// 1番
// Hello, 世界
exception
基底例外クラスのExceptionがある
基底例外クラスを継承したカスタム例外クラスが、特定の処理の際に投げられる(ことが多い)
例外が投げられると例外クラスのオブジェクトが生成され、スタックを巻き戻す
catch可能な場所がない場合はアプリケーションがクラッシュする
catch
code:php
// case1
<?php
function main() {
recovery();
}
function recovery() {
try {
throw new Exception('例外!!');
} catch (Exception $e) {
echo '3番' . "\n";
echo "2番 - 例外出力: {$e->getMessage()}" . "\n";
echo '1番' . "\n";
}
echo 'Hello, 世界';
}
main();
// output: 3番 2番 - 例外出力: 例外!! 1番 Hello, 世界
code:php
// case2
<?php
class CustomException extends Exception
{
}
function main() {
recovery();
}
function recovery() {
try {
throw new CustomException('カスタム例外!!');
echo 'ここは実行されない' . "\n";
} catch (CustomException $e) {
echo '3番' . "\n";
echo "2番 - 例外出力: {$e->getMessage()}" . "\n";
echo '1番' . "\n";
} catch (Exception $e) {
echo '予期せぬ汎用例外をキャッチ' . "\n";
}
echo 'Hello, 世界';
}
main();
// output: 3番 2番 - 例外出力: カスタム例外!! 1番 Hello, 世界
Handler
code:php
<?php
class CustomException extends Exception
{
}
class Handler
{
public static function catchUncaught($e): void
{
if ($e instanceof CustomException) {
echo '3番' . "\n";
echo '2番' . "\n";
echo '1番' . "\n";
} else {
echo '予期せぬ汎用例外をキャッチ' . "\n";
}
}
}
function main() {
recovery();
echo 'この行は実行されない';
}
function recovery() {
throw new CustomException('ハンドラでキャッチ!!');
}
main();
// output: 3番 2番 - 例外出力: ハンドラでキャッチ!! 1番
共通点
スタックを巻き戻す
何もしないとアプリケーションがクラッシュする
関数シグネチャに現れない
相違点
panicは
any(どんな型でも)を渡せる
スタックを巻き戻しつつ、機械的にLIFOでdeferを実行する。型の比較はしない
スタックトレースでエラー発生箇所はわかるが、具体的に何が起こったかわかりづらい場合が多い
アプリケーションの回復(recover)はdefer内でのみ可能
exceptionは
投げれるのはThrowableインタフェースを実装したクラス
基底例外Exceptionクラスが準備されているので、開発者はExceptionを継承したカスタム例外クラスを使用することが多い
ErrorクラスもThrowableインタフェースを実装しているが、PHPの内部エラーで使用するので開発者の使用は避けたい
スタックを巻き戻しつつ、型の比較を行いcatch可能な場所を探索する
catch可能な場所が見つかると、そのブロックの処理が実行される
型により、どのようなエラーが発生したかわかりやすい
アプリケーションの回復はcatchブロックで可能
catchブロックの配置場所に特に制限はない
catchブロックの実行後、ブロックの直後から処理を再開する
例外Handlerをセットする仕組みが言語レベルで準備されている
catchで捕捉しきれなかった(しなかった)例外を一元的に捕捉する仕組み
予期せぬエラー時のログ出力など行い、アプリケーションをクラッシュすることができる
結論
goでは基本的にerrorを使用する
errorは関数シグネチャに現れるのでエラーハンドリングを忘れ難い
errorは文脈を付与しやすい
errorはラップされたエラーに対する処理がシンプルに実装できるようになっている
errors.Isやerrors.As
panicを使用すると、パスが増えてコードのシンプルさが無くなってしまう
/icons/hr.icon
参考