Google의 Go 언어는 디자인 선택으로 예외가 없으며 Linux 명성의 Linus는 예외를 쓰레기라고 불렀습니다. 왜?
Google의 Go 언어는 디자인 선택으로 예외가 없으며 Linux 명성의 Linus는 예외를 쓰레기라고 불렀습니다. 왜?
답변:
예외는 throw되는 예외가 불변성을 깨고 객체를 일관성없는 상태로 남겨 두는 코드를 작성하는 것을 정말 쉽게 만듭니다. 그들은 본질적으로 당신이 만드는 대부분의 진술이 잠재적으로 던질 수 있다는 것을 기억하고 올바르게 처리하도록 강요합니다. 그렇게하는 것은 까다 롭고 직관적이지 않을 수 있습니다.
다음과 같은 것을 간단한 예로 고려하십시오.
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
가정 FrobManager
것이다 오른쪽이 외모 확인을? 아니면 아닐 수도 있습니다 ... 그렇다면 또는 예외가 발생하는 경우를 상상해보십시오 . 이 예에서 증분은 롤백되지 않습니다. 따라서이 인스턴스를 사용하는 사람 은 개체가 손상되었을 수 있습니다.delete
FrobObject
FrobManager::HandleFrob()
operator new
m_NumberOfFrobs
Frobber
이 예제는 어리석은 것처럼 보일 수 있지만 (좋아요, 하나를 구성하기 위해 약간의 노력을 기울여야했습니다. :-)),하지만 핵심은 프로그래머가 예외를 계속 생각하지 않고 모든 상태 순열이 롤링되는지 확인하는 것입니다. 던질 때마다 이런 식으로 문제가 생깁니다.
예를 들어 뮤텍스를 생각하는 것처럼 생각할 수 있습니다. 중요한 섹션 내에서 데이터 구조가 손상되지 않았는지, 다른 스레드가 중간 값을 볼 수 없는지 확인하기 위해 여러 명령문에 의존합니다. 이러한 진술 중 하나가 무작위로 실행되지 않으면 고통의 세계에 빠집니다. 이제 잠금과 동시성을 제거하고 각 방법에 대해 그렇게 생각하십시오. 원한다면 각 방법을 객체 상태에 대한 순열 트랜잭션으로 생각하십시오. 메서드 호출을 시작할 때 개체는 깨끗한 상태 여야하고 마지막에는 깨끗한 상태 여야합니다. 그 사이에 변수 foo
는 다음과 일치하지 않을 수 있습니다.bar
, 그러나 귀하의 코드는 결국 그것을 수정할 것입니다. 예외의 의미는 귀하의 진술 중 하나가 언제든지 귀하를 방해 할 수 있다는 것입니다. 각 개별 메서드에서 문제가 발생하면 문제가 발생하면 롤백하거나 작업을 정렬하여 throw가 개체 상태에 영향을주지 않도록해야합니다. 잘못 이해하면 (그리고 이런 종류의 실수를하기 쉽습니다) 호출자는 결국 중간 값을 보게됩니다.
C ++ 프로그래머가이 문제에 대한 궁극적 인 해결책으로 언급하기를 좋아하는 RAII와 같은 메서드는이를 방지하기 위해 먼 길을갑니다. 그러나 그것들은 은총 알이 아닙니다. 던질 때 리소스를 해제 할 수는 있지만 객체 상태의 손상과 호출자가 중간 값을 보는 것에 대해 생각할 필요가 없습니다. 따라서 많은 사람들에게 코딩 스타일의 명목상 예외 는 없다고 말하는 것이 더 쉽습니다 . 작성하는 코드의 종류를 제한하면 이러한 버그를 도입하기가 더 어렵습니다. 그렇지 않으면 실수하기가 매우 쉽습니다.
C ++의 예외 안전 코딩에 대해 전체 책이 작성되었습니다. 많은 전문가들이 잘못 알고 있습니다. 정말 그렇게 복잡하고 뉘앙스가 너무 많다면 해당 기능을 무시해야한다는 좋은 신호일 수 있습니다. :-)
Go에 예외가없는 이유는 Go 언어 디자인 FAQ에 설명되어 있습니다.
예외도 비슷한 이야기입니다. 예외에 대한 많은 디자인이 제안되었지만 각각은 언어와 런타임에 상당한 복잡성을 추가합니다. 본질적으로 예외는 함수와 고 루틴에 걸쳐 있습니다. 그들은 광범위한 의미를 가지고 있습니다. 그들이 도서관에 미칠 영향에 대한 우려도 있습니다. 정의상 예외적이지만이를 지원하는 다른 언어에 대한 경험은 라이브러리 및 인터페이스 사양에 큰 영향을 미친다는 것을 보여줍니다. 일반적인 오류를 모든 프로그래머가 보상해야하는 특수 제어 흐름으로 바꾸지 않고 진정으로 예외적 일 수있는 디자인을 찾는 것이 좋을 것입니다.
제네릭과 마찬가지로 예외도 여전히 미해결 문제입니다.
즉, Go에서 만족 스럽다고 생각하는 방식으로 예외를 지원하는 방법을 아직 파악하지 못했습니다. 그들은 예외가 그 자체 로 나쁘다고 말하는 것이 아닙니다 .
업데이트-2012 년 5 월
Go 디자이너는 이제 울타리에서 내려 왔습니다. 그들의 FAQ는 다음과 같이 말합니다.
try-catch-finally 관용구 에서처럼 예외를 제어 구조에 결합하면 코드가 복잡해집니다. 또한 프로그래머가 파일을 열지 못하는 것과 같은 너무 많은 일반적인 오류를 예외적으로 분류하도록 장려하는 경향이 있습니다.
Go는 다른 접근 방식을 취합니다. 일반 오류 처리를 위해 Go의 다중 값 반환을 사용하면 반환 값을 오버로드하지 않고도 오류를 쉽게보고 할 수 있습니다. Go의 다른 기능과 결합 된 표준 오류 유형은 오류 처리를 즐겁게하지만 다른 언어의 오류 처리와는 상당히 다릅니다.
Go에는 또한 진정으로 예외적 인 조건에서 신호를 보내고 복구 할 수있는 몇 가지 내장 기능이 있습니다. 복구 메커니즘은 오류 후 해체되는 함수 상태의 일부로 만 실행됩니다. 이는 재앙을 처리하기에 충분하지만 추가 제어 구조가 필요하지 않으며 잘 사용하면 깨끗한 오류 처리 코드를 생성 할 수 있습니다.
자세한 내용은 지연, 패닉 및 복구 문서를 참조하십시오.
따라서 짧은 대답은 다중 값 반환을 사용하여 다르게 할 수 있다는 것입니다. (그리고 어쨌든 예외 처리의 한 형태가 있습니다.)
... 그리고 Linux 명성의 Linus는 예외를 쓰레기라고 불렀습니다.
Linus가 예외가 쓰레기라고 생각하는 이유를 알고 싶다면 주제에 대한 그의 글을 찾는 것이 가장 좋습니다. 지금까지 내가 추적 한 유일한 것은 C ++ 에 대한 두 개의 이메일에 포함 된이 인용문입니다 .
"전체 C ++ 예외 처리는 근본적으로 깨졌습니다. 특히 커널에서 깨졌습니다."
그는 특히 C ++ 예외에 대해 이야기하고 있으며 일반적인 예외는 아닙니다. (그리고 C ++ 예외 에는 올바르게 사용하기 까다로운 몇 가지 문제가있는 것 같습니다.)
내 결론은 Linus가 예외 (일반적으로)를 전혀 "쓰레기"라고 부르지 않았다는 것입니다!
예외는 그 자체로 나쁘지는 않지만 많이 발생할 것이라는 것을 알고 있다면 성능 측면에서 비용이 많이들 수 있습니다.
경험상 예외는 예외 조건에 플래그를 지정해야하며 프로그램 흐름을 제어하는 데 사용해서는 안된다는 것입니다.
나는 "예외 상황에서만 예외를 던진다"에 동의하지 않는다. 일반적으로 사실이지만 오해의 소지가 있습니다. 예외는 오류 조건 (실행 실패)에 대한 것입니다.
사용하는 언어에 관계없이 Framework Design Guidelines : Conventions, Idioms, Patterns for Reusable .NET Libraries (2nd Edition)를 선택하십시오. 예외 발생에 대한 장은 피어가 없습니다. 초판 (제 2 판)의 일부 인용문 :
예외의 이점 (API 일관성, 오류 처리 코드 위치 선택, 견고성 향상 등)에 대한 메모 페이지가 있습니다. 여러 패턴 (Tester-Doer, Try-Parse)을 포함하는 성능 섹션이 있습니다.
예외와 예외 처리는 하지 나쁜. 다른 기능과 마찬가지로 오용 될 수 있습니다.
golang의 관점에서 볼 때 예외 처리가 없으면 컴파일 프로세스가 간단하고 안전하게 유지됩니다.
Linus의 관점에서 나는 커널 코드가 코너 케이스에 관한 것임을 이해합니다. 따라서 예외를 거부하는 것이 합리적입니다.
코드에서 예외는 현재 작업을 바닥에 내려도 괜찮고 일반적인 경우 코드가 오류 처리보다 더 중요하다는 점입니다. 그러나 컴파일러에서 코드 생성이 필요합니다.
예를 들어 웹 및 데스크톱 응용 프로그램 코드와 같은 대부분의 고급 사용자 용 코드에서는 문제가 없습니다.
예외는 그 자체로 "나쁜"것이 아니라 때때로 나쁜 경향이있는 예외가 처리되는 방식입니다. 이러한 문제 중 일부를 완화하기 위해 예외를 처리 할 때 적용 할 수있는 몇 가지 지침이 있습니다. 이들 중 일부는 다음을 포함합니다 (하지만 이에 국한되지 않음).
Option<T>
아니라 null
요즘. 예를 들어 Java 8에 도입되어 Guava (및 기타)에서 힌트를 얻었습니다.
일반적인 주장은 특정 코드에서 어떤 예외가 나올지 (언어에 따라 다름), goto
s 와 너무 비슷 하여 정신적으로 실행을 추적하기 어렵게 만드는 방법 이 없다는 것 입니다.
http://www.joelonsoftware.com/items/2003/10/13.html
이 문제에 대한 합의는 확실히 없습니다. Linus와 같은 하드 코어 C 프로그래머의 관점에서 예외는 확실히 나쁜 생각이라고 말할 수 있습니다. 하지만 일반적인 자바 프로그래머는 매우 다른 상황에 처해 있습니다.
setjmp
/ longjmp
물건 도 있는데 , 꽤 나쁩니다.
예외는 나쁘지 않습니다. 그들은 C ++의 가장 우아한 점인 C ++의 RAII 모델과 잘 어울립니다. 예외적으로 안전하지 않은 코드가 이미 많이있는 경우 해당 컨텍스트에서 나쁘다. 리눅스 OS와 같이 정말 낮은 수준의 소프트웨어를 작성한다면 그것은 나쁘다. 많은 오류 반환 검사로 코드를 버리는 것을 좋아한다면 도움이되지 않습니다. 예외가 발생했을 때 (C ++ 소멸자가 제공하는) 리소스 제어에 대한 계획이 없다면 잘못된 것입니다.
따라서 예외에 대한 훌륭한 사용 사례는 ....
프로젝트에 참여하고 있고 모든 컨트롤러 (약 20 개의 다른 주요 컨트롤러)가 액션 메서드를 사용하여 단일 수퍼 클래스 컨트롤러를 확장한다고 가정 해 보겠습니다. 그런 다음 모든 컨트롤러는 한 경우에는 객체 B, C, D를 호출하고 다른 경우에는 F, G, D를 호출하여 서로 다른 작업을 수행합니다. 수많은 리턴 코드가 있고 모든 컨트롤러가이를 다르게 처리하는 많은 경우 예외가 여기에서 구출됩니다. 나는 그 모든 코드를 깨고 "D"에서 적절한 예외를 던졌고, 슈퍼 클래스 컨트롤러 액션 메서드에서 잡았고 이제 우리의 모든 컨트롤러는 일관성이 있습니다. 이전에 D는 최종 사용자에게 알리고 싶지만 할 수 없었던 여러 가지 오류 사례에 대해 null을 반환했습니다.
예, 각 레벨과 리소스 정리 / 누수에 대해 걱정해야하지만 일반적으로 컨트롤러 중 어느 누구도 이후에 정리할 리소스가 없었습니다.
감사합니다. 예외가 있었거나 큰 리팩터링을했고 간단한 프로그래밍 문제에 너무 많은 시간을 낭비했을 것입니다.
이론적으로 그들은 정말 나쁩니다. 완벽한 수학적 세계에서는 예외 상황을 얻을 수 없습니다. 기능적 언어를 보면 부작용이 없으므로 예외적 인 상황에 대한 소스가 거의 없습니다.
그러나 현실은 또 다른 이야기입니다. 우리는 항상 "예기치 않은"상황이 있습니다. 이것이 우리에게 예외가 필요한 이유입니다.
ExceptionSituationObserver에 대한 구문 설탕으로 예외를 생각할 수 있다고 생각합니다. 예외 알림 만받습니다. 더 이상은 없습니다.
Go를 사용하면 "예기치 않은"상황을 처리 할 무언가를 소개 할 것입니다. 나는 그들이 그것을 예외로 덜 파괴적으로 들리게 만들고 응용 프로그램 로직으로 더 많이 들리도록 노력할 것이라고 생각할 수 있습니다. 그러나 이것은 단지 내 추측입니다.
Java와 차례로 .net의 부분적 기반을 형성하는 C ++의 예외 처리 패러다임은 몇 가지 좋은 개념을 소개하지만 몇 가지 심각한 제한 사항도 있습니다. 예외 처리의 주요 설계 의도 중 하나는 메서드가 사후 조건을 충족하거나 예외를 throw하는지 확인하고 메서드가 종료되기 전에 발생해야하는 모든 정리가 발생하는지 확인하는 것입니다. 불행히도 C ++, Java 및 .net의 예외 처리 패러다임은 모두 예기치 않은 요인으로 인해 예상 정리가 수행되지 않는 상황을 처리하는 좋은 방법을 제공하지 못합니다. 이는 예기치 않은 일이 발생하면 모든 것이 갑자기 중단 될 위험이 있음을 의미합니다 (예외 처리에 대한 C ++ 접근 방식은 스택 해제 중에 발생 함).
예외 처리가 일반적으로 좋더라도 다른 문제를 정리할 때 발생하는 문제를 처리 할 수있는 좋은 수단을 제공하지 못하는 예외 처리 패러다임을 받아 들일 수없는 것으로 간주하는 것은 합리적이지 않습니다. 그렇다고해서 다중 실패 시나리오에서도 합리적인 동작을 보장 할 수있는 예외 처리 패러다임으로 프레임 워크를 설계 할 수 없다는 말은 아니지만 상위 언어 나 프레임 워크는 아직 그렇게 할 수 없습니다.
나는 다른 모든 답변을 읽지 않았기 때문에 이미 언급되었지만 한 가지 비판은 프로그램이 긴 사슬로 끊어져 코드를 디버깅 할 때 오류를 추적하기 어렵게 만든다는 것입니다. 예를 들어, Foo ()가 ToString ()을 호출하는 Wah ()를 호출하는 Bar ()를 호출하면 실수로 잘못된 데이터를 ToString ()으로 푸시하면 거의 완전히 관련이없는 함수 인 Foo ()의 오류처럼 보입니다.
좋아, 여기에 지루한 대답. 정말 언어에 따라 다릅니다. 예외로 인해 할당 된 리소스가 남겨질 수있는 경우이를 피해야합니다. 스크립팅 언어에서는 응용 프로그램 흐름의 일부를 버리거나 뛰어 넘습니다. 그것은 그 자체로 싫은 일이지만 예외와 함께 거의 치명적인 오류를 피하는 것은 허용되는 아이디어입니다.
오류 신호의 경우 일반적으로 오류 신호를 선호합니다. 모두 API, 사용 사례 및 심각도 또는 로깅이 충분한 지 여부에 따라 다릅니다. 또한 나는 행동을 재정의하려고 노력하고 있습니다 throw Phonebooks()
. "예외"는 종종 막 다른 골목이지만 "전화 번호부"에는 오류 복구 또는 대체 실행 경로에 대한 유용한 정보가 포함되어 있습니다. (아직 좋은 사용 사례를 찾지 못했지만 계속 시도하십시오.)
나에게 문제는 매우 간단합니다. 많은 프로그래머가 예외 처리기를 부적절하게 사용합니다. 더 많은 언어 자원이 더 좋습니다. 예외를 처리 할 수 있어야합니다. 잘못된 사용의 한 가지 예는 검증되지 않은 정수 여야하는 값이거나 0으로 나누기를 확인하지 않고 나누는 다른 입력입니다 ... 예외 처리는 더 많은 작업과 어려운 생각을 피하는 쉬운 방법 일 수 있습니다. 프로그래머는 더티 단축키를 사용하고 예외 처리를 적용하고 싶을 수 있습니다 ... "전문 코드는 절대 실패하지 않습니다"라는 문장은 알고리즘에 의해 처리 된 문제 중 일부가 그 자체로 불확실한 경우 환상 일 수 있습니다. 아마도 알려지지 않은 상황에서 본질적으로 예외 처리기를 사용하는 것이 좋습니다. 좋은 프로그래밍 관행은 논쟁의 여지가 있습니다.