예외를 catch / throw하면 순수한 방법이 불완전합니까?


27

다음 코드 예제는 내 질문에 대한 컨텍스트를 제공합니다.

Room 클래스는 대리자로 초기화됩니다. Room 클래스의 첫 번째 구현에서는 예외를 발생시키는 델리게이트에 대한 가드가 없습니다. 이러한 예외는 대리자가 평가되는 North 속성으로 버블 링됩니다 (주 : Main () 메서드는 클라이언트 코드에서 Room 인스턴스가 사용되는 방법을 보여줍니다).

public sealed class Room
{
    private readonly Func<Room> north;

    public Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = new Room(north: evilDelegate);

        var room = kitchen.North; //<----this will throw

    }
}

North 속성을 읽을 때가 아니라 객체를 만들 때 오히려 실패하기 때문에 생성자를 private으로 변경하고 Create ()라는 정적 팩토리 메서드를 도입합니다. 이 메소드는 대리자가 던진 예외를 포착하고 의미있는 예외 메시지가있는 랩퍼 예외를 발생시킵니다.

public sealed class Room
{
    private readonly Func<Room> north;

    private Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static Room Create(Func<Room> north)
    {
        try
        {
            north?.Invoke();
        }
        catch (Exception e)
        {
            throw new Exception(
              message: "Initialized with an evil delegate!", innerException: e);
        }

        return new Room(north);
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = Room.Create(north: evilDelegate); //<----this will throw

        var room = kitchen.North;
    }
}

try-catch 블록이 Create () 메서드를 불완전하게 렌더링합니까?


1
Room Create 기능의 이점은 무엇입니까? 대리자를 사용하는 경우 고객이 'North'에 전화하기 전에 왜 전화를 걸까요?
SH-

1
@SH 대리자가 예외를 throw하면 Room 객체를 만들 때 알고 싶습니다. 클라이언트가 사용시 예외가 아닌 생성시 예외를 찾기를 원하지 않습니다. Create 메서드는 악의적 인 대리자를 노출하기에 완벽한 장소입니다.
Rock Anthony Johnson

3
@RockAnthonyJohnson, 그렇습니다. 대리인을 실행할 때 처음으로 만 작업하거나 두 번째 통화에서 다른 회의실을 반환 할 수있는 것은 무엇입니까? 대리인에게 전화를 걸면 부작용 자체로 간주 / 발생할 수 있습니까?
SH-

4
불순 함수를 호출하는 함수는 불순 함수이므로 대리자가 불순한 Create경우 호출하기 때문에 불완전합니다.
Idan Arye

2
Create함수는 속성을 가져올 때 예외가 발생하지 않도록 보호하지 않습니다. 대리인이 실제 상황에서 던지면 일부 상황에서만 던질 가능성이 큽니다. 건설 중에 던지기 조건이 존재하지 않지만 재산을 얻을 때 존재하는 경우가 있습니다.
Bart van Ingen Schenau

답변:


26

예. 그것은 사실상 불순한 기능입니다. 이로 인해 부작용이 발생합니다. 프로그램 실행은 함수가 리턴 될 위치가 아닌 다른 곳에서 계속됩니다.

순수한 함수, 함수 return에서 예상 값을 캡슐화하는 실제 오브젝트 및 Maybe오브젝트 또는 작업 단위 오브젝트 와 같이 가능한 오류 조건을 나타내는 값을 작성하십시오 .


16
"순수"는 정의 일뿐입니다. 던지는 함수가 필요하지만 다른 모든 방법으로 참조 적으로 투명한 함수가 필요한 경우 작성하는 것입니다.
Robert Harvey

1
haskell에서는 최소한 어떤 함수라도 던질 수 있지만, 잡기 위해서는 IO에 있어야합니다. 때로는 던지는 순수한 함수는 보통 partial
jk

4
귀하의 의견으로 인해 주현절이있었습니다. 나는 순결에 지적으로 흥미가 있지만 실제로 실제로 필요한 것은 결정론과 경건한 투명성입니다. 순도를 달성하면 추가 보너스 일뿐입니다. 고맙습니다. 참고로,이 경로를 따라 가게 된 것은 Microsoft IntelliTest가 결정적인 코드에 대해서만 효과적이라는 사실입니다.
Rock Anthony Johnson

