🚨

if err != nil — Ruby의 begin/rescue가 그리워지는 이유

Go는 예외를 안 쓴다. 에러를 값으로 반환한다. 코드의 절반이 에러 처리가 된다.

Ruby 개발자가 Go를 처음 쓸 때 가장 충격받는 부분이다.

Ruby에서는 에러가 나면 예외(Exception)가 던져지고, 어딘가에서 rescue가 잡는다. 잡지 않으면 스택을 타고 올라간다. 행복 경로(happy path)만 쓰고, 에러는 rescue 블록에서 한 번에 처리하면 된다.

Go는 완전히 다르다. 예외가 없다. 에러는 함수의 반환값이다.

Ruby vs Go 에러 처리 비교

Ruby:

begin
  data = File.read("config.yml")
  config = YAML.parse(data)
  save(config)
rescue => e
  puts "에러: #{e.message}"
end

Go:

data, err := os.ReadFile("config.yml")
if err != nil { return err }
config, err := yaml.Unmarshal(data)
if err != nil { return err }
err = save(config)
if err != nil { return err }

보이는가? Ruby는 3줄의 행복 경로 + 1줄의 rescue. Go는 3줄의 실제 로직 + 6줄의 에러 체크. if err != nil이 함수 호출 뒤마다 반복된다.

왜 이런 설계를 했는가

Go 팀의 철학: 에러는 무시하면 안 된다. 예외 기반 시스템에서는 rescue를 안 쓰면 에러가 조용히 전파된다. Go에서는 에러를 반환받고 명시적으로 처리해야 한다. 안 하면 컴파일러가 "err 변수를 사용하지 않았다"고 에러를 낸다.

errors.Is, errors.As — Ruby의 is_a? 같은 것

Ruby에서 rescue ActiveRecord::RecordNotFound 처럼 특정 예외 클래스를 잡듯이, Go에서는 errors.Is(err, sql.ErrNoRows) 또는 errors.As(err, &target) 로 에러 타입을 확인한다.

panic/recover — 정말 쓰지 마라

Go에도 panic()이 있다. Ruby의 raise와 비슷하다. 하지만 Go 커뮤니티에서 panic은 "프로그램이 계속 실행될 수 없는" 심각한 상황에서만 쓴다. HTTP 요청 처리 중 에러는 panic이 아니라 error 반환이다.

Ruby에서 raise/rescue를 일상적으로 쓰는 것과는 문화가 완전히 다르다.

Ruby에서 Go로

1

Ruby: begin/rescue/end → Go: if err != nil { return err } (매 호출마다)

2

Ruby: raise CustomError → Go: errors.New("message") 또는 fmt.Errorf()

3

Ruby: rescue SpecificError → Go: errors.Is(err, target) / errors.As(err, &target)

4

Ruby: raise (일상적) → Go: panic (프로그램 종료 수준에서만, 일상적 사용 금지)

장점

  • 에러를 무시할 수 없다 — 컴파일러가 강제. Ruby의 "rescue 안 썼는데 프로덕션에서 폭발" 문제 방지
  • 에러 흐름이 명시적 — 코드를 읽으면 어디서 에러가 발생하고 어떻게 처리되는지 보인다

단점

  • if err != nil이 코드의 40~50%를 차지 — 시각적 노이즈가 심하다
  • Ruby의 retry 패턴(rescue → retry)이 없어서 재시도 로직을 직접 구현해야 한다

사용 사례

Rails 컨트롤러의 rescue_from 패턴을 Go HTTP 핸들러의 에러 반환으로 전환할 때

참고 자료