예외, 오류 코드 및 차별적 노동 조합


80

최근에 C # 프로그래밍 작업을 시작했지만 Haskell에서 상당한 배경 지식을 얻었습니다.

그러나 C #은 객체 지향 언어라는 것을 알고 있습니다. 둥근 못을 사각형 구멍에 강요하고 싶지 않습니다.

나는 Exception Throwing Microsoft에서 기사를 읽었습니다 .

오류 코드를 반환 하지 마십시오 .

그러나 Haskell에 익숙해 져서 C # 데이터 형식을 사용하여 OneOf결과를 "올바른"값으로, 오류 (대부분 열거 형)를 "왼쪽"값으로 반환했습니다.

이것은 Either하스켈에서 의 관습과 매우 유사합니다 .

나에게 이것은 예외보다 안전 해 보인다. C #에서 예외를 무시해도 컴파일 오류가 발생하지 않으며 예외가 발견되지 않으면 버블 링되어 프로그램이 중단됩니다. 이것은 아마도 오류 코드를 무시하고 정의되지 않은 동작을 생성하는 것보다 낫지 만, 특히 클라이언트가 다른 많은 중요한 비즈니스 작업을 백그라운드에서 수행 할 때는 여전히 클라이언트 소프트웨어의 충돌이 좋지 않습니다.

를 사용 OneOf하여 포장을 풀고 반환 값과 오류 코드를 처리하는 것에 대해 분명히 명시해야합니다. 그리고 호출 스택의 해당 단계에서 처리하는 방법을 모른다면 현재 함수의 반환 값에 넣어야하므로 호출자는 오류가 발생할 수 있음을 알고 있습니다.

그러나 이것은 Microsoft가 제안한 접근법이 아닌 것 같습니다.

OneOf"일반적인"예외 (예 : 파일을 찾을 수 없음 등)를 처리하기 위해 예외 대신에 사용 하는 것이 합리적인 접근입니까, 아니면 끔찍한 관행입니까?


제어 흐름으로서의 예외는 심각한 반 패턴으로 간주 된다는 것을 들었을 것입니다 . "예외"가 프로그램을 끝내지 않고 일반적으로 처리하는 것이면 "제어 흐름"이 아닌가? 여기에 약간의 회색 영역이 있음을 이해합니다.

참고 OneOf"메모리 부족"과 같은 용도로 사용 하지 않는 경우 복구 할 것으로 예상되지 않는 조건에서 여전히 예외가 발생합니다. 그러나 구문 분석하지 않는 사용자 입력과 같은 상당히 합리적인 문제처럼 느껴지는 것은 본질적으로 "제어 흐름"이며 아마도 예외를 던져서는 안됩니다.


후속 생각 :

이 토론에서 현재 내가 취하는 것은 다음과 같습니다.

  1. 즉각적인 호출자가 catch대부분의 시간 동안 예외를 처리하고 처리하고 다른 경로를 통해 작업을 계속하려면 아마도 리턴 유형의 일부 여야합니다. Optional또는 OneOf여기서 유용 할 수 있습니다.
  2. 직접 호출자가 대부분 예외를 포착하지 않을 것으로 예상되는 경우 예외를 던져 수동으로 스택에 전달하는 어리 석음을 저장하십시오.
  3. 직통 발신자가 무엇을할지 확실하지 않은 경우 like Parse및을 모두 제공하십시오 TryParse.

13
F # Result;-) 사용
Astrinus

8
다소 주제를 벗어난 주제이지만,보고있는 접근 방식에는 개발자가 오류를 올바르게 처리 할 수있는 방법이 없다는 일반적인 취약점이 있습니다. 물론, 타이핑 시스템은 알림 역할을 할 수 있지만 오류 처리에 실패하여 프로그램을 종료하는 기본값을 잃어 예기치 않은 상태에 빠질 가능성이 높습니다. 그래도 그 기본값을 잃을 가치가 있는지 여부는 공개 토론입니다. 예외가 어느 시점에서 종료 될 것이라고 가정하면 모든 코드를 작성하는 것이 더 낫다고 생각합니다. 알림의 가치가 떨어집니다.
jpmc26

9
관련 기사 : 예외의 네 가지 "버킷"에 관한 Eric Lippert . 그가 외 인적 인식에 대해 제시 한 예는 예외를 처리해야하는 시나리오가 있음을 보여줍니다 FileNotFoundException.
Søren D. Ptæus 2016 년

7
나는 이것에 혼자있을 수도 있지만 충돌이 좋다고 생각합니다 . 적절한 추적으로 충돌 로그보다 소프트웨어에 문제가 있다는 더 좋은 증거는 없습니다. 30 분 이내에 패치를 수정하고 배포 할 수있는 팀이있는 경우 해당 추악한 친구를 표시하는 것은 나쁘지 않을 수 있습니다.
T. Sar

