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 Early 와 Flatter Structure 를 사용: 오류를 먼저 처리하고 나머지 작업을 진행하거나, 더 이상 진행할 필요가 없는 조건은 즉시 반환
파일 및 패키지 이름 짓기
util.go,misc.go,constants.go,interfaces/interfaces.go와 같이 모호한 이름을 피함- 파일 이름은 해당 파일이 하는 일을 의미하도록 지정, 구조상의 위치가 아니라 기능을 나타내야 함
- 관련 코드를 같은 디렉터리에 두고, 서로 떨어뜨리지 않음
선언 그룹화 및 순서
- 의미에 따라 그룹화하고, 타입에 따라 구분하지 않음 (예: 컨트롤러를 별도로 그룹화하지 않음)
- 선언 순서는 중요도에 따라 정렬:
- Exported API‑facing functions/structs (사용자가 먼저 보게 할 것)
- 위의 부분을 설명하거나 지원하는 helper functions
변수 이름 짓기
- 타입을 이름에 붙이지 말 것 (예:
userMap,idStr,injectFn) – 변수 이름은 무엇을 담고 있는지만 나타내야 함 - 이름 길이는 스코프에 맞게 조절: 짧은 스코프에서는 짧은 이름, 전역에서는 의미를 충분히 전달할 수 있는 이름 사용
Documentation
- 문서는 “왜” 에 답해야 하며, “무엇을” 만 설명해서는 안 됨
- 기능이나 코드가 존재하는 이유와 그 방법을 선택한 이유를 제공
- 주석은 코드의 목적이나 이점을 전달해야 하며, 단순히 동작을 반복해서 설명해서는 안 됨
- Documentation 은 “방법”이 아니라 “의도” – 독자가 설계 의도를 이해하도록 돕는 것
“Writing Better Go: Lessons from 10 Code Reviews” 를 Konrad Reiche 가 쓴 내용을 요약 (Asleep‑Actuary‑4428 번역)