패키지 시스템 — Go 코드 구성의 기본
Go의 모든 코드는 패키지(package) 단위로 구성됩니다. 패키지는 관련된 기능을 하나로 묶는 Go의 기본 코드 구조 단위입니다.
패키지 선언과 임포트
package main // 패키지 선언 — 모든 .go 파일의 첫 줄
import (
"fmt" // 표준 라이브러리
"math/rand" // 서브패키지
"os"
"github.com/gin-gonic/gin" // 외부 패키지 (모듈 필요)
)
func main() {
fmt.Println("Hello, Go!")
}
패키지 이름 규칙:
- 소문자만 사용
- 짧고 명확하게 (가능하면 한 단어)
- 디렉터리 이름과 일치 권장 (예외:
main,_test) - 복수형이나 공통 접두어 피하기
공개(Exported) vs 비공개(Unexported)
Go는 이름의 첫 글자 대소문자 로 공개/비공개를 결정합니다.
package geometry
import "math"
// ✅ 공개 — 다른 패키지에서 접근 가능
type Circle struct {
Radius float64 // 공개 필드
color string // 비공개 필드 (같은 패키지에서만)
}
// ✅ 공개 함수
func NewCircle(radius float64) *Circle {
return &Circle{
Radius: radius,
color: "red", // 비공개 필드는 같은 패키지에서만 설정 가능
}
}
// ✅ 공개 메서드
func (c *Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// ❌ 비공개 메서드 — 같은 패키지에서만 사용
func (c *Circle) validate() bool {
return c.Radius > 0
}
// 공개 상수
const Pi = 3.14159
// 비공개 상수
const maxRadius = 1000.0
사용 예:
package main
import (
"fmt"
"myproject/geometry"
)
func main() {
c := geometry.NewCircle(5.0)
fmt.Println(c.Area()) // ✅ 공개 메서드 접근 가능
fmt.Println(c.Radius) // ✅ 공개 필드 접근 가능
// fmt.Println(c.color) // ❌ 컴파일 에러
// c.validate() // ❌ 컴파일 에러
}
init 함수 — 패키지 초기화
init() 함수는 패키지가 처음 로드될 때 자동으로 실행됩니다.
package database
import (
"fmt"
"log"
)
var (
connectionPool *Pool
maxConnections = 10
)
// init 함수: 패키지 로드 시 자동 실행
func init() {
fmt.Println("database 패키지 초기화 중...")
var err error
connectionPool, err = newPool(maxConnections)
if err != nil {
log.Fatalf("DB 풀 초기화 실패: %v", err)
}
fmt.Println("database 패키지 초기화 완료")
}
type Pool struct {
size int
}
func newPool(size int) (*Pool, error) {
return &Pool{size: size}, nil
}
init 함수 특징
package mypackage
import "fmt"
var order []string
func init() {
order = append(order, "first init")
fmt.Println("init #1 실행")
}
func init() {
// 같은 파일에 여러 init 함수 가능!
order = append(order, "second init")
fmt.Println("init #2 실행")
}
// init은 인자도, 반환값도 없음
// 직접 호출 불가 (init() 호출 시 컴파일 에러)
init 실행 순서:
- 패키지 레벨 변수 초기화
- 파일 순서대로
init()함수 실행 - 임포트된 패키지의 init이 먼저 실행됨
package main
import (
_ "myproject/database" // 부수 효과만을 위한 임포트 (init 실행)
"fmt"
)
func main() {
fmt.Println("main 실행")
// 출력 순서:
// database 패키지 초기화 중...
// database 패키지 초기화 완료
// main 실행
}
임포트 별칭과 블랭크 임포트
package main
import (
"fmt"
// 별칭 임포트 — 이름 충돌 해결
mrand "math/rand"
crand "crypto/rand"
// 블랭크 임포트 — init 실행만을 위해
_ "github.com/lib/pq" // PostgreSQL 드라이버 등록
// 점 임포트 — 패키지명 없이 사용 (권장하지 않음)
. "math"
)
func main() {
fmt.Println(mrand.Intn(100)) // math/rand 사용
fmt.Println(crand.Reader) // crypto/rand 사용
fmt.Println(Pi) // math.Pi를 Pi로 직접 사용
}
패키지 구조 실전 예시
현실적인 패키지 구조를 살펴봅니다.
myapp/
├── main.go # package main
├── cmd/
│ └── server/
│ └── main.go # package main (서버 진입점)
├── internal/ # 외부 패키지에서 임포트 불가
│ ├── auth/
│ │ └── auth.go # package auth
│ └── db/
│ └── db.go # package db
├── pkg/ # 외부에 공개할 패키지
│ └── validator/
│ └── validator.go # package validator
└── api/
└── handlers.go # package api
// internal/auth/auth.go
package auth
import (
"crypto/rand"
"encoding/hex"
"errors"
)
// ErrInvalidToken — 공개 에러 (센티넬)
var ErrInvalidToken = errors.New("유효하지 않은 토큰")
// Token — 공개 타입
type Token struct {
Value string
UserID string
ExpiresAt int64
}
// Generate — 공개 함수
func Generate(userID string) (*Token, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return &Token{
Value: hex.EncodeToString(b),
UserID: userID,
}, nil
}
// validate — 비공개 (내부에서만)
func validate(token string) bool {
return len(token) == 64
}
패키지 문서화
Go 패키지에 문서를 추가하려면 패키지 선언 위에 주석을 작성합니다.
// Package geometry는 기하학적 도형 계산을 위한 패키지입니다.
//
// 원, 삼각형, 사각형 등 다양한 도형의 면적과 둘레를 계산합니다.
//
// 사용 예:
//
// c := geometry.NewCircle(5.0)
// fmt.Println(c.Area())
package geometry
go doc 명령으로 문서를 확인할 수 있습니다:
go doc geometry
go doc geometry.Circle
go doc geometry.Circle.Area
핵심 정리
- 공개/비공개: 첫 글자 대문자 = 공개, 소문자 = 비공개
- init 함수: 패키지 로드 시 자동 실행, 직접 호출 불가
_블랭크 임포트: init 실행이나 부수 효과를 위한 임포트- 패키지명 = 디렉터리명: 관례적으로 일치시킴
- internal 패키지: 동일 모듈 내부에서만 사용 가능