태그 보관물: exceptions

exceptions

예외를 던지거나 코드 실패 이 스타일에 반대되는 장단점이 있는지

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

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입니다.



답변

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

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


답변

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

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


답변

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

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

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

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

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

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

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

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


답변

여기에 이미 몇 가지 답변이 있지만 여기에 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에서와 같은 람다 및 표현식을 사용하면 성능이 저하 될 수 있습니다)


답변

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

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

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

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


답변

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

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은 존재하지 않는 것 같습니다.


답변

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

전화하면 :

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

같은 키로

System.ArgumentException

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

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

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