실행을 중단해서는 안되는 몇 가지 가능한 오류를 처리하기 위해 error
클라이언트가 확인하고 예외를 throw하는 데 사용할 수 있는 변수가 있습니다. 안티 패턴입니까? 이것을 처리하는 더 좋은 방법이 있습니까? 이에 대한 예제는 PHP의 mysqli API를 볼 수 있습니다 . 가시성 문제 (접근 자, 공용 및 개인 범위, 클래스 또는 전역 변수)가 올바르게 처리되었다고 가정합니다.
실행을 중단해서는 안되는 몇 가지 가능한 오류를 처리하기 위해 error
클라이언트가 확인하고 예외를 throw하는 데 사용할 수 있는 변수가 있습니다. 안티 패턴입니까? 이것을 처리하는 더 좋은 방법이 있습니까? 이에 대한 예제는 PHP의 mysqli API를 볼 수 있습니다 . 가시성 문제 (접근 자, 공용 및 개인 범위, 클래스 또는 전역 변수)가 올바르게 처리되었다고 가정합니다.
답변:
언어가 본질적으로 예외를 지원하는 경우 예외를 처리하는 것이 좋으며 클라이언트는 예외가 발생하지 않도록 예외를 잡을 수 있습니다. 실제로 코드의 클라이언트는 예외를 예상하고 반환 값을 확인하지 않기 때문에 많은 버그가 발생합니다.
선택 사항이있는 경우 예외를 사용하면 몇 가지 장점이 있습니다.
메시지
예외에는 개발자가 디버깅에 사용하거나 원하는 경우 사용자에게 표시 할 수있는 사용자가 읽을 수있는 오류 메시지가 포함됩니다. 소비 코드가 예외를 처리 할 수없는 경우, 항상 예외를 로그 할 수 있으므로 개발자는 다른 모든 추적에서 중지 값을 파악하고 테이블에 맵핑하여 실제 예외.
리턴 값을 사용하면 추가 정보를 쉽게 제공 할 수 없습니다. 일부 언어는 마지막 오류 메시지를 얻기 위해 메소드 호출을 지원하므로이 문제는 약간 완화되지만 호출자는 추가 호출을해야하고 때로는이 정보를 전달하는 '특수 오브젝트'에 액세스해야합니다.
예외 메시지의 경우 가능한 많은 컨텍스트를 제공합니다.
사용자 프로필에서 참조 된 "bar"사용자에 대해 "foo"라는 이름의 정책을 검색 할 수 없습니다.
이것을 리턴 코드 -85와 비교하십시오. 어느 것이 더 좋은가요?
콜 스택
예외에는 일반적으로 코드를 더 빠르고 빠르게 디버그하는 데 도움이되는 자세한 호출 스택이 있으며 원하는 경우 호출 코드로 기록 할 수도 있습니다. 이를 통해 개발자는 일반적으로 문제를 정확하게 파악할 수 있으므로 매우 강력합니다. 다시 한 번,이 값을 반환 값 (예 : -85, 101, 0 등)이있는 로그 파일과 비교하십시오.
빠른 바이어스 접근 실패
실패한 곳에서 메소드를 호출하면 예외가 발생합니다. 호출 코드는 예외를 명시 적으로 억제해야합니다. 그렇지 않으면 실패합니다. 개발 및 테스트 중 (및 프로덕션에서도) 코드가 빠르게 실패하여 개발자가이를 수정해야하기 때문에 이것이 실제로 놀라운 것으로 나타났습니다. 반환 값의 경우 반환 값 확인이 누락되면 오류가 자동으로 무시되고 버그가 예상치 못한 곳에 나타나며 대개 디버깅 및 수정 비용이 훨씬 높습니다.
예외 랩핑 및 랩핑 해제
예외는 다른 예외에 싸서 필요한 경우 래핑 해제 할 수 있습니다. 예를 들어, ArgumentNullException
호출 코드 UnableToRetrievePolicyException
에서 해당 작업이 실패했기 때문에 코드에서 호출 코드를 감쌀 수있는 코드를 던질 수 있습니다 . 사용자에게 위에 제공된 예제와 유사한 메시지가 표시 될 수 있지만 일부 진단 코드는 예외를 풀고 ArgumentNullException
문제가 발생한 것을 발견 할 수 있습니다 . 이는 소비자 코드의 코딩 오류임을 의미합니다. 그러면 개발자가 코드를 수정할 수 있도록 경고가 발생할 수 있습니다. 이러한 고급 시나리오는 반환 값으로 구현하기가 쉽지 않습니다.
코드의 단순성
이것은 설명하기가 조금 어렵지만 반환 값과 예외를 모두 사용 하여이 코딩을 통해 배웠습니다. 반환 값을 사용하여 작성된 코드는 일반적으로 호출 한 다음 반환 값이 무엇인지에 대한 일련의 검사를 수행합니다. 어떤 경우에는 다른 메소드를 호출하여 해당 메소드의 리턴 값에 대한 일련의 검사를 수행하게됩니다. 예외를 제외하면 예외 처리는 대부분의 경우가 아니라면 훨씬 간단합니다. try / catch / finally 블록이 있으며 런타임은 정리를 위해 finally 블록에서 코드를 실행하기 위해 최선을 다합니다. 중첩 된 try / catch / finally 블록조차도 중첩 된 if / else 및 여러 메서드의 관련 반환 값보다 추적 및 유지 관리가 상대적으로 쉽습니다.
결론
사용중인 플랫폼에서 예외 (예 : Java 또는 .NET)를 지원하는 경우 예외를 발생시키는 지침이 있으며 클라이언트가 예상 할 것이므로 예외를 발생시키는 것 외에 다른 방법은 없다고 가정해야합니다 그래서. 라이브러리를 사용하는 경우 예외가 발생하기를 기대하기 때문에 반환 값을 확인하지 않아도됩니다. 이러한 플랫폼의 세계입니다.
그러나 C ++ 인 경우 큰 코드베이스가 이미 리턴 코드와 함께 존재하고 많은 개발자가 예외가 아닌 값을 반환하도록 조정되어 있기 때문에 결정하기가 약간 더 어려울 것입니다 (예 : Windows는 HRESULT를 사용합니다) . 또한 많은 응용 프로그램에서 성능 문제가 될 수도 있습니다.
ErrorStateReturnVariable
수퍼 클래스를 생성하기 만하면되고, 그 속성 중 하나는 InnerErrorState
(의 인스턴스 임 ErrorStateReturnVariable
) 서브 클래스를 구현하면 일련의 오류를 보여 주도록 설정할 수 있습니다. : p
오류 변수는 예외를 사용할 수없는 C와 같은 언어의 유물입니다. 오늘날, C 프로그램 (또는 예외 처리없이 유사한 언어)에서 잠재적으로 사용되는 라이브러리를 작성할 때를 제외하고는이를 피해야합니다.
물론 "경고"로 더 잘 분류 될 수있는 유형의 오류가있는 경우 (= 라이브러리가 유효한 결과를 제공 할 수 있고 호출자가 중요하지 않다고 생각하면 경고를 무시할 수 있음) 양식의 상태 표시기 예외가있는 언어에서도 변수의 의미를 이해할 수 있습니다. 그러나 조심하십시오. 라이브러리 호출자는 그러한 경고를 무시해서는 안되는 경우에도 무시하는 경향이 있습니다. 따라서 그러한 구조를 lib에 도입하기 전에 두 번 생각하십시오.
오류를 알리는 방법에는 여러 가지가 있습니다.
오류 변수의 문제점은 확인을 잊어 버리기 쉽다는 것입니다.
예외의 문제는 숨겨진 실행 경로를 생성한다는 것입니다. try / catch를 작성하기는 쉽지만 catch 절에서 적절한 복구를 수행하는 것은 실제로 어렵습니다 (유형 시스템 / 컴파일러의 지원 없음).
조건 핸들러의 문제점은 잘 작성되지 않는다는 것입니다. 동적 코드 실행 (가상 함수)이있는 경우 어떤 조건을 처리해야하는지 예측할 수 없습니다. 또한 동일한 조건을 여러 지점에서 발생시킬 수 있으면 매번 균일 한 솔루션을 적용 할 수 있다는 말이없고 빠르게 혼란스러워집니다.
Either a b
하스켈의 다형성 리턴 은 지금까지 내가 가장 좋아하는 솔루션입니다.
유일한 문제는 잠재적으로 과도한 점검으로 이어질 수 있다는 것입니다. 그것들을 사용하는 언어는 그것을 사용하는 함수의 호출을 연결하는 관용구를 가지고 있지만 여전히 약간의 타이핑 / 정리가 필요할 수 있습니다. Haskell에서 이것은 모나드 일 것입니다 . 그러나 이것은 소리보다 훨씬 두껍습니다 . 철도 지향 프로그래밍을 참조하십시오 .
끔찍하다고 생각합니다. 현재 예외 대신 반환 값을 사용하는 Java 앱을 리팩토링하고 있습니다. Java로 작업하지 않을 수도 있지만 그럼에도 불구하고 이것이 적용됩니다.
다음과 같은 코드로 끝납니다.
String result = x.doActionA();
if (result != null) {
throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
throw new Exception(result);
}
아니면 이거:
if (!x.doActionA()) {
throw new Exception(x.getError());
}
if (!x.doActionB()) {
throw new Exception(x.getError());
}
나는 오히려 액션이 스스로 예외를 던지기를 원하므로 다음과 같은 결과가 나옵니다.
x.doActionA();
x.doActionB();
try-catch로 랩핑하고 예외에서 메시지를 가져 오거나 이미 사라 졌을 수있는 항목을 삭제하는 경우와 같이 예외를 무시하도록 선택할 수 있습니다. 스택 추적이있는 경우 스택 추적도 유지합니다. 방법 자체도 쉬워졌습니다. 예외 자체를 처리하는 대신 잘못 된 것을 던집니다.
현재 (끔찍한) 코드 :
private String doActionA() {
try {
someOperationThatCanGoWrong1();
someOperationThatCanGoWrong2();
someOperationThatCanGoWrong3();
return null;
} catch(Exception e) {
return "Something went wrong!";
}
}
새롭고 개선 된 기능 :
private void doActionA() throws Exception {
someOperationThatCanGoWrong1();
someOperationThatCanGoWrong2();
someOperationThatCanGoWrong3();
}
쓸모없는 "문제가 발생했습니다!"대신 Strack 추적이 유지되고 예외적으로 메시지를 사용할 수 있습니다.
물론 더 나은 오류 메시지를 제공 할 수 있습니다. 그러나이 게시물은 현재 작업중 인 현재 코드가 고통 스럽기 때문에 여기에 있습니다.
throw new Exception("Something went wrong with " + instanceVar, ex);
"발생할 수있는 몇 가지 오류를 처리하기 위해 실행을 중단해서는 안됩니다."
오류가 현재 함수의 실행을 중단해서는 안되지만 호출자에게 어떤 방식으로보고해야한다는 것을 의미한다면 실제로 언급되지 않은 몇 가지 옵션이 있습니다. 이 경우는 실제로 오류보다 경고입니다. Throw / Returning은 현재 기능을 종료하므로 옵션이 아닙니다. 단일 오류 메시지 매개 변수 또는 리턴은 이러한 오류 중 최대 하나만 발생하도록 허용합니다.
내가 사용한 두 가지 패턴은 다음과 같습니다.
전달되거나 멤버 변수로 유지되는 오류 / 경고 콜렉션. 당신은 물건을 추가하고 처리를 계속합니다. 나는 개인적으로이 접근 방식이 마음에 들지 않아 발신자를 불쾌하게 만듭니다.
오류 / 경고 처리기 객체를 전달하거나 멤버 변수로 설정합니다. 그리고 각 오류는 핸들러의 멤버 함수를 호출합니다. 이렇게하면 호출자가 종료되지 않은 이러한 오류로 수행 할 작업을 결정할 수 있습니다.
이 컬렉션 / 핸들러에 전달하는 것은 오류가 "올바로"처리 될 수 있도록 충분한 컨텍스트를 포함해야합니다. 문자열은 일반적으로 너무 작아서 예외의 일부 인스턴스를 전달하는 경우가 종종 있습니다. .
오류 처리기를 사용하는 일반 코드는 다음과 같습니다.
class MyFunClass {
public interface ErrorHandler {
void onError(Exception e);
void onWarning(Exception e);
}
ErrorHandler eh;
public void canFail(int i) {
if(i==0) {
if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
}
if(i==1) {
if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
throw new RuntimeException("canFail called with i=2");
}
if(i==2) {
if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
}
}
}
warnings
이 문제에 대한 또 다른 패턴을 제공 하는 Python 패키지를 언급 할 가치가 있습니다.
질문에 이미 답변되어 있지만 도움을 드릴 수 없습니다.
예외가 모든 사용 사례에 대한 솔루션을 제공한다고 기대할 수는 없습니다. 누구 망치?
예를 들어, 메소드가 요청을 받고 전달 된 모든 필드의 유효성을 검사 할 책임이있는 경우 (예 : 첫 번째 필드 만이 아니라 예외)가 전부가 아닌 예외가있는 경우가 있습니다. 둘 이상의 필드에 대한 오류의 원인을 나타냅니다. 유효성 검사의 특성으로 인해 사용자가 더 이상 갈 수 없는지 여부도 표시 할 수 있어야합니다. 그 예는 강력한 암호가 아닙니다. 입력 한 비밀번호가 강력하지는 않지만 충분히 강력하다는 메시지를 사용자에게 표시 할 수 있습니다.
이러한 모든 유효성 검사는 유효성 검사 모듈 끝에서 예외로 throw 될 수 있지만 이름 이외의 오류 코드 일 수 있습니다.
따라서 교훈은 다음과 같습니다. 예외는 오류 코드와 마찬가지로 위치가 있습니다. 현명하게 선택하십시오.
Validator
에는 문제의 메소드 (또는 그 뒤에있는 객체)에 (인터페이스)가 주입되어 있어야합니다. 주입 Validator
된 방법에 따라이 방법은 잘못된 암호로 진행됩니다. 주변 코드 WeakValidator
는 사용자가 예를 들어 WeakPasswordException
처음 시도한에 의해 던져진 후에 요청하면 시도 할 수 StrongValidator
있습니다.
MiddlyStrongValidator
무언가가 있습니다. 그리고 이것이 실제로 흐름을 방해하지 않는다면 Validator
, 사용자가 여전히 암호를 입력하는 동안 흐름을 진행하기 전에 미리 호출해야합니다. 그러나 검증은 우선 문제의 방법의 일부가 아니 었습니다. :) 아마 맛의 문제 ...
AggregateException
(또는 유사한 ValidationException
)을 만들고 각 유효성 검사 문제에 대한 특정 예외를 InnerExceptions에 넣습니다. 예를 들어 BadPasswordException
"사용자 암호가 최소 길이 6보다 작습니다"또는 MandatoryFieldMissingException
"이름이 사용자에게 제공되어야합니다"등일 수 있습니다. 이는 오류 코드와 동일하지 않습니다. 이러한 모든 메시지는 사용자가 이해할 수있는 방식으로 사용자에게 표시 될 수 있으며 NullReferenceException
대신 a 가 발생하면 버그가 발생합니다.
오류 코드가 예외보다 선호되는 사용 사례가 있습니다.
오류에도 불구하고 코드가 계속 될 수 있지만보고가 필요한 경우 예외가 플로우를 종료하므로 예외를 선택하는 것이 좋지 않습니다. 예를 들어, 데이터 파일을 읽는 중이고 터미널에 불량이 아닌 일부 데이터가 포함되어있는 것을 발견하면 파일의 나머지 부분을 읽고 오류없이 오류를보고하는 것이 좋습니다.
다른 답변은 예외 코드가 일반적으로 오류 코드보다 선호되는 이유를 다루었습니다.
AcknowledgePossibleCorruption
메서드 를 호출하지 않고 데이터를 읽을 수 없도록 할 수 있습니다 . .
예외가 잘 맞지 않을 때 예외를 사용하지 않으면 아무런 문제가 없습니다.
코드 실행이 중단되지 않아야하는 경우 (예 : 컴파일하는 프로그램이나 프로세스에 대한 형태와 같은 여러 오류가있을 수 있습니다 사용자 입력에 작용), 내가 좋아하는 에러 변수의 오류를 수집하는 것을 발견 has_errors
하고하는 것은 error_messages
참으로 던지는 것보다 훨씬 더 우아한 디자인입니다 첫 번째 오류의 예외 사용자가 불필요하게 다시 제출하지 않고도 사용자 입력의 모든 오류를 찾을 수 있습니다.
일부 동적 프로그래밍 언어에서는 오류 값 과 예외 처리를 모두 사용할 수 있습니다 . 오류 값처럼 확인할 수있는 일반 반환 값 대신 throw되지 않은 예외 객체를 반환하여 수행 되지만 확인되지 않은 경우 예외가 발생합니다.
Perl 6 에서는을 통해 수행되며 fail
, no fatal;
범위를 포함하는 경우 특수 던지지 않은 예외 Failure
객체를 반환 합니다.
에 펄 5 당신이 사용할 수있는 콘텐츠를 :: 반환 당신이이 작업을 수행 할 수 있습니다 return FAIL
.
매우 구체적인 것이 없다면 유효성 검사를 위해 오류 변수를 갖는 것이 나쁜 생각이라고 생각합니다. 목적은 유효성 검사에 소요되는 시간을 절약하는 것입니다 (변수 값만 반환 할 수 있음)
그러나 무엇이든 변경하면 어쨌든 해당 값을 다시 계산해야합니다. 나는 멈추고 예외 던지기에 대해 더 말할 수 없다.
편집 : 나는 이것이 특정한 경우가 아니라 소프트웨어 패러다임의 문제라는 것을 깨닫지 못했습니다.
내 대답이 이해되는 특정 사례에 대한 요점을 더 명확하게 설명하겠습니다.
두 가지 종류의 오류가 있습니다.
서비스 계층에서는 Result 객체를 래퍼로 사용하는 것 외에는 오류 변수와 동등한 선택이 없습니다. http와 같은 프로토콜에서 서비스 호출을 통해 예외를 시뮬레이션하는 것은 가능하지만 확실히 좋은 일은 아닙니다. 나는 이런 종류의 오류에 대해 이야기하고 있지 않으며 이것이이 질문에서 묻는 종류의 오류라고 생각하지 않았습니다.
나는 두 번째 종류의 오류에 대해 생각하고있었습니다. 그리고 제 대답은이 두 번째 종류의 오류에 관한 것입니다. 실체 개체에는 선택의 여지가 있으며 그중 일부는
유효성 검사 변수를 사용하는 것은 각 엔터티 개체에 대해 단일 유효성 검사 방법을 사용하는 것과 같습니다. 특히, 사용자는 세터를 순전히 세터로 유지하거나 부작용이없는 방식으로 값을 설정하거나 (이것은 종종 좋은 방법 임) 각 세터에 유효성 검증을 통합 한 다음 결과를 유효성 검증 변수에 저장할 수 있습니다. 이것의 장점은 시간을 절약하고 유효성 검사 결과를 유효성 검사 변수에 캐시하여 사용자가 validation ()을 여러 번 호출 할 때 다중 유효성 검사를 수행 할 필요가 없다는 것입니다.
이 경우 가장 좋은 방법은 유효성 검사 오류를 캐시하기 위해 유효성 검사를 사용하지 않고도 단일 유효성 검사 방법을 사용하는 것입니다. 이것은 세터를 그냥 세터로 유지하는 데 도움이됩니다.
try
/catch
존재하는 것입니다. 또한, 당신은 넣을 수 있습니다try
/catch
(문제의 큰 분리 가능)을 처리하기위한보다 적절한 위치에 훨씬 더 위로 스택.