호출자의 입력 매개 변수 유효성 검사 : 코드 복제?


16

함수의 입력 매개 변수를 확인하는 가장 좋은 곳은 어디입니까?

코딩 스타일을 개선하고 싶을 때이 문제에 대한 모범 사례 또는 일부 규칙을 찾으려고 노력합니다. 언제 그리고 무엇이 더 낫습니다.

이전 프로젝트에서는 함수 내부의 모든 입력 매개 변수를 확인하고 처리했습니다 (예 : null이 아닌 경우). 이제 여기에 일부 답변과 Pragmatic Programmer 책에서 입력 매개 변수의 유효성 검사가 호출자의 책임이라는 것을 읽었습니다.

따라서 함수를 호출하기 전에 입력 매개 변수를 확인해야합니다. 어디서나 함수가 호출됩니다. 그리고 그것은 하나의 질문을 제기합니다 : 함수가 호출되는 모든 곳에서 조건을 확인하는 중복을 생성하지 않습니까?

나는 null 조건에 관심이 없지만 모든 입력 변수 (음수 값을 sqrt함수로 나누거나 0으로 나누기, 상태와 우편 번호의 잘못된 조합 또는 다른 것)의 유효성 검사에 관심이 있습니다.

입력 조건을 확인할 위치를 결정하는 몇 가지 규칙이 있습니까?

나는 몇 가지 주장에 대해 생각하고 있습니다.

  • 유효하지 않은 변수의 처리가 다를 수있는 경우 호출자 측에서 변수를 확인하는 것이 좋습니다 (예 : sqrt()함수-복잡한 숫자로 작업하고 싶을 수 있으므로 호출자의 상태를 처리합니다)
  • 점검 조건이 모든 호출자에서 동일 할 때 중복을 피하기 위해 함수 내부에서 점검하는 것이 좋습니다.
  • 호출자에서 입력 매개 변수의 유효성 검증은이 매개 변수로 많은 함수를 호출하기 전에 한 번만 발생합니다. 따라서 각 함수의 매개 변수 유효성 검사는 효과적이지 않습니다.
  • 올바른 솔루션은 특정 사례에 따라 다릅니다.

이 질문이 다른 질문과 중복되지 않기를 바랍니다.이 문제를 검색했는데 비슷한 질문이 있지만 정확히이 사례는 언급하지 않았습니다.

답변:


15

때에 따라 다르지. 유효성을 검증 할 위치를 결정 하는 것은 방법에 의해 암시 된 (또는 문서화 된) 계약설명 및 강점을 기반으로해야합니다 . 유효성 검사는 특정 계약 준수를 강화하는 좋은 방법입니다. 어떤 이유로 든 방법이 매우 엄격한 계약을 가지고 있다면, 전화하기 전에 확인해야합니다.

이것은 기본적으로 일부 메소드가 일부 조작을 수행한다고 광고하기 때문에 공용 메소드 를 작성할 때 특히 중요한 개념 입니다. 당신이하는 말을하는 것이 좋습니다!

다음 방법을 예로 들어 보겠습니다.

public void DeletePerson(Person p)
{            
    _database.Delete(p);
}

계약은 무엇을 의미 DeletePerson합니까? 프로그래머 Person는 전달 된 것이 있으면 삭제 될 것이라고 가정 할 수 있습니다. 그러나 이것이 항상 사실은 아니라는 것을 알고 있습니다. 만약에 pA는 null값은? p데이터베이스에 존재하지 않으면 어떻게 합니까? 데이터베이스 연결이 끊어지면 어떻게됩니까? 따라서 DeletePerson은 계약을 제대로 이행하지 않는 것 같습니다. 때로는 개인을 삭제하고 때로는 NullReferenceException 또는 DatabaseNotConnectedException을 발생 시키거나 때로는 개인이 이미 삭제 된 경우와 같이 아무 것도 수행하지 않습니다.

이와 같은 API는 사용하기 어려운 것으로 알려져 있습니다. 메소드의 "블랙 박스"를 호출하면 모든 종류의 끔찍한 일이 발생할 수 있기 때문입니다.

