PHpullh
심층 가이드/Go/Hello World

GO · 심층 가이드

Go Hello World 완전 정리

Go의 첫 프로그램을 패키지·모듈 관점에서 이해하고, go mod init부터 디렉터리 배치와 빌드 자동화까지 프로젝트 뼈대를 스스로 세우도록 안내합니다.

주제 5개 · 예제 코드 포함 · 최종 수정 2026-08-30 · 작성 pullh 편집팀

Go에서 첫 프로그램이 특별한 이유는 문법이 아니라 단위 때문입니다. 파일이 아니라 패키지가 컴파일 단위이고, 실행 파일이 되려면 package mainfunc main()이라는 두 조건을 동시에 만족해야 합니다. 스크립트 언어에서 넘어온 사람은 "파일을 실행한다"는 감각을 그대로 들고 오는데, Go는 "패키지를 빌드해 바이너리를 만든다"에 가깝습니다. 이 차이를 초반에 잡아두면 이후 import 경로가 왜 그렇게 생겼는지가 한 번에 풀립니다.

다섯 편은 좁은 곳에서 넓은 곳으로 나아갑니다. Hello World와 패키지 구조가 최소 단위를 보여주면, go mod init과 모듈 초기화가 그 코드에 이름을 붙입니다. 이때 정한 모듈 경로가 곧 다른 패키지에서 쓰는 import 접두사가 됩니다. go.mod와 모듈 시스템에서 의존성이 어떻게 기록되고 고정되는지 확인한 뒤, 프로젝트 디렉토리 구조로 파일을 어디에 둘지 결정하고, 마지막 빌드와 배포 자동화에서 이 모든 걸 한 명령으로 묶습니다.

초반에 흔한 사고는 모듈 경로를 아무렇게나 지어 두는 것입니다. 나중에 저장소를 공개하거나 다른 프로젝트에서 가져다 쓸 때 경로가 실제 위치와 어긋나 import가 꼬입니다. 처음부터 저장소 주소 형태로 정하는 편이 안전합니다. 또 대소문자 규칙을 얕보면 안 됩니다. Go는 접근 제어 키워드가 없고 식별자의 첫 글자가 대문자인지 여부가 패키지 외부 공개를 결정합니다. 소문자로 시작한 함수는 같은 패키지 밖에서 절대 보이지 않습니다.

01Hello World & 패키지 구조

Go 프로그램의 최소 구조. package mainfunc 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 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.