성공할 때 true / false 대 void를 반환하고 실패 할 때 예외를 던지는 함수


22

파일을 업로드하는 함수 인 API를 만들고 있습니다. 이 함수는 파일이 올바로 업로드 된 경우 아무 것도 반환하지 않고 문제가 발생하면 예외를 발생시킵니다.

왜 거짓이 아닌 예외입니까? 예외 내에서 실패 이유 (연결 없음, 파일 이름 누락, 잘못된 암호, 파일 설명 누락 등)를 지정할 수 있습니다. API 사용자가 모든 오류를 처리하는 데 도움이되는 열거 형을 사용하여 사용자 지정 예외를 만들고 싶었습니다.

이것이 좋은 습관입니까 아니면 객체를 반환하는 것이 더 좋습니까 (부울 내부, 선택적 오류 메시지 및 오류 열거)?



59
참과 거짓은 잘 어울립니다. 무효와 예외가 잘 어울립니다. 고양이와 세금처럼 진실과 예외가 함께합니다. 언젠가 나는 당신의 코드를 읽고있을 것입니다. 제발 이러지 마
candied_orange

4
나는 성공 또는 실패를 캡슐화하는 객체가 반환되는 패턴을 매우 좋아합니다. 예를 들어, Rust는 성공한 경우 결과 는 Result<T, E>어디에 T있고 E실패하면 오류입니다. 예외를 던지면 (Java에서는 확실하지 않을 수 있음) 호출 스택을 해제하는 것과 관련이 있기 때문에 비용이 많이 들지만 Exception 객체를 만드는 것이 저렴합니다. 분명히, 당신이 사용하는 언어의 기존 패턴을 고수하지만 부울과 예외를 결합하지 마십시오.
Dan Pantry

4
여기서 조심하십시오. 당신은 토끼 구멍 아래로 향하고 있습니다. 프로그램이 문제를 처리 할 수 ​​있으면 일반적으로 사전 조건을 확인하여 감지하고 처리하는 것이 더 쉽습니다. 예외를 거의 포착하지 않고 코드로 데이터를 검사 한 후 문제점을 수정 한 후 다시 시도하십시오. 따라서 예외 유형이 매우 많은 경우는 거의 없습니다. 문제 를 기록 하려는 경우 훨씬 더 의미가 있지만 상당한 양의 코드가 포함 된 여러 가지 catch 블록을 작성할 것으로 기대하지 마십시오.
jpmc26

4
@DanPantry Java에서는 예외를 던질 때가 아니라 예외를 작성할 때 호출 스택을 검사 합니다. fillInStackTrace();클래스의 슈퍼 생성자에서 호출됩니다Throwable
Simon Forsberg

답변:


35

예외를 던지는 것은 단순히 메소드가 값을 리턴하게하는 추가 방법입니다. 호출자는 예외를 포착하는 것만 큼 쉽게 반환 값을 확인하고 확인할 수 있습니다. 따라서 다른 기준을 결정 throw하고 return다른 기준을 요구합니다.

프로그램의 효율성을 위험에 빠뜨릴 경우 예외를 던지는 것을 피해야합니다 (예외 객체를 구성하고 호출 스택을 해제하는 것이 단순히 값을 푸시하는 것보다 컴퓨터에서 더 많은 작업). 그러나 방법의 목적이 파일을 업로드하는 것이라면 병목 현상은 항상 네트워크 및 파일 시스템 I / O가되므로 반환 방법을 최적화하는 것은 의미가 없습니다.

API 사용자의 기대에 위배되기 때문에 간단한 제어 흐름 (예 : 값을 찾아서 검색하는 경우)에 대한 예외를 발생시키는 것도 좋지 않습니다. 그러나 그 목적을 달성하지 못한 방법 이다 나는 예외를 throw하지 않는 이유를 볼 수 있도록 (또는 적어도 그것이 있어야) 예외적 인 경우. 그리고 그렇게하면 사용자 정의적이고 유익한 예외로 만들 수도 있습니다 (그러나 표준과 같은 일반적인 예외의 하위 클래스로 만드는 것이 좋습니다 IOException).