9
어떻게 답변 중에 있음을 명확히하지 와서 모나드 오류 코드가 아닙니다 , 그리고도있다 ? 그것들은 근본적으로 다르기 때문에 그 질문은 오해에 근거한 것 같습니다. (수정 된 형식이지만 여전히 유효한 질문입니다.)EitherOneOf
Konrad Rudolph

답변:


131

그러나 클라이언트 소프트웨어 충돌은 여전히 ​​좋은 일이 아닙니다.

가장 좋은 방법입니다.

정의되지 않은 시스템은 데이터 손상, 하드 드라이브 포맷, 대통령 위협 이메일 전송 등의 불쾌한 작업을 수행 할 수 있기 때문에 시스템을 정의되지 않은 상태로 두는 것을 원합니다. 시스템을 복구하고 정의 된 상태로 되돌릴 수없는 경우 충돌이 가장 큰 원인입니다. 이것이 우리가 조용히 찢어지지 않고 충돌하는 시스템을 구축하는 이유입니다. 이제 우리는 모두 충돌하지 않는 안정적인 시스템을 원하지만 시스템이 정의 된 예측 가능한 안전 상태를 유지할 때만 원합니다.

제어 흐름과 같은 예외는 심각한 반 패턴으로 간주됩니다.

그것은 절대적으로 사실이지만 종종 오해됩니다. 그들이 예외 시스템을 발명했을 때 그들은 구조화 된 프로그래밍을 깨뜨리는 것을 두려워했습니다. 우리가 왜 구조적 프로그래밍은 for, while, until, break, 그리고 continue모든 우리가 필요로 할 때,이 모든 작업을 수행 할 수있다 goto.

Dijkstra 는 비공식적으로 (즉, 원하는 곳을 뛰어 다니며) 코드를 사용하면 코드를 읽는 것이 악몽이된다고 가르쳤다. 그들이 우리에게 예외 시스템을 주었을 때 그들은 goto를 재발견하는 것을 두려워했습니다. 그래서 그들은 우리가 이해하기를 바라면서 "흐름 제어에 사용하지 말라고 우리에게 말했다. 불행하게도, 우리 중 많은 사람들이 그렇지 않았습니다.

이상하게도, 우리는 goto와 마찬가지로 스파게티 코드를 만들기 위해 예외를 남용하지 않는 경우가 많습니다. 조언 자체가 더 많은 문제를 일으킨 것 같습니다.

근본적으로 예외는 가정을 거부하는 것입니다. 파일 저장을 요청하면 파일을 저장할 수 있고 저장할 수 있다고 가정합니다. 이름이 불법이거나 HD가 가득 찼거나 쥐가 데이터 케이블을 통해 gna 아 먹었 기 때문에 발생할 수없는 예외입니다. 모든 오류를 다르게 처리하거나 모두 같은 방식으로 처리하거나 시스템을 중지시킬 수 있습니다. 코드에는 가정이 맞아야하는 행복한 길이 있습니다. 한 가지 또는 다른 예외로 인해 그 행복한 길을 벗어날 수 있습니다. 엄밀히 말하면, 그것은 일종의 "흐름 제어"이지만 그들이 경고 한 것은 아닙니다. 그들은 다음 과 같이 넌센스 에 대해 이야기 했습니다 .

여기에 이미지 설명을 입력하십시오

"예외는 예외적이어야한다". 예외 시스템 설계자가 스택 추적을 빌드 할 시간이 필요하기 때문에이 작은 타우 톨 로지가 탄생했습니다. 뛰어 다니는 것에 비해 느립니다. CPU 시간을 먹는다. 그러나 다음 시스템을 시작하기 전에 시스템을 기록하고 정지 시키거나 현재 시간 집약적 인 처리를 중단하려는 경우 시간을 내야합니다. 사람들이 "흐름 제어를 위해"예외를 사용하기 시작하면 시간에 대한 가정은 모두 창 밖으로 나옵니다. 따라서 성능 예외로 "예외가 예외적이어야합니다"가 실제로 우리에게 주어졌습니다.

그보다 훨씬 더 중요한 것은 우리를 혼동하지 않습니다. 위의 코드에서 무한 루프를 발견하는 데 얼마나 걸렸습니까?

오류 코드를 반환 하지 마십시오 .

... 일반적으로 오류 코드를 사용하지 않는 코드 기반에있을 때는 좋은 조언입니다. 왜? 아무도 반환 값을 저장하고 오류 코드를 확인하는 것을 기억하지 않기 때문입니다. C에있을 때는 여전히 훌륭한 컨벤션입니다.

OneOf

또 다른 규칙을 사용하고 있습니다. 컨벤션을 설정하고 다른 컨벤션과 싸우지 않는 한 괜찮습니다. 동일한 코드베이스에 두 가지 오류 규칙이있는 것이 혼란 스럽습니다. 어떻게 든 다른 규칙을 사용하는 모든 코드를 제거했다면 계속하십시오.

나는 대회 자체를 좋아한다. 그것의 최고의 설명 중 하나는 내가 발견 여기 * :

