객체의 상태가 유효하지 않은 경우 getter에서 예외가 발생해야합니까?


55

일반적인 OOP 문제라고 생각하더라도 특히 Java 에서이 문제가 자주 발생합니다. 즉, 예외를 발생 시키면 디자인 문제가 드러납니다.

String name필드와 필드 가있는 클래스가 있다고 가정하십시오 String surname.

그런 다음 해당 필드를 사용하여 인보이스와 같은 일종의 문서에 표시하기 위해 사람의 완전한 이름을 작성합니다.

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

이제 displayCompleteNameOnInvoice이름을 지정하기 전에 호출 하면 오류가 발생하여 클래스의 동작을 강화하고 싶습니다 . 좋은 생각 같지 않습니까?

getCompleteName메소드에 예외 발생 코드를 추가 할 수 있습니다 . 그러나 이런 식으로 나는 클래스 사용자와의 '암시 적'계약을 위반하고 있습니다. 일반적으로 게터는 값을 설정하지 않으면 예외를 throw하지 않습니다. 좋아, 이것은 단일 필드를 반환하지 않기 때문에 표준 게터가 아니지만 사용자 관점에서 구별하기가 너무 미묘 할 수 있습니다.

또는 에서 내부에서 예외를 던질 수 있습니다 displayCompleteNameOnInvoice. 그러나 그렇게하려면 직접 name또는 surname필드를 테스트해야하며로 표현하는 추상화를 위반합니다 getCompleteName. 완전한 이름을 확인하고 작성하는 것은이 방법의 책임입니다. 다른 데이터를 기반으로 한 결정으로 일부 경우에 충분하다고 결정할 수도 surname있습니다.

그래서 유일한 가능성이 방법의 의미를 변경하는 것 getCompleteName까지 composeCompleteName, 그것보다 '적극적인'행동과, 예외를 던지는 능력이 있음을 의미합니다.

이것이 더 나은 디자인 솔루션입니까? 나는 항상 단순성과 정확성 사이의 최상의 균형을 찾고 있습니다. 이 문제에 대한 디자인 참조가 있습니까?


8
"일반적으로 게터는 값을 설정하지 않으면 예외를 throw하지 않습니다." -그럼 어떻게해야합니까?
Rotem

2
@scriptin 그런 논리를 따르면, return을 반환 displayCompleteNameOnInvoice하면 예외를 던질 수 있습니까? getCompleteNamenull
Rotem

2
@Rotem 그것은 할 수 있습니다. 문제는 그것이 필요한지에 관한 것입니다.
scriptin

22
주제에 대해 거짓 프로그래머이름대해 믿는 것은 왜 '이름'과 '성'이 매우 좁은 개념인지에 대해 잘 읽습니다.
Lars Viklund

9
객체의 상태가 유효하지 않은 경우 이미 문제가 해결 된 것입니다.
user253751

답변:


151

이름을 지정하지 않고 수업을 구성하지 마십시오.


9
흥미롭지 만 근본적인 해결책입니다. 많은 경우 클래스가 더 유동적이어서 특정 메소드가 호출되는 경우 에만 문제가되는 상태를 허용해야합니다. 그렇지 않으면 사용하기에는 너무 많은 설명이 필요한 작은 클래스가 확산 될 수 있습니다.
AgostinoX

71
이것은 주석이 아닙니다. 그가 답변에 조언을 적용하면 더 이상 문제가 설명되지 않습니다. 답입니다.
DeadMG

14
@AgostinoX : 반드시 필요한 경우에만 수업을 유동적으로 만들어야합니다. 그렇지 않으면 가능한 한 변하지 않아야합니다.
DeadMG

25
@ gnat : 모든 질문의 질과 PSE에 대한 모든 대답에 대해 의문을 제기하지만 때로는 IMHO가 너무 어색합니다.
Doc Brown

9
그들이 선택적으로 유효 할 수 있다면, 그가 처음에 던질 이유가 없습니다.
DeadMG

75

DeadMG가 제공 한 답변은 거의 손톱을 깎지 만 조금 다르게 표현 해 보겠습니다. 상태가 잘못된 객체는 피하십시오. 개체는 수행하는 작업의 맥락에서 "전체"이어야합니다. 또는 존재하지 않아야합니다.

유동성을 원한다면 Builder Pattern 과 같은 것을 사용하십시오 . 또는 "실제"와 대조적으로 구성중인 개체를 개별적으로 구체화하는 다른 것 getCompleteName.


7
So if you want fluidity, use something like the builder pattern-유동성 또는 불변성 (어떻든 의미하지 않는 한). 변경할 수없는 객체를 만들고 싶지만 일부 값 설정을 연기 해야하는 경우 따라야 할 패턴은 빌더 객체에서 하나씩 설정하고 수정에 필요한 모든 값을 수집 한 후에 만 ​​변경할 수없는 패턴을 만드는 것입니다 주
Konrad Morawski

