태그 보관물: constructors

constructors

C #의 생성자 매개 변수 유효성 검사-모범 사례 if (String.IsNullOrEmpty(text))

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

간단한 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);
        }
    }
}


답변

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

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

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

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

답변

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

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

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


답변

예외적으로 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하는 것입니다.

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


답변

이 경우 팩토리 방법을 사용합니다. 기본적으로 클래스에는 전용 생성자가 있고 객체의 인스턴스를 반환하는 팩토리 메소드가 있습니다. 초기 매개 변수가 유효하지 않으면 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 상태를 유효하게 유지하면서 예외 및 성능 불이익을 피할 수 있습니다.


답변

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

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

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

답변

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

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


답변

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