예외를 던지거나 코드 실패


52

이 스타일에 반대되는 장단점이 있는지 궁금합니다.

private void LoadMaterial(string name)
{
    if (_Materials.ContainsKey(name))
    {
        throw new ArgumentException("The material named " + name + " has already been loaded.");
    }

    _Materials.Add(
        name,
        Resources.Load(string.Format("Materials/{0}", name)) as Material
    );
}

이 방법은 각각에 대해 name한 번만 실행 해야합니다 . _Materials.Add()같은 시간에 여러 번 호출되면 예외가 발생합니다 name. 결과적으로 경비원이 완전히 중복되거나 덜 명백한 이점이 있습니까?

누군가 관심이 있다면 C #, Unity입니다.


3
가드없이 재료를 두 번 적재하면 어떻게됩니까? 않는 _Materials.Add예외를 던져?
user253751

2
실제로 나는 예외를 던질 때 이미 언급했다 : P
async

9
참고 사항 : 1) string.Format예외 메시지를 작성하기 위해 문자열 연결을 사용 하는 것이 좋습니다. 2) as캐스트가 실패 할 것으로 예상되는 경우 에만 사용 하고 결과를 확인하십시오 null. 항상을 얻으려면 다음 Material과 같은 캐스트를 사용하십시오 (Material)Resources.Load(...). 명확한 캐스트 예외는 나중에 발생하는 널 참조 예외보다 디버그하기가 더 쉽습니다.
코드 InChaos

3
이 경우 발신자가 동료 LoadMaterialIfNotLoaded또는 ReloadMaterial이 이름으로 개선이 가능할 수도 있습니다.
jpmc26

1
다른 참고 사항 :이 패턴은 때때로 항목을 목록에 추가하기에 좋습니다. 그러나 큰 목록을 사용하면 성능 문제가 발생하는 O (n ^ 2) 상황이 발생하므로 전체 목록을 채우는 데이 패턴을 사용하지 마십시오. 100 개 항목에서는 눈치 채지 못하지만 1000 개 항목에서는 확실히 10.000 개 항목에서 큰 병목 현상이 발생합니다.
Pieter B

답변:


109

이점은 "사용자 정의"예외에이 기능 을 구현하는 방법을 모르고이 기능 호출하는 모든 사람에게 의미있는 오류 메시지가 있다는 것입니다 (향후에는 귀하가 될 수 있습니다).

물론,이 경우 "표준"예외의 의미를 추측 할 수 있지만 코드에서 이상한 버그를 발견하지 않고 계약을 위반했다는 것을 여전히 명확하게 알 수 있습니다.


3
동의하다. 전제 조건 위반에 대한 예외를 던지는 것이 거의 항상 좋습니다.
Frank Hileman

22
"이점은"사용자 정의 "예외에이 기능을 구현하는 방법을 알지 못하고이 기능을 호출하는 모든 사람에게 의미있는 오류 메시지가 있다는 것입니다. 최고의 조언.
async

2
"명시적인 것이 묵시적인 것보다 낫다." - 파이썬의 선
jpmc26

4
발생한 오류 _Materials.Add가 호출자에게 전송하려는 오류가 아니더라도 오류의 레이블을 다시 지정하는이 방법은 비효율적이라고 생각합니다. 모든 내용은 성공 의 호출 LoadMaterial(정상 작동) 동일한 정신 테스트를 할 것이다 에서 LoadMaterial다시 다음과 _Materials.Add. 더 많은 레이어가 랩핑되어 각각 동일한 스타일을 사용하는 경우 동일한 테스트를 여러 번 수행 할 수도 있습니다.
Marc van Leeuwen

15
... 대신 _Materials.Add무조건 으로 뛰어 들고 잠재적 인 오류 를 포착 하고 처리기에서 다른 오류를 처리하십시오. 추가 작업은 이제 효율성에 대해 전혀 걱정하지 않는 예외적 인 실행 경로 인 잘못된 경우에만 수행됩니다.
Marc van Leeuwen

100

