메소드가 잘못된 입력을 리턴 할 수없는 경우에도 메소드 호출의 리턴 값을 검증해야합니까?


30

내가 호출하는 메소드가 그러한 기대를 충족시킬 것이라는 것을 알고 있더라도 메소드 호출의 리턴 값에 대해 방어해야하는지에 대해 궁금합니다.

주어진

User getUser(Int id)
{
    User temp = new User(id);
    temp.setName("John");

    return temp;
}

해야 돼

void myMethod()
{
    User user = getUser(1234);
    System.out.println(user.getName());
}

또는

void myMethod()
{
    User user = getUser(1234);

    // Validating
    Preconditions.checkNotNull(user, "User can not be null.");
    Preconditions.checkNotNull(user.getName(), "User's name can not be null.");

    System.out.println(user.getName());
}

나는 이것을 개념적 수준에서 요구하고 있습니다. 내가 메소드의 내부 작동을 알고 있다면 내가 부르고 있습니다. 내가 작성했거나 검사했기 때문입니다. 그리고 반환 가능한 값의 논리는 내 전제 조건을 충족시킵니다. 유효성 검사를 건너 뛰는 것이 "보다 낫거나"더 적절합니까? 또는 항상 통과해야하더라도 현재 구현중인 방법으로 진행하기 전에 여전히 잘못된 값을 방어해야합니까?


모든 답변에서 내 결론 (자유롭게 나올 수 있음) :

언제 주장
  • 이 방법은 과거에 오작동하는 것으로 나타났습니다
  • 이 방법은 신뢰할 수없는 출처에서 온 것입니다.
  • 이 방법은 다른 곳에서 사용되며 사후 조건을 명시 적으로 나타내지 않습니다.
다음과 같은 경우에는 주장하지 마십시오.
  • 이 방법은 귀하와 밀접한 관계를 유지합니다 (자세한 내용은 선택한 답변 참조).
  • 이 방법은 적절한 문서, 유형 안전, 단위 테스트 또는 사후 조건 확인과 같은 계약을 명시 적으로 정의합니다.
  • 성능이 중요합니다 (이 경우 디버그 모드 어설 션이 하이브리드 방식으로 작동 할 수 있음)

1
% 100 코드 범위를 목표로하십시오!
Jared Burrows

9
왜 하나의 문제에만 집중하고 Null있습니까 ( )? 이름이 ""(null이 아니라 빈 문자열) 또는 인 경우 똑같이 문제가 될 수 "N/A"있습니다. 결과를 신뢰할 수 있거나 적절한 편집증이 있어야합니다.
MSalters

3
코드베이스에는 이름 지정 체계가 있습니다. getFoo ()는 null을 반환하지 않겠다고 약속하고 getFooIfPresent ()는 null을 반환 할 수 있습니다.
pjc50

코드 위생에 대한 가정은 어디에서 왔습니까? 값은 입력시 유효성이 검증되거나 값을 검색하는 구조로 인해 항상 널이 아닌 것으로 가정합니까? 더 중요한 것은 가능한 모든 에지 시나리오를 테스트하고 있습니까? 악성 스크립트의 가능성을 포함하여?
Zibbobz

답변:


42

getUser와 myMethod가 변경 될 가능성 과 더 중요 하게는 서로 독립적으로 변경 될 가능성에 따라 다릅니다 .

getUser가 미래에 절대 변하지 않을 것이라는 점을 어떻게 든 알고 있다면, 예를 들어 i즉시 3의 가치 를 갖는 검증 시간을 낭비하는 것만 큼 그것을 검증하는 것은 시간 낭비 i = 3;입니다. 실제로, 당신은 그것을 모른다. 그러나 당신이 알아야 할 것이 있습니다 :

  • 이 두 기능이 "함께 살아"있습니까? 다시 말해서, 그들은 같은 사람들이 그들을 관리하고, 같은 파일의 일부이며, 따라서 그들 스스로 "동기화"상태를 유지할 가능성이 있습니까? 이 경우 유효성 검사를 추가하는 것은 너무 과잉 일 수 있습니다. 두 함수가 변경되거나 다른 수의 함수로 리팩토링 될 때마다 변경해야하는 코드가 더 많기 때문일 수 있습니다.

  • getUser 함수는 특정 계약이있는 문서화 된 API의 일부입니까? myMethod는 다른 코드베이스에서 해당 API의 클라이언트일까요? 그렇다면 반환 값의 유효성을 검사해야하는지 (또는 입력 매개 변수를 사전 유효성 검사해야하는지) 행복한 경로를 맹목적으로 따르는 것이 안전한지 확인하려면 해당 설명서를 읽을 수 있습니다. 설명서에이 내용이 명확하지 않은 경우 관리자에게 해당 문제를 해결하도록 요청하십시오.

  • 마지막으로,이 특정 기능이 과거에 갑자기 예기치 않게 동작을 변경했다면 코드를 손상시키는 방식으로 편집증을 가질 권리가 있습니다. 버그는 클러스터되는 경향이 있습니다.

