GO · 심층 가이드
Go Hello World 완전 정리
Go의 첫 프로그램을 패키지·모듈 관점에서 이해하고, go mod init부터 디렉터리 배치와 빌드 자동화까지 프로젝트 뼈대를 스스로 세우도록 안내합니다.
Go에서 첫 프로그램이 특별한 이유는 문법이 아니라 단위 때문입니다. 파일이 아니라 패키지가 컴파일 단위이고, 실행 파일이 되려면 package main과 func main()이라는 두 조건을 동시에 만족해야 합니다. 스크립트 언어에서 넘어온 사람은 "파일을 실행한다"는 감각을 그대로 들고 오는데, Go는 "패키지를 빌드해 바이너리를 만든다"에 가깝습니다. 이 차이를 초반에 잡아두면 이후 import 경로가 왜 그렇게 생겼는지가 한 번에 풀립니다.
다섯 편은 좁은 곳에서 넓은 곳으로 나아갑니다. Hello World와 패키지 구조가 최소 단위를 보여주면, go mod init과 모듈 초기화가 그 코드에 이름을 붙입니다. 이때 정한 모듈 경로가 곧 다른 패키지에서 쓰는 import 접두사가 됩니다. go.mod와 모듈 시스템에서 의존성이 어떻게 기록되고 고정되는지 확인한 뒤, 프로젝트 디렉토리 구조로 파일을 어디에 둘지 결정하고, 마지막 빌드와 배포 자동화에서 이 모든 걸 한 명령으로 묶습니다.
초반에 흔한 사고는 모듈 경로를 아무렇게나 지어 두는 것입니다. 나중에 저장소를 공개하거나 다른 프로젝트에서 가져다 쓸 때 경로가 실제 위치와 어긋나 import가 꼬입니다. 처음부터 저장소 주소 형태로 정하는 편이 안전합니다. 또 대소문자 규칙을 얕보면 안 됩니다. Go는 접근 제어 키워드가 없고 식별자의 첫 글자가 대문자인지 여부가 패키지 외부 공개를 결정합니다. 소문자로 시작한 함수는 같은 패키지 밖에서 절대 보이지 않습니다.
01Hello World & 패키지 구조
Go 프로그램의 최소 구조. package main과 func main()이 진입점입니다.
Go code
package main
import (
"fmt"
"runtime"
)
func main() {
// 기본 출력
fmt.Println("Hello, World!")
// Printf — 포맷 지정
fmt.Printf("언어: %s, 버전: %s
", "Go", runtime.Version())
// Sprintf — 문자열 반환
msg := fmt.Sprintf("GOOS: %s, GOARCH: %s",
runtime.GOOS, runtime.GOARCH)
fmt.Println(msg)
// 여러 값 출력
fmt.Println("값:", 42, true, 3.14)
}fmt.Println은 자동 공백과 줄바꿈, fmt.Printf는 포맷 지정, fmt.Print는 공백/줄바꿈 없음입니다.
import한 패키지를 사용하지 않으면 컴파일 에러입니다. Go는 미사용 import를 허용하지 않습니다. 임시로 사용하려면 import _ "pkg"를 사용하세요.
02go.mod & 모듈 시스템
Go 모듈 시스템으로 의존성을 관리합니다. Go 1.11+에서 표준 방식입니다.
Go code
// 터미널 명령어:
// go mod init github.com/username/myproject
// go mod tidy — 의존성 정리
// go get pkg@version — 패키지 추가
// go.mod 파일 예시:
// module github.com/username/myproject
//
// go 1.22
//
// require (
// github.com/gin-gonic/gin v1.9.1
// github.com/stretchr/testify v1.8.4
// )
// main.go
package main
import "fmt"
func main() {
fmt.Println("모듈 시스템 시작!")
}
// 빌드 & 실행:
// go run . — 현재 디렉토리 실행
// go run main.go — 특정 파일 실행
// go build -o app . — 바이너리 빌드
// go test ./... — 모든 테스트 실행
// go vet ./... — 정적 분석
// gofmt -w . — 포맷 정리go mod tidy는 import된 패키지를 go.mod에 추가하고, 사용하지 않는 의존성을 제거합니다. 커밋 전에 습관적으로 실행하세요.
GOPATH 방식은 레거시입니다. Go 1.16+에서는 모듈 모드가 기본이며, 프로젝트 어디서든 개발할 수 있습니다.
03go mod init & 모듈 초기화
go mod init으로 새 Go 모듈을 초기화하고, go mod tidy로 의존성을 정리합니다. Go 1.22+에서는 워크스페이스 모드도 지원합니다.
Go code
package main
import "fmt"
// 터미널에서 모듈 초기화:
// go mod init example.com/myapp
// go mod tidy
// 워크스페이스 모드 (멀티 모듈):
// go work init ./app ./lib
// go work use ./shared
func main() {
fmt.Println("모듈 초기화 완료!")
fmt.Println("go mod tidy 로 의존성을 정리하세요")
}go mod init 시 모듈 경로를 GitHub 리포지토리 경로(예: github.com/user/repo)로 지정하면 다른 프로젝트에서 import하기 쉽습니다.
go mod init을 실행하지 않고 go build를 시도하면 모듈 외부에서 빌드할 수 없다는 에러가 발생합니다.
04프로젝트 디렉토리 구조
Go 프로젝트의 표준 디렉토리 구조. cmd/, internal/, pkg/ 패턴이 커뮤니티 표준입니다.
Go code
package main
import "fmt"
// 권장 프로젝트 구조:
// myapp/
// ├── cmd/
// │ └── server/
// │ └── main.go // 진입점
// ├── internal/
// │ ├── handler/ // HTTP 핸들러
// │ ├── service/ // 비즈니스 로직
// │ └── repository/ // 데이터 접근
// ├── pkg/ // 외부 공개 패키지
// ├── go.mod
// └── go.sum
func main() {
fmt.Println("cmd/ → 실행 파일 진입점")
fmt.Println("internal/ → 외부 import 불가 (캡슐화)")
fmt.Println("pkg/ → 외부에서 import 가능")
}internal/ 디렉토리는 Go 컴파일러가 강제하는 접근 제한입니다. 외부 모듈에서 절대 import할 수 없으므로 내부 구현을 안전하게 숨길 수 있습니다.
모든 코드를 main 패키지에 넣으면 테스트와 재사용이 어렵습니다. 비즈니스 로직은 별도 패키지로 분리하세요.
05빌드 & 배포 자동화
Makefile과 go build 플래그를 조합하여 빌드/테스트/배포를 자동화합니다. 버전 정보를 -ldflags로 바이너리에 주입합니다.
Go code
package main
import "fmt"
// -ldflags로 주입되는 변수
var (
Version = "dev"
BuildTime = "unknown"
GitCommit = "none"
)
func main() {
fmt.Printf("Version: %s\n", Version)
fmt.Printf("BuildTime: %s\n", BuildTime)
fmt.Printf("GitCommit: %s\n", GitCommit)
// Makefile 예시:
// build:
// go build -ldflags "-X main.Version=1.0.0 \
// -X main.BuildTime=$(date -u +%Y%m%d%H%M%S) \
// -X main.GitCommit=$(git rev-parse --short HEAD)" \
// -o bin/app ./cmd/server
}-ldflags의 -X 플래그는 main.변수명=값 형식으로 문자열 변수만 주입할 수 있습니다. 패키지 경로를 정확히 지정하세요.
-ldflags에서 변수 이름을 패키지 경로 없이 쓰면 주입되지 않습니다. main.Version처럼 전체 경로를 사용하세요.
정리하며
- 실행 파일은 package main과 func main이 함께 있을 때만 만들어집니다
- go mod init에 준 모듈 경로가 그대로 내부 import 접두사가 되므로 신중히 정합니다
- 식별자 첫 글자의 대소문자가 패키지 외부 공개 여부를 결정합니다
- 빌드 명령을 스크립트로 고정해 두면 환경이 바뀌어도 같은 산출물을 얻습니다
더 깊이 들어가고 싶다면 Go 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.