4
@RockAnthonyJohnson : 모든 코드는 결정적 이거나 최소한 결정 론적이어야합니다. 참조 투명성은 그 결정론을 얻는 한 가지 방법 일뿐입니다. 스레드와 같은 일부 기술은 해당 결정론에 반하는 경향이 있으므로 스레딩 모델의 비 결정적 경향을 개선하는 작업과 같은 다른 기술이 있습니다. 난수 생성기 및 Monte-Carlo 시뮬레이터와 같은 것 역시 결정 론적이지만 확률에 따라 다른 방식입니다.
Robert Harvey

3
@zzzzBov : "순수"에 대한 실제적이고 엄격한 정의는 중요하지 않습니다. 위의 두 번째 의견을
Robert Harvey

12

글쎄, 그래 ... 아니

순수한 함수는 참조 투명도 를 가져야합니다. 즉, 프로그램의 동작을 변경하지 않고 순수한 함수에 대한 가능한 모든 호출을 반환 된 값으로 바꿀 수 있어야합니다. * 함수는 항상 특정 인수에 대해 던져 질 수 있으므로 함수 호출을 대체 할 반환 값이 없으므로 무시 하십시오. 이 코드를 고려하십시오.

{
    var ignoreThis = func(arg);
}

func순수한 경우 옵티마이 저는 func(arg)결과로 대체 될 수 있다고 결정할 수 있습니다. 결과가 무엇인지 아직 알지 못하지만 사용되지 않는다는 것을 알 수 있으므로이 문장이 효과가 없다고 추론하고 제거 할 수 있습니다.

그러나 func(arg)던질 경우이 문장은 무언가를 수행합니다-예외가 발생합니다! 따라서 옵티마이 저는이를 제거 할 수 없습니다. 함수가 호출되는지 여부는 중요합니다.

그러나...

실제로 이것은 매우 중요하지 않습니다. 적어도 C #에서는 예외가 예외입니다. 당신은 그것을 일반적인 제어 흐름의 일부로 사용해서는 안됩니다-그것을 시도하고 잡아야하며, 무언가를 잡으면 오류를 처리하여 현재하고있는 일을 되돌 리거나 어떻게 든 여전히 달성하십시오. 실패한 코드가 최적화되어 프로그램이 제대로 작동하지 않으면 예외를 잘못 사용하고있는 것입니다 (테스트 코드가 아니라면 테스트를 위해 빌드 할 때 예외를 최적화하지 않아야 함).

그 말은 ...

순수한 함수에서 예외를 포착하지 않기 위해 예외를 던지지 마십시오. 함수형 언어가 스택 해제 예외 대신 모나드를 선호하는 이유가 있습니다.

C #에 ErrorJava (및 다른 많은 언어)와 같은 클래스 Error가 있으면 Exception. 대신 을 던지도록 제안했을 것입니다 . 함수의 사용자가 무언가 잘못했음을 나타냅니다 (던지는 함수를 전달 함). 그런 것들은 순수한 함수에서 허용됩니다. 그러나 C #에는 Error클래스 가 없으며 사용 오류 예외가에서 파생 된 것으로 보입니다 Exception. 내 제안은를 던져서 ArgumentException함수가 잘못된 인수로 호출되었음을 분명히하는 것입니다.


* 계산적으로 말하기. 순진 재귀를 사용하여 구현 된 피보나치 함수는 많은 수의 시간이 오래 걸리고 머신의 리소스를 소모 할 수 있지만, 시간과 메모리가 무한하므로 함수는 항상 동일한 값을 반환하고 부작용 (할당 제외)이 없습니다 메모리와 메모리 변경)-여전히 순수한 것으로 간주됩니다.


1
+1 ArgumentException의 제안 된 사용과 "잡을 의도로 순수한 함수에서 예외를 발생시키지 말 것"을 권장합니다.
Rock Anthony Johnson

당신의 첫 번째 주장 ( "하지만 ..."까지)은 나에게 들리지 않는 것 같습니다. 예외는 기능 계약의 일부입니다. 개념적으로 함수를 다시 작성 (value, exception) = func(arg)하고 예외를 던질 가능성을 제거 할 수 있습니다 (일부 언어 및 일부 예외 유형의 경우 실제로 인수 또는 리턴 값과 같은 정의로 나열해야 함). 이제는 이전과 마찬가지로 순수합니다 ( 항상 동일한 인수가 주어지면 일명 예외를 throw하는 경우). 이 해석을 사용 하면 예외를 던지는 것은 부작용 이 아닙니다 .
AnoE