두 기능의 원래 작성자 인 경우에도 위의 모든 내용이 적용됩니다. 우리는이 두 기능이 그들의 남은 생애 동안 "함께 살아"있을지, 또는 그것들이 천천히 분리 된 모듈로 분리 될 것인지, 또는 당신이 어떻게 든 오래된 버그로 발에 쏠 렸는지 알 수 없습니다. getUser 버전. 그러나 아마도 꽤 괜찮은 추측을 할 수 있습니다.


2
또한 : getUser나중에 변경하여 null을 반환 할 수 있다면 "NullPointerException"과 줄 번호에서 얻을 수없는 정보를 명시 적으로 확인합니까?
user253751

확인할 수있는 두 가지가 있습니다. (1) 버그가없는 것처럼이 방법이 올바르게 작동합니다. (2)이 방법은 계약을 존중합니다. # 1에 대한 유효성 검사가 과도하게 수행되는 것을 볼 수 있습니다. 대신 # 1을 수행하는 테스트가 있어야합니다. 그렇기 때문에 i = 3;의 유효성을 검사하지 않습니다. 내 질문은 # 2와 관련이 있습니다. 나는 그것에 대한 당신의 대답을 좋아하지만, 동시에 그것은 당신의 판단 답변을 사용합니다. 계약을 명시 적으로 확인한 다음 어설 션을 작성하는 데 약간의 시간을 낭비하여 발생하는 다른 문제가 있습니까?
Didier A.

다른 "문제"는 계약에 대한 해석이 잘못되었거나 잘못되면 모든 유효성 검사를 변경해야한다는 것입니다. 그러나 이것은 다른 모든 코드에도 적용됩니다.
Ixrec

1
군중을 대적 할 수 없습니다. 이 답변은 내 질문의 주제를 다루는 데 가장 정확합니다. 나는 모든 대답, 특히 @ MikeNakis를 읽음으로써 적어도 Ixrec의 마지막 두 글 머리 기호 중 하나가있을 때 적어도 디버그 모드에서 사전 조건을 주장해야한다고 생각합니다. 첫 번째 글 머리 기호에 직면 한 경우 사전 조건을 지정하는 것이 과도 할 수 있습니다. 마지막으로, 적절한 문서, 유형 안전, 단위 테스트 또는 사후 조건 확인과 같은 명시 적 계약이 주어지면 사전 요구 사항을 주장해서는 안됩니다.
Didier A.

22

Ixrec의 대답은 좋지만 고려할 가치가 있다고 생각하기 때문에 다른 접근법을 취할 것입니다. 이 토론의 목적을 위해 나는 당신 Preconditions.checkNotNull()이 전통적으로 알려진 이름이기 때문에 주장에 대해 이야기 할 것 입니다.

내가 제안하고 싶은 것은 프로그래머가 무언가가 어떤 방식으로 행동 할 것이라고 확신하는 정도를 과대 평가하고 종종 그렇게 행동하지 않는 결과를 과소 평가한다는 것입니다. 또한 프로그래머는 종종 문서로서 주장의 힘을 깨닫지 못합니다. 결과적으로, 내 경험상 프로그래머는 생각보다 훨씬 덜 자주 주장합니다.

내 모토는 :

항상 스스로에게 물어봐야 할 질문은 "내가 이것을 주장해야 하는가?"가 아닙니다. "어설 션을 잊어 버린 게 있나요?"