계약을 개선 할 수있는 몇 가지 방법은 다음과 같습니다.

  • 유효성 검사를 추가하고 계약에 예외를 추가하십시오. 이로 인해 계약이 강화 되지만 발신자가 유효성 검사를 수행해야합니다. 그러나 차이점은 이제 요구 사항을 알고 있다는 것입니다. 이 경우 C # XML 주석과 통신하지만 대신 throws(Java) 를 추가 하거나을 사용 Assert하거나 Code Contracts와 같은 계약 도구를 사용할 수 있습니다.

    ///<exception>ArgumentNullException</exception>
    ///<exception>ArgumentException</exception>
    public void DeletePerson(Person p)
    {            
        if(p == null)
            throw new ArgumentNullException("p");
        if(!_database.Contains(p))
            throw new ArgumentException("The Person specified is not in the database.");
    
        _database.Delete(p);
    }
    

    참고 사항 :이 스타일에 대한 논쟁은 종종 모든 호출 코드에 의해 과도한 사전 유효성 검사를 유발한다는 것이지만, 내 경험에 따르면 종종 그렇지 않습니다. 널 (Null) 개인을 삭제하려는 시나리오를 생각해보십시오. 어떻게 된거 지? 귀족은 어디에서 왔습니까? 예를 들어 이것이 UI 인 경우 현재 선택이없는 경우 Delete 키가 처리 된 이유는 무엇입니까? 이미 삭제 된 경우 디스플레이에서 이미 제거하지 않아야합니까? 분명히 이것에는 예외가 있지만 프로젝트가 커짐에 따라 버그가 시스템 깊숙이 침투하는 것을 막는 이와 같은 코드에 감사드립니다.

  • 방어 적으로 검증 및 코드를 추가하십시오. 이제이 방법은 사람을 삭제하는 것 이상을 수행하기 때문에 계약이 느슨해 집니다. 이를 반영하기 위해 메소드 이름을 변경했지만 API가 일관성이 있으면 필요하지 않을 수 있습니다. 이 방법에는 장단점이 있습니다. TryDeletePerson모든 종류의 유효하지 않은 입력에서 전달을 호출 할 수 있으며 예외에 대해 걱정하지 않아도됩니다. 물론 코드 사용자가이 메소드를 너무 많이 호출하거나 p가 null 인 경우 디버깅이 어려워 질 수 있습니다. 이는 단일 책임 원칙을 약간 위반 한 것으로 간주 될 수 있으므로 화염 전쟁이 발발 할 경우이를 염두에 두십시오.

    public void TryDeletePerson(Person p)
    {            
        if(p == null || !_database.Contains(p))
            return;
    
        _database.Delete(p);
    }
    
  • 접근 방식을 결합하십시오. 때로는 외부 발신자가 규칙을 면밀히 준수하여 (코드 책임을지게하기 위해) 두 가지 중 일부를 원하지만 개인 코드가 유연 해지기를 원할 수 있습니다.

    ///<exception>ArgumentNullException</exception>
    ///<exception>ArgumentException</exception>
    public void DeletePerson(Person p)
    {            
        if(p == null)
            throw new ArgumentNullException("p");
        if(!_database.Contains(p))
            throw new ArgumentException("The Person specified is not in the database.");
    
        TryDeletePerson(p);
    }
    
    internal void TryDeletePerson(Person p)
    {            
        if(p == null || !_database.Contains(p))
            return;
    
        _database.Delete(p);
    }
    

내 경험상, 엄격한 규칙보다는 암묵적인 계약에 집중하는 것이 가장 효과적입니다. 호출자가 작업이 유효한지 판단하기 어렵거나 어려운 경우 방어 적 코딩이 더 잘 작동하는 것으로 보입니다. 엄격한 계약은 호출자가 실제로 실제로 의미가있을 때만 메소드 호출을 수행 할 것으로 예상되는 곳에서 더 잘 작동하는 것으로 보입니다.


예를 들어 아주 좋은 답변을 주셔서 감사합니다. 나는 "방어"와 "엄격한 계약"접근 방식이 마음에 든다.
srnka

