Go를 더 잘 쓰는 방법: 10개의 코드 리뷰에서 얻은 교훈

발행: (2026년 1월 9일 오후 05:37 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

Handle Errors

  • 오류를 조용히 무시하지 말 것 (예: _ 로 오류 값을 받기)
  • 이 오류가 “허용 가능”이라고 생각하지 말 것 예: if err != nil { return nil }
  • 오류가 발생하면 즉시 확인하고 처리할 것 – 예를 들어 로그를 남기고 그 오류는 여기서 처리했으므로 다른 곳에 전달하지 않음
  • 같은 오류를 중복해서 로그하지 말 것 — 로그를 남긴 뒤 return 해서 다른 사람이 또 로그를 남기면 로그가 중복됨

예시 (bad vs. good)

// Bad
if err != nil {
    slog.Error(err)
    return err
}

// Good
if err != nil {
    slog.Error(err)
    return nil // 혹은 여기서 오류를 처리하고 전달하지 않음
}

호출 측 부담 줄이기

  • return result, nil – 결과를 반환하고 오류가 없으므로 호출자가 이해하기 쉬움
  • return nil, err – 오류가 있어 처리해야 하며 결과는 없음
  • return nil, nil – 나쁨. 호출자가 결과인지 오류인지 해석해야 함 → 피할 것
  • return result, err – 나쁨. 호출자가 두 값을 모두 확인해야 사용 가능 여부를 알 수 있음

Interface 사용

  • 인터페이스를 먼저 만들지 말 것 (Java 같은 다른 언어에서 온 경우가 많음) 혹은 테스트용 mock만을 위해 만들지 말 것
  • accept interfaces, return concrete types – 인터페이스로 입력을 받고 구체 타입을 반환
  • 구체 타입을 사용하다가 정말 필요할 때만 인터페이스로 전환
  • Litmus Test: 인터페이스 없이 테스트가 가능하면 만들 필요 없음
  • 테스트를 위해서만 인터페이스를 만들지 말고, 인터페이스에 의존하지 않는 테스트를 작성할 것

Mutexes Before Channels

  • 채널을 사용하면 코드가 복잡해지고 패닉 위험이 높아짐 (예: 열려 있는 채널을 닫거나 이미 닫힌 채널에 보내기) 혹은 deadlock 발생 가능
  • 먼저 간단한 방법부터 시작: sync.Mutex, sync.WaitGroup 으로 공유 상태 관리
  • 프로파일링 결과 병목이 실제로 존재할 때만 goroutine이나 채널을 추가
  • 복잡한 상황에서만 채널을 사용하고, 간단한 작업에는 사용하지 말 것

선언을 사용 지점에 가깝게

  • constants, variables, functions, types 를 같은 파일 내에서 실제 사용 지점에 가깝게 선언
  • Export(대문자로 시작하는 이름) 은 외부 패키지에서 필요할 때만 사용
  • 함수 내부에서는 변수 선언을 가장 가까운 사용 지점에 배치
  • 범위를 가능한 좁게 제한 – 예를 들어 if 안에 한 줄만 두기

Runtime Panic 방지

  • 외부 입력을 사용하기 전에 반드시 검증
  • 데이터 흐름을 직접 제어한다면 if x == nil 로 검사하지 말고 해당 지점에서 오류 처리를 신뢰
  • 포인터를 역참조할 때 (*p = 10) 반드시 nil 여부를 먼저 확인
  • 가장 안전한 방법은 설계가 허용한다면 포인터 사용을 피하는 것

Formatting (Spacing)

  • 중첩된 로직(if, for)을 과도하게 감싸지 말 것
  • Return EarlyFlatter Structure 를 사용: 오류를 먼저 처리하고 나머지 작업을 진행하거나, 더 이상 진행할 필요가 없는 조건은 즉시 반환

파일 및 패키지 이름 짓기

  • util.go, misc.go, constants.go, interfaces/interfaces.go 와 같이 모호한 이름을 피함
  • 파일 이름은 해당 파일이 하는 일을 의미하도록 지정, 구조상의 위치가 아니라 기능을 나타내야 함
  • 관련 코드를 같은 디렉터리에 두고, 서로 떨어뜨리지 않음

선언 그룹화 및 순서

  • 의미에 따라 그룹화하고, 타입에 따라 구분하지 않음 (예: 컨트롤러를 별도로 그룹화하지 않음)
  • 선언 순서는 중요도에 따라 정렬:
    1. Exported API‑facing functions/structs (사용자가 먼저 보게 할 것)
    2. 위의 부분을 설명하거나 지원하는 helper functions

변수 이름 짓기

  • 타입을 이름에 붙이지 말 것 (예: userMap, idStr, injectFn) – 변수 이름은 무엇을 담고 있는지만 나타내야 함
  • 이름 길이는 스코프에 맞게 조절: 짧은 스코프에서는 짧은 이름, 전역에서는 의미를 충분히 전달할 수 있는 이름 사용

Documentation

  • 문서는 “왜” 에 답해야 하며, “무엇을” 만 설명해서는 안 됨
  • 기능이나 코드가 존재하는 이유와 그 방법을 선택한 이유를 제공
  • 주석은 코드의 목적이나 이점을 전달해야 하며, 단순히 동작을 반복해서 설명해서는 안 됨
  • Documentation 은 “방법”이 아니라 “의도” – 독자가 설계 의도를 이해하도록 돕는 것

“Writing Better Go: Lessons from 10 Code Reviews” 를 Konrad Reiche 가 쓴 내용을 요약 (Asleep‑Actuary‑4428 번역)

Back to Blog

관련 글

더 보기 »

Go의 비밀스러운 삶: 에러 처리

제12장: 깨지는 유리 소리 월요일 아침, 무거운 회색 안개가 도시에 뒤덮이며 찾아왔다. 아카이브 안에서는 침묵이 완전했으며, 깨졌다...

Code Standards와 Best Practices for Growing Teams

왜 Explicit Code Standards가 중요한가? 엔지니어링 팀이 작을 때는 비공식적인 합의가 종종 잘 작동한다. 모든 사람이 어떻게 해야 하는지에 대한 공유된 이해가 있다.