당연히 무언가가 특정 방식으로 동작한다는 것을 절대적으로 확신 한다면 실제로는 그 방식으로 동작했다고 주장하는 것을 자제 할 것이며 이는 대부분 합리적입니다. 당신이 아무것도 믿을 수 없다면 실제로 소프트웨어를 작성하는 것은 불가능합니다.

그러나이 규칙에는 예외가 있습니다. 흥미롭게도, Ixrec의 예를 들자면, 실제로 특정 범위 내에있는 int i = 3;주장 을 따르는 것이 바람직한 상황 유형이 있습니다 i. 이것은 i다양한 what-if 시나리오를 시도하는 사람이 변경하기 쉬운 상황이며 다음 코드 i는 특정 범위 내의 값 을 갖는 것에 의존 하며 다음 코드를 신속하게 살펴보면 즉시 명확하지 않습니다. 허용되는 범위입니다. 따라서 int i = 3; assert i >= 0 && i < 5;처음에는 무의미 해 보일지 모르지만, 생각하면을 가지고 놀 수 있다는 것을 알려주며 i, 머무를 범위를 알려줍니다. 어설 션은 가장 좋은 유형의 문서입니다.그것은 기계에 의해 시행 되므로 완벽한 보장이며 코드와 함께 리팩토링 되므로 항상 관련성이 있습니다.

귀하의 getUser(int id)예는 예와 비교할 때 매우 먼 개념의 상대가 아닙니다 assign-constant-and-assert-in-range. 정의에 따르면 버그가 예상 한 동작과 다른 동작을 나타내는 경우 버그가 발생합니다. 사실, 우리는 모든 것을 주장 할 수 없으므로 때로는 디버깅이있을 것입니다. 그러나 업계에서 오류를 빨리 발견할수록 비용이 적게 든다는 것은 잘 알려진 사실입니다. 당신이 물어봐야 할 질문은 getUser()널 (null)을 리턴하지 않을 것 (그리고 널 (null) 이름을 가진 사용자를 리턴하지 않을 것)이 얼마나 확실한지뿐만 아니라 코드의 나머지 부분에서 어떤 종류의 나쁜 일이 일어날 지에 대한 것입니다. 사실 언젠가 예상치 못한 것을 반환하고, 코드의 나머지 부분을 신속하게보고 예상했던 것을 정확하게 알면 얼마나 쉬운 지 알 수 getUser()있습니다.

예기치 않은 동작으로 인해 데이터베이스가 손상되면 101 % 확실하지 않은 경우에도 데이터베이스를 손상시켜야합니다. 경우 null사용자 이름이 나중에 더 아래 라인과 클럭 사이클 수십억의 이상한 수수께끼 추적 불가능한 오류 수천 원인이, 아마도 그것은 가능한 한 빨리 실패하는 것이 가장 좋습니다.

따라서 Ixrec의 답변에 동의하지 않더라도 어설 션에 비용이 들지 않는다는 사실을 진지하게 고려할 것을 권장합니다. 따라서 자유롭게 사용함으로써 잃어 버릴 것이 없으며 얻을 수있는 것은 없습니다.


2
예. 그것을 주장하십시오. 나는이 대답을 다소 줄이려고 여기에 왔습니다. ++
RubberDuck

2
@SteveJessop 나는 이것을 다음과 같이 수정했다 ... && sureness < 1).
Mike Nakis

2
@MikeNakis : 당신은 101 %는 당신이 바로 그때가되면 단위 테스트를 작성할 때 항상 악몽, 반환 값의 실제 어쨌든 어떤 입력 ;-)에서 생성하는 인터페이스 거의 불가능 허용, 당신이있어 확실히 잘못 !
Steve Jessop

2
내가 대답에서 언급하는 것을 완전히 잊어 버린 +1.
Ixrec

1
@DavidZ 맞습니다. 제 질문은 런타임 유효성 검사를 가정했습니다. 그러나 디버그에서 메소드를 신뢰하지 않고 prod에서 메소드를 신뢰한다고 말하는 것이 주제에 대한 좋은 견해입니다. 따라서 C 스타일 어설 션은이 상황을 처리하는 좋은 방법 일 수 있습니다.
Didier A.

8

확실히 아니야

