여기에 예외를 던지는 것이 반 패턴입니까?


33

코드 검토 후 디자인 선택에 대해 토론했습니다. 나는 당신의 의견이 무엇인지 궁금합니다.

Preferences키-값 쌍을위한 버킷 인 이 클래스 가 있습니다 . null 값은 합법적입니다 (중요). 특정 값은 아직 저장되지 않을 것으로 예상되며 요청시 사전 정의 된 기본값으로 초기화하여 이러한 사례를 자동으로 처리하려고합니다.

논의 된 솔루션은 다음 패턴을 사용했습니다 (참고 : 이것은 실제 코드 가 아니며 , 분명히 설명을 위해 단순화되었습니다).

public class Preferences {
    // null values are legal
    private Map<String, String> valuesFromDatabase;

    private static Map<String, String> defaultValues;

    class KeyNotFoundException extends Exception {
    }

    public String getByKey(String key) {
        try {
            return getValueByKey(key);
        } catch (KeyNotFoundException e) {
            String defaultValue = defaultValues.get(key);
            valuesFromDatabase.put(key, defaultvalue);
            return defaultValue;
        }
    }

    private String getValueByKey(String key) throws KeyNotFoundException {
        if (valuesFromDatabase.containsKey(key)) {
            return valuesFromDatabase.get(key);
        } else {
            throw new KeyNotFoundException();
        }
    }
}

흐름을 제어하기 위해 패턴을 남용하지 않는 예외 로 비난 받았다 . KeyNotFoundException-하나의 유스 케이스에만 생명을 불어 넣었습니다.-이 클래스의 범위를 벗어나지 않습니다.

본질적으로 서로 가져 오기 위해 두 가지 방법으로 가져 오기를 수행합니다.

데이터베이스에 존재하지 않는 키는 놀라운 것이 아니며 예외적입니다. 새로운 환경 설정이 추가 될 때마다 발생할 것으로 예상되므로 필요한 경우 기본값으로 정상적으로 초기화하는 메커니즘입니다.

반론는 것이 었습니다 getValueByKey지금 정의에 대한 대중 방법을 알리는 자연적인 방법이 없습니다 - 민간 방법 - 값을, 여부 키가이었다. 그렇지 않은 경우 값을 업데이트 할 수 있도록 추가해야합니다.

반환 null은 모호 할 것입니다. 왜냐하면 null완벽하게 합법적 인 가치이기 때문에 키가 존재하지 않았는지 또는가 있는지를 알 수 없습니다 null.

