나는 또는이 대답에 다른 언어 프로그래머 Pythonistas의 진노가 (내가 많이 파이썬을 사용하지 않기 때문에 모르는) 호출 할 수 있지만, 제 생각에 대부분의 기능을해야 하지 이 catch
블록, 이상적으로 말하기. 이유를 설명하기 위해 80 년대 후반과 90 년대 초에 Turbo C로 작업 할 때해야했던 수동 오류 코드 전파와 이것을 대조해 보겠습니다.
따라서 사용자가로드 할 이미지 파일을 선택하는 것에 대한 응답으로 이미지 또는 이와 유사한 것을로드하는 기능이 있다고 가정 해 봅시다. 이것은 C와 어셈블리로 작성됩니다.
저수준 함수를 생략했지만 오류 처리와 관련한 책임에 따라 색상으로 구분 된 여러 범주의 함수를 식별했음을 알 수 있습니다.
장애 지점 및 복구
이제 내가 "가능한 실패 지점"( throw
즉, 실패 ) 및 "오류 복구 및보고"기능 (즉, 실패)이라고 부르는 기능 범주를 작성하는 것은 결코 어려운 일이 아닙니다 catch
.
이러한 기능은 항상 메모리를 할당하는데 실패와 같은 외부 고장으로 실행할 수있는 기능은, 단지 반환 할 수 있기 때문에 예외 처리가 가능했던 제대로하기 전에 작성하는 사소한 있었다 NULL
또는 0
또는 -1
또는이 효과에 글로벌 오류 코드 또는 뭔가를 설정합니다. 그리고 오류 복구 및보고는 호출 스택에서 장애를 복구 및보고하는 것이 합리적 수준으로 진행되면 오류 코드 및 / 또는 메시지를 가져 와서 사용자에게보고하기 때문에 항상 쉬웠습니다. 그리고 자연스럽게이 계층의 잎에서 함수는 미래에 어떻게 변경되는지에 관계없이 절대 실패하지 않을 수 있습니다 ( Convert Pixel
)는 (적어도 오류 처리와 관련하여) 올바르게 작성하는 것이 간단합니다.
오류 전파
그러나 인적 오류가 발생하기 쉬운 지루한 기능은 오류 전파자 인데, 이는 직접 실패하지는 않았지만 계층 구조의 깊숙한 곳에서 실패 할 수있는 기능이라고 하는 오류 전파자 입니다. 이 시점에서 Allocate Scanline
오류를 처리 한 malloc
다음 오류를로 되돌려 보내야 할 수도 있습니다 Convert Scanlines
. 그런 다음 Convert Scanlines
해당 오류를 확인한 후 오류를보고 Decompress Image
한 Decompress Image->Parse Image
,, 및 Parse Image->Load Image
, Load Image
사용자 엔드 명령으로 전달해야합니다. .
오류를 제대로 처리 할 때 전체 기능 계층에 대한 오류를 확인하고 전달하는 데 오류 전파자가 하나만 걸리기 때문에 많은 사람이 실수를 저지르는 곳입니다.
또한 오류 코드가 함수에 의해 반환되면 코드베이스의 90 %에서 성공 에 대한 관심 값을 반환하는 기능이 거의 손실 되므로 많은 함수가 오류 코드 를 반환하기 위해 반환 값을 예약해야하기 때문에 실패 .
인적 오류 감소 : 전역 오류 코드
그렇다면 어떻게 인간의 실수 가능성을 줄일 수 있습니까? 여기서는 일부 C 프로그래머의 분노를 불러 일으킬 수도 있지만 OpenGL과 같은 전역 오류 코드 를 사용하는 것이 내 의견을 즉시 개선하는 것 입니다 glGetError
. 이것은 적어도 함수가 성공에 대한 의미있는 관심 값을 리턴하도록 해방합니다. 오류 코드가 스레드에 지역화 된 경우이 스레드 안전하고 효율적으로 만드는 방법이 있습니다.
함수가 오류를 일으킬 수있는 경우도 있지만 이전 오류를 발견 한 결과로 조기에 반환되기 전에 조금 더 오래가는 것이 상대적으로 무해합니다. 이를 통해 모든 단일 함수에서 수행되는 함수 호출의 90 %에 대해 오류를 확인할 필요없이 이러한 일이 발생할 수 있으므로 여전히 세심한 오류없이 적절한 오류 처리가 가능합니다.
휴먼 오류 줄이기 : 예외 처리
그러나 위의 솔루션은 수동 if error happened, return error
유형의 코드 줄 수를 줄인 경우에도 수동 오류 전파의 제어 흐름 측면을 처리하기 위해 여전히 많은 기능이 필요 합니다. 오류를 검사하고 거의 모든 단일 오류 전파 기능을 반환하는 장소가 하나 이상 있어야하기 때문에 완전히 제거하지는 않습니다. 따라서 예외 처리가 사진을 찍어 하루를 저장하는 경우입니다.
그러나 여기서 예외 처리의 가치는 수동 오류 전파의 제어 흐름 측면을 처리 할 필요가 없다는 것입니다. 그것은 그 가치가 catch
코드베이스 전체에 보트로드를 작성하지 않아도되는 능력과 관련이 있음을 의미합니다 . 위의 다이어그램에서 catch
블록 을 가져야하는 유일한 위치 Load Image User Command
는 오류가보고 된 곳입니다. catch
그렇지 않으면 오류 코드 처리만큼 지루하고 오류가 발생하기 쉽기 때문에 다른 어떤 것도 이상적으로해야 할 일은 없습니다 .
따라서 나에게 묻는다면, 우아한 방식으로 예외 처리의 이점을 실제로 얻을 수있는 코드베이스가 있다면 최소catch
블록 수를 가져야합니다 (최소한 나는 0을 의미하지 않지만 모든 고유 한 최고에 대해 하나 더 중앙 명령 시스템을 통해 모든 고급 사용자 작업이 호출되는 경우 실패 할 수있는 최종 사용자 작업, 그리고 아마도 더 적은 작업).
자원 정리
그러나 예외 처리는 정상적인 실행 흐름과 별 개인 예외 경로에서 오류 전파의 제어 흐름 측면을 수동으로 처리하지 않아도됩니다. 종종 오류 전파자 역할을하는 기능은 EH를 사용하여 자동으로 수행하더라도 파괴해야 할 리소스를 여전히 확보 할 수 있습니다. 예를 들어, 그러한 함수는 무엇이든 상관없이 함수에서 복귀하기 전에 닫아야하는 임시 파일을 열거 나, 뮤텍스를 잠그면 잠금 해제해야합니다.
이를 위해 모든 종류의 언어에서 많은 프로그래머의 분노를 불러 일으킬 수 있지만 C ++ 접근 방식이 이상적이라고 생각합니다. 이 언어 는 객체가 범위를 벗어난 순간 결정적 방식으로 호출되는 소멸자 를 소개 합니다. 이 때문에 소멸자가있는 범위가 지정된 뮤텍스 객체를 통해 뮤텍스를 잠그는 C ++ 코드는 수동으로 잠금을 해제 할 필요가 없습니다. 발생). 따라서 잘 작성된 C ++ 코드가 로컬 리소스 정리를 처리 할 필요가 없습니다.
소멸자가없는 언어에서는 finally
로컬 리소스를 수동으로 정리하기 위해 블록을 사용해야 할 수도 있습니다 . 즉, 여전히 수동 에러 전파와 코드가 쓰레기를 가지고 뛰는 말했다 제공 당신이없는 catch
모든 괴물이 여기 저기 예외.
외부 부작용 반전
이다 해결하기 가장 어려운 개념 문제. 오류 전파이든 오류 지점이든 외부 부작용을 유발하는 기능이있는 경우 해당 기능을 롤백하거나 "실행 취소"하여 시스템이 작동이 발생하지 않은 것처럼 " half-valid "상태이며 작업이 중간에 성공했습니다. 나는 불변성과 지속적인 데이터 구조를 중심으로하는 기능적 언어와 같이 외부 부작용을 일으키는 대부분의 기능의 필요성을 간단히 줄여주는 언어를 제외하고는이 개념적인 문제를 훨씬 쉽게 해줄 언어가 없다.
finally
가변성과 부작용을 중심으로하는 언어에서 문제에 대한 가장 우아한 해결책은 다음과 같습니다. 종종 이러한 유형의 논리는 특정 기능에 매우 구체적이고 "자원 정리"개념에 잘 맞지 않기 때문입니다. ". 그리고이 finally
경우 자유롭게 사용하여 catch
블록 이 필요한지 여부에 관계없이 함수가 지원하는 언어의 부작용을 되돌릴 수 있도록하는 것이 좋습니다 (다시 물어 보면 잘 작성된 코드의 최소 개수는 catch
블록과 모든 catch
블록은 위의 다이어그램과 같이 가장 의미가있는 곳에 있어야합니다 Load Image User Command
.
꿈의 언어
그러나 IMO finally
는 부작용 반전에 이상적이지만 그다지 좋지 않습니다. boolean
조기 종료의 경우 (예외 예외에서 또는 기타로) 부작용을 효과적으로 롤백하려면 하나의 변수 를 도입해야합니다 .
bool finished = false;
try
{
// Cause external side effects.
...
// Indicate that all the external side effects were
// made successfully.
finished = true;
}
finally
{
// If the function prematurely exited before finishing
// causing all of its side effects, whether as a result of
// an early 'return' statement or an exception, undo the
// side effects.
if (!finished)
{
// Undo side effects.
...
}
}
언어를 디자인 할 수 있다면이 문제를 해결하는 꿈의 방법은 위 코드를 자동화하는 것과 같습니다.
transaction
{
// Cause external side effects.
...
}
rollback
{
// This block is only executed if the above 'transaction'
// block didn't reach its end, either as a result of a premature
// 'return' or an exception.
// Undo side effects.
...
}
... 소멸자는 그래서 우리는 필요하고, 지역 자원의 정리를 자동화하는 transaction
, rollback
그리고 catch
(나는 아직도 추가 할 수 있지만 finally
말을 위해 자신을 정리하지 않는 C 자원과 작업). 그러나 finally
로모그래퍼 boolean
변수 내 꿈의 언어를 결여 지금까지 발견 한 것을이 간단하기에 가장 가까운 것입니다. 내가 찾은 두 번째로 가장 간단한 솔루션은 C ++ 및 D와 같은 언어의 스코프 가드 이지만 "리소스 정리"및 "부작용 반전"이라는 개념을 흐리게하므로 스코프 가드는 개념적으로 항상 다소 어색하다는 것을 알았습니다. 제 생각에는 그것들은 다른 방식으로 다루어 질 매우 독특한 아이디어입니다.
언어에 대한 나의 작은 파이프 꿈은 불변성과 영구적 인 데이터 구조를 중심으로 크게 회전하여 함수가 발생하더라도 대량의 데이터 구조를 전체적으로 딥 카피 할 필요가없는 효율적인 함수를 작성하는 것이 더 쉽지는 않지만 필요하지는 않습니다. 부작용이 없습니다.
결론
어쨌든, 내 멍청이를 제쳐두고, try/finally
파이썬에는 소멸자와 동등한 C ++이 없다는 것을 고려할 때 소켓을 닫는 코드가 훌륭하고 훌륭하다고 생각하며 개인적으로 부작용을 되돌려 야 할 장소에 자유롭게 사용해야한다고 생각합니다. catch
가장 적합한 곳으로 가야 할 곳의 수를 최소화하십시오 .