7
파일 업로드 요청이 실패 할 것으로 예상되는 결과라면 내 얼굴에 예외 (특히 메소드 서명에 반환 값이있는 경우)에 놀랄 것입니다.
hoffmale

1
표준 예외를 확장하는 이유는 많은 (아마도 대부분의) 호출자가 문제 를 해결 하기 위해 코드를 작성하지 않기 때문 입니다. 결과적으로, 대부분의 호출자는 명시 적으로 모두 나열하지 않고도 간단한 "이 큰 예외 그룹을 포착"할 수 있습니다.
jpmc26

4
@gbjbaanb 이것이 바로 고칠 수없는 상황이있을 때 예외를 사용해야하는 이유입니다. 파일을 열려고하는데 파일을 읽을 수없는 경우, 예외적 인 경우입니다. 파일 존재 여부를 확인할 때는 파일이 없을 수 있으므로 부울을 사용해야합니다.
Malcolm

21
킬리안은 재귀 적 슬로건에서 "예외는 예외적 인 상황에 대한 예외"라고 말하면서 예외적 인 상황은 그 기능이 목적을 달성 할 수 없을 때라고 말한다. 함수가 파일을 읽도록되어 있고 파일을 읽을 수없는 경우 예외 입니다. 함수가 파일이 존재하는지 여부를 알려 주어야하는 경우 (TOCTOU의 가능성을 염두에 두지 않음) 함수가 여전히 수행 할 수있는 기능을 수행 할 수 있기 때문에 기존 파일 이 예외가 아닙니다 . 함수가 파일이 존재 함을 주장 예상되는 경우는 존재하지 않는 것은 특별하다 그 무엇 "어설"의미이기 때문에.
Steve Jessop

10
파일의 존재 여부를 알려주는 함수와 파일이 존재한다고 주장하는 함수의 유일한 차이점은 파일이 존재하지 않는 경우가 예외인지 여부입니다. 사람들은 때때로 그것이 예외적인지 아닌지에 따라 오류 상태의 속성 인 것처럼 말합니다. 오류 조건의 속성이 아니며 오류 조건의 결합 된 속성과 발생하는 기능의 목적입니다. 그렇지 않으면 예외가 발생하지 않습니다.
Steve Jessop

31

실패로 true돌아 오지 않으면 성공 으로 돌아올 이유가 전혀 없습니다 false. 클라이언트 코드는 어떻게 보입니까?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

이 경우, 호출자 는 어쨌든 try-catch 블록이 필요하지만 더 잘 쓸 수 있습니다.

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

따라서 true반환 값은 호출자에게 완전히 영향을 미치지 않습니다. 따라서 방법을 유지하십시오 void.


일반적으로 실패 모드를 설계하는 세 가지 방법이 있습니다.

  1. 참 / 거짓 반환
  2. 사용 void, 던져 (체크) 예외
  3. 중간 결과 개체를 반환 합니다.

반품 true/false

이전의 대부분 c 스타일 API에서 사용됩니다. 단점은 명백하다. 당신은 무엇이 잘못되었는지 전혀 모른다. PHP는 이것을 자주 수행하여 다음과 같은 코드를 생성합니다.

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

멀티 스레드 컨텍스트에서는 더 나빠집니다.

예외 확인 (확인)

이것은 좋은 방법입니다. 어떤 시점에서는 실패를 예상 할 수 있습니다. Java는 소켓으로 이것을 수행합니다. 기본 가정은 호출이 성공해야하지만 모든 사람이 특정 작업이 실패 할 수 있다는 것을 알고 있습니다. 소켓 연결이 그중 하나입니다. 따라서 호출자는 실패를 처리해야합니다. 호출자가 실제로 실패를 처리하고 호출자에게 실패를 처리하는 우아한 방법을 제공하기 때문에 멋진 디자인입니다.

결과 객체 반환

이것은 이것을 처리하는 또 다른 좋은 방법입니다. 종종 구문 분석이나 유효성 검사가 필요한 것들에 사용됩니다.

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

