C #의 생성자 매개 변수 유효성 검사-모범 사례


34

생성자 매개 변수 유효성 검사에 대한 모범 사례는 무엇입니까?

간단한 C #을 가정 해 봅시다.

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

예외를 던질 수 있습니까?

내가 직면 한 대안은 인스턴스화하기 전에 사전 검증이었습니다.

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}

10
"예외를 던질 수 있습니까?" 대안은 무엇입니까?
S.Lott

10
@ S.Lott : 아무것도하지 마십시오. 기본값을 설정하십시오. 메시지를 콘솔 / 로그 파일에 인쇄하십시오. 종을 울리다. 표시등이 깜박입니다.
FrustratedWithFormsDesigner

3
@FrustratedWithFormsDesigner : "아무것도?" "텍스트가 비어 있지 않음"제약 조건은 어떻게 충족됩니까? "기본값 설정"? 텍스트 인수 값은 어떻게 사용됩니까? 다른 아이디어는 괜찮지 만,이 두 가지 아이디어는이 클래스의 제약 조건을 위반하는 상태의 객체로 이어질 것입니다.
S.Lott

1
@ S.Lott 자신을 반복하는 것에 대한 두려움 때문에, 내가 알지 못하는 다른 접근법이있을 가능성에 개방적이었습니다. 나는 모두가 아는 것은 아니라고 믿습니다.
MPelletier

3
@MPelletier, 미리 매개 변수를 확인하면 DRY를 위반하게됩니다. (자신을 반복하지 마십시오)이 사전 유효성 검사를이 클래스를 인스턴스화하려는 코드로 복사해야합니다.
CaffGeek

답변:


26

생성자에서 모든 유효성 검사를 수행하는 경향이 있습니다. 거의 항상 불변의 객체를 생성하기 때문에 이것은 필수입니다. 귀하의 특정 사례에 대해서는 이것이 허용됩니다.

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

.NET 4를 사용하는 경우이 작업을 수행 할 수 있습니다. 물론 이것은 공백 만 포함 된 문자열을 유효하지 않은 것으로 간주하는지 여부에 따라 다릅니다.

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");

그의 "특정 사례"가 그러한 특정 조언을 제공 할만큼 구체적인지 확실하지 않습니다. 우리는 이 문맥에서 예외를 던지거나 객체가 어쨌든 존재하도록 허용하는 것이 의미 가 있는지 전혀 모른다 .
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner-당신이 이것을 저에게 투표했다고 가정합니까? 물론 내가 외삽하고 있지만 입력 텍스트가 비어있는 경우이 이론적 객체가 유효하지 않아야합니다. Init예외가 잘 작동하고 객체가 항상 유효한 상태를 유지하도록 할 때 일종의 오류 코드를 받기 위해 호출 해야합니까?
ChaosPandion

2
나는 첫 번째 경우를 두 가지 별도의 예외로 나누는 경향이 있습니다. 하나는 null을 테스트하고 an을 던지고 ArgumentNullException다른 하나는 empty를 테스트하고 a를 던집니다 ArgumentException. 예외 처리에 원하는 경우 발신자가 더 까다로울 수 있습니다.
Jesse C. Slicer

1
@Jesse-나는 그 기술을 사용했지만 그 전반적인 유용성에 대해 여전히 울타리에 있습니다.
ChaosPandion

3
불변 개체의 경우 +1! 나는 또한 가능한 한 그것들을 사용합니다 (개인적으로 거의 항상 찾습니다).

22

많은 사람들이 생성자가 예외를 던져서는 안된다고 말합니다. 예를 들어이 페이지의 KyleG 가 바로 그 일을합니다. 솔직히, 나는 왜 그렇지 않은 이유를 생각할 수 없습니다.

C ++에서 생성자에서 예외를 throw하는 것은 좋지 않은 아이디어입니다. 초기화되지 않은 객체를 포함하는 할당 된 메모리가 남아 있기 때문에 (예 : 고전적인 메모리 누수). 그것은 아마도 오명에서 비롯된 부분 일 것입니다. 많은 구식 C ++ 개발자들이 C # 학습을 절반 비방하고 C ++에서 알고있는 것을 적용했습니다. 반대로 Objective-C에서 Apple은 할당 단계를 초기화 단계와 분리하여 해당 언어의 생성자가 예외 throw 할 수 있습니다 .