여기에 이미지 설명을 입력하십시오

그러나 내가 좋아하는 한 나는 여전히 다른 규칙과 섞지 않을 것입니다. 하나를 고르세요. 1

1 : 즉, 둘 이상의 컨벤션을 동시에 생각하게하지 마십시오.


후속 생각 :

이 토론에서 현재 내가 취하는 것은 다음과 같습니다.

  1. 직접 호출자가 예외를 대부분 잡아서 처리하고 다른 경로를 통해 작업을 계속할 것으로 예상되는 경우 아마도 리턴 유형의 일부 여야합니다. 선택적 또는 OneOf가 여기서 유용 할 수 있습니다.
  2. 직접 호출자가 대부분 예외를 포착하지 않을 것으로 예상되는 경우 예외를 던져 수동으로 스택에 전달하는 어리 석음을 저장하십시오.
  3. 직접 발신자가 무엇을할지 확실하지 않은 경우 Parse 및 TryParse와 같이 둘 다 제공 할 수 있습니다.

정말 간단하지 않습니다. 이해해야 할 기본 사항 중 하나는 0입니다.

5 월에 며칠이 남았습니까? 0 (5 월이 아니기 때문에 이미 6 월이므로)

예외는 가정을 거부하는 방법이지만 유일한 방법은 아닙니다. 가정을 거부하기 위해 예외를 사용하는 경우 행복한 길을 떠나십시오. 그러나 가정이 단순하지 않다는 신호를 보내는 행복한 길을 보내기 위해 가치를 선택했다면 그 가치를 다룰 수있는 한 그 길을 계속 유지할 수 있습니다. 때로는 0은 이미 무언가를 의미하는 데 사용되므로 아이디어를 거부하는 가정을 매핑 할 다른 값을 찾아야합니다. 당신은이 아이디어가 좋은 대수학 에서 사용됨을 인식 할 수 있습니다 . 모나드는 그것을 도울 수 있지만 항상 모나드 일 필요는 없습니다.

예를 들어 2 :

IList<int> ParseAllTheInts(String s) { ... }

의도적으로 무언가를 던지도록 설계되어야하는 합당한 이유가 있습니까? int를 파싱 할 수 없을 때 얻는 것을 추측하십시오. 난 당신에게 말할 필요가 없습니다.

좋은 이름의 표시입니다. 죄송하지만 TryParse 는 좋은 이름이 아닙니다.

우리는 종종 대답이 동시에 여러 가지 일 수있을 때 아무것도 얻지 못하는 것에 대해 예외를 던지는 것을 피하지만 어떤 대답이 하나의 것이거나 아무것도 아닌 경우 우리에게 하나의 일을 주거나 던지겠다고 고집하는 것에 집착합니다.

IList<Point> Intersection(Line a, Line b) { ... }

평행선이 실제로 예외를 발생시켜야합니까? 이 목록에 둘 이상의 포인트가 포함되지 않으면 실제로 그렇게 나쁩니 까?

어쩌면 의미 상 당신은 그것을 취할 수 없습니다. 그렇다면 유감입니다. 그러나 아마도 임의의 크기가 아닌 Monads List는 더 나은 느낌을 줄 것입니다.

Maybe<Point> Intersection(Line a, Line b) { ... }

Monads는 특별한 용도로 사용되는 컬렉션이 거의 없으므로 테스트 할 필요가없는 특정 방식으로 사용됩니다. 우리는 그것들이 무엇을 포함하는지에 관계없이 그것들을 다루는 방법을 찾아야합니다. 그렇게하면 행복한 길은 단순하게 유지됩니다. 모든 Monad를 열어서 테스트하고 터치하면 잘못 사용하고 있습니다.

알아, 이상해 그러나 그것은 새로운 도구입니다. 시간을 좀주세요 나사에서 망치 사용을 중지하면 망치가 더 의미가 있습니다.


당신이 나를 탐닉한다면, 나는이 의견을 다루고 싶습니다 :

어떻게 답을 찾지 못하면 모나드가 오류 코드가 아니며 OneOf도 아니 었습니까? 그것들은 근본적으로 다르기 때문에 그 질문은 오해에 근거한 것 같습니다. (수정 된 형식이지만 여전히 유효한 질문입니다.) – Konrad Rudolph 6 월 4 일 18시 14 분 8 초

이것은 절대적으로 사실입니다. 모나드는 예외, 플래그 또는 오류 코드보다 모음에 훨씬 더 가깝습니다. 그들은 현명하게 사용될 때 그러한 것들을 위해 훌륭한 용기를 만듭니다.


9
빨간색 트랙이 상자 아래 에있어 통과하지 않는 것이 더 적절 하지 않습니까?
Flater

4
논리적으로, 당신은 맞습니다. 그러나이 특정 다이어그램의 각 상자는 모나드 바인드 작업을 나타냅니다. 바인드는 빨간색 트랙을 포함합니다. 전체 프레젠테이션을 보는 것이 좋습니다. 흥미롭고 Scott은 훌륭한 연사입니다.
Gusdor