1
@ KonradMorawski : 혼란 스러워요. 내 진술을 명확하게하거나 모순합니까? ;)
back2dos

3
나는 그것을 보완하려고 노력했습니다 ...
Konrad Morawski

40

상태가 잘못된 개체가 없습니다.

생성자 또는 빌더의 목적은 오브젝트의 상태를 일관되고 사용 가능한 것으로 설정하는 것입니다. 이 보증이 없으면 개체의 상태가 잘못 초기화되어 문제가 발생합니다. 이 문제는 동시성을 처리하고 개체 설정을 완료하기 전에 개체에 액세스 할 수있는 경우 더욱 복잡해집니다.

따라서이 부분에 대한 질문은 "이름이나 성을 null로 허용하는 이유는 무엇입니까?"입니다. 그것이 제대로 작동하기 위해 클래스에서 유효한 것이 아닌 경우 허용하지 마십시오. 객체의 생성을 올바르게 검증하는 생성자 또는 작성기를 보유하고 옳지 않은 경우 해당 시점에서 문제를 제기하십시오. 존재 하는 @NotNull주석 중 하나를 사용하여 "널이 될 수 없음"을 알리고 코딩 및 분석에 적용 할 수 있습니다.

이러한 보장을 통해 코드에 대해 추론하고 코드의 기능을 쉽게 파악할 수 있으며 홀수 위치에서 예외를 발생 시키거나 getter 함수를 과도하게 검사 할 필요가 없습니다.

더 많은 게터.

이 주제에 대해서는 약간의 내용이 있습니다. 당신이있어 게터에 얼마나 많은 논리어떻게 getter 및 setter 내에서 허용되어야 하는가? 여기 에서 스택 오버플로에서 추가 논리수행하는 게터 및 세터 . 이것은 클래스 디자인에서 계속해서 발생하는 문제입니다.

이것의 핵심은 다음과 같습니다.

일반적으로 게터는 값을 설정하지 않으면 예외를 throw하지 않아야합니다.

그리고 당신 말이 맞아요 그들은해서는 안됩니다. 그들은 세계의 다른 사람들에게 '멍청한'모습을 보여야하고 예상되는 일을해야합니다. 거기에 너무 많은 논리를 넣으면 가장 놀랍게도 법을 위반하는 문제가 발생합니다. 그리고 try catch우리가 일반적으로하는 것을 얼마나 많이 알고 있기 때문에 게터를 감싸고 싶지는 않습니다 .

거기 상황 또한 있어야 용도 getFoo등의 자바 빈즈 프레임 워크로 방법은 당신이 뭔가를 할 때 EL 있도록 (빈을 얻을 것으로 예상 호출 <% bar.foo %>실제로 호출 getFoo()클래스에 - (가) '빈이 구성을 수행하거나해야한다 따로 설정 어느 쪽이든 다른 쪽이 분명히 정답이 될 수있는 상황을 쉽게 생각 해낼 수 있기 때문입니다.)

또한 주어진 getter가 인터페이스에 지정되거나 리팩토링되는 클래스에 대해 이전에 공개 된 공개 API의 일부일 수 있음을 인식하십시오 (이전 버전에는 'completeName'이 리턴되었고 리팩토링이 중단되었습니다) 두 필드로).

하루의 끝에서...

추론하기 가장 쉬운 것을하십시오. 이 코드를 설계하는 것보다이 코드를 유지하는 데 더 많은 시간을 할애 할 것입니다 (디자인에 소요되는 시간이 적을수록 유지 관리에 더 많은 시간을 할애합니다). 이상적인 선택은 유지하기 위해 많은 시간이 소요되지 않는 방법으로 그것을 설계,하지만 일 동안 그것에 대해 생각하고 거기에 앉아하지 않는 것입니다 중 하나 -이 정말 디자인 선택하지 않는 것입니다 나중에 의미의 일을 가지고는 .

게터의 의미 적 순결을 고수하려고하면 private T foo; T getFoo() { return foo; }어떤 시점에서 문제 가 생길 수 있습니다. 때로는 코드가 해당 모델에 맞지 않고 그 방식으로 유지하려고 시도하는 변형이 의미가 없습니다 ... 결국 디자인하기가 더 어려워집니다.

코드에서 원하는 방식으로 디자인의 이상을 실현할 수없는 경우가 있습니다. 지침은 족쇄가 아닌 지침입니다.


2
분명히 내 예제는 블로킹 문제가 아니라 코드 형식을 추론하여 코딩 스타일을 향상시키는 변명 일뿐입니다. 그러나 나는 당신과 다른 사람들이 생성자에게주는 것처럼 보이는 중요성에 상당히 당황합니다. 이론적으로 그들은 약속을 지킵니다. 실제로, 클래스에서 인터페이스를 추출 할 때 사용할 수없는 상속을 통해 다루기가 번거로워집니다. '클래스'아이디어의 우산 아래에는 다양한 범주의 객체가 있습니다. 값 객체에는 유효성 검사 생성자가있을 수 있지만 이것은 한 가지 유형 일뿐입니다.
AgostinoX