@AnoE 만약 당신이 func그런 식으로 작성했다면 , 스택을 해제하는 대신에 던져진 예외가로 인코딩 되었기 때문에 내가 준 블록은 최적화되었을 수있다 ignoreThis. 우리는 무시한다. 스택을 푸는 예외와 달리 반환 형식으로 인코딩 된 예외는 순도를 위반하지 않으므로 많은 기능 언어가이를 사용하는 것을 선호합니다.
Idan Arye

정확히 ... 당신은이 상상 된 변환에 대한 예외를 무시하지 않았을 것입니다. 나는 그것이 의미론 / 정의의 약간의 문제라고 생각합니다. 예외의 존재 또는 발생이 반드시 순수성의 모든 개념을 무효화하지는 않는다는 것을 지적하고 싶었습니다.
AnoE

1
@AnoE하지만 writted 한 코드 이 상상 변환에 대한 예외를 무시합니다. func(arg)튜플을 반환 (<junk>, TheException()), 그래서 ignoreThis튜플을 포함 (<junk>, TheException()),하지만 우리는 절대 사용하지 마십시오 때문에 ignoreThis예외를 아무것도에 영향을주지 않습니다.
Idan Arye

1

한 가지 고려 사항은 try-catch 블록이 문제가 아니라는 것입니다. (위의 질문에 대한 나의 의견을 바탕으로).

주요 문제는 North 속성이 I / O 호출이라는 것 입니다.

코드 실행 시점에서 프로그램은 클라이언트 코드에서 제공하는 I / O를 확인해야합니다. (입력이 위임의 형태이거나 입력이 명목상 이미 전달 된 것은 관련이 없습니다).

입력 제어를 잃으면 기능이 순수하다는 것을 보장 할 수 없습니다. (특히 함수가 던질 수있는 경우).


왜 Move [Check] Room 통화를 확인하고 싶지 않은지 잘 모르겠습니다. 질문에 대한 나의 의견에 따라 :

예. 대리인을 실행할 때 처음으로 만 작업하거나 두 번째 통화에서 다른 회의실을 반환 할 수 있습니까? 대리인에게 전화를 걸면 부작용 자체로 간주 / 발생할 수 있습니까?

Bart van Ingen Schenau가 위에서 말했듯이

Create 함수는 속성을 가져올 때 예외가 발생하지 않도록 보호하지 않습니다. 대리인이 실제 상황에서 던지면 일부 상황에서만 던질 가능성이 큽니다. 건설 중에는 던지기 조건이 없지만 재산을 얻을 때 존재하는 경우가 있습니다.

일반적으로 지연로드 의 모든 유형은 해당 시점까지 오류를 내재적으로 지연 시킵니다.


Move [Check] Room 메서드를 사용하는 것이 좋습니다. 이를 통해 불완전한 I / O 측면을 한 곳으로 분리 할 수 ​​있습니다.

Robert Harvey의 답변과 유사합니다 .

순수한 함수로 만들려면 함수에서 예상 값을 캡슐화하는 실제 오브젝트와 가능한 오브젝트 또는 작업 단위 오브젝트와 같은 가능한 오류 조건을 나타내는 값을 리턴하십시오.

입력에서 (가능한) 예외를 처리하는 방법을 결정하는 것은 코드 작성기에 달려 있습니다. 그런 다음이 메소드는 Room 객체 또는 Null Room 객체를 반환하거나 예외를 버블 링 할 수 있습니다.

이 시점은 다음에 달려 있습니다.

  • Room 도메인은 Room Exception을 Null 또는 Something Worse로 취급합니까?
  • Null / Exception Room에서 North를 호출하는 클라이언트 코드에 알리는 방법. (보석 / 상태 변수 / 전역 상태 / 모나드 반환 / 무엇이든; 일부는 다른 것보다 더 순수합니다 :)).

-1

흠 ... 나는 이것에 대해 기분이 좋지 않았다. 나는 당신이하려는 일이 다른 사람들이 자신이하는 일을 알지 못하고 자신의 일을 올바르게 수행하도록하는 것이라고 생각합니다.

이 기능은 소비자가 전달하기 때문에 사용자 나 다른 사람이 작성할 수 있습니다.

Room 객체가 생성 될 때 함수 전달이 실행되지 않으면 어떻게됩니까?

코드에서, 나는 북한이 무엇을하고 있는지 확신 할 수 없었다.

기능이 방을 예약하는 것이며 예약 시간과 기간이 필요한 경우 정보가 준비되지 않은 경우 예외가 발생합니다.