7
실제로 예외를 GOTO가 아닌 COMEFROM 명령 과 비슷하다고 생각합니다 . 왜냐하면 throw실제로 우리가 어디로 이동할지 알 수 없기 때문입니다 .)
Warbo

3
경계를 설정할 수 있다면 믹싱 규칙에는 아무런 문제가 없습니다. 캡슐화가 전부입니다.
Robert Harvey

4
이 답변의 첫 번째 섹션 전체는 인용 된 문장 다음에 질문을 읽지 않은 것처럼 강하게 읽 힙니다. 이 질문은 프로그램 충돌이 정의되지 않은 동작으로 넘어가는 것보다 우수하다는 것을 인정합니다. 런타임 충돌은 계속적이고 정의되지 않은 실행이 아니라 잠재적 오류를 고려하지 않고 프로그램을 빌드하지 못하게하는 컴파일 타임 오류와 대조적입니다 그리고 그것을 다룰 때 (아무것도 할 일이 없으면 충돌이 일어날 수 있지만, 잊어 버린 것이 아니라 원하기 때문에 충돌이 발생합니다).
KRyan

35

C #은 Haskell이 아니며 C # 커뮤니티의 전문가 합의를 따라야합니다. 대신 C # 프로젝트에서 Haskell 관행을 따르려고하면 팀의 다른 모든 사람들을 소외 시키게되며 결국 C # 커뮤니티가 다르게 행동하는 이유를 알게 될 것입니다. 한 가지 큰 이유는 C #이 차별적 인 노조를 편리하게 지원하지 않기 때문입니다.

제어 흐름으로서의 예외는 심각한 반 패턴으로 간주된다고 들었습니다.

이것은 보편적으로 인정되는 진실이 아닙니다. 예외를 지원하는 모든 언어에서 선택은 예외를 던지거나 (호출자가 처리 할 수없는) 복합 값 (호출자가 처리해야하는)을 반환합니다.

호출 스택을 통해 오류 조건을 위로 전파하려면 모든 수준에서 조건이 필요하므로 이러한 방법의 순환 복잡성이 두 배가되고 결과적으로 단위 테스트 사례 수가 두 배가됩니다. 일반적인 비즈니스 응용 프로그램에서는 많은 예외가 복구를 넘어서고 최고 수준 (예 : 웹 응용 프로그램의 서비스 시작 지점)으로 전파 될 수 있습니다.


9
모나드 전파에는 아무 것도 필요하지 않습니다.
Basilevs 2016 년

23
@Basilevs C #에는없는 코드를 읽을 수 있도록 유지하면서 모나드 전파를 지원해야합니다. 당신은 if-return 명령어 나 람다 체인을 반복해서받습니다.
Sebastian Redl

4
@SebastianRedl 람다는 C #에서 정상적으로 보입니다.
Basilevs

@Basilevs 구문 관점에서 그렇습니다. 그러나 성능 관점에서 그들은 덜 매력적이라고 ​​생각합니다.
Pharap

5
현대 C #에서는 아마도 부분적으로 로컬 함수를 사용하여 약간의 속도를 되 찾을 수 있습니다. 어쨌든 대부분의 비즈니스 소프트웨어는 다른 것보다 IO에 의해 훨씬 더 느려집니다.
Turksarama 2016 년

26

흥미로운 시간에 C #에 왔습니다. 비교적 최근까지만해도이 언어는 명령형 프로그래밍 공간에 단단히 자리 잡고있었습니다. 예외를 사용하여 오류를 전달하는 것이 확실히 표준이었습니다. 리턴 코드를 사용하면 언어 지원 부족 (예 : 차별 조합 및 패턴 일치 등)이 발생했습니다. 합리적으로 Microsoft의 공식 지침은 리턴 코드를 피하고 예외를 사용하는 것이 었습니다.

그러나 TryXXX메소드 의 형태로는 항상 예외가 있었으며 , 이는 boolean성공 결과를 리턴 하고 out매개 변수 를 통해 두 번째 값 결과를 제공합니다 . 이는 함수형 프로그래밍의 "시도"패턴과 매우 유사하며 결과는 Maybe<T>반환 값이 아닌 출력 매개 변수를 통해 발생 합니다.

그러나 상황이 변하고 있습니다. 함수형 프로그래밍이 점점 더 대중화되고 있으며 C #과 같은 언어가 응답하고 있습니다. C # 7.0은 기본 패턴 일치 기능을 언어에 도입했습니다. C # 8은 스위치 식, 재귀 패턴 등을 포함하여 훨씬 더 많은 것을 소개 할 것입니다. 이와 함께, C #을위한 "함수 라이브러리"(예 : 내 자신의 Succinc <T> 라이브러리 )에서도 차별적 인 노동 조합을 지원합니다. .

이 두 가지가 결합되면 다음과 같은 코드가 점차 인기를 얻고 있음을 의미합니다.

