Go Mastery Skill (Go 숙련 스킬)
프로덕션 수준의 관용적 Go 코드를 모든 목적으로 작성하세요. 이 스킬은 Go 1.22+ (2024-2026) 기준 Go 생태계의 집단적 지혜를 인코딩합니다.
Go 버전 기준: 1.22 | 기능 범위: 1.26까지 | 업데이트: 2026년 2월
Go 코드를 작성하기 전에 아래 원칙을 내면화하세요. 특정 주제에 대한 심층 분석이 필요하면 참조 파일을 읽으세요.
참조 파일 — 필요 시 읽기
| 파일 | 읽어야 할 때 |
|---|---|
references/concurrency.md | 고루틴, 채널, 동기화 프리미티브 또는 병렬 작업 관련 |
references/http-and-apis.md | HTTP 서버, 라우터, 미들웨어, REST/gRPC API, WebSocket 관련 |
references/database.md | SQL, ORM, 커넥션 풀, 마이그레이션, 트랜잭션 관련 |
references/testing.md | 단위 테스트, 통합 테스트, 벤치마크, 퍼징 관련 |
references/errors-and-resilience.md | 오류 처리, 재시도, 회로 차단기, 정적 성능 저하 관련 |
references/performance.md | 프로파일링, 할당량 감소, GC 튜닝, 벤치마킹 관련 |
references/cli-and-config.md | CLI 프레임워크, 설정, 환경 관리 관련 |
references/project-patterns.md | 프로젝트 레이아웃, DI, 플러그인 시스템, 코드 생성 관련 |
references/platform-and-build.md | 크로스 컴파일, Windows 특화 사항, 임베딩, 배포 관련 |
references/security.md | 인증, 비밀, 입력 검증, TLS, OWASP 패턴 관련 |
references/modern-go.md | Go 1.21–1.26 기능, 더 이상 사용되지 않는 패턴, 사전 학습이 잘못 알고 있는 내용 |
references/advanced-patterns.md | 제네릭, 상태 머신, 스트리밍, 스케줄링, AI/LLM 통합 관련 |
references/advanced-resources.md | 선별된 외부 리소스: 컴파일러/런타임 내부, 어셈블리, unsafe, cgo, Raft, K8s Operator, Wasm, 암호화, 프로파일링, 도서, 컨퍼런스, 학술 논문 |
references/observability.md | OpenTelemetry 설정, 추적, 메트릭, 추적 상관 관계가 있는 구조화된 로깅, 상태 확인, 비행 기록기, 프로덕션 계측 체크리스트 |
references/ecosystem-and-tooling.md | 선별된 목록 (awesome-go), 프레임워크 비교 (웹, ORM, CLI, DI, 설정), 스타일 가이드, CI/CD, Docker, 릴리스 관리, 취약점 스캐닝, 학습 플랫폼, Go 거버넌스 |
references/internals.md | GMP 스케줄러, 가비지 컬렉터, 메모리 모델, 스택 관리, 메모리 할당자, 컴파일러 파이프라인, unsafe, 리플렉션, 컴파일러 지시문 |
references/networking.md | TCP/UDP 서버, net.Conn 데드라인, 커넥션 풀링, mTLS, 유닉스 소켓, DNS 리졸버, 커스텀 바이너리 프로토콜, gnet |
references/mcp-and-agents.md | MCP 서버 (mcp-go, 공식 SDK), 도구/리소스/프롬프트, 에이전트 프레임워크 (ADK, langchaingo, eino), 다중 에이전트 패턴, gopls MCP |
references/api-design.md | REST 명명, 페이지네이션 (커서/오프셋), RFC 9457 오류, 속도 제한, API 버전 관리, OpenAPI 코드 생성 (oapi-codegen), 이전 버전과의 호환성, 지원 중단 헤더 |
references/cgo-and-interop.md | CGo 기본, C 타입 매핑, 경계를 넘는 메모리 관리, 포인터 전달 규칙, 콜백, 정적/동적 링킹, 크로스 컴파일, 순수 Go 대안 |
references/design-patterns.md | Go의 GoF 패턴 (팩토리, 빌더, 전략, 옵저버, 데코레이터), 상속보다 구성, 의사 결정 트리 |
references/distributed-systems.md | Raft, outbox 패턴, 사가, 분산 락킹, 멱등성, CRDT, 일관된 해싱 |
references/cloud-native.md | Kubernetes client-go, controller-runtime Operator, 어드미션 웹훅, 리더 선출, Helm/Kustomize |
references/debugging-and-diagnostics.md | Delve 디버거, 스택 트레이스, GODEBUG 플래그, 프로파일링 워크플로우, 증상-진단 표 |
references/style-synthesis.md | Google + Uber + 커뮤니티 스타일 규칙 병합, 명명, 포맷팅, 코드 구성 |
references/modules-and-dependencies.md | go.mod 작업, MVS, 작업 공간, GOPROXY, GOAUTH, 벤더링, 다중 모듈 모노레포 |
references/encoding-and-serialization.md | JSON (v1/v2), Protocol Buffers, MessagePack, CBOR, CSV, YAML, TOML, 포맷 결정 표 |
references/wasm-and-embedded.md | WebAssembly (WASI, 브라우저), go:wasmexport, TinyGo, 임베디드/IoT 패턴 |
references/migration-guides.md | Python, Java, TypeScript, Rust, C++에서의 관용적 Go 변환 — 개념 맵 및 함정 |
references/testing-advanced.md | 속성 기반 테스트 (rapid), 계약 테스트 (Pact), 부하 테스트 (Vegeta), 카오스 엔지니어링, synctest 고급 |
references/supply-chain-security.md | govulncheck, SBOM 생성, SLSA 프레임워크, cosign, 재현 가능한 빌드, GODEBUG 호환성 |
references/ebpf.md | Go의 eBPF (cilium/ebpf), bpf2go 워크플로우, 추적, 네트워킹, 커널 수준 프로파일링 |
references/event-driven.md | Kafka, RabbitMQ, NATS 소비자/프로듀서, Watermill 프레임워크, DLQ, 정확히 한 번 패턴 |
references/data-structures-and-caching.md | container/heap, container/list, LRU 캐시, singleflight, 샤딩된 맵, 블룸 필터, 약한 참조 캐시, 캐시 전략 패턴 (aside/write-through/write-behind) |
references/ai-ml-beyond-llm.md | ONNX Runtime 추론, Gonum, 벡터 DB 클라이언트, 임베딩 파이프라인, Go의 ML 서빙 |
references/file-io.md | 파일 작업, io.Reader/Writer 구성, 버퍼링된 I/O, 스트리밍, 경로 처리, os.Root, fs.FS 추상화, fsnotify |
핵심 철학
Go는 의도적으로 단순합니다. 다른 생태계의 복잡성을 가져오려는 충동을 억제하세요.
- 모호함보다 명확함. 코드는 작성되는 것보다 10배 더 많이 읽힙니다. 명시적인 것이 암시적인 것보다 낫습니다. 미래의 유지보수자 (자신의 미래 모습 포함)가 감사할 것입니다.
- 상속보다 구성. 인터페이스와 구조체를 임베드하고, 깊은 계층 구조를 피하세요. Go에는 클래스가 없으며, 이는 의도적입니다. 작고 구성 가능한 조각이 복잡한 타입 트리보다 낫습니다.
- 인터페이스를 받고, 구조체를 반환하세요. 함수는 동작(인터페이스)에 의존하고 구체적인 타입을 노출해야 합니다. 이렇게 하면 호출 사이트의 유연성을 유지하면서 구현체를 발견 가능하고 디버깅 가능하게 유지할 수 있습니다.
- 제로 값을 유용하게 만드세요.
var x T를 초기화 없이 바로 사용할 수 있도록 타입을 설계하세요. 이는 nil 포인터 버그의 전체 클래스를 제거하고 API를 자연스럽게 만듭니다 (bytes.Buffer,sync.Mutex,http.Client모두 제로 값에서 작동합니다). - 오류는 값입니다. 처리하세요; 절대 무시하지 마세요.
fmt.Errorf("doing X: %w", err)를 사용하여 컨텍스트를 감싸세요. 무시된 오류는 몇 시간 후 프로덕션에서 표면화되는 silent failure가 됩니다. - 중지할 수 없는 고루틴을 시작하지 마세요. 모든 고루틴에는 컨텍스트 취소 또는 채널 닫기를 통한 종료 경로가 필요합니다. 누수된 고루틴은 메모리를 무기한 소비하고 정상적인 종료를 불가능하게 만듭니다.
- 표준 라이브러리 우선. Go의 표준 라이브러리는 비정상적으로 완벽합니다. 표준 라이브러리가 진정으로 부족할 때만 서드파티 라이브러리를 사용하세요. 종속성이 적을수록 보안 감사, 버전 충돌 및 빌드 시간이 줄어듭니다.
- 코드는
gofmt-clean, 빌드, vet이 완료되어야 끝납니다.gofmt/goimports,go build,go vet,go test -race,golangci-lint를 실행하세요 — CI 게이트로 만드세요; 레이스 디텍터는 리뷰에서 보이지 않는 버그를 찾아냅니다. 도구 접근 없이 코드를 출력할 때는 동일한 기준을 수동으로 유지하세요: gofmt 형식을 유지하고 실제로 사용하는 import만 포함하세요 (사용하지 않는 import는 컴파일 오류입니다 — 한 관용구를 다른 관용구로 바꾼 직후의 전형적인 실수입니다). - 호출자의 계약을 존중하고, 그런 다음 최적화하세요. 작업이 정확한 식별자, 시그니처, 출력 형식 또는 타입을 지정하는 경우, 이름이나 구조를 다르게 지정하더라도 정확히 재현하세요. 사양을 충족하는 가장 간단한 올바른 솔루션을 기본값으로 사용하고, 작업의 복잡성이 요구하는 경우에만 추상화, 간접 참조 또는 방어 계층을 추가하세요. 요구되는 표면을 일치시키는 것 자체가 정확성의 일부입니다. 무분별한 이름 변경이나 구조 조정은 호출자와 테스트를 손상시킵니다.
프로젝트 구조
단순하게 시작하세요. 프로젝트가 요구할 때만 구조를 추가하세요.
단일 바이너리 (CLI 또는 소규모 서비스)
myapp/
├── go.mod
├── main.go # package main, 진입점
├── app.go # 핵심 애플리케이션 로직
├── app_test.go
└── internal/ # 필요에 따른 비공개 패키지
└── parser/
├── parser.go
└── parser_test.go
다중 바이너리 / 프로덕션 서비스
myproject/
├── go.mod
├── cmd/ # 진입점 — 얇은 main() 함수
│ ├── api-server/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/ # 비공개 애플리케이션 코드 (컴파일러에 의해 강제됨)
│ ├── auth/
│ ├── user/ # 도메인 패키지: handler + service + repo
│ │ ├── handler.go
│ │ ├── service.go
│ │ ├── repository.go
│ │ └── model.go
│ ├── platform/ # 횡단 관심사: db, cache, logging
│ │ ├── postgres/
│ │ └── redis/
│ └── middleware/
├── pkg/ # 선택 사항: 공개 재사용 가능 라이브러리
├── api/ # OpenAPI 스펙, protobuf 정의
├── migrations/ # SQL 마이그레이션 파일
├── scripts/ # 빌드, 배포, 개발 도구
├── configs/ # 기본 설정 파일
└── Makefile
핵심 규칙:
internal/은 Go 컴파일러에 의해 강제됩니다 — 여기 있는 코드는 외부 모듈에서 가져올 수 없습니다.cmd/*/main.go는 얇아야 합니다: 플래그 파싱, 의존성 연결,run()호출, 종료 처리.- 도메인/기능별로 그룹화하고, 기술 계층별로 그룹화하지 마세요.
models/,controllers/,services/를 피하세요 — 이것은 Go의 패키지 모델과 충돌하는 Java 스타일 계층화입니다. utils/,helpers/,common/패키지를 피하세요 — 패키지가 제공하는 기능에 따라 이름을 지정하세요. 모호한 이름은 관련 없는 코드를 끌어들이고 import를 무의미하게 만듭니다.- 디렉토리는 패키지입니다. 구성을 위해서만 디렉토리를 만들지 마세요.
명명 규칙
// 패키지: 짧고, 소문자, 단일 단어. 밑줄이나 mixedCaps 없음.
package user // 좋음
package userService // 나쁨 — mixedCaps
// 내보내기: MixedCaps. 내보내지 않음: mixedCaps. 밑줄은 합법적이지만
// 커뮤니티에서는 스타일 버그로 취급합니다 — 관용적이지 않은 코드를 나타냅니다.
func ParseRequest() // 내보내기
func parseHeader() // 내보내지 않음
// 인터페이스: 단일 메서드 인터페이스의 경우 메서드 + "-er" 접미사
// 좋은 예: func (u *User) Name() string // getter — "Get" 접두사는 Go스럽지 않음
// 나쁜 예: func (u *User) SetName(n string) // setter
// 약어: 모두 대문자. URL, HTTP, ID, API — Url, Http, Id, Api가 아님.
type HTTPClient struct{}
var userID int
// 오류 변수: ErrXxx. 오류 타입: XxxError.
var ErrNotFound = errors.New("not found")
type ValidationError struct { Field string; Message string }
// 컨텍스트: 첫 번째 매개변수, 항상 이름은 ctx. 이것은 보편적인 Go 관례입니다.
// 컨텍스트 전파를 모든 코드베이스에서 즉시 인식 가능하게 만듭니다.
func (s *Service) GetUser(ctx context.Context, id string) (*User, error)
오류 처리
이것은 Go의 가장 독특한 패턴입니다. 숙달하세요.
// 모든 오류 확인 — %w로 컨텍스트를 감싸기
f, err := os.Open(name)
if err != nil {
return fmt.Errorf("opening config %s: %w", name, err)
}
defer f.Close()
// errors.AsType — 제네릭 타입 오류 추출 [Go 1.26+]
// errors.As보다 선호: 타입 안전, 미리 선언된 변수 불필요.
if pathErr, ok := errors.AsType[*fs.PathError](err); ok {
fmt.Println("failed at path:", pathErr.Path)
}
핵심 규칙:
- 컨텍스트 없이
return err하면 디버깅이 어려워집니다. 항상fmt.Errorf("doing X: %w", err)로 감싸세요. - 라이브러리 코드에서
log.Fatal(err)를 호출하면os.Exit(1)이 실행되어 지연된 정리가 건너뛰어집니다 — 오류를 호출자에게 반환하세요. panic()은 프로그래머 버그를 위한 것이며, 런타임 조건을 위한 것이 아닙니다 — 대신 오류를 반환하세요.- 서드파티 오류는
%v로 감싸고 (%w아님), 호출자가 기본 오류 타입에 결합되는 것을 피하세요. - 오류 메시지 스타일: 소문자, 구두점 없음:
fmt.Errorf("reading config file: %w", err).
자세한 내용은 references/errors-and-resilience.md를 참조하세요: 센티널 오류, 커스텀 오류 타입, errors.Join, 재시도 패턴, 회로 차단기, 정적 성능 저하.
동시성 필수 요소
전체 패턴(errgroup, 워커 풀, fan-out/fan-in)은 references/concurrency.md를 참조하세요. 핵심 규칙:
- 취소를 위해 컨텍스트를 사용하세요. 모든 고루틴은
ctx.Done()을 확인해야 합니다. 그렇지 않으면 "작업 중지"를 알릴 방법이 없습니다. - 원시
sync.WaitGroup보다errgroup을 선호하세요. 대기 + 오류 수집 + 컨텍스트 취소를 묶습니다. 전체 예제는concurrency.md의SetLimit를 참조하세요. - 공유 상태를 보호하세요. 통신에는 채널, 더 간단할 때는 뮤텍스를 사용하세요. 각 뮤텍스가 무엇을 보호하는지 주석을 남기세요.
- 모든 고루틴에는 종료 경로가 필요합니다. 취소 확인 없음 = 누수된 고루틴 = 메모리 누수.
- 고루틴의 패닉은 전체 프로세스를 종료시킵니다. 생성된 고루틴에서 복구하거나
sourcegraph/conc를 사용하세요.
새로운 동기화 프리미티브:
sync.OnceValue / sync.OnceValues [Go 1.21+] — sync.Once + 패키지 변수를 대체하는 타입 안전 지연 초기화:
var loadConfig = sync.OnceValues(func() ([]byte, error) {
return os.ReadFile("config.json") // 정확히 한 번 실행, 모든 고루틴에서 안전
})
data, err := loadConfig() // 이후 호출은 캐시된 결과 반환
sync.WaitGroup.Go [Go 1.25+] — Add(1) + go func + Done() 결합:
var wg sync.WaitGroup
wg.Go(func() { process(1) })
wg.Go(func() { process(2) })
wg.Wait()
채널 크기: 동기화에는 버퍼 없음 (0), 신호에는 버퍼 있음 (1), 알려진 범위의 작업에는 버퍼 있음 (N).
인터페이스
// 인터페이스를 받고, 구체적인 타입을 반환하세요
type UserStore interface {
GetUser(ctx context.Context, id string) (*User, error)
}
func NewService(store UserStore) *Service { return &Service{store: store} }
// 인터페이스를 작게 유지하세요 — 1-3개의 메서드. 작은 인터페이스로 큰 인터페이스를 구성하세요.
type ReadWriter interface { Reader; Writer }
(원문이 잘렸으므로, 이어지는 내용은 참조 파일과 동일한 원칙을 따릅니다.)