대체를위한 중첩 된 try-catch에 대한 대안


14

객체를 검색하려고하는 상황이 있습니다. 조회가 실패하면 몇 가지 폴 백이 있으며 각각 실패 할 수 있습니다. 따라서 코드는 다음과 같습니다.

try {
    return repository.getElement(x);
} catch (NotFoundException e) {
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        try {
            return repository.getParentElement(x);
        } catch (NotFoundException e2) {
            //can't recover
            throw new IllegalArgumentException(e);
        }
    }
}

이것은 끔찍하게 보인다. 나는 null을 반환하는 것을 싫어하지만이 상황에서 더 좋습니까?

Element e = return repository.getElement(x);
if (e == null) {
    e = repository.getSimilarElement(x);
}
if (e == null) {
    e = repository.getParentElement(x);
}
if (e == null) {
    throw new IllegalArgumentException();
}
return e;

다른 대안이 있습니까?

중첩 된 try-catch 블록을 사용하는 것이 안티 패턴입니까? 관련이 있지만 "때때로 피하는 방법"을 말하지 않고 "때때로 피할 수 있습니다"라는 문구가 있습니다.


1
는 IS NotFoundException실제로 특별한 무언가는?

잘 모르겠습니다. 아마 문제가있는 것 같습니다. 전자 상거래 환경에서 제품이 매일 중단됩니다. 누군가가 이후에 단종 된 제품을 북마크 한 다음 북마크를 열려고하면 ... 그 예외입니까?
Alex Wittig

내 의견으로는 @FiveNine, 확실히 아니오-그것은 예상됩니다. 참조 stackoverflow.com/questions/729379/...
콘라드 Morawski

답변:


17

중첩을 제거하는 일반적인 방법은 함수를 사용하는 것입니다.

Element getElement(x) {
    try {
        return repository.getElement(x);
    } catch (NotFoundException e) {
        return fallbackToSimilar(x);
    }  
}

Element fallbackToSimilar(x) {
    try {
        return repository.getSimilarElement(x);
     } catch (NotFoundException e1) {
        return fallbackToParent(x);
     }
}

Element fallbackToParent(x) {
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        throw new IllegalArgumentException(e);
    }
}

이러한 대체 규칙이 보편적 인 경우 예외 대신 repository일반 if문만 사용할 수 있는 객체 에서 직접 구현하는 것을 고려할 수 있습니다 .


1
이와 관련 method하여보다 나은 단어가 될 것 function입니다.
Sulthan

12

이것은 옵션 모나드와 같은 것이 정말 쉽습니다. 불행히도 Java에는 그러한 것이 없습니다. Scala에서는이 Try유형 을 사용 하여 첫 번째 성공적인 솔루션을 찾습니다.

함수형 프로그래밍 사고 방식에서 다양한 가능한 소스를 나타내는 콜백 목록을 설정하고 첫 번째 성공적인 소스를 찾을 때까지 루프백합니다.

interface ElementSource {
    public Element get();
}

...

final repository = ...;

// this could be simplified a lot using Java 8's lambdas
List<ElementSource> sources = Arrays.asList(
    new ElementSource() {
        @Override
        public Element get() { return repository.getElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getSimilarElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getParentElement(); }
    }
);

Throwable exception = new NoSuchElementException("no sources set up");
for (ElementSource source : sources) {
    try {
        return source.get();
    } catch (NotFoundException e) {
        exception = e;
    }
}
// we end up here if we didn't already return
// so throw the last exception
throw exception;

실제로 소스가 많거나 런타임에 소스를 구성해야하는 경우에만 권장 할 수 있습니다. 그렇지 않으면, 이것은 불필요한 추상화이며 코드를 단순하고 어리석게 유지하는 것이 더 이익이되며, 그 미묘한 중첩 된 try-catch를 사용하면됩니다.


Try스칼라 에서 유형을 언급하고 모나드를 언급하고 루프를 사용하는 솔루션에 +1합니다 .
Giorgio

Java 8을 이미 사용하고 있다면 이것으로 갈 것입니다.하지만 당신이 말했듯이, 몇 가지 단점이 있습니다.
Alex Wittig

