본문 바로가기
Language/Go

Exception

by ocwokocw 2022. 6. 14.

- 출처: https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right

- History

C는 단일값을 반환하는 구조였기 때문에 함수 실행과정에서 문제가 생겼을 때 이를 파악하는 과정이 복잡했다. 물론 이런 문제를 다루기 위한 프로그래밍 스킬들이 있었다. 예를 들면 구조체의 내용을 변경하는 함수에 포인터를 넘겨주면 반환된 코드가 해당 작업을 성공했는지를 나타내는것과 같은 방식이다. 다른 스킬들도 있지만 이 글에서 다루고자하는 중심적인 얘기는 아니므로 넘어가겠다.
 
C++이 되면서 error를 다루는 작업이 진화했다. 어떤 함수가 값을 반환하거나 exception을 던지면 이를 잡아서 다루는게 가능해졌다. C++ 프로그래머들은 error 상황을 함수가 반환하는 단일 값으로 표현하지 않고 error에 대한 신호를 보낼 수 있게 되었다. 더 나아가서 exception은 호출 스택의 어느곳에서든 다룰 수 있게 되었다. 만약 해당 exception을 어떻게 다루어야 하는지 모른다면 해당 exception은 계속해서 상위로 전파된다. errno 및 스레드문제가 해결된것이다.
 
C++ exception의 단점은 호출하는 함수가 exception을 던질 수 있는지 여부를 알 수 없다는것이다. 자원에 대한 누수와 소멸자에 대한 걱정외에도 exception이 발생해서 호출 스택 어딘가에 있는 경우 작성한 메소드가 exception에 안전한지를 보장하기 위해 RAll 과 트랜잭션의 의미체계에 대해 걱정해야 했었다. C++은 하나의 문제를 해결했지만 또 다른 문제를 만들었다.
 
Java 설계자들은 exception 자체가 문제가 아니라 아무런 고지 없이 exception이 던져질 수 있다는게 문제라고 생각하여 checked exception을 생각해냈다. 메소드 시그니처에 exception에 대한 언급을 하지 않으면 메소드 안에서 exception을 던질 수 없게되었고, exception을 던질 수 있는 메소드에 대해서 해당 exception을 감싸서 처리하는 로직을 넣지 않으면 해당 메소드를 호출할 수 없게 설계했다. 컴파일 시간에 이를 해결하도록 하여 error 문제를 해결한것이다.
 
exception의 처리가 안전해지면서 개발자들은 허점이 없나 또다시 분석하기 시작했다. 구축된 Java webapp 들이 시작시에 호출 스택에서 의무적으로 log를 출력하고 무시하는 패턴이 일반화되었다. Java의 exception들은 더이상 예외적인 상황이 아니라 일반적인 상황이 되기에 이르렀다. exception들은 비교적 가벼운것부터 심각한 상황에까지 모든곳에 사용되며 exception의 심각도는 함수의 호출자에 의해 구분된다.
 
Java에서는 치명적인 exception이 아니면 checked가 아닌 java.Error와 java.RuntimeException의 서브클래스인 unchecked exception이다. 작성자는 이들을 선언할필요없이 던지기만 하면 된다. 설계당시에는 지금은 구현하기 간단해진 null 참조나 배열 오류같은것을 고려했겠지만 동시에 모든 exception은 java에서 java.Exception을 상속받기 때문에 코드의 모든 부분에서 catch가 가능해지며 아래와 같은 패턴으로 사용될 가능성이 크다.
 
catch (e Exception) { // ignore }
 
Java는 C++의 unchecked exception 문제를 대부분 해결했지만 이를 위해 많은 새로운 것들을 도입했다. 그러나 Java는 실질적인 문제를 해결하지 못했는데 작성한 함수에서 무엇이 잘못되었는지를 호출자에게 전달하지 못했다는것이다.

- Enter Go

Go는 이 문제를 해결하기 위해 exception을 도입하지 않았다. 대신 함수가 error type을 반환할 수 있게 하였고, 다중 값 반환을 가능하게 설계하여 이 문제를 해결하였다. error type 인터페이스 반환값을 선언해서 호출자에게 이 메소드가 잘못될 동작을 할수도 있다는 사실을 알려주는것이다. 만약 함수가 값과 error를 반환하면, error를 검사하기 까지는 값이 정상적이라고 섣불리 가정할수가 없다. 
 
Go는 panic 이라는 기능이 있는데 얼핏보면 throw와 비슷하다고 생각할 수 있지만 이는 잘못된것이다. 만약 exception을 던지면 그건 호출자의 문제가 된다. 
 
throw new SomeoneElsesProblem();
 
예를 들어 C++에서 enum을 문자열로 바꿀 수 없거나 Java에서 문자열을 date로 파싱할 수 없을 때 exception을 던질것이다. 네트워크의 입력을 믿을 수 없는 상황에서 문자열을 date로 파싱하는데 실패한 상황이 정말 예외적인 상황일까?
 
Go에서는 panic은 매우 심각한 상황이라고 보면 된다.
 
panic("inconceivable")
 
panic은 언제나 프로그램에 치명적이다. panic이 발생하면 호출자가 해당 문제를 해결할 수 없다. panic은 코드가 계속될 수 없는 정말로 예외적인 상황에서 사용해야 한다. 
 
Go에서 exception을 포함하지 않은 결정은 Go가 얼마나 단순한지를 보여주는 단편적인 예이다. 다중 반환 값과 간단한 convention을 이용하여 Go는 무언가 잘못되었을 때 프로그래머들에게 어떻게 알릴것인지에 대한 문제를 해결 하였고, 정말로 예외적인 경우를 위해 panic을 남겨두었다.
 
 

'Language > Go' 카테고리의 다른 글

Zero value  (0) 2022.06.17
Error handling을 간단하게  (0) 2022.06.16
Error handling  (0) 2022.06.12
Effective Go - Errors  (0) 2022.06.05
Effective Go - Concurrency - 3  (0) 2022.06.02

댓글