static Maybe<int> TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some<int>(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

비록 2 년 동안 C # 개발자들 사이의 일반적인 적대감에서 그러한 코드로의 변화가 그러한 기술을 사용하는 것에 대한 관심이 증가함에 따라 지금도 여전히 틈새 시장입니다.

아직 " 오류 코드를 반환 하지 마십시오 ." 라고 말하지 않았습니다 . 구식이며 은퇴해야합니다. 그러나 그 목표를 향한 길은 행진하고 있습니다. 따라서 선택하십시오 : 게이 포기를 통해 예외를 던지는 옛날 방식을 고수하십시오. 또는 C #에서 기능 언어의 규칙이 점점 대중화되는 새로운 세계를 탐험 해보십시오.


22
이것이 내가 C #을 싫어하는 이유입니다. 결국, 어떤 규칙도 없습니다. 완벽주의 프로그래머는 몇 시간 동안 어떤 방법이 "가장"더 적합한지를 결정할 수 있으며, 새로운 프로그래머는 다른 사람의 코드를 읽기 전에 모든 것을 배워야합니다.
Aleksandr Dubinsky 2018 년

24
@AleksandrDubinsky, C #뿐만 아니라 일반적인 프로그래밍 언어의 핵심 문제에 부딪 쳤습니다. 새로운 언어는 세련되고 신선하며 현대적인 아이디어로 가득합니다. 그리고 그것들을 사용하는 사람들은 거의 없습니다. 시간이 지남에 따라 기존 코드가 손상되어 제거 할 수없는 기존 기능을 기반으로 사용자와 새로운 기능을 얻게됩니다. 팽창이 자랍니다. 그래서 사람들은 새롭고 신선하며 현대적인 아이디어로 가득 찬 새로운 언어를 창조합니다 ...
David Arno

7
@AleksandrDubinsky는 1960 년대의 기능을 추가 할 때 싫어하지 않습니다.
ctrl-alt-delor

6
@AleksandrDubinsky 그래. 자바는 일 추가하려고하기 때문에 한 번 (제네릭), 그들은 우리가 지금 이렇게 결과가 전혀 아무것도를 추가 교훈을 배운 종료 한 끔찍한 혼란과 함께 생활해야 실패했습니다)
Agent_L

3
@ 에이전트 _L : 당신은 자바가 추가 된 java.util.Stream(반쯤 가능한 IEnumerable과 비슷 함) 람다를 알고 있습니까? :)
cHao

5

나는 DU를 사용하여 오류를 반환하는 것이 합리적이라고 생각하지만 OneOf를 쓴 것처럼 말할 것입니다.) 그러나 어쨌든 F # 등의 일반적인 관행이므로 (예 : 다른 사람들이 언급 한 결과 유형) ).

나는 일반적으로 예외적 인 상황으로 반환되는 오류를 생각하지 않지만 대신 정상적이지만, 처리해야 할 수도 있고 처리해야 할 수도 있지만 모델링해야 할 상황은 거의 발생하지 않기를 바랍니다.

다음은 프로젝트 페이지의 예입니다.

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

이러한 오류에 대한 예외를 던지는 것은 과도한 것으로 보입니다. 메소드 서명에 명시 적이 지 않거나 철저한 일치를 제공한다는 단점 외에도 성능 오버 헤드가 있습니다.

C #에서 OneOf가 관용적으로 어느 정도인지, 그것은 또 다른 질문입니다. 구문은 유효하고 직관적입니다. DU와 레일 지향 프로그래밍은 잘 알려진 개념입니다.

가능한 곳에서 무언가가 깨진 것을 나타내는 예외를 저장합니다.


당신이 말하는 것은 OneOfNullable을 반환하는 것과 같이 반환 값을 더 풍부하게 만드는 것입니다. 예외적이지 않은 상황에 대한 예외를 대체합니다.
Aleksandr Dubinsky

예-발신자에서 철저한 일치를 수행하는 쉬운 방법을 제공합니다. 상속 기반 결과 계층 구조를 사용하면 불가능합니다.
mcintyre321

4