C #은 생성자 호출에 실패하여 메모리를 누출시킬 수 없습니다. .NET 프레임 워크의 일부 클래스조차도 생성자에서 예외를 throw합니다.


당신은 C ++ 주석으로 깃털을 주름지게 할 것입니다. :)
ChaosPandion

5
어, C ++는 훌륭한 언어이지만, 나의 애완 동물 친구 중 하나는 한 언어를 잘 배우고 항상 그렇게 쓰는 사람들입니다 . C #은 C ++이 아닙니다.
앤트

10
ctor가 관리되지 않는 리소스를 할당 한 후에는 절대 폐기 되지 않기 때문에 C #의 ctor에서 예외를 throw하는 것은 여전히 ​​위험 할 수 있습니다 . 그리고 불완전하게 구성된 객체가 마무리되는 시나리오에 대비하여 최종화를 작성해야합니다. 그러나 이러한 시나리오는 비교적 드 rare니다. C # Dev Center에 대한 다음 기사에서는 이러한 시나리오와 방어 적으로 코드를 작성하는 방법을 다루므로 자세한 내용은 해당 공간을 확인하십시오.
Eric Lippert

6
뭐라고? 의 ctor에서 예외를 던지고 개체를 만들 수없는 경우는 C ++로 할 수있는 유일한 방법이며,이 이유가 없다 메모리 누수의 원인은 (하지만 분명히 그까지).
jk.

@ jk : 흠, 맞아! 대안은 좋지 않은 객체를 "좀비"로 표시하는 것인데, STL은 예외를 처리하는 대신 분명히 수행합니다. yosefk.com/c++fqa/exceptions.html#fqa-17.2 Objective-C의 솔루션은 훨씬 더 깔끔 합니다.
개미

12

예외적으로 IFF를 발생 시키면 클래스의 의미 적 사용과 관련하여 클래스를 일관된 상태로 만들 수 없습니다. 그렇지 않으면하지 마십시오. 개체가 일관성이없는 상태로 존재하지 않도록하십시오. 여기에는 완전한 생성자를 제공하지 않는 것이 포함됩니다 (객체가 실제로 완전히 빌드되기 전에 빈 생성자 + initialize () 사용) ... JUST SAY NO!

한 번에 모든 사람들이 그렇게합니다. 나는 다른 날에 좁은 범위 내에서 매우 좁게 사용 된 물체를 위해 그것을했다. 언젠가는 저나 다른 누군가가 그 슬립 가격을 실제로 지불 할 것입니다.

"생성자"는 클라이언트가 객체를 만들기 위해 호출하는 것을 의미합니다. "Constructor"라는 이름의 실제 구성체가 아닌 다른 것이 될 수도 있습니다. 예를 들어, C ++에서 이와 같은 것은 IMNSHO 원칙을 위반하지 않습니다.

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

를 만드는 유일한 방법 은 실제 "생성자"가 작업을 완료하지 않아도 유효하지 않은 객체가 존재하지 않도록하는 원칙을 funky_object호출 build_funky하는 것입니다.

그것은 의심스러운 이익 (아마도 손실)을 위해 많은 추가 작업입니다. 여전히 예외 경로를 선호합니다.


9

이 경우 팩토리 방법을 사용합니다. 기본적으로 클래스에는 전용 생성자가 있고 객체의 인스턴스를 반환하는 팩토리 메소드가 있습니다. 초기 매개 변수가 유효하지 않으면 null을 반환하고 호출 코드가 수행 할 작업을 결정하도록합니다.

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

조건이 예외적 인 경우가 아니면 예외를 던지지 마십시오. 이 경우 빈 값을 쉽게 전달할 수 있다고 생각합니다. 이 경우이 패턴을 사용하면 MyClass 상태를 유효하게 유지하면서 예외 및 성능 불이익을 피할 수 있습니다.


