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로
Ruby: begin/rescue/end → Go: if err != nil { return err } (매 호출마다)
Ruby: raise CustomError → Go: errors.New("message") 또는 fmt.Errorf()
Ruby: rescue SpecificError → Go: errors.Is(err, target) / errors.As(err, &target)
Ruby: raise (일상적) → Go: panic (프로그램 종료 수준에서만, 일상적 사용 금지)
장점
- ✓ 에러를 무시할 수 없다 — 컴파일러가 강제. Ruby의 "rescue 안 썼는데 프로덕션에서 폭발" 문제 방지
- ✓ 에러 흐름이 명시적 — 코드를 읽으면 어디서 에러가 발생하고 어떻게 처리되는지 보인다
단점
- ✗ if err != nil이 코드의 40~50%를 차지 — 시각적 노이즈가 심하다
- ✗ Ruby의 retry 패턴(rescue → retry)이 없어서 재시도 로직을 직접 구현해야 한다