다른 사람들은 왜 일찍 던지는 지 꽤 잘 요약했습니다 . 대신 늦게 잡는 이유에 집중하도록하겠습니다 . 그 맛에 대해서는 만족스러운 설명을 보지 못했습니다.
예외는 무엇입니까?
왜 예외가 처음에 존재하는지에 대해서는 상당히 혼란스러워 보입니다. 예외의 이유와 예외 처리는 ... ABSTRACTION 입니다.
다음과 같은 코드를 보셨습니까?
static int divide(int dividend, int divisor) throws DivideByZeroException {
if (divisor == 0)
throw new DivideByZeroException(); // that's a checked exception indeed
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
try {
int res = divide(a, b);
System.out.println(res);
} catch (DivideByZeroException e) {
// checked exception... I'm forced to handle it!
System.out.println("Nah, can't divide by zero. Try again.");
}
}
그것은 예외가 사용되는 방식이 아닙니다. 위와 같은 코드는 실제로 존재하지만 수차에 가깝고 실제로 예외입니다. 예를 들어 순수한 수학에서도 나눗셈 의 정의 는 조건부입니다. 입력 도메인을 제한하기 위해 예외적으로 0을 처리해야하는 것은 항상 "호출자 코드"입니다. 못 생겼어 항상 발신자에게 고통입니다. 그럼에도 불구하고 그러한 상황에서 점검 후 패턴은 자연스럽게 갈 수있는 방법입니다.
static int divide(int dividend, int divisor) {
// throws unchecked ArithmeticException for 0 divisor
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
if (b != 0) {
int res = divide(a, b);
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
또는 다음과 같이 OOP 스타일에 대한 특공대를 취할 수 있습니다.
static class Division {
final int dividend;
final int divisor;
private Division(int dividend, int divisor) {
this.dividend = dividend;
this.divisor = divisor;
}
public boolean check() {
return divisor != 0;
}
public int eval() {
return dividend / divisor;
}
public static Division with(int dividend, int divisor) {
return new Division(dividend, divisor);
}
}
static void doDivide() {
int a = readInt();
int b = readInt();
Division d = Division.with(a, b);
if (d.check()) {
int res = d.eval();
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
보시다시피, 호출자 코드는 사전 점검의 부담이 있지만 이후 예외 처리는 수행하지 않습니다. 이 경우 ArithmeticException
지금 전화에서 오는 divide
또는 eval
다음, 그 것이다 당신은 당신이를 잊었 때문에, 예외 처리를하고 코드를 수정하는 사람 check()
. 비슷한 이유로 a를 잡는 NullPointerException
것은 거의 항상 잘못된 일입니다.
이제 메소드 / 함수 서명에서 예외적 인 경우를보고 싶다고 말하는 사람들이 있습니다. 즉, 명시 적으로 출력 도메인을 확장하려는 것 입니다. 그들은 예외 를 선호하는 사람들입니다 . 물론 출력 도메인을 변경하면 직접 호출자 코드를 강제로 적용해야하며 실제로는 예외를 확인해야합니다. 그러나 예외는 필요하지 않습니다! 그렇기 때문에 Nullable<T>
일반 클래스 , 사례 클래스 , 대수 데이터 형식 및 공용체 형식이 있습니다 . 일부 OO 사람들 은 다음과 같은 간단한 오류 사례 로 돌아 가기 null
를 선호 할 수도 있습니다 .
static Integer divide(int dividend, int divisor) {
if (divisor == 0) return null;
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
Integer res = divide(a, b);
if (res != null) {
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
기술적으로 예외 는 위와 같은 목적으로 사용될 수 있지만 여기에 요점이 있습니다 . 그러한 용도에는 예외가 없습니다 . 프로 추상화는 예외입니다. 간접적 인 예외는 예외입니다. 예외는 직접 클라이언트 계약 을 위반하지 않고 "결과"도메인을 확장 하고 오류 처리를 "어딘가에"지연 시키는 것을 허용합니다 . 코드 사이에 추상화 계층이없는 동일한 코드의 직접 호출자에서 처리되는 예외가 발생하면 잘못 하고 있습니다.
늦게 잡는 방법?
그래서 여기 있습니다. 위의 시나리오에서 예외를 사용하는 것이 예외가 사용되는 방식이 아니라는 것을 보여주기 위해 내 길을 주장했습니다. 그러나 예외 처리에 의해 제공되는 추상화 및 간접적 인 지시가 반드시 필요한 실제 사용 사례가 있습니다. 이러한 사용을 이해하는 것은 이해하는 데 도움이됩니다 캐치에게 말 도 추천.
유스 케이스는 다음과 같습니다. 자원 추상화에 대한 프로그래밍 ...
예, 비즈니스 로직은 구체적인 구현이 아닌 추상화 에 대해 프로그래밍 해야합니다 . 최상위 레벨 IOC "배선"코드는 자원 추상화의 구체적인 구현을 인스턴스화하여 비즈니스 로직으로 전달합니다. 여기에 새로운 것은 없습니다. 그러나 이러한 자원 추상화의 구체적인 구현으로 인해 구현 고유의 예외 가 발생할 수 있습니다.
그렇다면 누가 구현 관련 예외를 처리 할 수 있습니까? 비즈니스 로직에서 자원 별 예외를 처리 할 수 있습니까? 아뇨. 비즈니스 로직은 추상화에 대해 프로그래밍되며, 구현 특정 예외 세부 사항에 대한 지식은 제외됩니다.
"아하!"라고 말할 수 있습니다. "그렇기 때문에 예외를 서브 클래 싱하고 예외 계층을 만들 수 있습니다"( Spring !를 확인하십시오 ). 내가 말해 줄게, 그것은 잘못된 것입니다. 첫째, OOP에 대한 모든 합리적인 책은 구체적인 상속이 나쁘다고 말하지만 어떻게 든 JVM의 핵심 구성 요소 인 예외 처리는 구체적인 상속과 밀접한 관련이 있습니다. 아이러니하게도 Joshua Bloch 는 작동하는 JVM에 대한 경험을 얻기 전에 효과적인 Java 책 을 작성할 수 없었습니다. 이 책은 다음 세대를위한 "학습자가 배우는"책입니다. 둘째, 더 중요한 것은 예외적 인 예외를 잡으면 어떻게 처리 할 것인가?PatientNeedsImmediateAttentionException
: 그녀에게 약을 주거나 다리를 절단해야합니까!? 가능한 모든 서브 클래스에 대한 switch 문은 어떻습니까? 당신의 다형성이 있고, 추상화가 있습니다. 당신은 요점을 얻었다.
그렇다면 누가 리소스 별 예외를 처리 할 수 있습니까? 결석을 아는 사람이어야합니다! 자원을 인스턴스화 한 사람! 물론 "배선"코드! 이것 좀 봐:
추상화에 대해 코딩 된 비즈니스 로직 ... 정확한 리소스 오류 처리가 없습니다!
static interface InputResource {
String fetchData();
}
static interface OutputResource {
void writeData(String data);
}
static void doMyBusiness(InputResource in, OutputResource out, int times) {
for (int i = 0; i < times; i++) {
System.out.println("fetching data");
String data = in.fetchData();
System.out.println("outputting data");
out.writeData(data);
}
}
한편 다른 곳에서 구체적인 구현은 ...
static class ConstantInputResource implements InputResource {
@Override
public String fetchData() {
return "Hello World!";
}
}
static class FailingInputResourceException extends RuntimeException {
public FailingInputResourceException(String message) {
super(message);
}
}
static class FailingInputResource implements InputResource {
@Override
public String fetchData() {
throw new FailingInputResourceException("I am a complete failure!");
}
}
static class StandardOutputResource implements OutputResource {
@Override
public void writeData(String data) {
System.out.println("DATA: " + data);
}
}
그리고 마지막으로 배선 코드 ... 누가 구체적인 자원 예외를 처리합니까? 그들에 대해 아는 사람!
static void start() {
InputResource in1 = new FailingInputResource();
InputResource in2 = new ConstantInputResource();
OutputResource out = new StandardOutputResource();
try {
ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
}
catch (FailingInputResourceException e)
{
System.out.println(e.getMessage());
System.out.println("retrying...");
ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
}
}
이제 나와 함께하십시오. 위의 코드 는 단순합니다. 여러 범위의 IOC 컨테이너 관리 자원이있는 엔터프라이즈 애플리케이션 / 웹 컨테이너가 있고 세션 또는 요청 범위 자원 등의 자동 재시도 및 재 초기화가 필요하다고 말할 수 있습니다. 하위 레벨 범위의 배선 로직에는 정확한 구현을 알지 못하는 리소스를 만듭니다. 더 높은 수준의 스코프 만이 이러한 낮은 수준의 리소스가 어떤 예외를 throw 할 수 있는지 알 수 있습니다. 잠깐만!
불행하게도 예외는 호출 스택을 통한 간접적 인 허용 만 허용하며 서로 다른 카디널리티를 가진 다른 범위는 일반적으로 여러 다른 스레드에서 실행됩니다. 예외를 제외하고는 의사 소통 할 방법이 없습니다. 여기에 더 강력한 것이 필요합니다. 답변 : 비동기 메시지 전달 . 하위 수준 범위의 루트에서 모든 예외를 포착하십시오. 아무것도 무시하지 말고 미끄러지지 마십시오. 현재 범위의 호출 스택에 생성 된 모든 리소스가 닫히고 처리됩니다. 그런 다음 예외 처리 루틴에서 메시지 대기열 / 채널을 사용하여 Concretion이 알려진 수준에 도달 할 때까지 오류 메시지를 더 높은 범위로 전파하십시오. 그것을 처리하는 방법을 알고있는 사람입니다.
서머 서머 럼
그래서 내 해석에 따라 캐치 후반에 가장 편리한 장소에서 예외를 포착하는 것을 의미 더 이상 추상을 깨는되지 않습니다 . 너무 일찍 잡지 마십시오! 자원 추상화의 인스턴스를 발생시키는 구체적인 예외를 생성하는 계층에서 예외를 포착하십시오.이 계층은 추상화의 생성을 알고 있습니다. "배선"레이어.
HTH. 행복한 코딩!