속성 getter 또는 setter 내에서 예외를 throw하는 것이 적절한시기는 언제입니까? 언제 적절하지 않습니까? 왜? 주제에 대한 외부 문서에 대한 링크가 도움이 될 것입니다 ... Google은 놀랍게도 거의 나타나지 않았습니다.
속성 getter 또는 setter 내에서 예외를 throw하는 것이 적절한시기는 언제입니까? 언제 적절하지 않습니까? 왜? 주제에 대한 외부 문서에 대한 링크가 도움이 될 것입니다 ... Google은 놀랍게도 거의 나타나지 않았습니다.
답변:
Microsoft는 http://msdn.microsoft.com/en-us/library/ms229006.aspx 에서 속성 디자인 방법에 대한 권장 사항을 제공합니다 .
기본적으로 속성 게터는 항상 호출하기에 안전한 경량 접근 자일 것을 권장합니다. 예외가 발생해야하는 경우 getter를 메서드로 재 설계하는 것이 좋습니다. setter의 경우 예외가 적절하고 허용 가능한 오류 처리 전략임을 나타냅니다.
인덱서의 경우 Microsoft는 getter와 setter 모두에서 예외를 throw 할 수 있음을 나타냅니다. 실제로 .NET 라이브러리의 많은 인덱서가이 작업을 수행합니다. 가장 일반적인 예외는 ArgumentOutOfRangeException
.
속성 getter에서 예외를 throw하지 않으려는 몇 가지 좋은 이유가 있습니다.
obj.PropA.AnotherProp.YetAnother
.-이런 종류의 구문에서는 예외 catch 문을 삽입 할 위치를 결정하는 데 문제가됩니다.참고로, 속성이 예외를 throw 하도록 설계 되지 않았다고해서 예외가 발생하지 않는다는 의미는 아닙니다. 그렇게하는 코드를 쉽게 호출 할 수 있습니다. 새로운 객체 (문자열과 같은)를 할당하는 간단한 동작조차도 예외를 초래할 수 있습니다. 항상 방어 적으로 코드를 작성하고 호출하는 모든 항목에서 예외를 예상해야합니다.
setter에서 예외를 던지는 것은 잘못된 것이 아닙니다. 결국 주어진 속성에 대해 값이 유효하지 않음을 나타내는 더 좋은 방법은 무엇입니까?
게터의 경우 일반적으로 눈살을 찌푸리며 매우 쉽게 설명 할 수 있습니다. 일반적으로 속성 게터는 객체의 현재 상태를보고합니다. 따라서 getter가 throw하는 것이 합리적인 유일한 경우는 상태가 유효하지 않은 경우입니다. 그러나 일반적으로 처음에 유효하지 않은 객체를 얻거나 정상적인 수단을 통해 유효하지 않은 상태로 만드는 것이 불가능하도록 클래스를 설계하는 것도 일반적으로 좋은 생각으로 간주됩니다 (즉, 항상 생성자에서 전체 초기화를 보장하고 상태 유효성 및 클래스 불변과 관련하여 메서드를 예외 안전으로 만드십시오.) 이 규칙을 고수하는 한, 속성 게터는 잘못된 상태를보고해야하는 상황에 처해서는 안됩니다.
내가 아는 한 가지 예외가 있으며 실제로는 다소 중요한 예외 IDisposable
입니다. Dispose
객체를 유효하지 않은 상태로 만드는 방법으로 특별히 고안되었으며 ObjectDisposedException
,이 경우에 사용되는 특별한 예외 클래스도 있습니다. 객체가 삭제 된 후 ObjectDisposedException
속성 getter ( Dispose
자체 제외 )를 포함한 모든 클래스 멤버에서 throw하는 것은 완벽하게 정상 입니다.
IDisposable
후 쓸모해야한다을 Dispose
. 멤버를 호출 할 때 사용할 Dispose
수 없게 된 리소스를 사용해야하는 경우 (예 : 멤버가 닫힌 스트림에서 데이터를 읽을 것임) 멤버는 ObjectDisposedException
예를 들어 유출하지 않고 던져야 ArgumentException
합니다. 특정 필드의 값을 요구하는 것보다 폐기 후 (마지막 입력 된 값을 산출) 이러한 속성을 읽을 수 있도록하는 것이 훨씬 더 도움이 될 것입니다 ...
Dispose
이러한 속성을 모두 읽을 때까지 연기됩니다. 한 스레드가 객체에 대한 읽기 차단을 사용하고 다른 스레드가 객체를 닫을 Dispose
수 있고 Dispose
데이터가. 그렇지 않으면 존재하지 않아도되는 상황에서 Close
와 사이에 인위적인 구별을 강요해서는 안됩니다 Dispose
.
Get...
대신 방법 으로 더 나을 수도 있습니다 . 여기서 예외는 속성을 제공해야하는 기존 인터페이스를 구현해야하는 경우입니다.
getter에는 거의 적합하지 않으며 때로는 setter에도 적합합니다.
이러한 종류의 질문에 가장 적합한 리소스는 Cwalina 및 Abrams의 "프레임 워크 디자인 지침"입니다. 이 책은 제본 된 책으로 제공되며 대부분 온라인에서도 볼 수 있습니다.
섹션 5.2 : 부동산 디자인
속성 게터에서 예외를 던지는 것을 피하십시오. 속성 게터는 간단한 작업이어야하며 전제 조건이 없어야합니다. getter가 예외를 throw 할 수있는 경우 메서드로 다시 디자인해야합니다. 이 규칙은 인수 유효성 검사의 결과로 예외가 예상되는 인덱서에는 적용되지 않습니다.
이 지침은 속성 게터에만 적용됩니다. 속성 설정자에서 예외를 throw하는 것은 괜찮습니다.
ObjectDisposedException
개체가 Dispose()
호출되고 나중에 속성 값을 요청 하면 던지는 것을 고려해야하는 지침과 어떤 관련이 있습니까? 지침은 "ObjectDisposedExcpetion을 던지는 것을 고려해야하는 경우에 객체가 삭제 된 경우가 아니라면 속성 getter에서 예외를 던지지 마십시오"여야하는 것처럼 보입니다.
예외에 대한 한 가지 좋은 접근 방식은 다음과 같이 자신과 다른 개발자를 위해 코드를 문서화하는 데 사용하는 것입니다.
예외는 예외적 인 프로그램 상태에 대한 것이어야합니다. 이것은 당신이 원하는 곳에 그것들을 쓰는 것이 좋다는 것을 의미합니다!
그것들을 getter에 넣으려는 한 가지 이유는 클래스의 API를 문서화하는 것입니다. 프로그래머가 잘못 사용하려고하면 소프트웨어가 예외를 던지면 잘못 사용하지 않을 것입니다! 예를 들어 데이터 읽기 프로세스 중에 유효성 검사가있는 경우 데이터에 치명적인 오류가있는 경우 계속해서 프로세스 결과에 액세스 할 수없는 경우가 있습니다. 이 경우 다른 프로그래머가이 조건을 확인하도록 오류가있는 경우 출력을 throw 할 수 있습니다.
이는 서브 시스템 / 방법 / 무엇이든간에 가정과 경계를 문서화하는 방법입니다. 일반적인 경우에는 잡히면 안됩니다! 이는 또한 시스템이 예상 한 방식으로 함께 작동하면 절대로 처리되지 않기 때문입니다. 예외가 발생하면 코드 조각의 가정이 충족되지 않았 음을 보여줍니다. 예를 들어 주변 세계와 상호 작용하지 않습니다. 원래 의도 된 것입니다. 이 목적으로 작성된 예외를 발견하면 시스템이 예측할 수없는 / 일관되지 않은 상태에 들어갔다는 의미 일 수 있습니다. 이것은 궁극적으로 감지 / 디버그가 훨씬 더 어려운 데이터 또는 이와 유사한 충돌 또는 손상으로 이어질 수 있습니다.
예외 메시지는 오류를보고하는 매우 거친 방법입니다. 한꺼번에 수집 할 수 없으며 실제로 문자열 만 포함합니다. 이로 인해 입력 데이터의 문제를보고하는 데 적합하지 않습니다. 정상적인 실행에서 시스템 자체는 오류 상태가되어서는 안됩니다. 결과적으로 메시지는 사용자가 아닌 프로그래머를 위해 설계되어야합니다. 입력 데이터에서 잘못된 것을 발견하여보다 적합한 (사용자 정의) 형식으로 사용자에게 전달할 수 있습니다.
이 규칙에 대한 예외 (haha!)는 예외가 제어되지 않고 사전에 확인할 수없는 IO와 같은 것입니다.
이것은 모두 MSDN에 문서화되어 있지만 (다른 답변에 링크 됨) 여기에 일반적인 경험 법칙이 있습니다 ...
setter에서 속성이 유형을 넘어서 유효성을 검사해야하는 경우. 예를 들어 PhoneNumber라는 속성에는 정규식 유효성 검사가 있어야하며 형식이 유효하지 않으면 오류가 발생해야합니다.
게터의 경우 값이 null 일 수 있지만 대부분의 경우 호출 코드에서 처리 할 수 있습니다 (디자인 지침에 따라).
MSDN : 표준 예외 유형 포착 및 던지기
이것은 매우 복잡한 질문이며 개체가 어떻게 사용되는지에 따라 다릅니다. 경험상 "지연 바인딩"인 속성 getter 및 setter는 예외를 throw해서는 안되며, 독점적으로 "early binding"인 속성은 필요할 때 예외를 throw해야합니다. BTW, Microsoft의 코드 분석 도구는 속성 사용을 너무 좁게 정의하고 있습니다.
"지연 바인딩"은 속성이 반사를 통해 발견됨을 의미합니다. 예를 들어 Serializeable "속성은 속성을 통해 객체를 직렬화 / 역 직렬화하는 데 사용됩니다. 이런 종류의 상황에서 예외를 throw하면 치명적인 방식으로 문제가 발생하며 예외를 사용하여 더 강력한 코드를 만드는 좋은 방법이 아닙니다.
"초기 바인딩"은 컴파일러에 의해 코드에서 속성 사용이 바인딩됨을 의미합니다. 예를 들어 작성하는 일부 코드가 속성 getter를 참조하는 경우입니다. 이 경우 이해가되는 경우 예외를 throw하는 것이 좋습니다.
내부 속성이있는 개체는 해당 속성의 값에 따라 상태가 결정됩니다. 객체의 내부 상태를 인식하고 민감한 속성을 표현하는 속성은 후기 바인딩에 사용해서는 안됩니다. 예를 들어, 열고 액세스 한 다음 닫아야하는 개체가 있다고 가정 해 보겠습니다. 이 경우 open을 먼저 호출하지 않고 속성에 액세스하면 예외가 발생합니다. 이 경우 예외를 던지지 않고 예외를 던지지 않고 값에 대한 코드 액세스를 허용한다고 가정 해 보겠습니다. 말도 안되는 게터에서 값을 얻었음에도 불구하고 코드는 행복해 보일 것입니다. 이제 getter를 호출하는 코드를 나쁜 상황에 놓았습니다. 값이 말도 안되는지 확인하기 위해 값을 확인하는 방법을 알아야하기 때문입니다. 이는 코드가 유효성을 검사하기 위해 속성 getter에서 가져온 값에 대해 가정해야 함을 의미합니다. 이것이 잘못된 코드가 작성되는 방식입니다.
어떤 예외를 던질 지 확실하지 않은 곳에이 코드가있었습니다.
public Person
{
public string Name { get; set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
if (person.Name == null) {
throw new Exception("Name of person is null.");
// I was unsure of which exception to throw here.
}
Console.WriteLine("Name is: " + person.Name);
}
생성자에서 인수로 강제하여 모델이 속성이 null이되는 것을 방지했습니다.
public Person
{
public Person(string name)
{
if (name == null) {
throw new ArgumentNullException(nameof(name));
}
Name = name;
}
public string Name { get; private set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
Console.WriteLine("Name is: " + person.Name);
}