발신자에게도 좋고 깔끔한 논리.

두 번째 경우를 사용할 때와 세 번째 경우를 사용할 때 약간의 논쟁이 있습니다. 어떤 사람들은 예외가되어야한다고 생각 뛰어난 당신이 마음에서 예외의 가능성을 설계하지 말아야하며, 거의 항상 세 번째 옵션을 사용하는 것으로합니다. 괜찮아 그러나 우리는 Java에서 예외를 확인했기 때문에 예외 를 사용 하지 않을 이유가 없습니다 . 소켓을 사용하는 것처럼 호출이 성공 해야 한다는 기본 가정이있을 때 확인 된 expetions를 사용하지만 실패가 가능하며 호출이 성공 해야하는지 확실하지 않은 경우 (데이터 유효성 검사와 같은) 세 번째 옵션을 사용합니다. 그러나 이것에 대해 다른 의견이 있습니다.

귀하의 경우에는 void+ 와 함께 갈 것입니다 Exception. 파일 업로드가 성공할 경우 예외가 발생합니다. 그러나 호출자는 해당 실패 모드를 처리해야하며 어떤 종류의 오류가 발생했는지 올바르게 설명하는 예외를 반환 할 수 있습니다.


5

이것은 실제로 실패가 예외적 이거나 예상 된 것인지 의지 합니다.

오류가 일반적으로 예상되는 것이 아닌 경우 API 사용자는 특별한 오류 처리없이 메소드를 호출 할 가능성이 높습니다. 예외를 던지면 스택을 눈에 띄는 곳으로 버블 링 할 수 있습니다.

다른 한편으로 오류를 일반화하는 경우, 당신은 그들을 확인하는 개발자를 위해 최적화해야하고, try-catch절은 좀 더 복잡보다 if/elif또는 switch시리즈.

API를 사용하는 사람의 마음에 항상 API를 설계하십시오.


1
제 경우에는 누군가 지적한 것처럼 API 사용자가 파일을 업로드 할 수없는 예외적 인 실패가되어야합니다.
Accollativo

4

반환하지 않으려는 경우 부울을 반환하지 마십시오 false. 실패시 void예외 ( IOException적절한)를 발생 시킬 방법 과 문서를 작성하십시오 .

그 이유는 부울을 반환하면 API 사용자가 다음과 같이 할 수 있다고 결론을 내릴 수 있기 때문입니다.

if (fileUpload(file) == false) {
    // Handle failure.
}

물론 작동하지 않습니다. 즉, 방법의 계약과 동작 사이에 불일치가 있습니다. 확인 된 예외를 throw하면 메소드 사용자가 실패를 처리해야합니다.

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}

3

메소드에 리턴 값이있는 경우 예외를 발생 시키면 사용자에게 놀라게 될 수 있습니다. 메서드가 부울을 반환하는 것을 볼 경우 성공하면 true를 반환하고 그렇지 않으면 false를 반환하고 if-else 절을 ​​사용하여 코드를 구조화 할 것이라고 확신합니다. 어쨌든 예외가 열거 형을 기반으로 할 경우 Windows HResults와 유사하게 대신 열거 형 값을 반환하고 메서드가 성공할 때 열거 형 값을 유지할 수 있습니다.

또한 프로 시저의 마지막 단계가 아닌 경우 메소드에서 예외가 발생하는 것은 성가신 일입니다. 캐치 블록에 try-catch 및 전환 제어 흐름을 작성해야하는 것은 스파게티를위한 좋은 레시피이므로 피해야합니다.

예외로 진행하는 경우 대신 void를 반환하십시오. 예외가 발생하지 않으면 사용자는 예외를 처리하고 if-else 절을 ​​사용하여 제어 흐름을 전환하려고 시도하지 않습니다.


1
열거 형은 API 사용자가 오류 메시지를 구문 분석하지 않고 다양한 유형의 오류를 처리하도록 돕는 아이디어 일뿐입니다. 따라서 귀하의 관점에서 열거 형을 반환하고 "OK"에 대한 열거 형을 추가하는 것이 좋습니다.
Accollativo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.