Ixrec의 답변에 동의합니다 . 그러나 세 번째 대안 인 함수를 dem 등원으로 만드는 것을 고려할 수도 있습니다. 즉,을 던지는 대신 일찍 반환합니다 ArgumentException. LoadMaterial매번 호출하기 전에 이미로드되어 있는지 확인해야하는 경우이 방법이 종종 바람직합니다 . 전제 조건이 적을수록 프로그래머의인지 부하가 ​​줄어 듭니다.

실제로 프로그래머가 실수하는 경우 예외를 던지는 것이 바람직합니다. 여기서로드하기 전에 런타임에 확인할 필요없이 자료가 이미로드 된 경우 컴파일 타임에 명확하고 알려야합니다.


2
반면, 자동으로 기능이 실패하면 예기치 않은 동작이 발생하여 나중에 발생하는 버그를 찾기가 더 어려워 질 수 있습니다.
Philipp

30
@ 필립 기능은 자동으로 실패하지 않습니다. dem 등원이라는 것은 여러 번 실행하면 한 번 실행하는 것과 같은 효과가 있음을 의미합니다. 즉, 자재가 이미로드 된 경우 사양 ( "재료를로드해야 함")이 이미 충족되었으므로 아무 것도 수행 할 필요가 없습니다. 우리는 그냥 돌아올 수 있습니다.
Warbo

8
나는 이것을 실제로 더 좋아한다. 물론 응용 프로그램 요구 사항에서 제공하는 제약 조건에 따라 이는 잘못된 것일 수 있습니다. 다시, 나는 dem 등식 방법의 절대 팬입니다.
FP

6
@Philipp에 대해 생각 LoadMaterial하면 다음과 같은 제약 조건이 있습니다. "호출 한 후에는 항상 재료가로드되고로드 된 재료 수가 줄어들지 않았습니다." 세 번째 제약 조건에 대한 예외를 던지는 것은 "재료를 두 번 추가 할 수 없습니다"는 어리 석고 반 직관적 인 것 같습니다. 함수를 pot 등원으로 만들면 코드의 실행 순서 및 응용 프로그램 상태에 대한 의존도가 줄어 듭니다. 그것은 나를 위해 이중 승리처럼 보인다.
Benjamin Gruenbaum

7
나는이 아이디어를 좋아하지만 사소한 변경을 제안합니다. 호출자가 실제로 응용 프로그램 논리에 관심이 있으면이를 확인하고 예외를 throw 할 수 있습니다.
user949300

8

당신이 물어봐야 할 근본적인 질문은 다음과 같습니다. 함수를위한 인터페이스가 무엇입니까? 특히, _Materials.ContainsKey(name)조건이 함수의 전제 조건입니까?

전제 조건 이 아닌 경우 , 함수는의 모든 가능한 vales에 대해 잘 정의 된 결과를 제공해야합니다 name. 이 경우, name일부가 아닌 경우 예외가 발생 _Materials하여 함수 인터페이스의 일부가 됩니다. 이는 인터페이스 문서의 일부가되어야한다는 것을 의미하며, 앞으로 해당 예외를 변경하기로 결정한 경우 인터페이스 변경 이 크게 중단 될 것 입니다.

더 흥미로운 질문은 그것이 전제 조건이라면 어떻게되는지이다. 이 경우 전제 조건 자체가 함수 인터페이스의 일부가되지만이 조건을 위반할 때 함수가 작동하는 방식이 인터페이스의 일부일 필요 는 없습니다 .

이 경우, 전제 조건 위반을 확인하고 오류를보고하는 게시 된 접근 방식은 방어 프로그래밍이라고 합니다. 방어 프로그래밍은 실수했을 때 사용자에게 조기에 알리고 가짜 인수로 함수를 호출한다는 점에서 우수합니다. 사용자 코드가 특정 조건에서 전제 조건 위반을 처리하는 것으로 사용자 코드에 의존 할 수 있으므로 유지 보수 부담이 크게 증가한다는 점에서 좋지 않습니다. 특히, 런타임 점검이 향후 성능 병목 현상이되는 것을 발견 한 경우 (단순한 경우는 아니지만 더 복잡한 전제 조건에서는 매우 일반적 임)이를 더 이상 제거하지 못할 수 있습니다.