getValueByKeyTuple<Boolean, String>키가 이미 있으면 bool을 true로 설정하여 일종의 a를 반환해야 하므로 (true, null)and를 구별 할 수 있습니다 (false, null). (과 out매개 변수는 C #으로 사용하지만 자바의 수).

이것이 더 좋은 대안입니까? 예,의 효과에 대해 일부 일회용 클래스를 정의해야 Tuple<Boolean, String>하지만, 우리는을 제거 KeyNotFoundException하여 그런 종류의 균형을 유지합니다. 우리는 또한 예외 처리의 오버 헤드를 피하고 있습니다. 실제적인 용어로는 중요하지는 않지만 성능에 대한 고려 사항은 없으며 클라이언트 응용 프로그램이며 사용자 기본 설정이 초당 수백만 번 검색되는 것과는 다릅니다.

이 접근 방식의 변형은 Optional<String>일부 사용자 지정 대신 구아바 (구아바는 이미 프로젝트 전체에서 사용됨)를 Tuple<Boolean, String>사용할 수 있으며 Optional.<String>absent(), "적절한"을 구분할 수 있습니다 null. 그래도 이해하기 쉬운 이유로 여전히 해킹을 느낍니다. 두 가지 수준의 "무효 성"을 도입 Optional하면 처음에는 s를 만드는 데 뒤쳐진 개념을 남용하는 것처럼 보입니다 .

다른 옵션은 키가 존재하는지 명시 적으로 확인하는 것입니다 ( boolean containsKey(String key)메소드를 추가하고 getValueByKey이미 존재한다고 주장한 경우에만 호출 하십시오).

마지막으로 개인 메서드를 인라인 할 수도 있지만 실제 getByKey는 내 코드 샘플보다 다소 복잡하므로 인라이닝하면 상당히 불쾌하게 보일 수 있습니다.

나는 머리카락을 여기에서 나눌 수 있지만,이 경우 모범 사례에 가장 가까운 것에 베팅 한 것이 궁금합니다. Oracle 또는 Google 스타일 가이드에서 답을 찾지 못했습니다.

코드 샘플에서와 같은 예외를 사용하는 것이 안티 패턴입니까, 아니면 대안이 매우 깨끗하지 않은 경우 허용됩니까? 그렇다면 어떤 상황에서 괜찮을까요? 그 반대?


1
@ GlenH7의 승인 된 (그리고 가장 높은 투표율) 답변은 "코드 혼잡을 줄이면서 오류 처리를보다 쉽게 ​​할 수있는 경우에는 [예외]를 사용해야합니다"라고 요약합니다. 여기에 나열된 대안이 "더 적은 코드 혼란"으로 간주되어야하는지 묻습니다.
Konrad Morawski

1
나는 VTC를 철회했다-당신은 관련이 있지만 내가 제안한 사본과 다른 것을 요구하고 있습니다.

3
getValueByKey공개 될 때 질문이 더 흥미로워 진다고 생각합니다 .
Doc Brown

2
나중에 참조 할 수 있도록 올바른 작업을 수행했습니다. 이것은 코드 예제이기 때문에 Code Review에서 다루지 않았을 것입니다.
RubberDuck

1
그냥 차임하기 --- 이것은 완벽하게 관용적 인 파이썬이며, 그 언어 로이 문제를 처리하는 데 선호되는 방법이라고 생각합니다. 그러나 언어마다 다른 규칙이 있습니다.
Patrick Collins

답변:


75

예, 동료가 옳습니다. 잘못된 코드입니다. 오류를 로컬에서 처리 할 수 ​​있으면 즉시 처리해야합니다. 예외가 발생하면 즉시 처리하면 안됩니다.

이것은 버전보다 훨씬 깨끗합니다 ( getValueByKey()방법이 제거되었습니다).

public String getByKey(String key) {
    if (valuesFromDatabase.containsKey(key)) {
        return valuesFromDatabase.get(key);
    } else {
        String defaultValue = defaultValues.get(key);
        valuesFromDatabase.put(key, defaultvalue);
        return defaultValue;
    }
}

오류를 로컬로 해결하는 방법을 모르는 경우에만 예외가 발생해야합니다.


14
답변 해주셔서 감사합니다. 기록을 위해, 나는이 동료입니다 (원래 코드가 마음에 들지 않았습니다), 나는 질문이 중립적으로 표현되어로드되지 않았습니다. :)
Konrad Morawski

59
광기의 첫 징후 : 자신과 대화하기 시작합니다.
Robert Harvey

1
@ BЈовић : 글쎄, 그 경우에는 괜찮다고 생각하지만 누락 된 키를 자동 생성하지 않고 값을 검색하는 한 가지 방법과 두 가지 변형이 필요한 경우는 어떻습니까? 그렇다면 가장 깨끗한 대안은 무엇이라고 생각하십니까?
Doc Brown

3
@RobertHarvey :) 다른 누군가가이 코드를 작성했습니다. 실제 별도의 사람입니다. 저는 리뷰어였습니다.
Konrad Morawski

1
@Alvaro, 예외를 자주 사용하는 것은 문제가되지 않습니다. 언어와 상관없이 예외를 잘못 사용하면 문제가됩니다.
kdbanman 2019

11

나는 예외 사용을 안티 패턴이라고 부르지 않고 복잡한 결과를 전달하는 문제에 대한 최상의 해결책은 아닙니다.

가장 좋은 해결책은 (여전히 Java 7을 사용한다고 가정 할 때) Guava의 Optional을 사용하는 것입니다. 이 경우에 사용하는 것이 해킹 일 것이라는 데 동의하지 않습니다. 이에 따라, 날 것으로 보인다 옵션의 구아바의 확장 된 설명 이 그것을 사용하는 경우의 완벽한 예입니다. "값을 찾을 수 없음"과 "값을 찾을 수 없음"을 구별하고 있습니다.


1
나는 Optionalnull을 유지할 수 있다고 생각하지 않습니다 . Optional.of()null이 아닌 참조 만 사용하고 Optional.fromNullable()null을 "value not present"로 처리합니다.
Navin

1
@Navin은 null을 유지할 수 없지만 마음대로 사용할 수 있습니다 Optional.absent(). 그리고, Optional.fromNullable(string)동일 될 것입니다 Optional.<String>absent()경우 string, 또는 null의 Optional.of(string)그것이 아니었다면.
Konrad Morawski

