GoSuda

Go에는 왜 Try-Catch가 없을까?

By Rabbit Princess
views ...

go는 의도적으로 try-catch 를 지원하지 않고, panic-recover 문법만 지원한다. 이는 try-catch 를 이용한 에러처리에 익숙한 수많은 개발자들의 원성을 사고 있다. 그렇다면 try-catch 를 넣지 않는 이유는 무엇일까? 그것은 try-catch 가 여러 문제점을 가지고 있기 때문이다.

try-catch-finally

트라이캐치 (try-catch) 구문은 프로그램 실행 도중(런타임) 발생할 수 있는 에러 상황과 예외 상황을 처리하기 위한 구문이다. 또한 finally 구문은 예외발생 여부와 상관없이 무조건 실행되어야 할 코드를 적는다.

에러 처리와 책임

80년대와 90년대 에는 에러 처리가 매우 단순했다. 에러 메세지는 '플로피 디스크 가득 참', '드라이브에 플로피 디스크 없음', '플로피 디스크 쓰기 권한 없음' 이 전부였고, 이 시절의 개발자는 에러가 발생하면 해당 에러를 에러 처리 지점까지 throw 해서 공통화해 처리했다. 이러한 상황에서 try-catch 구문은 효율적으로 작동하였다.

하지만, 시간이 지나면서 상황이 달라졌다. 비즈니스 로직은 복잡해졌고, 데이터베이스와 트랜잭션이 생겼으며, 네트워크를 통해 수많은 api 를 호출하면서 수많은 리퀘스트 메세지를 해석해야만 했고, 심지어는 동시성 프로그래밍의 등장으로 인해 메인이 아닌 다른 쓰레드에서 에러를 처리해야만 했다.

에러는 더이상 한 곳에서 처리할 수 없을 정도로 복잡해졌고, 어느 한 곳에서 모든 에러를 책임질 수 없게 되었다. 여기서 트라이 캐치에 심각한 문제가 발생한다.

try-catch 의 책임 전가

try-catch 는, 한마디로 말해서, 에러를 터뜨린 주체가 에러 발생에 대한 책임(뒤처리)을 누군가에게 미루는 방법이다. 그 미루는 대상은 catch 구문일수도, 자신의 부모 메소드일수도, 자신의 부모의 부모의 부모의 부모의.. 누군가일수도 있다. 다시 말해, 에러 처리가 많아지고 복잡해지는 세상에서, try-catch 가 택한 방법은 바로 '누군가 해주겠지' 인 것이다. 아래 코드를 보자.

1try {
2    data = readFile("hello.txt");
3    structuredData = parseData(data);
4    insertDBStatus(structuredData[1]);
5    startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7    e.printStackTrace();
8}

위 코드의 문제점은 printStackTrace 를 처리하는 주체가 무엇인지도 명확치 않고, 어떤 코드에서 에러가 났는지도 알 수 없게 된다는 점이다. 심지어 try 명령문 안에 더 많은 로직이 생길 수록 문제는 더더욱 끔찍해진다. 하지만 역설적이게도, 개발이 복잡해지면 복잡해질수록 개발자들은 책임을 전가하는 트라이캐치 구문에 중독되었고, 에러 처리에 대한 고민을 하지 않고, 책임의식 또한 옅어졌으며, 결국에는 에러와 예외 처리의 본질에 대해 잊게 되었다. 그렇다면 golang 은 어떻게 이 문제를 해결했을까?

panic, recover

go 의 장점 중 하나는 개발자를 나쁜 길로 빠지게 하지 않고 좋은 개발자로 만들어주기 위한 여러 시스템이 있다는 점일 것이다. panic - recover 또한 그 예로 들 수 있다. ​ 패닉과 리커버는 언뜻 보면 try-catch 와 다른 점이 없어 보이나, 문제를 일으켰을 때, 책임을 외부로 돌리지 않는다는 점에서 다르다. go 에서 패닉이 일어났을때는 해당 값을 외부로 돌리기보단 패닉이 난 위치에서 해결을 해야 한다. 이는 개발자에게 에러 처리에 대한 책임을 부여해 해당 에러를 어디서, 누가, 어떻게 처리해야 할 지 더 심도있게 고민하도록 유도한다. 사용자에게 자율성을 보장하면서도, 생각할 여지를 남겨주는 셈이다.

또한 panic 이라는 단어의 선정도 매우 탁월하다고 할 수 있는데, try-recover 와 달리 panic-recover 는 단어 만으로 예외 상황이 아닌 명백한 에러 상황에서만 사용해야만 한다는 압박을 개발자에게 부여한다. 자연스레 개발자는 recover 를 남용하지 않고 필요한 곳에서만 사용하게 된다. 이는 go 에서 간결한 코드를 짜는 데에 큰 도움이 된다.