호출자는 호출하는 기능이 계약을 준수하는지 여부를 확인해서는 안됩니다. 그 이유는 간단합니다. 잠재적으로 매우 많은 호출자가 있으며 각각의 모든 호출자에 대한 개념적 관점에서 다른 기능의 결과를 확인하기 위해 논리를 복제하는 것은 비현실적이며 틀림없이 잘못된 것입니다.

점검이 필요한 경우 각 기능은 자체 사후 조건을 점검해야합니다. 사후 조건은 함수를 올바르게 구현했으며 사용자가 함수 계약에 의존 할 수 있다는 확신을줍니다.

특정 함수가 널 (null)을 리턴하지 않으려는 경우이를 문서화하여 해당 함수 계약의 일부가되어야합니다. 이것이 바로 이러한 계약의 요점입니다. 사용자에게 의존 할 수있는 불변성을 제공하여 자신의 함수를 작성할 때 더 적은 장애에 직면합니다.


나는 일반적인 정서에 동의하지만 반환 값의 유효성을 검사하는 것이 분명한 상황이 있습니다. 예를 들어 외부 웹 서비스를 호출하는 경우 응답 형식 (xsd 등)의 유효성을 검사하려는 경우가 있습니다.
AK_

방어 적 프로그래밍 방식보다는 계약 방식으로 디자인을 옹호하는 것 같습니다. 나는 이것이 큰 대답이라고 생각합니다. 방법이 엄격한 계약을 가지고 있다면 믿을 수 있습니다. 내 경우에는 그렇지 않지만 변경하도록 변경할 수 있습니다. 그래도 안 돼? 구현을 신뢰해야한다고 주장 하시겠습니까? 문서에 명시 적으로 명시되어 있지 않더라도 null을 반환하지 않거나 실제로 사후 조건 검사가 있습니까?
Didier A.

제 생각에는 저자가 옳은 일을하도록 얼마나 신뢰하는지, 함수의 인터페이스가 변경 될 가능성, 시간 제약이 얼마나 엄격한 지 등 여러 가지 측면에서 최선의 판단과 가중치를 사용해야합니다. 대부분의 경우 함수 작성자는 함수를 문서화하지 않더라도 함수의 계약이 무엇인지에 대한 명확한 아이디어를 가지고 있습니다. 이 때문에 필자는 먼저 저자가 옳은 일을하도록 신뢰하고 기능이 제대로 작성되지 않았다는 것을 알고있을 때에 만 방어 적이어야한다고 말합니다.
Paul

다른 사람들이 올바른 일을하도록 먼저 신뢰하면 일을 더 빨리 끝내는 데 도움이됩니다. 즉, 문제가 발생하면 내가 작성한 응용 프로그램의 종류를 쉽게 수정할 수 있습니다. 보다 안전에 중요한 소프트웨어를 사용하는 사람은 내가 너무 관대하다고 생각하고보다 방어적인 접근 방식을 선호 할 수 있습니다.
Paul

5

null이 getUser 메소드의 유효한 리턴 값 인 경우, 코드는 예외가 아닌 If로 처리해야합니다. 예외가 아닙니다.

null이 getUser 메소드의 유효한 리턴 값이 아닌 경우, getUser에 버그가 있습니다. getUser 메소드는 예외를 발생 시키거나 그렇지 않으면 수정되어야합니다.

getUser 메소드가 외부 라이브러리의 일부이거나 변경 될 수없고 널 리턴의 경우 예외가 발생하도록하려면 클래스와 메소드를 랩하여 점검을 수행하고 모든 인스턴스가 getUser 호출은 코드에서 일관됩니다.


널은 getUser의 올바른 리턴 값이 아닙니다. getUser를 검사하여 구현이 null을 반환하지 않는 것을 알았습니다. 내 질문은 검사를 신뢰하고 검사 방법을 확인하지 않거나 검사를 의심하고 검사해야하는 것입니다. 미래에 메소드가 변경되어 null을 반환하지 않을 것이라는 기대를 깨뜨릴 수도 있습니다.
Didier A.

getUser 또는 user.getName이 예외를 발생하도록 변경되면 어떻게됩니까? 확인이 필요하지만 사용자를 사용하는 방법은 아닙니다. getUser 코드 중단에 대한 향후 변경 사항이 걱정되는 경우 getUser 메소드 (및 User 클래스)를 랩하여 계약을 시행하십시오. 또한 수업의 행동에 대한 가정을 확인하기 위해 단위 테스트를 만듭니다.
MatthewC

