예외 또는 중복없이 입력 유효성 검사를 수행하는 방법


11

특정 프로그램에 대한 인터페이스를 만들려고 할 때 일반적으로 유효성이 검사되지 않은 입력에 의존하는 예외가 발생하지 않도록 노력하고 있습니다.

그래서 종종 발생하는 일은 다음과 같은 코드를 생각한 것입니다 (이것은 예제를위한 예일뿐입니다. 예를 들어 Java의 기능을 신경 쓰지 마십시오).

public static String padToEvenOriginal(int evenSize, String string) {
    if (evenSize % 2 == 1) {
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

자, 그것은 evenSize실제로 사용자 입력에서 파생 되었다고 가정하십시오 . 그래서 나는 그것이 확실하지 않다. 그러나 예외가 발생할 가능성이있는이 메소드를 호출하고 싶지 않습니다. 그래서 나는 다음과 같은 기능을합니다 :

public static boolean isEven(int evenSize) {
    return evenSize % 2 == 0;
}

그러나 이제 동일한 입력 유효성 검사를 수행하는 두 가지 검사가 있습니다. if명령문 의 표현식 과 명시 적 체크인입니다 isEven. 코드가 중복되어 좋지는 않으므로 리팩토링하십시오.

public static String padToEvenWithIsEven(int evenSize, String string) {
    if (!isEven(evenSize)) { // to avoid duplicate code
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

문제가 해결되었지만 이제 다음과 같은 상황이 발생합니다.

String test = "123";
int size;
do {
    size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)

이제 우리는 중복 검사를 받았습니다. 우리는 이미 값이 짝수임을 알고 있지만 padToEvenWithIsEven이미이 함수를 호출 한 것처럼 항상 true를 반환하는 매개 변수 검사를 수행합니다.

지금 당장 isEven은 문제가되지 않지만, 매개 변수 확인이 더 번거 롭다면 비용이 너무 많이들 수 있습니다. 게다가, 중복 통화를 수행하는 것은 단순히 기분이 좋지 않습니다.

때로는 "유효한 유형"을 도입하거나이 문제가 발생할 수없는 함수를 만들어이 문제를 해결할 수 있습니다.

public static String padToEvenSmarter(int numberOfBigrams, String string) {
    int size = numberOfBigrams * 2;
    if (string.length() >= size) {
        return string;
    }

    StringBuilder sb = new StringBuilder(size);
    sb.append(string);
    for (int i = string.length(); i < size; i++) {
        sb.append('x');
    }
    return sb.toString();
}

그러나 이것은 똑똑한 사고와 상당히 큰 리 팩터가 필요합니다.

중복 isEven매개 변수 검사를 수행하고 이중 매개 변수 검사를 수행 할 수있는 일반적인 방법이 있습니까? 솔루션이 실제로 padToEven잘못된 매개 변수로 호출하지 않고 예외를 트리거 하고 싶습니다 .


예외없이 나는 예외가없는 프로그래밍을 의미하지는 않지만, 사용자 입력이 의도적으로 예외를 트리거하지 않는 반면, 일반 함수 자체에는 여전히 매개 변수 검사가 포함되어 있습니다 (프로그래밍 오류로부터 보호하기 위해).


예외를 제거하려고합니까? 실제 크기가 무엇인지 가정하여이를 수행 할 수 있습니다. 예를 들어 13을 입력하면 12 또는 14로 패딩하고 수표를 모두 피하십시오. 이러한 가정 중 하나를 수행 할 수 없으면 매개 변수를 사용할 수없고 기능을 계속할 수 없기 때문에 예외가 발생합니다.
Robert Harvey

@RobertHarvey 저의 견해로는, 최소한의 놀람의 원칙과 실패의 빠른 원칙에 반대합니다. 입력 매개 변수가 null 인 경우 null을 반환하는 것과 같습니다 (물론 결과를 알 수없는 NPE로 올바르게 처리하는 것을 잊어 버립니다).
Maarten Bodewes

Ergo, 예외. 권리?
Robert Harvey

2
무엇을 기다립니다? 당신은 조언을 위해 여기에 왔습니까? 내가 말하고있는 것은 ( 아마도 미묘하지 않은 방식으로) 예외는 의도적으로 설계된 것이므로 예외 를 제거하려면 정당한 이유가 있어야합니다. 그건 그렇고, 나는 amon에 동의합니다 : 아마도 매개 변수에 대해 홀수를 받아 들일 수없는 함수가 없어야합니다.
Robert Harvey

1
@MaartenBodewes : 사용자 입력의 유효성 검사를 수행 padToEvenWithIsEven 하지 않는다는 것을 기억해야 합니다. 호출 코드의 프로그래밍 오류 로부터 자신을 보호하기 위해 입력에 대한 유효성 검사를 수행합니다 . 이 유효성 검사의 범위는 호출 코드를 작성하는 사람이 잘못된 매개 변수에 전달할 위험에 대비하여 검사 비용을 부과하는 비용 / 위험 분석에 따라 다릅니다.
Bart van Ingen Schenau

답변:


7

예제의 경우 가장 일반적인 해결책은보다 일반적인 패딩 기능을 사용하는 것입니다. 발신자가 균일 한 크기로 패딩하기를 원하면 스스로 확인할 수 있습니다.

public static String padString(int size, String string) {
    if (string.length() >= size) {
        return string;
    }

    StringBuilder sb = new StringBuilder(size);
    sb.append(string);
    for (int i = string.length(); i < size; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

값에 대해 동일한 유효성 검사를 반복적으로 수행하거나 유형의 일부 값만 허용하려는 경우 마이크로 타입 / 작은 유형이 도움이 될 수 있습니다. 패딩과 같은 범용 유틸리티의 경우 이는 좋은 생각이 아니지만 도메인 모델에서 값이 특정 역할을 수행하는 경우 기본 값 대신 전용 유형을 사용하는 것이 큰 발전 일 수 있습니다. 여기에서 다음을 정의 할 수 있습니다.

final class EvenInteger {
  public final int value;

  public EvenInteger(int value) {
    if (!(value % 2 == 0))
      throw new IllegalArgumentException("EvenInteger(" + value + ") is not even");
    this.value = value;
  }
}

이제 선언 할 수 있습니다

public static String padStringToEven(EvenInteger evenSize, String string)
    ...

내부 유효성 검사를 수행 할 필요가 없습니다. 이러한 간단한 테스트의 경우 객체 내부에 int를 래핑하면 런타임 성능 측면에서 비용이 많이 들지만 유형 시스템을 유리하게 사용하면 버그를 줄이고 디자인을 명확하게 할 수 있습니다.

이러한 작은 유형을 사용하면 유효성 검사를 수행하지 않는 경우에도 (예 : a FirstName를 나타내는 문자열을 명확하게하기 위해) 유용 할 수 있습니다 LastName. 저는이 패턴을 정적으로 유형이 지정된 언어로 자주 사용합니다.


경우에 따라 두 번째 함수에서 여전히 예외가 발생하지 않습니까?
Robert Harvey

1
@RobertHarvey 예. 널 포인터의 경우 예. 그러나 이제는 숫자가 짝수인지에 대한 주요 점검이 기능에서 호출자의 책임으로 강제 실행됩니다. 그런 다음 적절한 방식으로 예외를 처리 할 수 ​​있습니다. 나는 유효성 검사 코드에서 코드 중복을 제거하는 것보다 모든 예외를 제거하는 데 덜 집중한다고 생각합니다.
amon

1
좋은 대답입니다. 물론 Java에서는 데이터 클래스를 작성하는 데 따르는 페널티가 상당히 높지만 (추가 코드가 많음) 이는 제시 한 아이디어보다 언어 문제입니다. 내 문제가 발생할 모든 유스 케이스에는이 솔루션을 사용하지 않을 것이지만, 프리미티브를 과도하게 사용하지 않거나 매개 변수 검사 비용이 예외적으로 어려운 경우에는이 솔루션을 사용하는 것이 좋습니다.
Maarten Bodewes

1
@MaartenBodewes 검증을 원한다면 예외와 같은 것을 제거 할 수 없습니다. 대체 방법은 검증 된 객체를 반환하거나 실패시 null을 반환하는 정적 함수를 사용하는 것이지만 기본적으로 장점은 없습니다. 그러나 유효성 검사를 함수에서 생성자로 이동하면 유효성 검사 오류없이 함수를 호출 할 수 있습니다. 그것은 발신자에게 모든 통제권을 부여합니다. 예 :EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
amon

1
@MaartenBodewes 중복 유효성 검사는 항상 발생합니다. 예를 들어, 문을 닫으려고 시도하기 전에 이미 닫혔는지 확인할 수 있지만 이미 닫혀 있으면 문 자체를 다시 닫을 수 없습니다. 호출자가 유효하지 않은 작업을 수행하지 않는다는 것을 항상 신뢰하는 경우를 제외하고는이 방법을 사용할 수 없습니다. 귀하의 경우 unsafePadStringToEven검사를 수행하지 않는 작업이 있을 수 있지만 유효성 검사를 피하는 것은 나쁜 생각처럼 보입니다.
plalx

9

@amon의 답변에 대한 확장으로 EvenInteger, 기능 프로그래밍 커뮤니티가 "Smart Constructor"라고 부르는 기능과 벙어리 생성자를 감싸고 객체가 유효한 상태인지 확인하는 기능과 결합 할 수 있습니다 스마트 생성자 만 사용되도록 생성자 클래스 또는 비 클래스 기반 언어 모듈 / 패키지 개인용). 트릭은 Optional함수를 더 작게 구성 할 수 있도록 (또는 동등한) 함수 를 반환하는 것 입니다.

public final class EvenInteger {
    private final int value;

    private EvenInteger(value) {
        this.value = value;
    }

    public static Optional<EvenInteger> of(final int value) {
        if (value % 2 == 0) {
            return Optional.of(new EvenInteger(value));
        }
        return Optional.empty();
    }

    public int getValue() {
        return this.value;
    }
}

그런 다음 표준 Optional방법을 사용 하여 입력 논리 를 쉽게 작성할 수 있습니다 .

class GetEvenInput {
    public Optional<EvenInteger> askOnce() {
        int size = getSizeFromInput();
        return EvenInteger.of(size);
    }

    public EvenInteger keepAsking() {
        return askOnce().orElseGet(() -> keepAsking());
    }
}

keepAsking()do-while 루프를 사용하여보다 관용적 인 Java 스타일로 작성할 수도 있습니다 .

Optional<EvenInteger> result;
do {
    result = askOnce();
} while (!result.isPresent());

return result.get();

그런 다음 나머지 코드에서 EvenInteger실제로 고르게 될 것임을 확신하고 의존 할 수 있습니다 EvenInteger::of.


일반적으로 선택 사항은 잠재적으로 유효하지 않은 데이터를 나타냅니다. 이것은 많은 TryParse 메소드와 유사하며, 입력 유효성을 나타내는 플래그와 데이터를 모두 리턴합니다. 준수 시간 확인.
Basilevs 2012 년

1

유효성 검사 결과가 동일하고 유효성이 동일한 클래스에서 수행되는 경우 유효성 검사를 두 번 수행하면 문제가됩니다. 그것은 당신의 모범이 아닙니다. 리팩토링 된 코드에서 가장 먼저 수행되는 검사는 입력 유효성 검사이며, 실패하면 새로운 입력이 요청됩니다. 이 클래스의 외부에서 호출 될 수있는 퍼블릭 메소드의 padToEvenWithEven에, 상기 제 2 체크는 제 완전히 독립 하고 다른 결과 (제외)을 갖는다.

문제는 실수로 동일한 코드가 비 건조에 대해 혼동되는 문제와 유사합니다. 구현과 디자인을 혼동하고 있습니다. 그것들은 동일하지 않으며, 당신이 동일한 하나 또는 12 개의 라인을 가지고 있다고해서 그들이 동일한 목적을 제공하고 있으며 영원히 상호 교환 될 수 있다는 것을 의미하지는 않습니다. 또한 수업이 너무 많을 수도 있지만 장난감 예제 일뿐이므로 건너 뛰십시오.

이것이 성능 문제인 경우, 유효성 검사를 수행하지 않는 개인용 메소드를 작성하여 문제점을 해결할 수 있습니다. 이는 유효성 검증을 수행 한 후 공용 padToEvenWithEven이 호출 한 후 다른 메소드가 대신 호출하는 유효성 검증을 수행하지 않습니다. 성능 문제가 아닌 경우 다른 방법으로 할당 된 작업을 수행하는 데 필요한 검사를 수행하십시오.


OP는 함수 던지기를 방지하기 위해 입력 유효성 검사가 수행되었다고 언급했습니다. 따라서 검사는 완전히 의존적이며 의도적으로 동일합니다.
D Drmmr

@ DDrmmr : 아니오, 그들은 의존적이지 않습니다. 계약의 일부이기 때문에 함수가 발생합니다. 내가 말했듯이 대답은 개인 메소드를 작성하여 예외를 던지는 것을 제외한 모든 것을 수행하고 공개 메소드가 개인 메소드를 호출하도록하는 것입니다. public 메소드는 확인되지 않은 입력을 처리하기 위해 다른 목적으로 사용되는 검사를 유지합니다.
jmoreno
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.