이러한 단점은 매우 중요 할 수 있으며, 이는 특정 분야에서 방어 프로그래밍에 나쁜 평판을 주었다. 그러나 초기 목표는 여전히 유효합니다. 우리는 기능을 사용하는 사용자가 실수했을 때 조기에 알아 차리기를 원합니다.

따라서 오늘날 많은 개발자들은 이러한 종류의 문제에 대해 약간 다른 접근법을 제안합니다. 예외를 던지는 대신, 전제 조건을 확인하기 위해 assert-like 메커니즘을 사용합니다. 즉, 사용자가 실수를 조기에 발견 할 수 있도록 디버그 빌드에서 사전 조건을 확인할 수 있지만 기능 인터페이스의 일부는 아닙니다. 그 차이는 언뜻보기에 미묘 해 보일 수 있지만 실제로는 큰 차이를 만들 수 있습니다.

기술적으로, 전제 조건을 위반 한 함수를 호출하는 것은 정의되지 않은 동작입니다. 그러나 구현시 해당 사례를 감지하고 그러한 경우 사용자에게 즉시 알릴 수 있습니다. 유감스럽게도 예외는 사용자 코드가 이에 반응하여 자신의 존재에 의존 할 수 있기 때문에이를 구현하는 좋은 도구가 아닙니다.

고전적인 방어 접근 방식의 문제점에 대한 자세한 설명과 주장 스타일 사전 조건 확인을위한 가능한 구현에 대해서는 John Lakos의 CppCon 2014에서 바로 작성된 방어적인 프로그래밍 ( 슬라이드 , 비디오 )을 참조하십시오.


4

여기에 이미 몇 가지 답변이 있지만 여기에 Unity3D를 고려한 답변이 있습니다 (답변은 Unity3D에 매우 고유합니다. 대부분의 상황 에서이 대부분을 다르게 수행합니다).

일반적으로 Unity3D는 전통적으로 예외를 사용하지 않습니다. Unity3D에서 예외를 발생시키는 경우 일반적인 .NET 애플리케이션과 달리 프로그램이 중지되지 않습니다. 대부분 편집기를 일시 중지하도록 구성 할 수 있습니다. 그냥 기록됩니다. 이를 통해 게임을 유효하지 않은 상태로 쉽게 전환 할 수 있으며 오류를 추적하기 어려운 캐스케이드 효과를 만들 수 있습니다. 따라서 Unity의 경우 Add예외를 던지는 것이 특히 바람직하지 않은 옵션입니다.

그러나 일부 플랫폼에서 Mono가 Unity에서 작동하는 방식으로 인해 예외 속도를 검사하는 것이 조기 최적화의 경우가 아닙니다. 실제로 iOS의 Unity3D는 일부 고급 스크립트 최적화를 지원하며 비활성화 된 예외 *는 그 중 하나의 부작용입니다. 이러한 최적화는 많은 사용자에게 매우 유용한 것으로 입증되어 Unity3D에서 예외 사용을 제한하는 것을 고려한 현실적인 사례를 보여 주므로 실제로 고려해야 할 사항입니다. (* 코드가 아닌 엔진에서 관리되는 예외)

Unity에서는 좀 더 전문화 된 접근 방식을 원할 수도 있습니다. 아이러니하게도, 이 글을 쓸 때 매우 투표가 적은 대답 은 Unity3D의 맥락에서 구체적으로 이와 같은 것을 구현할 수있는 한 가지 방법을 보여줍니다 (이와 같은 것은 실제로 받아 들일 수 없으며 심지어 Unity에서는 매우 우아하지 않습니다).

내가 고려해야 할 또 다른 접근법은 실제로 호출자가 관련된 한 오류를 나타내는 것이 아니라 Debug.LogXX함수를 사용하는 것입니다. 이렇게하면 처리되지 않은 예외 (Unity3D가 처리하는 방식 때문에)를 처리하는 것과 같은 동작이 발생합니다. 또한 이것이 실제로 오류인지 고려하십시오 (같은 자료를 두 번로드 해야하는 경우 반드시 오류가 있습니까? 아니면 Debug.LogWarning더 적용 가능한 경우 일 수 있습니다).