@didibus 답을 바꾸려면 ... 웃는 이모티콘 은 getUser의 유효한 반환 값이 아닙니다. getUser를 검사하여 구현이 웃는 이모티콘을 반환하지 않는 것을 알았습니다 . 내 질문은 검사를 신뢰하고 검사 방법을 확인하지 않거나 검사를 의심하고 검사해야하는 것입니다. 미래에 방법이 변경되어 웃는 이모티콘을 반환하지 않을 것이라는 기대를 깨뜨릴 수도 있습니다 . 호출 된 기능이 계약을 이행하는지 확인하는 것은 호출자의 임무가 아닙니다.
빈스 오 설리번

@ VinceO'Sullivan "계약"이 문제의 중심에있는 것 같습니다. 그리고 내 질문은 암시 적 계약과 명시 적에 관한 것입니다. int를 반환하는 메서드는 문자열을 얻지 못한다고 주장하지 않습니다. 그러나 긍정적 인 정수 만 원한다면해야합니까? 내가 검사했기 때문에 메소드가 음수 int를 반환하지 않는 것은 암시 적입니다. 서명이나 문서에서 명확하지 않은 것은 아닙니다.
Didier A.

1
계약이 암시 적이거나 명시적인지는 중요하지 않습니다. 호출 된 메소드가 유효한 데이터가 제공 될 때 유효한 데이터를 리턴하는지 검증하는 것은 호출 코드의 역할이 아닙니다. 당신은 예를 들어, 부정적인 inits가 오답이 아니라는 것을 알고 있지만 positive int가 올바른지 알고 있습니까? 방법이 작동하고 있음을 증명하기 위해 얼마나 많은 테스트가 필요합니까? 올바른 방법은 방법이 올바른 것으로 가정하는 것입니다.
빈스 오 설리번

5

나는 당신의 질문의 전제가 꺼져 있다고 주장합니다 : 왜 null 참조를 사용하여 시작합니까?

널 객체 패턴은 본질적으로 아무것도하지 않는 개체를 반환하는 방법입니다. "에는 사용자를"대표없는 개체를 반환, 오히려 사용자의 경우는 null보다가

이 사용자는 비활성 상태이고 이름이 비어 있으며 "여기에 아무것도 없음"을 나타내는 널이 아닌 다른 값이 있습니다. 주요 이점은 프로그램에서 null 검사, 로깅 등을 수행하여 객체를 사용하는 것입니다.

알고리즘이 null 값을 고려해야하는 이유는 무엇입니까? 단계는 동일해야합니다. 0, 빈 문자열 등을 반환하는 null 객체를 갖는 것이 쉽고 명확합니다.

다음은 null을 검사하는 코드의 예입니다.

User u = getUser();
String displayName = "";
if (u != null) {
  displayName = u.getName();
}
return displayName;

다음은 null 객체를 사용하는 코드의 예입니다.

return getUser().getName();

어느 것이 더 명확합니까? 두 번째는 믿습니다.


질문에 태그가 지정되어 있지만 참조를 사용할 때 null 객체 패턴이 C ++에서 실제로 유용하다는 점에 주목할 가치가 있습니다. Java 참조는 C ++ 포인터와 비슷하며 널을 허용합니다. C ++ 참조는를 보유 할 수 없습니다 nullptr. C ++에서 null 참조와 비슷한 것을 원한다면 null 객체 패턴이 그것을 달성하는 유일한 방법입니다.


널은 내가 주장하는 것의 예일뿐입니다. 비어 있지 않은 이름이 필요할 수도 있습니다. 내 질문이 계속 적용됩니다. 확인 하시겠습니까? 또는 int를 반환하는 것일 수 있으며 음수 int를 가질 수 없거나 주어진 범위에 있어야합니다. 한마디로 null 문제로 생각하지 마십시오.
Didier A.

1
왜 메소드가 잘못된 값을 반환합니까? 이것이 점검되어야하는 유일한 장소는 응용 프로그램의 경계에 있습니다 : 사용자 및 다른 시스템과의 인터페이스. 그렇지 않으면 예외가 있습니다.