OneOfJava에서 확인 된 예외와 같습니다. 몇 가지 차이점이 있습니다.

  • OneOf부작용을 요구하는 메소드를 생성하는 메소드에서는 작동하지 않습니다 ( 의미 적으로 호출 될 수 있고 결과는 단순히 무시 됨). 분명히 우리 모두가 순수한 기능을 사용하려고 노력해야하지만 이것이 항상 가능한 것은 아닙니다.

  • OneOf문제가 발생한 위치에 대한 정보를 제공하지 않습니다. 이것은 성능을 절약하고 (Java에서 예외는 스택 추적을 채우므로 비용이 많이 들며 C #에서는 동일 할 것입니다) 오류가 발생하여 OneOf자체 오류 멤버에 충분한 정보를 제공해야 합니다. 이 정보가 충분하지 않아서 문제의 원인을 찾는 데 어려움을 겪을 수도 있습니다.

OneOf 확인 된 예외에는 공통적으로 중요한 "기능"이 있습니다.

  • 그들은 둘 다 당신을 호출 스택의 모든 곳에서 처리하도록 강요합니다.

이것은 당신의 두려움을 방지

C #에서 예외를 무시해도 컴파일 오류가 발생하지 않으며 예외가 발견되지 않으면 버블 링되어 프로그램이 중단됩니다.

그러나 이미 말했듯이,이 두려움은 비이성적입니다. 기본적으로 프로그램에는 일반적으로 모든 것을 포착해야하는 단일 장소가 있습니다 (이미 프레임 워크를 사용하고있을 가능성이 있습니다). 귀하와 귀하의 팀은 일반적으로 몇 주 또는 몇 년 동안 프로그램 작업을하므로 잊지 않습니까?

무시할 수없는 예외 의 가장 큰 장점은 스택 추적의 어느 곳이 아니라 처리하려는 모든 위치에서 처리 할 수 ​​있다는 것입니다. 이것은 많은 상용구를 제거하고 ( "throws ...."를 선언하거나 예외를 줄 바꿈하는 일부 Java 코드를 보면) 코드를 덜 버그하게 만듭니다. 모든 메소드가 발생할 수 있으므로 인식해야합니다. 다행스럽게도 올바른 행동은 일반적으로 아무 것도하지 않는 것입니다 . 즉, 적절한 처리가 가능한 곳으로 버블 링되도록하는 것입니다.


+1인데 왜 OneOf가 부작용으로 작동하지 않습니까? (반환 값을 할당하지 않으면 검사를 통과하기 때문이지만 OneOf를 모르거나 C #을 잘 모르는 경우 명확성이 답변을 향상시킬 것입니다.)
dcorking

1
반환 된 오류 개체에 유용한 정보 (예 : 오류 세부 정보를 제공하는 OneOf<SomeSuccess, SomeErrorInfo>위치)를 포함시켜 OneOf로 오류 정보를 반환 할 수 있습니다 SomeErrorInfo.
mcintyre321

@dcorking 편집했습니다. 물론 반환 값을 무시하면 어떤 문제가 발생했는지 알 수 없습니다. 순수한 기능 으로이 작업을 수행하면 괜찮습니다 (시간 낭비).
maaartinus

2
@ mcintyre321 물론, 정보를 추가 할 수 있지만 수동으로해야하므로 게으름과 잊어 버릴 수 있습니다. 더 복잡한 경우 스택 추적이 더 도움이 될 것 같습니다.
maaartinus

4

"일반적인"예외 (예 : File Not Found 등)를 처리하기 위해 예외 대신 OneOf를 사용하는 것이 합리적인 접근입니까?

제어 흐름으로서의 예외는 심각한 반 패턴으로 간주된다는 것을 들었을 것입니다. 만약 "예외"가 프로그램을 끝내지 않고 일반적으로 처리하는 것이면 "제어 흐름"이 아닌가?

오류에는 여러 가지 차원이 있습니다. 나는 그들이 예방할 수 있는지, 얼마나 자주 발생하는지, 그리고 그들이 회복 될 수 있는지의 3 가지 차원을 식별한다. 한편, 오류 신호는 주로 한 차원으로, 발신자를 머리 위로 넘겨서 오류를 처리하도록하거나 오류를 "조용히"예외로 처리하도록 결정합니다.

예방할 수없고 자주 발생하며 복구 할 수없는 오류는 실제로 호출 사이트에서 처리해야하는 오류입니다. 그들은 발신자가 그들과 대면하도록 강요하는 것이 가장 타당합니다. Java에서는 예외를 확인하고 Try*메소드 를 만들 거나 차별적 인 노조를 반환하여 비슷한 효과를 얻습니다. 차별 조합은 함수에 반환 값이있을 때 특히 좋습니다. 그들은 훨씬 더 세련된 버전의 귀국 null입니다. try/catch블록 의 구문은 괄호가 많지 않아서 대안이 더 좋아 보입니다. 또한 예외는 스택 추적을 기록하기 때문에 약간 느립니다.

예방할 수있는 오류 (프로그래머 오류 / 과실로 인해 방지되지 않음)와 복구 할 수없는 오류는 일반적인 예외와 같이 잘 작동합니다. 프로그래머가 종종 예측하려는 노력을 기울일 가치가없는 경우가 많기 때문에 (예 : 프로그램의 목적에 따라 다름) 드물게 발생하는 오류는 일반적인 예외로 더 잘 작동합니다.

중요한 점은 종종 사용 사이트가 방법의 오류 모드가 이러한 차원에 어떻게 맞는지 결정하는 것이므로 다음을 고려할 때 명심해야합니다.

내 경험상, 오류를 예방할 수없고 빈번하며 복구 할 수있을 때 호출자가 오류 조건에 직면하도록하는 것이 좋지만 , 호출자가 필요하지 않거나 원치 않을 때 후프를 뛰어 넘을 때 매우 성가시다 . 호출자가 오류가 발생하지 않는다는 것을 알고 있거나 코드를 탄력적으로 만들고 싶지 않기 때문일 수 있습니다. Java에서 이것은 확인 된 예외를 자주 사용하고 Optional을 반환하는 것에 대한 불안감을 설명합니다 (아마도 비슷하고 최근에 많은 논란 속에서 소개되었습니다). 확실하지 않은 경우 예외를 던지고 발신자가 예외 처리 방법을 결정하도록합니다.

마지막으로, 오류 상태에 대한 가장 중요한 것은 오류 신호 방식과 같은 다른 고려 사항보다 훨씬 앞서 오류를 철저히 문서화하는 것 입니다.


2

다른 답변은 예외와 오류 코드에 대해 충분히 자세하게 설명 했으므로 질문에 다른 관점을 추가하고 싶습니다.

OneOf는 오류 코드가 아니며 모나드와 비슷합니다.

그것이 사용되는 방식 OneOf<Value, Error1, Error2>은 실제 결과 또는 일부 오류 상태를 나타내는 컨테이너입니다. Optional값이 없으면 그 이유에 대한 자세한 정보를 제공 할 수 있다는 점을 제외하고는와 같습니다 .

오류 코드의 주요 문제점은 오류 코드를 잊어 버리는 것입니다. 그러나 여기서는 문자 그대로 오류를 확인하지 않고 결과에 액세스 할 수 없습니다.

유일한 문제는 결과에 신경 쓰지 않을 때입니다. OneOf 문서의 예는 다음과 같습니다.

public OneOf<User, InvalidName, NameTaken> CreateUser(string username) { ... }

... 그리고 각 결과에 대해 다른 HTTP 응답을 생성하므로 예외를 사용하는 것보다 훨씬 낫습니다. 그러나이 방법을 호출하면

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

당신은 문제가 및 예외는 더 나은 선택이 될 것입니다. Rail 's save와을 비교하십시오 save!.


1

고려해야 할 한 가지는 C #의 코드가 일반적으로 순수하지 않다는 것입니다. 부작용으로 가득 찬 코드베이스와 일부 함수의 반환 값으로 수행하는 작업에 신경 쓰지 않는 컴파일러를 사용하면 접근 방식에 심각한 슬픔이 발생할 수 있습니다. 예를 들어, 파일을 삭제하는 메소드가있는 경우, "행복한 경로"에서 오류 코드를 확인할 이유가 없더라도 메소드가 실패했을 때 애플리케이션이 통지하는지 확인하려고합니다. 이것은 사용하지 않는 반환 값을 명시 적으로 무시해야하는 기능적 언어와의 상당한 차이입니다. C #에서 반환 값은 항상 암시 적으로 무시됩니다 (속성 게터 IIRC 만 예외).

MSDN이 "오류 코드를 반환하지 말 것"이라고 말하는 이유는 바로 이것입니다. 아무도 오류 코드를 읽도록 강요하지 않습니다 . 컴파일러는 전혀 도움이되지 않습니다. 그러나 경우 함수가 부작용 무료이며, 당신이 할 수 처럼 안전하게 뭔가를 사용 Either- 포인트가 오류의 결과 (있는 경우)를 무시하더라도, 당신이 할 만 할 수 있다는 것입니다 당신은 "적절한 결과를"사용하지 않는 경우 어느 한 쪽. 거기 당신이 달성 할 수있는 방법 몇 가지 방법입니다 - 예를 들어, 당신은 단지 성공을 처리하기위한 대리자를 전달하여 값을 "읽기"허용 할 수 있습니다 오류의 경우는, 당신은 쉽게 같은 방식으로 그 결합 할 수 있습니다 철도 지향 프로그래밍 은 않습니다 ( C #에서 잘 작동합니다).

int.TryParse중간 어딘가에 있습니다. 순수합니다. 항상 두 결과의 값을 정의하므로 반환 값이 false이면 출력 매개 변수가로 설정됩니다 0. 여전히 반환 값을 확인하지 않고 출력 매개 변수를 사용하지 못하게하는 것은 아니지만 적어도 함수가 실패하더라도 결과가 무엇인지 보장합니다.

그러나 여기서 절대적으로 중요한 것은 일관성 입니다. 누군가가 그 기능을 부작용으로 변경하면 컴파일러는 당신을 저장하지 않을 것입니다. 또는 예외를 던질 수 있습니다. 따라서 C #에서는이 방법이 완벽하지만 (사용하고 있습니다) 팀의 모든 사람이 이해하고 사용해야 합니다 . 함수형 프로그래밍이 제대로 작동하는 데 필요한 많은 불변은 C # 컴파일러에 의해 강제되지 않으며 자신을 따르고 있는지 확인해야합니다. Haskell이 기본적으로 시행하는 규칙을 따르지 않으면 깨지는 것들에 대해 스스로 알고 있어야합니다. 이는 코드에 소개하는 기능적 패러다임에 대해 명심해야합니다.


0

올바른 설명은 예외에 대해 오류 코드를 사용하지 마십시오! 예외가 아닌 경우에는 예외를 사용하지 마십시오.

코드에서 복구 할 수없는 (즉, 작동하게하는) 무언가가 발생하면 예외입니다. 어떤 방식 으로든 사용자에게 코드를 제공하거나 로그를 제공하기 위해 코드를 위쪽으로 건네주는 것을 제외하고는 즉각적인 코드가 오류 코드와 관련이 없습니다. 그러나 이것은 예외가 정확히 무엇인지입니다. 모든 처리를 처리하고 실제로 일어난 일을 분석하는 데 도움이됩니다.

그러나 자동으로 다시 포맷하거나 사용자에게 데이터 수정을 요청하여 (그리고 그 경우를 생각하여) 처리 할 수없는 잘못된 입력과 같은 문제가 발생하는 경우 실제로는 처음부터 예외가 아닙니다. 기대해 오류 코드 또는 다른 아키텍처를 사용하여 이러한 경우를 자유롭게 처리 할 수 ​​있습니다. 예외는 아닙니다.

(C #에서는 경험이 많지 않지만 예외는 무엇인지의 개념적 수준에 따라 일반적인 경우에 대답해야합니다.)


7
나는이 답변의 전제에 근본적으로 동의하지 않습니다. 언어 디자이너가 예외를 복구 가능한 오류에 사용하지 않으려는 경우 catch키워드가 존재하지 않습니다. 그리고 우리가 C # 컨텍스트에서 이야기하고 있기 때문에 여기에서 설명 된 철학 뒤에는 .NET 프레임 워크가 따르지 않습니다. "처리 할 수없는 잘못된 입력" 의 가장 간단한 상상 가능한 예 는 숫자가 예상되는 필드에 숫자가 아닌 숫자를 입력 int.Parse하고 예외를 throw하는 사용자입니다.
Mark Amery

@MarkAmery int.Parse의 경우 복구 할 수 없거나 예상 할 수있는 것이 없습니다. catch 절은 일반적으로 외부 뷰에서 완전한 실패를 피하기 위해 사용되며 사용자에게 새로운 데이터베이스 자격 증명 등을 요구하지 않으며 프로그램 충돌을 피합니다. 응용 프로그램 제어 흐름이 아닌 기술적 인 오류입니다.
Frank Hopkins

3
조건이 발생할 때 함수가 예외를 발생시키는 것이 더 나은지 여부에 대한 문제는 함수의 즉시 호출자 가 해당 조건을 유용하게 처리 할 수 있는지 여부에 따라 다릅니다 . 가능한 경우, 즉시 호출자가 잡아야하는 예외를 던지면 호출 코드 작성자를위한 추가 작업과이를 처리하는 기계에 대한 추가 작업을 나타냅니다. 그러나 호출자가 조건을 처리 할 준비가되지 않으면 예외로 인해 호출자가 명시 적으로 코드를 작성해야 할 필요성이 완화됩니다.
supercat

@Darkwing "오류 코드 나 다른 아키텍처로 이러한 경우를 자유롭게 처리 할 수 ​​있습니다." 예, 자유롭게 할 수 있습니다. 그러나 .NET CL 자체는 오류 코드 대신 예외를 사용하기 때문에 .NET에서 지원하지 않습니다. 따라서 많은 경우에 .NET 예외를 잡아서 예외 버블을 방지하는 대신 해당 오류 코드를 반환해야 할뿐만 아니라 팀의 다른 사람들이 사용해야하는 다른 오류 처리 개념으로 강제하고 있습니다. 예외와 병행하여 표준 오류 처리 개념.
Aleksander

-2

'끔찍한 생각'입니다.

적어도 .net에는 가능한 예외 목록이 없기 때문에 끔찍합니다. 모든 코드는 예외를 발생시킬 수 있습니다. 특히 OOP를 수행하고 선언 된 유형이 아닌 하위 유형에서 재정의 된 메소드를 호출 할 수있는 경우

따라서 모든 메소드와 함수를로 변경해야합니다. OneOf<Exception, ReturnType>다른 예외 유형을 처리하려면 유형 If(exception is FileNotFoundException)등 을 검사해야합니다.

try catchOneOf<Exception, ReturnType>

편집하다 ----

나는 당신이 돌아 오는 것을 제안한다는 것을 알고 OneOf<ErrorEnum, ReturnType>있지만 이것이 차이가없는 구별이라고 생각합니다.

리턴 코드, 예상 예외 및 분기별로 예외를 결합합니다. 그러나 전체적인 효과는 같습니다.


2
'정상적인'예외도 끔찍한 생각입니다. 단서는 이름에 있습니다
Ewan

4
나는 Exceptions를 반환하도록 제안하고 있지 않다 .
클린턴

당신이 중 하나에 다른 것을 제안하는 것은 예외를 통해 제어 흐름이다
이완
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.