그리고 Debug.LogXX예외 대신 함수 와 같은 것을 사용하는 것과 관련하여, GetMaterial과 같은 값을 반환하는 무언가에서 예외가 발생할 때 어떤 일이 발생하는지 고려해야합니다. 오류 로깅과 함께 null을 전달하여 다시 접근하는 경향이 있습니다 ( 다시 Unity에서만 ). 그런 다음 MonoBehaviors에서 null 검사를 사용하여 재료와 같은 종속성이 null 값이 아닌지 확인하고 MonoBehavior가 있으면 비활성화합니다. 몇 가지 종속성이 필요한 간단한 동작의 예는 다음과 같습니다.

    public void Awake()
    {
        _inputParameters = GetComponent<VehicleInputParameters>();
        _rigidbody = GetComponent<Rigidbody>();
        _rigidbodyTransform = _rigidbody.transform;
        _raycastStrategySelector = GetComponent<RaycastStrategySelectionBehavior>();

        _trackParameters =
            SceneManager.InstanceOf.CurrentSceneData.GetValue<TrackParameters>();

        this.DisableIfNull(() => _rigidbody);
        this.DisableIfNull(() => _raycastStrategySelector);
        this.DisableIfNull(() => _inputParameters);
        this.DisableIfNull(() => _trackParameters);
    }

SceneData.GetValue<>예외를 발생시키는 사전에서 함수를 호출한다는 점에서 예제와 비슷합니다. 그러나 예외를 던지는 대신 Debug.LogError일반 예외처럼 스택 추적을 제공하고 null을 반환 하는 예외를 사용 합니다. 다음 * 점검은 동작이 유효하지 않은 상태로 유지되도록하는 대신 동작을 비활성화합니다.

* 수표는 게임 개체를 비활성화 할 때 서식이 지정된 메시지를 인쇄하는 작은 도우미 때문에 사용됩니다 **. if여기 에서 작동하는 간단한 null 검사 (** 도우미의 검사는 Assert와 같은 디버그 빌드에서만 컴파일됩니다. Unity에서와 같은 람다 및 표현식을 사용하면 성능이 저하 될 수 있습니다)


1
파일에 정말로 새로운 정보를 추가하기위한 +1 나는 그것을 기대하지 않았다.
Ixrec

3

나는 두 가지 주요 답변을 좋아하지만 함수 이름을 향상시킬 수 있다고 제안하고 싶습니다. Java에 익숙하기 때문에 YMMV이지만 항목이 이미있는 경우 "추가"메소드는 IMO에서 예외를 발생시키지 않아야합니다. 항목을 다시 추가 하거나 대상이 세트 인 경우 아무 것도 수행하지 않아야합니다 . 이것이 Materials.Add의 동작이 아니므로 TryPut 또는 AddOnce 또는 AddOrThrow 또는 이와 유사한 이름으로 변경해야합니다.

마찬가지로 LoadMaterial의 이름은 LoadIfAbsent 또는 Put 또는 TryLoad 또는 LoadOrThrow (응답 # 1 또는 # 2 사용 여부에 따라 다름) 또는 이와 같은 이름으로 변경해야합니다.

C # Unity 명명 규칙을 따르십시오.

이것은 동일한 것을 두 번로드 할 수있는 다른 AddFoo 및 LoadBar 함수가있는 경우 특히 유용합니다. 명확한 이름이 없으면 개발자는 좌절 할 것입니다.


C # Dictionary.Add () 시맨틱 ( msdn.microsoft.com/en-us/library/k7z0zy8k%28v=vs.110%29.aspx 참조 )과 Java Collection.add () 시맨틱 ( 문서 참조) 에는 차이가 있습니다 . oracle.com/javase/8/docs/api/java/util/... ). C #은 void를 반환하고 예외를 throw합니다. 반면 Java는 bool을 반환하고 이전에 저장된 값을 새 값으로 바꾸거나 새 요소를 추가 할 수없는 경우 예외를 발생시킵니다.
카스퍼 반 덴 버그

카스퍼-감사합니다. C # 사전에서 값을 어떻게 대체합니까? (Java put ()과 같습니다). 해당 페이지에 내장 메소드가 표시되지 않습니다.
user949300

좋은 질문은 Dictionary.Remove () ( msdn.microsoft.com/en-us/library/bb356469%28v=vs.110%29.aspx 참조 ) 다음에 Dictionary.Add () ( msdn.microsoft 참조)를 수행하는 것입니다. .com / en-us / library / bb338565 % 28v = vs.110 % 29.aspx ), 그러나 조금 어색합니다.
카스퍼 반 덴 버그