7

컨벤션, 문서 및 유스 케이스의 문제입니다.

모든 기능이 동일한 것은 아닙니다. 모든 요구 사항이 동일한 것은 아닙니다. 모든 유효성 검사가 동일한 것은 아닙니다.

예를 들어, Java 프로젝트가 가능할 때마다 널 포인터를 피하려고 시도하는 경우 (예 : Guava 스타일 권장 사항 참조 ) 여전히 모든 함수 인수의 유효성을 검사하여 널이 아닌지 확인합니까? 아마도 필요하지는 않지만 버그를 쉽게 찾기 위해 여전히 그렇게 할 가능성이 있습니다. 그러나 이전에 NullPointerException을 발생시킨 어설 션을 사용할 수 있습니다.

프로젝트가 C ++ 인 경우 어떻게해야합니까? C ++의 컨벤션 / 전통은 전제 조건을 문서화하는 것이지만 디버그 빌드에서 전제 조건을 검증하는 것입니다.

두 경우 모두 함수에 대해 문서화 된 전제 조건이 있습니다. 인수는 널이 될 수 없습니다. 대신 함수의 도메인을 확장하여 정의 된 동작으로 널을 포함 할 수 있습니다 (예 : "인수가 널인 경우 예외 발생"). 물론, 이것은 다시 C ++ 유산입니다. 자바에서는 이러한 방식으로 전제 조건을 문서화하는 것이 일반적입니다.

그러나 모든 전제 조건을 합리적으로 확인할 있는 것은 아닙니다 . 예를 들어, 이진 검색 알고리즘에는 검색 할 시퀀스를 정렬해야한다는 전제 조건이 있습니다. 그러나 그것이 O (N) 연산인지 확실히 확인하면 모든 호출에서 O (log (N)) 알고리즘을 사용하는 시점을 우선합니다. 방어 적으로 프로그래밍하는 경우 적은 수의 검사 (예 : 검색하는 각 파티션에 대해 시작, 중간 및 끝 값이 정렬되어 있는지 확인)를 수행 할 수 있지만 모든 오류가 발생하지는 않습니다. 일반적으로 충족되는 전제 조건에 의존해야합니다.

명시적인 검사가 필요한 실제 장소는 경계입니다. 프로젝트에 대한 외부 입력? 유효성 검사, 유효성 검사, 유효성 검사 회색 영역은 API 경계입니다. 실제로 클라이언트 코드를 얼마나 신뢰하고 싶은지, 유효하지 않은 입력으로 인한 피해량, 버그를 찾는 데 얼마나 많은 도움을 주어야하는지에 달려 있습니다. 모든 권한 경계는 물론 외부 권한으로 계산되어야합니다. 예를 들어 syscall은 높은 권한 컨텍스트에서 실행되므로 유효성 검사에 매우 신중해야합니다. 이러한 유효성 검사는 물론 syscall 내부에 있어야합니다.


답변 주셔서 감사합니다. 구아바 스타일 추천 링크를 제공해 주시겠습니까? 나는 구글과 그것의 의미를 찾을 수 없습니다. 경계를 확인하려면 +1
srnka

링크가 추가되었습니다. 실제로는 전체 스타일 가이드가 아니며 null이 아닌 유틸리티에 대한 설명서의 일부입니다.
Sebastian Redl

6

매개 변수 유효성 검사는 호출되는 함수의 문제입니다. 함수는 유효한 입력으로 간주되는 것과 그렇지 않은 것을 알아야합니다. 호출자는 특히 함수가 내부적으로 어떻게 구현되는지 모르는 경우이를 알지 못할 수 있습니다. 이 함수는 호출자의 매개 변수 값 조합을 처리해야합니다.

이 함수는 매개 변수의 유효성 검사를 담당하므로이 함수에 대해 단위 테스트를 작성하여 유효하고 유효하지 않은 매개 변수 값으로 의도 한대로 작동하는지 확인할 수 있습니다.