1
나는 이점을 보지 못한다. 생성자의 예외를 잡을 수있는 try-catch를 사용하는 것보다 공장 결과를 null로 테스트하는 것이 바람직한 이유는 무엇입니까? 귀하의 접근 방식은 "예외를 무시하고 오류의 메소드에서 반환 값을 명시 적으로 확인하는 것으로 돌아가십시오". 우리는 예외가 일반적으로 모든 메소드 리턴 값을 명시 적으로 테스트하여 오류를 테스트하는 것보다 우수하다는 점을 오래 전부터 받아들 였으므로 조언은 의심스러운 것 같습니다. 일반 규칙이 여기에 적용되지 않는다고 생각하는 이유에 대한 정당성을 확인하고 싶습니다.
DW

우리는 예외가 리턴 코드보다 우월하다는 것을 결코 받아들이지 않았습니다. 너무 자주 던져지면 엄청난 성능 저하가 될 수 있습니다. 그러나 관리되지 않는 것을 할당하면 .NET에서도 생성자에서 예외를 throw하면 메모리가 누출됩니다. 이 경우를 마무리하려면 파이널 라이저에서 많은 작업을 수행해야합니다. 어쨌든, 팩토리는 여기서 좋은 아이디어이며 TBH는 null을 반환하는 대신 예외를 throw해야합니다. '불량'클래스를 몇 개만 생성한다고 가정하면 팩토리 던지기가 바람직합니다.
gbjbaanb

2
  • 생성자는 부작용이 없어야합니다.
    • 개인 필드 초기화 이상의 것은 부작용으로 간주해야합니다.
    • 부작용이있는 생성자는 SRP (Single-Responsibilty-Principle)를 중단하고 OOP (Object-Oriented-Programming) 정신과 상반됩니다.
  • 생성자는 가벼워 야하며 절대 실패해서는 안됩니다.
    • 예를 들어, 생성자 내부에 try-catch 블록이 표시되면 항상 떨립니다. 생성자는 예외 나 로그 오류를 발생시키지 않아야합니다.

이 가이드 라인을 합리적으로 질문하고 "하지만이 규칙을 따르지 않고 코드가 제대로 작동합니다!"라고 말할 수 있습니다. 나는 "그렇지 않을 때까지는 사실일지도 모른다"고 대답 할 것이다.

  • 생성자 내부의 예외 및 오류는 예상치 못한 결과입니다. 그들이 그렇게하지 않으면, 미래의 프로그래머들은 이러한 생성자 호출을 방어 코드로 둘러싸 지 않을 것입니다.
  • 생산에 문제가 있으면 생성 된 스택 추적을 구문 분석하기 어려울 수 있습니다. 스택 추적의 상단은 생성자 호출을 가리킬 수 있지만 생성자에서 많은 일이 발생하지만 실패한 실제 LOC를 가리 키지 않을 수 있습니다.
    • 이것이 사실 인 많은 .NET 스택 추적을 구문 분석했습니다.

0

MyClass 가하는 일에 달려 있습니다. MyClass가 실제로 데이터 저장소 클래스이고 매개 변수 텍스트가 연결 문자열 인 경우 ArgumentException을 발생시키는 것이 가장 좋습니다. 그러나 MyClass가 StringBuilder 클래스 (예 :)이면 비워 둘 수 있습니다.

따라서 매개 변수 텍스트가 메소드에 얼마나 필수적인지에 달려 있습니다. 객체가 null 또는 공백 값으로 의미가 있습니까?


2
나는 질문에 클래스가 실제로 문자열이 비어 있지 않아야 함을 암시한다고 생각합니다. StringBuilder 예제에서는 그렇지 않습니다.
Sergio Acosta

그렇다면 대답은 '예'여야합니다. 예외를 던지는 것은 허용됩니다. 이 오류를 시도 / 잡지 않으려면 팩토리 패턴을 사용하여 빈 문자열 케이스를 포함하는 객체 생성을 처리 할 수 ​​있습니다.
Steven Striga

0