@ user949300 dictionary [key] = 값이 이미 존재하는 경우 해당 값을 대체합니다
Rupe

@Rupe는 아마 그렇지 않으면 무언가를 던집니다. 모두 나에게 매우 어색해 보인다. 항목이 wheteher DICT를 설정하는 대부분의 클라이언트는 상관 없어 했다 가 그들은 단지 자신의 설정 코드 후에는 것을 신경, 이다 있다. Java Collections API (IMHO)에서 1 점을 얻습니다. PHP는 또한 dicts로 어색합니다. C #이 그 선례를 따르는 것은 실수였습니다.
user949300

3

모든 답변은 귀중한 아이디어를 추가합니다.

LoadMaterial()작업 의 의도적 및 예상 의미론을 결정하십시오 . 최소한 다음 옵션이 존재합니다.

  • name로드 된 재료 에 대한 전제 조건 : →

    전제 조건이 위반되면, 효과는 LoadMaterial()(같이 지정되지 않음 의해 ComicSansMS ). 이를 통해 구현 및 향후 변경시 대부분의 자유가 허용됩니다 LoadMaterial(). 또는,

  • 호출의 효과 LoadMaterial(name)nameLoadedMaterials이 지정됩니다; 어느 한 쪽:

    • 사양에서는 예외가 발생한다고 명시하고 있습니다. 또는
    • 명세서는 결과 (로 멱등 중임 않음 의해 칼 Bielefeldt )

      • 같은 가능성 (A "컬렉션이 변경되었습니다"부울을 반환 제안 에 의해 User949300제안 에 의해) 일부 해석에 ( Mrlveck .

시맨틱을 결정할 때 구현을 선택해야합니다. 제안 된 경우 다음 옵션 및 고려 사항 :

  • (같은 사용자 정의 예외를 던져 제안 에 의해 Ixrec ) →

    이점은 "사용자 정의"예외에이 함수를 호출하는 모든 사람에게 의미있는 오류 메시지 ( IxrecUser16547)가 있다는 것입니다.

    • nameLoadedMaterials 를 반복적으로 확인하는 비용을 피하기 위해 Marc van Leeuwen의 조언을 따를 수 있습니다 .

      ... 대신에 _Materials로 넘어가는 것을 고려하십시오. 무조건 추가 한 다음 잠재적 인 오류를 포착하고 핸들러에서 다른 오류를 처리하십시오.

  • 하자 Dictionay.Add가 → 예외를 던져

    예외 발생 코드는 중복입니다. — 존 레이너

    대부분의 유권자들이 Ixrec에 더 동의하지만

    이 구현을 선택해야하는 추가 이유는 다음과 같습니다.

    • 발신자가 이미 ArgumentException예외를 처리 할 수 있으며
    • 스택 정보가 손실되지 않도록합니다.

    그러나이 두 가지 이유가 중요한 경우 사용자 지정 예외를 파생 ArgumentException시켜 원본을 연결 예외로 사용할 수도 있습니다 .

  • 확인 LoadMaterial()으로 나무 등 anwser 에 의해 칼 Bielefeldt 가장 자주 (75 회) upvoted.

    • 같은 가능성 (A "컬렉션이 변경되었습니다"부울을 반환 제안 에 의해 User949300제안 에 의해) 일부 해석에 ( Mrlveck .

    이 동작에 대한 구현 옵션 :

    • Dictionary.ContainsKey ()로 확인하십시오.

    • 항상 전화 Dictionary.Add ()가 캐치 ArgumentException키가 이미 존재 삽입하고 그 예외를 무시하는 경우가 발생합니다. 예외를 무시하는 것이 당신이 의도 한 것과 그 이유임을 문서화하십시오.

      • LoadMaterials()(거의) 항상 각 한 번씩라고 name,이 방지 반복 검사 비용 nameLoadedMaterials의 참조를 마크 반 리웬 . 하나,
      • LoadedMaterials()종종 같은 여러 번 호출 name, 이것은 던지고의 (비용) 비용을 초래 ArgumentException하고 스택 해제합니다.
    • TryGet ()TryAdd유사한 -method 아날로그 가 있다고 생각 하여 비싼 예외 예외를 피하고 Dictionary.Add에 대한 호출 실패의 스택 해제를 피할 수있었습니다.

      그러나이 방법 TryAdd은 존재하지 않는 것 같습니다.


1
가장 포괄적 인 답변 인 +1 이것은 아마도 OP가 원래했던 것보다 훨씬 뛰어날 것이지만 모든 옵션에 대한 유용한 요약입니다.
Ixrec

1

예외 발생 코드는 중복입니다.

전화하면 :

_Materials.Add (이름, Resources.Load (string.Format ( "Materials / {0}", name))을 재료로 추가

같은 키로

System.ArgumentException

"같은 키를 가진 항목이 이미 추가되었습니다."라는 메시지가 표시됩니다.

ContainsKey본질적으로 같은 동작이없이 달성되기 때문에 검사가 중복됩니다. 다른 유일한 항목은 예외의 실제 메시지입니다. 사용자 지정 오류 메시지가 있으면 실제 이점이있는 경우 가드 코드에 장점이 있습니다.

그렇지 않으면이 경우 가드를 리팩터링합니다.


0

이것이 코딩 컨벤션 질문보다 API 디자인에 대한 질문이라고 생각합니다.

전화의 예상 결과 (계약)는 무엇입니까?

LoadMaterial("wood");

호출자 가이 방법을 호출 한 후 재료 "나무"가로 드 될 것으로 기대 / 보장 할 수 있다면 해당 재료가 이미로드되었을 때 예외를 던질 이유가 없습니다.

자료를로드 할 때 오류가 발생하는 경우 (예 : 데이터베이스 연결을 열 수 없거나 저장소에 자료 "wood"가없는 경우) 호출자에게 해당 문제점을 알리기 위해 예외를 던지는 것이 맞습니다.


-2

왜 메소드를 변경하여 'true'를 반환하면 문제가 추가되고 'false'가 아닌 경우 반환됩니까?

private bool LoadMaterial(string name)
{
   if (_Materials.ContainsKey(name))

    {

        return false; //already present
    }

    _Materials.Add(
        name,
        Resources.Load(string.Format("Materials/{0}", name)) as Material
    );

    return true;

}

15
오류 값을 반환하는 함수는 예외가 더 이상 사용되지 않는 반 패턴입니다.
Philipp

항상 그런가요? OP 코드 샘플에서는 시스템에 재료를 추가하지 못하는 것이 실제로 문제가되지 않기 때문에 나는 세미 프레디 어 ( en.wikipedia.org/wiki/Semipredicate_problem ) 의 경우라고 생각했습니다 . 이 방법은 재료를 실행할 때 재료가 시스템에 있는지 확인하기 위해 사용되며,이 방법이하는 것과 정확히 같습니다. (실패하더라도) 반환 부울은 메소드가 이미이 중요한 것을 추가하려고 시도했는지 여부를 나타냅니다. 추신 : 같은 이름을 가진 여러 법적 문제가있을 수 있다고 생각하지 않습니다.
MrIveck

1
솔루션을 직접 찾은 것 같습니다 : en.wikipedia.org/wiki/… 이것은 Ixrec의 답변이 가장 정확함을
나타냅니다

@Philipp이 경우 코더 / 사양은 두 번로드하려고 할 때 오류가 없다고 결정했습니다 . 그런 의미에서 반환 값은 오류 값이 아니라 정보입니다.
user949300
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.