대답 해줘서 고마워요. 따라서이 함수는 모든 경우에 유효한 입력 매개 변수와 유효하지 않은 입력 매개 변수를 모두 확인해야한다고 생각합니다. Pragmatic Programmer 서적 확인과 다른 점 : "입력 매개 변수 확인은 호출자의 책임입니다". "함수가 유효한 것으로 알고 있어야합니다 ... 발신자가 알지 못할 수도 있습니다"... 좋은 생각입니다. 전제 조건을 사용하고 싶지 않습니까?
srnka

1
원하는 경우 사전 조건을 사용할 수 있지만 ( 세바스찬 답변 참조 ) 나는 방어적이고 가능한 모든 입력을 처리하는 것을 선호합니다.
Bernard

4

함수 자체 내에서. 함수가 두 번 이상 사용되면 모든 함수 호출에 대한 매개 변수를 확인하지 않을 것입니다.

또한 함수가 매개 변수의 유효성 검사에 영향을 미치는 방식으로 업데이트되는 경우 호출자 유효성 검사가 발생할 때마다 검색하여 업데이트해야합니다. 그것은 사랑스럽지 않습니다 :-).

가드 조항을 참조 할 수 있습니다

최신 정보

제공 한 각 시나리오에 대한 내 답변을 참조하십시오.

  • 유효하지 않은 변수의 처리가 다를 수있는 경우 호출자 측에서 변수를 확인하는 것이 좋습니다 (예 : sqrt()함수-복잡한 숫자로 작업하고 싶을 수 있으므로 호출자의 상태를 처리합니다)

    대답

    대다수의 프로그래밍 언어는 기본적으로 정수가 아닌 실수를 지원하지만, 복잡한 숫자는 지원하지 않으므로 구현은 sqrt음수가 아닌 숫자 만 허용합니다. sqrt복소수를 반환 하는 함수 가 유일한 경우 는 Mathematica 와 같이 수학을 지향하는 프로그래밍 언어를 사용할 때입니다.

    또한 sqrt대부분의 프로그래밍 언어가 이미 구현되어 있으므로 수정할 수 없으므로 구현을 바꾸려고하면 (원숭이 패치 참조) 공동 작업자는 왜 sqrt갑자기 음수를 받아들이 는지에 대해 완전히 충격을 받습니다.

    원하는 경우 sqrt음수를 처리하고 복소수를 반환하는 사용자 지정 함수를 감싸십시오 .

  • 점검 조건이 모든 호출자에서 동일 할 때 중복을 피하기 위해 함수 내부에서 점검하는 것이 좋습니다.

    대답

    예, 이것은 코드 전체에서 매개 변수 유효성 검사가 흩어지는 것을 피하는 것이 좋습니다.

  • 호출자에서 입력 매개 변수의 유효성 검증은이 매개 변수로 많은 함수를 호출하기 전에 한 번만 발생합니다. 따라서 각 함수의 매개 변수 유효성 검사는 효과적이지 않습니다.

    대답

    발신자가 함수라면 좋을 것입니다.

    발신자 내의 함수가 다른 발신자가 사용하는 경우 발신자가 호출 한 함수 내에서 매개 변수의 유효성을 검사 할 수없는 이유는 무엇입니까?

  • 올바른 솔루션은 특정 사례에 따라 다릅니다.

    대답

    유지 관리 가능한 코드를 목표로합니다. 매개 변수 유효성 검증을 이동하면 함수가 수용 할 수있는 항목에 대한 진실의 소스가 보장됩니다.


대답 해줘서 고마워요. sqrt ()는 단지 예일뿐입니다. 입력 매개 변수와 동일한 동작을 다른 많은 함수에서 사용할 수 있습니다. "매개 변수의 유효성 검사에 영향을주는 방식으로 함수가 업데이트되면 모든 호출자 유효성 검사를 검색해야합니다."-나는 이에 동의하지 않습니다. 그런 다음 반환 값에 대해서도 동일하게 말할 수 있습니다. 반환 값에 영향을 미치는 방식으로 함수가 업데이트되면 모든 호출자를 수정해야합니다. 어쨌든 발신자의 변경이 필요합니다.
srnka