1
실제로이 답변이 게시 될 때 Optional모나드 ( proof ) 를 지원하는 Java 8 이 이미 릴리스되었습니다.
mkalkov

3

이러한 많은 리포지토리 호출이 NotFoundException발생할 것으로 예상되는 경우 리포지토리 주위에 래퍼를 사용하여 코드를 간소화 할 수 있습니다. 정상적인 작업에는 권장하지 않습니다.

public class TolerantRepository implements SomeKindOfRepositoryInterfaceHopefully {

    private Repository repo;

    public TolerantRepository( Repository r ) {
        this.repo = r;
    }

    public SomeType getElement( SomeType x ) {
        try {
            return this.repo.getElement(x);
        }
        catch (NotFoundException e) {
            /* For example */
            return null;
        }
    }

    // and the same for other methods...

}

3

@amon의 제안에서 더 모나 딕 한 답변이 있습니다. 그것은 매우 삶은 버전이며 몇 가지 가정을 받아 들여야합니다.

  • "unit"또는 "return"함수는 클래스 생성자입니다.

  • "바인드"작업은 컴파일시 발생하므로 호출에서 숨겨집니다.

  • "action"함수는 컴파일 타임에 클래스에 바인딩됩니다

  • 클래스는 일반적이며 임의의 클래스 E를 래핑하지만 실제로이 경우 과잉이라고 생각합니다. 그러나 나는 당신이 할 수있는 일의 예로서 그 길을 떠났습니다.

이러한 고려 사항으로 모나드는 유창한 래퍼 클래스로 변환됩니다 (순전히 기능적인 언어로 얻을 수있는 많은 유연성을 포기하더라도).

public class RepositoryLookup<E> {
    private String source;
    private E answer;
    private Exception exception;

    public RepositoryLookup<E>(String source) {
        this.source = source;
    }

