원본 주소: 링크를 열려면 클릭하세요
이 기사는 다음에서 번역되었습니다. CodeProject 기사 기사, 원래 주소.
목차
소개
최악에 대비하세요
미리 확인하세요
외부 데이터를 믿지 마세요
신뢰할 수 있는 장치: 카메라, 마우스, 키보드
"쓰기 작업"도 실패할 수 있습니다
안전한 프로그래밍
"new Exception()"을 발생시키지 마십시오
메시지 속성에 중요한 예외 정보를 저장하지 마십시오
각 스레드에는 try/catch 블록이 포함되어야 합니다
예외를 포착한 후 기록하세요
Exception.Message 값만 기록하지 말고 Exception.ToString()
특정 예외를 캡처하려면
예외 발생을 중단하지 마세요.
정리 코드 finally 블록에 배치해야 합니다
using
을 사용하는 것을 잊지 마세요. Exception
리소스가 발생했음을 나타내기 위해 "예외 발생" 메서드를 사용하지 마세요. 존재하지 않음
함수 실행 결과로 “예외 발생”을 사용하지 마세요
'예외 발생' 방법을 사용하면 무시할 수 없는 것을 강조할 수 있습니다. 오류
스택 추적 정보를 지우지 마세요
예외 클래스는 직렬화 가능으로 표시되어야 합니다
Debug.Assert 대신 "예외 발생 예외"를 사용하세요
예외 클래스당 최소 3개의 생성자가 포함됩니다.
바퀴를 재발명하지 마세요
VB.NET
C#에서 using 문 시뮬레이션
구조화되지 않은 예외 처리를 사용하지 마세요(On Error goto) 🎜>소개
"내 소프트웨어 프로그램은 결코 잘못되지 않습니다." 당신은 그것을 믿습니까? 나는 모든 사람들이 내가 거짓말쟁이라고 소리칠 것이라고 거의 확신합니다. "버그가 없는 소프트웨어는 거의 불가능합니다!"
즉, 소프트웨어가 비교적 안정적이라는 뜻입니다.
소프트웨어에 버그가 있는 것은 당연합니다. 그러나 반복되는 버그이고 즉각적인 정보가 충분하지 않아 신속하게 수정할 수 없다면 이러한 상황은 용서할 수 없습니다.
위에서 말한 내용을 더 잘 이해하기 위해 예를 들어 보겠습니다. 하드 디스크가 부족할 때 수많은 상용 소프트웨어에서 다음 오류 메시지를 표시하는 경우가 많습니다.
"고객 정보 업데이트에 실패했습니다. 시스템 관리자에게 문의하신 후 다시 시도해 주세요."
이 외에 다른 정보는 기록되지 않습니다. 이 오류의 원인을 파악하는 것은 매우 시간이 많이 걸리는 프로세스입니다. 프로그래머는 실제로 문제의 원인을 찾기 전에 다양한 추측을 해야 할 수도 있습니다.
이 기사에서는 .NET 프로그래밍에서 예외를 더 잘 처리하는 방법에 대해 주로 설명합니다. 적절한 "오류 메시지"를 표시하는 방법에 대해서는 논의할 계획이 없습니다. 이 작업은 UI 인터페이스에 속한다고 생각하기 때문입니다. . 개발자이며 주로 UI 인터페이스 유형과 소프트웨어를 사용하게 될 사람에 따라 다릅니다. 예를 들어, 일반 사용자를 위한 텍스트 편집기의 "오류 메시지"는 소켓 통신 프레임워크의 메시지와 완전히 달라야 합니다. 후자의 직접 사용자는 프로그래머이기 때문입니다.
최악의 상황에 대비하세요
기본 사항을 따르세요. 디자인 원칙은 프로그램을 더욱 강력하게 만들고 오류 발생 시 사용자 경험을 향상시킬 수 있습니다. 여기서 말하는 "사용자 경험 개선"은 오류 프롬프트 양식이 사용자를 행복하게 할 수 있다는 의미가 아니라, 발생한 오류가 원본 데이터를 손상시키거나 컴퓨터 전체를 충돌시키지 않는다는 의미입니다. 프로그램에서 하드 디스크 부족 오류가 발생했지만 프로그램이 다른 부정적인 영향을 일으키지 않으면(오류 메시지만 표시하고 다른 문제는 일으키지 않음, 번역자 참고) 사용자 경험이 향상됩니다.
미리 확인
강력한 유형 검사 및 유효성 검사는 버그를 피하는 강력한 방법입니다. 문제를 빨리 발견할수록 더 빨리 해결할 수 있습니다. 몇 달이 지나도 "InvoiceItems 테이블의 ProductID 열에 CustomerID 데이터가 있는 이유는 무엇입니까?"를 알아내는 것은 쉽지도 않고 상당히 짜증나는 일입니다. 고객 데이터를 저장하기 위해 기본 유형(예: int, string) 대신 클래스를 사용하는 경우 컴파일러는 방금 발생한 일(번역가의 메모인 CustomerID와 ProductID를 혼동하는 내용 참조)이 발생하는 것을 허용하지 않습니다.
외부 데이터를 믿지 마세요
외부 데이터 신뢰할 수 없기 때문에 당사 소프트웨어 프로그램을 사용하기 전에 엄격하게 확인해야 합니다. 이러한 외부 데이터가 레지스트리, 데이터베이스, 하드 디스크, 소켓 또는 키보드로 작성한 파일에서 나온 것인지 여부에 관계없이 이러한 모든 외부 데이터는 사용하기 전에 엄격하게 확인해야 합니다. 구성 파일을 완전히 신뢰하는 프로그램을 여러 번 봅니다. 왜냐하면 이러한 프로그램을 개발하는 프로그래머는 항상 아무도 구성 파일을 편집하거나 손상시키지 않을 것이라고 가정하기 때문입니다.
신뢰할 수 있는 장치: 카메라, 마우스, 키보드
외부 데이터를 사용해야 하는 경우 다음과 같은 상황이 발생할 수 있습니다.
1) 보안 권한이 부족합니다
2) 데이터가 존재합니다
3) 데이터가 불완전합니다
4) 데이터는 완전하지만 형식이 잘못되었습니다
위 상황은 데이터 소스가 레지스트리의 키인지, 파일인지, 소켓인지, 데이터베이스인지, 웹 서비스인지, 시리얼 포트인지 관계없이 발생할 수 있습니다. 모든 외부 데이터는 오류가 발생할 수 있습니다.
"쓰기 작업"도 실패할 수 있습니다
아님 사용 가능 신뢰할 수 있는 데이터 원본은 신뢰할 수 없는 데이터 웨어하우스이기도 합니다. 데이터를 저장할 때 비슷한 상황이 계속 발생할 수 있습니다:
1) 보안 권한이 부족합니다
2) 장치가 존재하지 않습니다
3) 저장공간이 부족합니다
4) 저장장치에 물리적인 오류가 발생했습니다
이것은 이것이 일부 압축 소프트웨어가 소스 파일을 직접 수정하는 대신 작업 중에 임시 파일을 생성한 다음 작업이 완료되면 이름을 바꾸는 이유입니다. 그 이유는 하드디스크가 손상되거나 소프트웨어에 이상이 있을 경우 원본 데이터가 손실될 수 있기 때문입니다. (번역자가 이런 상황을 겪었습니다. 데이터를 백업하던 중 정전이 되어 원래의 오래된 백업이 손상되었습니다. 번역자 노트)
안전한 프로그래밍
내 친구가 나에게 이렇게 말했습니다. 좋은 프로그래머는 자신의 프로그램에 나쁜 코드를 절대 작성하지 않습니다. 저는 이것이 좋은 프로그래머가 되기 위한 필요조건이지 충분조건은 아니라고 생각합니다. 아래에는 예외 처리를 수행할 때 작성할 수 있는 "잘못된 코드"가 있습니다.
"new Exception() ”
이러지 마세요. Exception은 매우 추상적인 예외 클래스입니다. 이러한 유형의 예외를 포착하면 일반적으로 많은 부정적인 영향을 미칩니다. 일반적으로 우리는 자체 예외 클래스를 정의하고 시스템(프레임워크)에서 발생한 예외와 자체적으로 발생한 예외를 구별해야 합니다.
메시지 속성에 중요한 예외 정보를 저장하지 마세요.
예외는 클래스에 캡슐화됩니다. 예외 정보를 반환해야 하는 경우 해당 정보를 Message 속성이 아닌 별도의 속성에 저장하세요. 그렇지 않으면 사람들이 Message 속성에서 필요한 정보를 구문 분석하기 어려울 수 있습니다. 예를 들어 맞춤법 오류만 수정해야 하는 경우 Message 속성에 오류 메시지 및 기타 프롬프트 내용을 String 형식으로 작성하면 다른 사람들이 어떻게 원하는 오류 메시지를 간단하게 얻을 수 있을까요? 그들이 얼마나 많은 노력을 해야 하는지 상상할 수 없습니다.
각 스레드에는 try/catch 블록이 포함되어야 합니다.
일반적으로 예외 처리는 프로그램의 상대적으로 중앙 집중화된 위치에 배치됩니다. 각 스레드에는 try/catch 블록이 있어야 합니다. 그렇지 않으면 일부 예외가 누락되어 이해하기 어려운 문제가 발생할 수 있습니다. 프로그램이 백그라운드 작업을 처리하기 위해 여러 스레드를 시작할 때 일반적으로 각 스레드의 실행 결과를 저장하는 유형을 만듭니다. 이때 각 스레드에서 발생할 수 있는 예외를 저장하기 위해 유형에 필드를 추가하는 것을 잊지 마십시오. 그렇지 않으면 메인 스레드가 다른 스레드의 예외를 알 수 없습니다. 일부 "실행 후 잊어버리기" 상황(메인 스레드가 스레드를 시작한 후 더 이상 스레드의 실행 상태에 관심이 없음을 의미)에서는 메인 스레드의 예외 처리 논리를 하위 스레드에 복사해야 할 수도 있습니다. . 가다.
Exception 발생 후 기록
상관없음 프로그램이 로그를 기록하기 위해 사용하는 방법(log4net, EIF, 이벤트 로그, TraceListeners 또는 텍스트 파일)은 중요하지 않습니다. 중요한 것은 예외가 발생하면 어딘가에 기록해야 한다는 것입니다. 그러나 한 번만 기록하십시오. 그렇지 않으면 많은 반복 정보를 포함하는 매우 큰 로그 파일이 생성됩니다.
Exception.Message 값만 기록하지 말고 Exception.ToString()도 기록해야 합니다
로깅에 관해 이야기할 때 Exception.Message가 아닌 Exception.ToString()의 값을 로깅해야 한다는 점을 잊지 마세요. Exception.ToString()에는 "스택 추적" 정보, 내부 예외 정보 및 메시지가 포함되어 있기 때문입니다. 일반적으로 이 정보는 매우 중요하며 Exception.Message만 기록하는 경우 "개체 참조가 힙의 인스턴스를 가리키지 않습니다."와 같은 프롬프트만 표시될 수 있습니다.
특정 예외를 포착하려면
원하는 경우 예외를 잡으려면 가능한 한 특정 예외(예외가 아님)를 잡으려고 노력하세요.
좋은 코드는 예외를 발생시키지 않는 코드라고 말하는 초보자를 종종 봅니다. 사실, 이 진술은 잘못된 것입니다. 좋은 코드는 필요할 때 해당 예외를 발생시켜야 하며, 좋은 코드는 처리 방법을 알고 있는 예외만 포착할 수 있습니다(이 문장에 유의하세요, 번역자의 메모).
다음 코드는 이 규칙에 대한 설명입니다. 아래 코드를 작성한 사람이 이 코드를 본다면 나를 죽일 것이라고 확신합니다. 그러나 이는 실제 프로그래밍 작업에서 가져온 코드 조각입니다.
첫 번째 클래스 MyClass가 한 어셈블리에 있고 두 번째 클래스 GenericLibrary가 다른 어셈블리에 있습니다. 개발 컴퓨터에서는 정상적으로 실행되지만, 테스트 컴퓨터에서는 매번 입력한 데이터가 합법적임에도 불구하고 항상 "잘못된 데이터!"라는 예외가 발생합니다.
이유를 알려주실 수 있나요?
public class MyClass { public static string ValidateNumber(string userInput) { try { int val = GenericLibrary.ConvertToInt(userInput); return "Valid number"; } catch (Exception) { return "Invalid number"; } } } public class GenericLibrary { public static int ConvertToInt(string userInput) { return Convert.ToInt32(userInput); } }
이 문제의 원인은 예외 처리가 그다지 구체적이지 않기 때문입니다. MSDN 소개에 따르면 Convert.ToInt32 메서드는 ArgumentException, FormatException 및 OverflowException의 세 가지 예외만 발생시킵니다. 따라서 우리는 이 세 가지 예외만 처리해야 합니다.
问题发生在我们程序安装的步骤上,我们没有将第二个程序集(GenericLibrary.dll)打包进去。所以程序运行后,ConvertToInt方法会抛出FileNotFoundException异常,但是我们捕获的异常是Exception,所以会提示“数据不合法”。
不要中止异常上抛
最坏的情况是,你编写catch(Exception)这样的代码,并且在catch块中啥也不干。请不要这样做。
清理代码要放在finally块中
大多数时候,我们只处理某一些特定的异常,其它异常不负责处理。那么我们的代码中就应该多一些finally块(就算发生了不处理的异常,也可以在finally块中做一些事情,译者注),比如清理资源的代码、关闭流或者回复状态等。请把这当作习惯。
有一件大家容易忽略的事情是:怎样让我们的try/catch块同时具备易读性和健壮性。举个例子,假设你需要从一个临时文件中读取数据并且返回一个字符串。无论什么情况发生,我们都得删除这个临时文件,因为它是临时性的。
让我们先看看最简单的不使用try/catch块的代码:
string ReadTempFile(string FileName) { string fileContents; using (StreamReader sr = new StreamReader(FileName)) { fileContents = sr.ReadToEnd(); } File.Delete(FileName); return fileContents; }
这段代码有一个问题,ReadToEnd方法有可能抛出异常,那么临时文件就无法删除了。所以有些人修改代码为:
string ReadTempFile(string FileName) { try { string fileContents; using (StreamReader sr = new StreamReader(FileName)) { fileContents = sr.ReadToEnd(); } File.Delete(FileName); return fileContents; } catch (Exception) { File.Delete(FileName); throw; } }
这段代码变得复杂一些,并且它包含了重复性的代码。
那么现在让我们看看更简介更健壮的使用try/finally的方式:
string ReadTempFile(string FileName) { try { string fileContents; using (StreamReader sr = new StreamReader(FileName)) { fileContents = sr.ReadToEnd(); } File.Delete(FileName); return fileContents; } catch (Exception) { File.Delete(FileName); throw; } }
变量fileContents去哪里了?它不再需要了,因为返回点在清理代码前面。这是让代码在方法返回后才执行的好处:你可以清理那些返回语句需要用到的资源(方法返回时需要用到的资源,所以资源只能在方法返回后才能释放,译者注)。
不要忘记使用using
仅仅调用对象的Dispose()方法是不够的。即使异常发生时,using关键字也能够防止资源泄漏。(关于对象的Dispose()方法的用法,可以关注我的书,有一章专门介绍。译者注)
不要使用特殊返回值去表示方法中发生的异常
因为这样做有很多问题:
1)直接抛出异常更快,因为使用特殊的返回值表示异常时,我们每次调用完方法时,都需要去检查返回结果,并且这至少要多占用一个寄存器。降低代码运行速度。
2)特殊返回值能,并且很可能被忽略
3)特殊返回值不能包含堆栈跟踪(stack trace)信息,不能返回异常的详细信息
4)很多时候,不存在一个特殊值去表示方法中发生的异常,比如,除数为零的情况:
public int pide(int x, int y) { return x / y; }
不要使用“抛出异常”的方式去表示资源不存在
微软建议在某些特定场合,方法可以通过返回一些特定值来表示方法在执行过程中发生了预计之外的事情。我知道我上面提到的规则恰恰跟这条建议相反,我也不喜欢这样搞。但是一些API确实使用了某些特殊返回值来表示方法中的异常,并且工作得很好,所以我还是觉得你们可以谨慎地遵循这条建议。
我看到了.NET Framework中很多获取资源的API方法使用了特殊返回值,比如Assembly.GetManifestStream方法,当找不到资源时(异常),它会返回null(不会抛出异常)。
不要将“抛出异常”作为函数执行结果的一种
这是一个非常糟糕的设计。代码中包含太多的try/catch块会使代码难以理解,恰当的设计完全可以满足一个方法返回各种不同的执行结果(绝不可能到了必须使用抛出异常的方式才能说明方法执行结果的地步,译者注),如果你确实需要通过抛出异常来表示方法的执行结果,那只能说明你这个方法做了太多事情,必须进行拆分。(这里原文的意思是,除非确实有异常发生,否则一个方法不应该仅仅是为了说明执行结果而抛出异常,也就是说,不能无病呻呤,译者注)
可以使用“抛出异常”的方式去着重说明不能被忽略的错误
我可以举个现实中的例子。我为我的Grivo(我的一个产品)开发了一个用来登录的API(Login),如果用户登录失败,或者用户并没有调用Login方法,那么他们调用其他方法时都会失败。我在设计Login方法的时候这样做的:如果用户登录失败,它会抛出一个异常,而并不是简单的返回false。正因为这样,调用者(用户)才不会忽略(他还没登录)这个事实。
不要清空了堆栈跟踪(stack trace)信息
堆栈跟踪信息是异常发生时最重要的信息,我们经常需要在catch块中处理一些异常,有时候还需要重新上抛异常(re-throw)。下面来看看两种方法(一种错误的一种正确的):
错误的做法:
try { // Some code that throws an exception } catch (Exception ex) { // some code that handles the exception throw ex; }
为什么错了?因为当我们检查堆栈跟踪信息时,异常错误源变成了“thorw ex;”,这隐藏了真正异常抛出的位置。试一下下面这种做法:
try { // Some code that throws an exception } catch (Exception ex) { // some code that handles the exception throw; }
有什么变化没?我们使用“throw;”代替了“throw ex;”,后者会清空原来的堆栈跟踪信息。如果我们在抛出异常时没有指定具体的异常(简单的throw),那么它会默认地将原来捕获的异常继续上抛。这样的话,上层代码捕获的异常还是最开始我们通过catch捕获的同一个异常。
拓展阅读:
C# 异常处理(Catch Throw)IL分析
异常类应标记为Serializable
很多时候,我们的异常需要能被序列化。当我们派生一个新的异常类型时,请不要忘了给它加上Serializable属性。谁会知道我们的异常类会不会用在Remoting Call或者Web Services中呢?
使用”抛出异常”代替Debug.Assert
当我们发布程序后,不要忘了Debug.Assert将会被忽略。我们在代码中做一些检查或者验证工作时,最好使用抛出异常的方式代替输出Debug信息。
将输出Debug信息这种方式用到单元测试或者那些只需要测试当软件真正发布后确保不会出错的场合。
每个异常类至少包含三个构造方法
做这件事相当简单(直接从其他的类型粘贴拷贝相同的代码即可),如果你不这样做,那么别人在使用你编写的异常类型时,很难遵守上面给出的一些规则的。
我指的哪些构造方法呢?这三个构造方法可以参见这里。
不要重复造轮子
已经有很多在异常处理方面做得比较好的框架或库,微软提供的有两个:
Exception Management Application Block
Microsoft Enterprise Instrumentation Framework
위에 언급한 일부 규칙을 따르지 않으면 이러한 라이브러리가 별로 유용하지 않을 수 있습니다.
VB.NET
전체 이 기사를 읽고 나면 모든 샘플 코드가 C#으로 작성되었음을 알 수 있습니다. 그 이유는 C#이 제가 선호하는 .NET 언어이고 VB.NET에는 고유한 몇 가지 특별한 규칙이 있기 때문입니다.
C#에서 using 문 시뮬레이션
안타깝게도 예 , VB.NET에는 using 문이 없습니다. 개체의 관리되지 않는 리소스를 해제할 때마다 다음을 수행해야 합니다.
따르지 않는 경우 위와 같은 방법으로 DISpose 메소드를 호출하면 오류가 발생할 가능성이 높습니다(Dispose 메소드 호출에 대한 내용은 신간을 참고하세요. 번역자 노트).
구조화되지 않은 예외 처리 사용 안 함(On Error Goto)
구조화되지 않은 예외 처리는 "On Error Goto"라고도 합니다. Djikstra는 1974년에 "goto 문은 도움이 되기보다는 해롭다"고 말했습니다. 이것은 이미 30년 전의 일입니다. 코드에서 모든 goto 문을 제거하세요. 장담컨대 아무 소용이 없습니다. (Edsger Dykstra는 "고토 유해 이론", 세마포어 및 PV 프리미티브를 제안하여 흥미로운 식사 철학자 문제를 해결했습니다. 책 "소프트웨어 스토리"에서 포트란 언어에 대해 이야기할 때 그에게 왔습니다. 번역가의 메모)
>이 기사가 일부 사람들의 코딩 품질을 향상시키는 데 도움이 되기를 바랍니다. 또한 이 기사가 예외를 효과적으로 처리하고 이를 구현하는 방법에 대한 논의의 시작이 되기를 바랍니다. 우리가 더욱 강력하게 작성하는 프로그램.
번역가의 말:
나에겐 부족한 점이 있다. 저랑 같은 네티즌도 계시네요. 나는 느린 버너이고 기술에 대해서도 마찬가지입니다. 나는 많은 것의 인기가 최고조에 달한 후에야 뭔가를 느끼기 시작했습니다. 주된 이유는 내가 새로운 것을 별로 좋아하지 않기 때문이다. 두 번째 이유는 나는 항상 그것을 익히기 전에 변화한다는 느낌을 받기 때문이다. 즉 중간에 포기한다는 뜻이다. 사실 이것이 매우 안타까운 일이라는 것도 알고 있습니다. 결국 IT 산업은 빠르게 발전하는 산업이고, 따라잡지 못하면 곧 뒤쳐지게 마련입니다.
이런 모순된 상황에 직면했을 때, 나는 지식을 배울 때 사람과 사람 사이의 소통을 배우는 데 집중한다. 소위 보편성이라고 불리는 기술은 10년, 20년, 심지어 30년 후에도 변하지 않거나 쇠퇴할 가능성이 없습니다. 현재 일하고 있는 회사가 실제 개발 과정에서 특정 프레임워크를 사용해 왔다면, 고집한다면 "이 프레임워크를 어떻게 사용하여 좋은 시스템을 만들 것인가"에 초점을 맞추지 않으면 몇 년 안에 뒤처질 수 있습니다. 그리고 프로토콜, 시스템 간 상호 작용 원리 등 프로그래밍의 공통점을 연구하면 오래된 것처럼 보이는 PC 프로그램이든, 웹 프로그램이든, 현재 인기 있는 모바일 APP이든 모든 네트워크 통신 시스템에서 사용됩니다. 사용되며 기본 원리는 동일합니다. 보면 볼수록 약을 바꾸지 않고 국물을 바꾸는 듯한 새로운 것들이 나온다는 걸 깨닫게 된다(약간 과장:-))
그래서 준다. 저처럼 새로운 일을 처음 접하는 분들이나 특정 유형의 고정적인 개발 작업을 오랫동안 해오신 분들을 위한 조언은 둘 사이의 공통점을 찾아보라는 것입니다. 새로운 것에 관심이 많고 에너지가 충분하지 않으면 기술의 표면에만 머물지 마십시오.
위의 말씀은 저희 회사 간담회에서도 나누었습니다.
저자: Zhou Jianzhi
출처: http://www.php.cn/이 글의 저작권은 글쓴이에게 있으며, 재인쇄는 환영합니다. 단, 이 글은 글쓴이의 동의 없이 그대로 유지되어야 하며, 글 페이지의 눈에 띄는 위치에 원문 링크를 제공해야 합니다. 그렇지 않은 경우 법적 책임을 추구할 권리가 있습니다.
보충:
CLR의 "2라운드 순회" 예외 처리 전략에 대해 설명합니다.
애플리케이션에 다층 중첩 예외 포착 구조가 있는 경우, 가장 낮은 계층에서 예외가 발생하면(실제로 중간 계층에서도 마찬가지임) CLR은 다음에서 catch 문 블록을 검색하는 데 우선순위를 부여합니다. 예외가 발생한 레이어를 살펴보세요. 이 유형의 예외에 대해 "호환되는"
처리 코드가 있습니까? 그렇지 않은 경우 이전 레이어로 "점프"하면 됩니다. 이전 레이어의 "이전 레이어"를 계속해서 검색합니다. 여기에서 애플리케이션의 최상위 레벨까지.
이는 CLR의 중첩된 예외 포착 구조 적용에 대한 "첫 번째 라운드" 탐색으로, 적절한 예외 처리기를 찾습니다.
특정 레이어에서 예외 핸들러가 발견되면 CLR은 이를 즉시 실행하지 않고 "사고 현장"으로 돌아가 다시 "두 번째 라운드" 순회를 수행하여 최종적으로 실행됩니다. "중간" 수준의 명령문 블록을 실행한 다음
을 실행하여 예외 처리기를 찾고 마지막으로 이 계층에서 맨 위로 이동하여 모든 finally 명령문 블록을 실행합니다.
위는 의 예외 처리 모범 사례(번역) 내용입니다. NET, 더 많은 관련 내용을 보려면 PHP 중국어 웹사이트(www.php.cn)를 주목하세요!