@ KononMorawski OP의 문제는 예외를 throw하지 않고 null 문자열과 존재하지 않는 문자열을 구별 할 수 없다는 것입니다. Optional.absent()이 시나리오 중 하나를 다룹니다. 다른 사람을 어떻게 대표합니까?
Navin

실제로 @Navin null.
Konrad Morawski

4
@ KonradMorawski : 그것은 정말 나쁜 생각처럼 들립니다. "이 메소드는 결코 리턴하지 않습니다 null!" 그것을 리턴으로 선언하는 것보다 Optional<...>. . .
ruakh

8

성능 고려 사항이없고 구현 세부 사항이므로 궁극적으로 어떤 솔루션을 선택하든 문제가되지 않습니다. 그러나 나는 그것이 나쁜 스타일이라는 것에 동의해야한다. 키 존재의 존재는 알고 뭔가 일이, 그리고 당신도 그것을 예외는 가장 유용한 곳이다 스택, 최대 두 개 이상의 통화를 처리하지 않습니다.

부울이 거짓이면 두 번째 필드는 의미가 없으므로 튜플 접근 방식은 약간 해키입니다. 지도가 키를 두 번 조회하기 때문에 키가 미리 존재하는지 확인하는 것은 어리석은 일입니다. (글쎄, 당신은 이미 그렇게하고 있으므로이 경우에는 3 번입니다.) Optional해결책은 문제에 완벽하게 맞습니다. 아이러니 조금이를 저장하기 위해 보이지 않는다 수 nullOptional있지만 사용자가하고 싶어 무엇을, 당신은 더 말할 수 없다.

의견에서 Mike가 언급했듯이 이에 문제가 있습니다. 구아바 나 자바 8 모두 Optional저장을 허용 하지 않습니다 null. 따라서 간단하지만 많은 양의 상용구가 포함 된 자체 롤링이 필요하므로 내부에서 한 번만 사용되는 무언가에 대해서는 너무 과도 할 수 있습니다. 지도 유형을로 변경할 수도 Map<String, Optional<String>>있지만 Optional<Optional<String>>s 처리 가 어색해집니다.

합리적인 타협은 예외를 유지하는 것이지만 "대체 수익"으로서의 역할을 인정합니다. 스택 추적이 비활성화 된 상태에서 새로 검사 된 예외를 생성하고 정적 변수에 보관할 수있는 미리 생성 된 인스턴스를 throw합니다. -이 저렴 가능성 로컬 브랜치로 저렴로 - 당신은 스택 추적의 부족은 문제가되지 않습니다, 그래서 그것을 처리하는 것을 잊지 수 있습니다.


1
글쎄, 실제로 열은 한 가지 유형이어야 Map<String, String>하기 때문에 데이터베이스 테이블 수준 에만 values있습니다. 그래도 각 키-값 쌍을 강력하게 입력하는 래퍼 DAO 계층이 있습니다. 그러나 명확성을 위해 이것을 생략했습니다. (물론 n 개의 열이있는 1 행 테이블을 가질 수 있지만 각각의 새로운 값을 추가하려면 테이블 스키마를 업데이트해야하므로 구문 분석과 관련된 복잡성을 제거하면 다른 복잡성을 도입해야합니다.)
Konrad Morawski

튜플에 대한 좋은 지적. 실제로, 무엇 (false, "foo")을 의미 하는가?
Konrad Morawski

적어도 Guava의 Optional을 사용하면 null을 넣으려고하면 예외가 발생합니다. Optional.absent ()를 사용해야합니다.
Mike Partridge

@MikePartridge 좋은 지적입니다. 추악한 해결 방법은지도를로 변경 Map<String, Optional<String>>한 다음로 끝나는 것입니다 Optional<Optional<String>>. 덜 혼란스러운 해결책은 허용 null하거나 허용하지 null않지만 3 가지 상태 (결석, null 또는 존재)를 갖는 유사한 대수 데이터 유형을 허용하는 옵션 (선택 사항)을 롤링 하는 것입니다. 내부적으로 만 사용되는 보일러 플레이트의 양은 많지만 구현하기 간단합니다.
Doval