2

함수는 사전 및 사후 조건을 명시해야합니다.
전제 조건은 호출자 가 함수를 올바르게 사용하고 입력 매개 변수의 유효성을 포함 할 수 있기 전에 충족시켜야하는 조건입니다 .
사후 조건은 함수가 호출자에게 제공하는 약속입니다.

함수 매개 변수의 유효성이 전제 조건의 일부인 경우 해당 매개 변수가 유효한지 확인하는 것은 호출자의 책임입니다. 그러나 이것이 모든 호출자가 호출 전에 각 매개 변수를 명시 적으로 확인해야한다는 의미는 아닙니다. 대부분의 경우 호출자의 내부 논리 및 사전 조건이 매개 변수가 유효한지 이미 확인하므로 명시적인 테스트가 필요하지 않습니다.

프로그래밍 오류 (버그)에 대한 안전 조치로 함수에 전달 된 매개 변수가 명시된 사전 조건을 충족하는지 확인할 수 있습니다. 이러한 테스트는 비용이 많이들 수 있으므로 릴리스 빌드를 위해 테스트를 끌 수있는 것이 좋습니다. 이러한 테스트가 실패하면 버그가 발생했을 가능성이 있으므로 프로그램을 종료해야합니다.

언뜻보기에 발신자의 수표는 코드 복제를 초대하는 것처럼 보이지만 실제로는 다른 방법입니다. 수신자의 확인으로 코드 중복이 발생하고 불필요한 작업이 많이 발생합니다.
여러 계층의 함수를 통해 매개 변수를 얼마나 자주 전달하고 그 과정에서 일부만 약간만 변경하면됩니다. 일관된 체크인 방법 을 일관되게 적용하는 경우 , 각 중간 기능은 각 매개 변수에 대한 점검을 다시 수행해야합니다.
이제 그 매개 변수 중 하나가 정렬 된 목록이라고 가정하십시오.
호출자를 확인하면 첫 번째 함수만이 목록이 실제로 정렬되었는지 확인해야합니다. 다른 모든 사람들은 목록이 이미 정렬되어 있다는 것을 알고 있습니다 (사전 조건에서 언급 한 바와 같이) 추가 검사없이 목록을 전달할 수 있습니다.


+1 답변 주셔서 감사합니다. 좋은 반영 : "수취인의 확인으로 코드가 중복되고 불필요한 작업이 많이 발생합니다." 그리고 문장에서 : "대부분의 경우, 호출자의 내부 논리와 사전 조건이 이미 보장하기 때문에 명시 적 테스트가 필요하지 않습니다."- "내부 논리"라는 표현의 의미는 무엇입니까? DBC 기능?
srnka

@ srnka : "내부 논리"란 함수의 계산 및 결정을 의미합니다. 본질적으로 함수의 구현입니다.
Bart van Ingen Schenau

0

가장 자주, 언제 그리고 어떻게 당신이 작성한 함수를 호출 할 것인지 알 수 없습니다. 최악의 경우를 가정하는 것이 가장 좋습니다. 잘못된 매개 변수로 함수가 호출됩니다. 그래서 당신은 분명히 그것을 커버해야합니다.

그럼에도 불구하고 사용하는 언어가 예외를 지원하는 경우 특정 오류를 확인하지 않고 예외가 발생하는지 확인할 수 있지만이 경우 문서에 사례를 설명해야합니다 (문서가 필요함). 예외는 호출자에게 발생한 상황에 대한 충분한 정보를 제공하고 유효하지 않은 인수에주의를 기울입니다.


실제로 매개 변수의 유효성을 검증하는 것이 좋으며 매개 변수가 유효하지 않은 경우 예외를 직접 처리하십시오. 이유는 다음과 같습니다. 루틴이 올바른 데이터를 제공하는지 확인하지 않고 루틴을 호출하는 광대는 유효하지 않은 데이터를 전달했음을 나타내는 오류 리턴 코드를 확인하지 않아도됩니다. 예외가 발생하면 문제가 해결됩니다.
John R. Strohm
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.