장기 실행 기능인 경우 어떻게합니까? Room 객체를 만드는 동안 프로그램을 차단하고 싶지 않습니다. 1000 Room 객체를 만들어야하는 경우

이 클래스를 소비하는 소비자를 쓰는 사람이라면 전달 된 함수가 올바르게 작성되었는지 확인하십시오.

소비자를 작성하는 다른 사람이있는 경우, 룸 객체를 생성하는 "특별한"조건을 알지 못할 수 있으며, 이로 인해 예외가 발생할 수 있으며 원인을 찾으려고 머리를 긁을 수 있습니다.

또 다른 것은 항상 새로운 예외를 던지는 것이 좋은 것은 아닙니다. 그 이유는 원래 예외 정보를 포함하지 않기 때문입니다. 오류가 발생한다는 것을 알고 있지만 (일반 예외의 경우 친숙한 메시지), 오류가 무엇인지 알 수 없습니다 (널 참조, 범위를 벗어난 인덱스, 0으로 편차 등). 이 정보는 조사가 필요할 때 매우 중요합니다.

처리 방법과 처리 방법을 알고있는 경우에만 예외를 처리해야합니다.

원래 코드가 충분하다고 생각합니다. 유일한 것은 "메인"(또는 처리 방법을 알고있는 레이어)에서 예외를 처리하고 싶을 것입니다.


1
두 번째 코드 예제를 다시 살펴보십시오. 더 낮은 예외로 새 예외를 초기화합니다. 전체 스택 추적이 유지됩니다.
Rock Anthony Johnson

죄송합니다. 내 실수.
user251630

-1

나는 다른 답변에 동의하고 말할 것이다 아니오 . 예외로 인해 순수한 기능이 불완전하게되는 것은 아닙니다.

오류 결과에 대한 명시 적 검사를 사용하기 위해 예외 사용을 다시 작성할 수 있습니다. 예외는 이것 외에도 구문 설탕으로 간주 될 수 있습니다. 편리한 구문 설탕은 순수한 코드를 불순하게하지 않습니다.


1
불순물을 다시 쓸 수 있다는 사실이 기능을 순수하게 만들지는 않습니다. Haskell은 불순 함수의 사용이 순수한 함수로 다시 쓰여질 수 있다는 증거입니다. 모나드를 사용하여 순수한 순수 함수의 리턴 유형에서 불순한 동작을 인코딩합니다. IO 모나드가 있다고해서 일반 IO가 순수하다는 것을 의미하지는 않습니다. 왜 모나드가 존재한다는 것이 왜 예외가 순수하다는 것을 의미해야합니까?
Idan Arye

@ IdanArye : 처음에는 불순물이 없다고 주장하고 있습니다. 재 작성의 예는 그것을 보여주기위한 것입니다. 정상적인 IO는 관찰 가능한 부작용을 유발하거나 일부 외부 입력으로 인해 동일한 입력으로 다른 값을 반환 할 수 있기 때문에 불완전합니다. 예외를 던지는 것은 그러한 일을하지 않습니다.
JacquesB

부작용 무료 코드로는 충분하지 않습니다. 참조 투명도 는 기능 순도에 대한 요구 사항입니다.
Idan Arye

1
결정론은 참조 투명성에 충분하지 않습니다. 부작용도 결정론적일 수 있습니다. 참조 투명도는 표현식을 결과로 대체 할 수 있음을 의미합니다. 따라서 참조 투명 표현식을 몇 번이나 어떤 순서로 평가하든 전혀 평가하지 않든 결과는 동일해야합니다. 예외가 결정 론적으로 던져지면 우리는 "얼마나 여러 번"기준에 적합하지만 다른 기준에는 적합하지 않습니다. 나는 내 자신의 대답에서 "무엇을 할 것인지 아닌지"기준에 대해 논쟁 해왔다. (계속)
Idan Arye

1
(... 계속)하고 "어떤 순서"에 대한 같은 - 경우 fg모두 순수 함수는 다음 f(x) + g(x)에 관계없이 당신이 평가 순서의 같은 결과를해야 f(x)하고 g(x). 하지만 f(x)발생 Fg(x)발생 G,이 표현에서 던진 예외가 될 F경우 f(x)먼저 평가되고 G있는 경우 g(x)먼저 평가 -이 기능은 행동해야하지 어떻게 순수! 예외가 반환 형식으로 인코딩되면 결과는 Error(F) + Error(G)평가 순서 와 독립적 으로 끝납니다 .
이단 아예
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.