2
"성능 고려 사항이없고 구현 세부 사항이므로 궁극적으로 어떤 솔루션을 선택하든 문제가되지 않습니다."-예외를 사용하여 C #을 사용하는 경우 "예외가 발생할 때 중단으로 디버깅하려고하는 사람을 성가 시게하는 것을 제외하고" "은 CLR 예외에 대해 설정되어 있습니다.
Stephen

5

이 Preferences 클래스는 키-값 쌍을위한 버킷입니다. null 값은 합법적입니다 (중요). 특정 값은 아직 저장되지 않을 것으로 예상되며 요청시 사전 정의 된 기본값으로 초기화하여 이러한 사례를 자동으로 처리하려고합니다.

문제는 정확히 이것입니다. 그러나 이미 솔루션을 직접 게시했습니다.

이 접근법의 변형은 일부 사용자 정의 Tuple 대신 Guava의 Optional (구아바는 이미 프로젝트 전체에서 사용됨) 을 사용할 수 있으며 Optional.absent ()와 "proper"null을 구별 할 수 있습니다 . 그럼에도 불구하고 이해하기 쉬운 이유로 여전히 해킹을 느낀다. 두 가지 수준의 "무효 성"을 도입하면 처음에는 옵션을 만드는 데 뒤쳐진 개념을 남용하는 것처럼 보인다.

그러나 사용하지 않는 null Optional . 사용할 수 있고 사용해야 Optional합니다. "hackish"느낌을 얻으려면 중첩 된 상태로 사용 Optional<Optional<String>>하면 데이터베이스에 키 (첫 번째 옵션 레이어)가있을 수 있고 미리 정의 된 값 (두 번째 옵션 레이어)이 포함될 수 있습니다.

이 방법은 예외를 사용하는 것보다 낫고 Optional새로운 것이 아닌 한 이해하기가 쉽습니다 .

또한 Optional편의 기능이 있으므로 모든 작업을 직접 수행 할 필요는 없습니다. 여기에는 다음이 포함됩니다.

  • static static <T> Optional<T> fromNullable(T nullableReference)데이터베이스 입력을 Optional유형 으로 변환
  • abstract T or(T defaultValue) 내부 옵션 레이어의 키 값을 얻거나 존재하지 않는 경우 기본 키 값을 얻습니다.

1
Optional<Optional<String>>그것은 어떤 매력을 가지고 있지만 내가 인정해야한다, 악을 찾습니다)
콘라드 Morawski

왜 그것이 악해 보이는지 더 설명 할 수 있습니까? 실제로와 같은 패턴 List<List<String>>입니다. 물론 (언어가 허용하는 경우) DBConfigKey랩핑 과 같은 새로운 유형 을 만들고 Optional<Optional<String>>원하는 경우 더 읽기 쉽게 만들 수 있습니다.
valenterry

2
@valenterry 매우 다릅니다. 목록의 목록은 어떤 종류의 데이터를 저장하는 합법적 인 방법입니다. 옵션의 옵션은 데이터가 가질 수있는 두 가지 종류의 문제를 나타내는 비정형적인 방법입니다.
Ypnypn

옵션의 옵션은 각 목록에 하나의 요소가 있거나없는 목록의 목록과 같습니다. 본질적으로 동일합니다. Java에서 자주 사용 되지 않는다고해서 본질적으로 나쁘다는 의미는 아닙니다.
valenterry

1
Jon Skeet이 의미하는 @valenterry 악; 그렇게 나쁘지는 않지만 약간 악한 "영리한 코드"입니다. 나는 그것이 단지 바로크 양식, 옵션의 옵션이라고 생각합니다. 어떤 수준의 선택성이 무엇을 나타내는지는 명확하지 않습니다. 누군가의 코드에서 발견되면 두 번 걸릴 것입니다. 특이하고 단조롭 기 때문에 이상하게 느껴질 수도 있습니다. 아마도 기능적 언어 프로그래머에게는 모나드와 모두를 가진 것은 멋진 일이 아닙니다. 직접
하지는 않겠지 만, 흥미로워 서

5

나는 파티에 늦었다는 것을 알고 있지만 어쨌든 사용 사례는 JavaProperties 가 기본 속성 세트를 정의하는 방법과 비슷 하며 인스턴스에 의해로드 된 해당 키가 없으면 확인됩니다.

Properties.getProperty(String)(Java 7에서) 구현 방법을 살펴보십시오 .

Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;

인용 한 것처럼 "흐름을 제어하기 위해 예외를 남용"할 필요는 없습니다.

@ BЈовић의 대답에 대한 비슷하지만 약간 더 간결한 접근법은 다음과 같습니다.