내가 가지고 있다고 상상해보십시오 : int i = getCount; 플로트 x = 100 / i; getCount가 0을 반환하지 않는다는 것을 안다면, 메소드를 작성하거나 검사했기 때문에 0 검사를 건너 뛰어야합니까?
Didier A.

@didibus : getCount() 문서 가 현재 0을 반환하지 않는다는 보장을 문서화 하면 체크를 건너 뛸 수 있으며 누군가가 변경을 결정하고 대처하기 위해 호출하는 모든 것을 업데이트하려고하는 경우를 제외하고는 나중에 그렇게하지 않을 것입니다 새로운 인터페이스. 현재 코드의 기능을 확인하는 것은 도움이되지 않지만 코드를 검사하려는 경우 검사 할 코드가에 대한 단위 테스트입니다 getCount(). 그들이 0을 리턴하지 않는 것으로 테스트하면, 0을 리턴하는 미래의 변경은 테스트가 실패하기 때문에 주요 변경으로 간주된다는 것을 알고 있습니다.
Steve Jessop

null 객체가 null보다 낫다는 것은 분명하지 않습니다. 귀 오리는 ck 수 있지만 오리는 아닙니다. 실제로 의미 상 오리의 대립입니다. null 객체를 도입하면 해당 클래스를 사용하는 모든 코드에서 해당 객체가 null 객체인지 확인해야 할 수 있습니다. 예를 들어 집합에 null 객체가 있으면 몇 개가 있습니까? 이것은 실패를 연기하고 오류를 난독 화하는 방법처럼 보입니다. 링크 된 기사는 특히 null 검사를 피하고 코드를 더 읽기 쉽게 만들기 위해 null 개체를 사용하지 않도록 경고합니다.
sdenham

1

일반적으로, 나는 주로 다음 세 가지 측면에 달려 있다고 말합니다.

  1. 견고성 : 호출 메소드가 null값을 처리 할 수 있습니까? 복잡성이 낮고 호출 / 호출 메소드가 동일한 작성자에 의해 작성되고 예상되지 않는 경우 (예 : 둘 다 동일한 패키지에 있음)를 제외하고 null값이 결과로 표시 될 경우 RuntimeException점검을 권장합니다 .nullprivate

  2. 코드 책임 : 개발자 A가 getUser()메소드를 책임지고 개발자 B가 메소드를 사용하는 경우 (예 : 라이브러리의 일부) 해당 값의 유효성을 검증하는 것이 좋습니다. 개발자 B가 잠재적 인 null리턴 값 을 초래하는 변경 사항을 알 수 없기 때문입니다 .

  3. 복잡성 : 프로그램이나 환경의 전체 복잡성이 높을수록 반환 값의 유효성을 검사하는 것이 좋습니다. null호출 메소드와 관련하여 오늘날에는 불가능하다고 확신하더라도 getUser()다른 유스 케이스 를 변경해야 할 수도 있습니다 . 몇 달 또는 몇 년이 지나고 수천 줄의 코드가 추가되면 이것은 함정이 될 수 있습니다.

또한 nullJavaDoc 주석에 잠재적 인 반환 값 을 문서화하는 것이 좋습니다 . 대부분의 IDE에서 JavaDoc 설명이 강조 표시되므로이를 사용하는 사람에게 유용한 경고가 될 수 있습니다 getUser().

/** Returns ....
  * @param: id id of the user
  * @return: a User instance related to the id;
  *          null, if no user with identifier id exists
 **/
User getUser(Int id)
{
    ...
}

-1

꼭 그럴 필요는 없습니다.

예를 들어, 리턴이 널이 될 수 없음을 이미 알고있는 경우-왜 널 점검을 추가 하시겠습니까? null 검사를 추가해도 아무런 문제가 발생하지 않지만 중복됩니다. 피할 수있는 한 중복 로직을 코딩하지 않는 것이 좋습니다.


1
나는 알고 있지만 다른 방법의 구현을 조사하거나 작성했기 때문입니다. 이 방법의 계약은 명시 적으로 할 수 없다고 명시하지 않습니다. 예를 들어 미래에 변화가 생길 수 있습니다. 또는 null을 반환하지 않는 버그가있을 수 있습니다.
Didier A.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.