    public RepositoryLookup<E> fetchElement() {
        if (answer != null) return this;
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookup(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchSimilarElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupVariation(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchParentElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupParent(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public boolean failed() {
        return exception != null;
    }

    public Exception getException() {
        return exception;
    }

    public E getAnswer() {
        // better to check failed() explicitly ;)
        if (this.exception != null) {
            throw new IllegalArgumentException(exception);
        }
        // TODO: add a null check here?
        return answer;
    }
}

(이것은 컴파일되지 않습니다 ... 샘플을 작게 유지하기 위해 특정 세부 사항이 완료되지 않은 상태로 남아 있습니다)

그리고 호출은 다음과 같습니다.

Repository<String> repository = new Repository<String>(x);
repository.fetchElement().orFetchParentElement().orFetchSimilarElement();

if (repository.failed()) {
    throw new IllegalArgumentException(repository.getException());
}

System.err.println("Got " + repository.getAnswer());

"가져 오기"작업을 원하는대로 구성 할 수있는 유연성이 있습니다. 찾을 수없는 것 이외의 답변이나 예외를 받으면 중지됩니다.

나는 이것을 정말로 빨리했다; 그것은 옳지 않지만 희망적으로 아이디어를 전달합니다.


1
repository.fetchElement().fetchParentElement().fetchSimilarElement();-내 의견으로는 : 악한 코드 (존
스케이트가

어떤 사람들은 그 스타일을 좋아하지 않지만 return this체인 객체 호출을 만드는 데 오랫동안 사용되어 왔습니다. OO는 변경 가능한 객체를 포함하므로 체인 return thisreturn null없는 것과 거의 동일합니다 . 그러나이 return new Thing<E>예제에 포함되지 않은 다른 기능의 문을 열어 주므로이 경로를 따라 가려면이 패턴이 중요합니다.
Rob

1
그러나 나는 그 스타일을 좋아하고 체인 호출이나 유창한 인터페이스에 반대하지 않습니다. 그러나이 CustomerBuilder.withName("Steve").withID(403)코드와 의 차이점은 .fetchElement().fetchParentElement().fetchSimilarElement()어떤 일이 발생하는지 명확하지 않기 때문에 여기에서 핵심 사항입니다. 그들은 모두 가져옵니다? 이 경우에는 누적되지 않으므로 직관적이지 않습니다. 나는 if (answer != null) return this그것을 정말로 얻기 전에 그것을 볼 필요가 있다. 아마도 적절한 이름 지정 ( orFetchParent) 문제 일 수도 있지만 어쨌든 "마법"입니다.
Konrad Morawski

1
그건 그렇고 (코드가 지나치게 단순화되고 개념 증명이라는 것을 알고 있습니다), 값을 반환하기 전에 answerin 의 복제본을 반환 getAnswer하고 answer필드 자체를 재설정 (삭제)하는 것이 좋습니다. 그렇지 않으면 요소를 가져 오도록 요청 (쿼리)하면 리포지토리 개체의 상태가 변경되고 ( answer재설정되지 않음) fetchElement다음에 호출 할 때 의 동작에 영향을주기 때문에 일종의 명령 / 쿼리 분리 원칙을 위반합니다 . 예, 조금 따끔 거립니다. 답이 유효하다고 생각합니다.
Konrad Morawski

1
그건 좋은 지적이야. 또 다른 방법은 "attemptToFetch ..."입니다. 중요한 점은이 경우 3 개의 메소드가 모두 호출되지만 다른 경우에는 클라이언트가 "attemptFetch (). attemptFetchParent ()"를 사용할 수 있다는 것입니다. 또한 "리포지토리"라고 부르는 것은 실제로 단일 페치를 모델링하기 때문에 잘못되었습니다. 어쩌면 "RepositoryLookup"과 "시도"라고 부르는 이름으로 소란스러워서 이것이 조회 주위에 특정 의미를 제공하는 일회성, 일시적인 인공물이라는 것을 분명히합니다.
Rob

2

이와 같은 일련의 조건을 구성하는 또 다른 방법은 조건을 함께 연결하기 위해 플래그를 운반하거나 null을 테스트하는 것입니다 (아직 더 좋은 대답이 있는지 결정하기 위해 Guava의 Optional을 사용하십시오).

Element e = null;

try {
    e = repository.getElement(x);
} catch (NotFoundException e) {
    // nope -- try again!
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    //can't recover
    throw new IllegalArgumentException(e);
}

return e;

그렇게하면 요소의 상태를보고, 상태에 따라, 즉 아직 답변이없는 한 올바른 전화를 걸 수 있습니다.

(하지만 @amon에 동의합니다. class Repository<E>멤버 E answer;와와 같은 래퍼 객체를 사용하여 Monad 패턴을 보는 것이 좋습니다 Exception error;. 각 단계에서 예외가 있는지 확인하고 필요한 경우 나머지 각 단계를 건너 뜁니다. 결국, 당신은 대답, 답이 없거나 예외로 남습니다. 그러면 어떻게 할 것인지 결정할 수 있습니다.)


-2

먼저, 다음과 같은 기능이 있어야합니다. repository.getMostSimilar(x) 주어진 요소에 가장 근접하거나 가장 유사한 요소를 찾는 데 사용되는 논리가있는 것처럼 (더 적절한 이름을 선택해야합니다) 이 있어야합니다.

그런 다음 저장소는 amons post에 표시된 것과 같은 논리를 구현할 수 있습니다. 즉, 예외가 발생해야하는 유일한 경우는 찾을 수있는 단일 요소가없는 경우입니다.

그러나 가장 가까운 요소를 찾는 논리를 저장소에 캡슐화 할 수있는 경우에만 가능합니다. 이것이 가능하지 않은 경우 가장 가까운 요소를 선택하는 방법 (기준에 따라)에 대한 자세한 정보를 제공하십시오.


대답은 명확성을 요구하지 않고 질문에 대답하는 것입니다
gnat

글쎄, 내 대답은 특정 조건에서 중첩 된 시도 / 캐치를 피하는 방법을 보여주기 때문에 그의 문제를 해결하는 것입니다. 이러한 조건이 충족되지 않는 경우에만 추가 정보가 필요합니다. 이것이 올바른 답변이 아닌 이유는 무엇입니까?
valenterry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.