public String getByKey(String key) {
    if (!valuesFromDatabase.containsKey(key)) {
        valuesFromDatabase.put(key, defaultValues.get(key));
    }
    return valuesFromDatabase.get(key);
}

4

@ BЈовић의 대답은 getValueByKey다른 곳에서는 필요하지 않다고 생각하지만 프로그램에 두 가지 사용 사례가 모두 포함되어 있으면 솔루션이 나쁘지 않다고 생각합니다.

  • 키가 미리 존재하지 않는 경우 자동 생성을 사용하여 키로 검색

  • 데이터베이스, 리포지토리 또는 키 맵에서 아무것도 변경하지 않고 자동화없이 검색 ( getValueByKey비공개가 아닌 공개 공개 생각 )

이것이 귀하의 상황이고 성능 저하가 허용되는 한 제안 된 솔루션이 완전히 정상이라고 생각합니다. 검색 코드의 중복을 피할 수 있다는 이점이 있으며 타사 프레임 워크에 의존하지 않으며 (적어도 내 눈에는) 매우 간단합니다.

실제로, 그러한 상황에서, 누락 된 키가 "예외적 인"상황인지 아닌지에 따라 상황에 의존한다. 그것이 존재하는 상황에서, 같은 getValueByKey것이 필요합니다. 자동 키 생성이 필요한 상황에서 이미 사용 가능한 기능을 재사용하는 방법을 제공하고 예외를 삼키고 다른 실패 동작을 제공하는 것은 완벽하게 의미가 있습니다. 이것은 getValueByKey"제어 흐름에 대한 예외가 남용"되는 기능이 아니라 확장 또는 "장식 자"로 해석 될 수 있습니다 .

물론, 세 번째 대안이 다음을 반환하는 세 번째, 개인 방법을 만들 Tuple<Boolean, String>당신이 제안, 그리고 모두이 방법을 다시 사용 getValueByKey하고 getByKey. 실제로 더 나은 대안이 될 수있는보다 복잡한 경우의 경우 여기에 표시된 것과 같은 간단한 경우에는 IMHO가 과도한 엔지니어링 냄새가 나는데 코드가 실제로 유지 관리가 더 잘되는 것은 의심 스럽습니다. 나는 여기입니다 칼 Bielefeldt에 의해 여기 맨 위의 대답 :

"코드를 단순화 할 때 예외를 사용하는 것에 대해 나쁘게 생각해서는 안됩니다."


3

Optional올바른 해결책입니다. 그러나 원하는 경우 "두 개의 null"느낌이 적은 대안이 있습니다. 센티넬을 고려하십시오.

keyNotFoundSentinel값 으로 개인 정적 문자열을 정의하십시오 new String(""). *

이제는 개인 방법이 return keyNotFoundSentinel아니라 throw new KeyNotFoundException(). 공용 메소드는 해당 값을 확인할 수 있습니다

String rval = getValueByKey(key);
if (rval == keyNotFoundSentinel) { // reference equality, not string.equals
    ... // generate default value
} else {
    return rval;
}

실제로 null는 센티넬의 특별한 경우입니다. 이 언어는 언어에 의해 정의 된 센티넬 값이며 몇 가지 특수한 동작이 있습니다 (예 : on 메소드를 호출하면 올바르게 정의 된 동작 null) 거의 모든 언어가 그것을 사용하는 것과 같은 센티넬을 갖는 것이 매우 유용합니다.

* 자바 new String("")가 단순히 ""문자열을 "인터 링"하는 것을 막기 보다는 의도적으로 사용하는데 , 이는 다른 빈 문자열과 동일한 참조를 제공합니다. 이 추가 단계를 수행 했으므로에 의해 참조되는 문자열 keyNotFoundSentinel이 고유 한 인스턴스임을 보장하므로 맵 자체에 절대로 나타나지 않아야합니다.


이것이 IMHO입니다.
fgp

2

모든 Java의 어려움에서 배운 프레임 워크에서 배우십시오.

.NET은이 문제에 대해 다음과 같은 두 가지 더 훌륭한 솔루션을 제공합니다.

후자는 Java로 작성하기가 매우 쉽고 전자는 "강력한 참조"헬퍼 클래스 만 필요합니다.


Dictionary패턴 의 공분산 친화적 인 대안 은 다음과 같습니다 T TryGetValue(TKey, out bool).
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.