2
코드를 추론 할 수있는 것은 API를 사용하여 코드를 작성하거나 코드를 유지 관리하는 데 중요합니다. 클래스는 그것이 만드는 잘못된 상태에있을 수있는 생성자가있는 경우 훨씬 통해 생각하기 어려워 "음, 이것은 무엇을 하는가를?" 왜냐하면 지금 생각하거나 의도 한 클래스의 디자이너보다 더 많은 상황을 고려해야하기 때문입니다. 이 추가 개념로드는 더 많은 버그와 코드 작성 시간이 더 길어집니다. 반면에 코드를보고 "이름과 성은 null이되지 않을 것"이라고 말하면 코드를 사용하여 코드를 작성하는 것이 훨씬 쉬워집니다.

2
불완전하다고해서 반드시 유효하지 않은 것은 아닙니다. 영수증이없는 인보이스가 진행 중일 수 있으며,이를 제공하는 공개 API는 유효한 상태임을 반영합니다. 경우 surname또는 name널 되 개체에 유효한 상태이고, 그에 따라이를 처리한다. 그러나 유효한 상태가 아닌 경우 클래스 내의 메서드에서 예외가 발생하면 초기화 된 시점부터 해당 상태에 있지 않아야합니다. 불완전한 객체를 전달할 수 있지만 잘못된 객체를 전달하는 것은 문제가 있습니다. null 성이 예외적 인 경우 나중에 성을 막으십시오.

2
관련 블로그 게시물 : 객체 초기화 디자인 및 인스턴스 변수 초기화를위한 4 가지 접근 방법에 주목하십시오. 그중 하나는 예외가 발생하지만 유효하지 않은 상태에있게하는 것입니다. 이것을 피하려고한다면, 그 접근 방식은 유효하지 않으며 다른 방법으로 작업해야합니다. 항상 유효한 객체를 보장해야합니다. 블로그에서 : "유효하지 않은 상태의 오브젝트는 항상 유효한 오브젝트보다 사용하기 어렵고 이해하기가 어렵습니다." 그리고 그것이 핵심입니다.

5
"추론하기 가장 쉬운 일을하십시오." 그건

5

나는 자바 사람이 아니지만 이것은 당신이 제시 한 두 가지 제약 조건을 모두 준수하는 것 같습니다.

getCompleteName이름이 초기화되지 않은 경우 예외가 발생 displayCompleteNameOnInvoice하지 않습니다.

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}

이것이 올바른 솔루션 인 이유를 확장하려면 : 이렇게하면 호출자가 getCompleteName()속성이 실제로 저장되어 있는지 또는 즉시 계산되는지를 신경 쓸 필요가 없습니다.
Bart van Ingen Schenau

18
이 솔루션이 미친 이유를 확장하려면에 대한 모든 단일 호출에서 null이 있는지 확인해야합니다 getCompleteName.
DeadMG

아니요, @DeadMG, getCompleteName이 null을 반환하는 경우 예외가 발생해야하는 경우에만 확인해야합니다. 어느 쪽이 좋을까. 이 솔루션은 맞습니다.
Dawood ibn Kareem

1
@DeadMG 그렇습니다. 그러나 우리는 getCompleteName()다른 사람이 수업의 나머지 부분이나 프로그램의 다른 부분에서 무엇을 사용하는지 알 수 없습니다 . 경우에 따라 반품 null이 필요한 것일 수도 있습니다. 그러나 스펙이 "이 경우 예외를 던진다"는 한 가지 방법이 있지만, 이것이 바로 우리가 코딩해야하는 것입니다.
다우드 이븐 카림

4
이것이 최선의 해결책 인 상황이있을 수 있지만 상상할 수는 없습니다. 대부분의 경우이 방법은 지나치게 복잡해 보입니다. 한 메서드는 불완전한 데이터를 던지고 다른 메서드는 null을 반환합니다. 발신자가이 정보를 잘못 사용할 가능성이 있습니다.
sleske

4

귀하의 질문에 아무도 대답하지 않는 것 같습니다.
사람들이 믿고 싶어하는 것과 달리, "유효하지 않은 객체를 피하는" 것은 종종 실용적인 해결책이 아닙니다.

정답은:

그렇습니다.

쉬운 예 : FileStream.LengthC # / .NETNotSupportedException 에서 파일을 찾을 수 없으면 던집니다 .


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
maple_shaft

1

당신은 전체 확인 이름을 거꾸로 가지고 있습니다.

getCompleteName()어떤 기능을 사용하는지 알 필요가 없습니다. 따라서 name전화를 거는 displayCompleteNameOnInvoice()것은 getCompleteName()책임 이 없습니다 .

그러나에 name대한 명확한 전제 조건입니다 displayCompleteNameOnInvoice(). 따라서 상태를 확인해야 할 책임이 있습니다 (또는 원하는 경우 다른 방법으로 확인 책임을 위임 해야 함 ).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.