내가 선호하는 것은 기본값을 설정하는 것이지만 Java에는 이와 같은 작업을 수행하는 "Apache Commons"라이브러리가 있다는 것을 알고 있으며 이는 상당히 좋은 생각이라고 생각합니다. 유효하지 않은 값으로 인해 개체를 사용할 수없는 상태로 만드는 경우 예외가 발생하는 문제가 표시되지 않습니다. 문자열이 좋은 예는 아니지만 가난한 사람의 DI를위한 것이면 어떨까요? 인터페이스 null대신에 값을 전달 하면 작동 할 수 없습니다 ICustomerRepository. 이와 같은 상황에서 예외를 던지는 것이 올바른 처리 방법입니다.


-1

C ++에서 일할 때 메모리 누수 문제로 인해 생성자에서 예외를 던지는 데 사용하지 않았지만 어려운 방법을 배웠습니다. C ++로 작업하는 사람은 메모리 누수가 얼마나 어렵고 문제가 될 수 있는지 알고 있습니다.

그러나 c # / Java에 있으면 가비지 수집기가 메모리를 수집하기 때문에이 문제가 없습니다. C #을 사용하는 경우 데이터 제약 조건이 보장되도록 훌륭하고 일관된 방법으로 속성을 사용하는 것이 좋습니다.

public class MyClass
{
    private string _text;
    public string Text 
    {
        get
        {
            return _text;
        } 
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Text cannot be empty");
            _text = value;
        } 
    }

    public MyClass(string text)
    {
        Text = text;
        // continue with normal construction
    }
}

-3

예외를 던지는 것은 클래스 사용자에게 큰 고통이 될 것입니다. 유효성 검사가 문제이면 일반적으로 개체를 2 단계 프로세스로 만듭니다.

1-인스턴스 작성

2-반환 결과와 함께 Initialize 메서드를 호출합니다.

또는

유효성 검사를 수행 할 수있는 클래스를 만드는 메서드 / 함수를 호출하십시오.

또는

isValid 플래그를 사용하십시오. 특히 마음에 들지는 않지만 일부 사람들은 좋아합니다.


3
@ 덩크, 그게 잘못이야.
CaffGeek

4
@ 차드, 그것은 당신의 의견이지만, 내 의견은 틀리지 않습니다. 그것은 당신과 다릅니다. try-catch 코드를 읽기가 훨씬 어려워 사람들이 일반적인 오류 처리보다 사소한 오류 처리를 위해 try-catch를 사용할 때 훨씬 더 많은 오류가 발생하는 것을 보았습니다. 그것은 내 경험이었고 잘못이 아닙니다. 그것이 무엇입니까.
덩크

5
요점은 생성자를 호출 한 후 입력이 유효하지 않아 생성하지 않으면 EXCEPTION이라는 것입니다. 단순히 객체를 만들고이라는 플래그를 설정하면 앱의 데이터가 일관성이 없거나 유효하지 않은 상태에 있습니다 isValid = false. 클래스가 유효하면 클래스를 만든 후에 테스트해야하는 것은 끔찍하고 오류가 발생하기 쉬운 디자인입니다. 그리고 당신은 말했듯이 생성자가 있고 initialize를 호출합니다 ... initialize를 호출하지 않으면 어떻게됩니까? 그럼 뭐야? Initialize에서 잘못된 데이터를 얻는 경우 지금 예외를 throw 할 수 있습니까? 수업을 사용하기 어렵지 않아야합니다.
CaffGeek

5
@ 덩크, 예외를 사용하기 어려운 것을 발견하면 잘못 사용하고 있습니다. 간단히 말해서, 잘못된 입력이 생성자에 제공되었습니다. 예외입니다. 수정되지 않은 경우 앱이 계속 실행되지 않아야합니다. 기간. 만약 그렇다면, 당신은 더 나쁜 문제, 나쁜 데이터로 끝납니다. 예외를 처리하는 방법을 모르는 경우 단순히 로깅 및 실행 중지를 의미하더라도 처리 할 수있을 때까지 호출 스택을 버블 링하도록합니다. 간단히 말해 예외를 처리하거나 테스트 할 필요가 없습니다. 그들은 단순히 작동합니다.
CaffGeek

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