[Go] package
개요
패키지(package)는 Go의 코드 모듈화 및 재사용 단위입니다.
주요 특징:
- 코드를 논리적으로 그룹화하는 기본 단위
- 디렉토리 하나당 하나의 패키지 (테스트 패키지 제외)
- 대문자로 시작하는 식별자는 export (공개)
init()함수로 패키지 초기화- import 별칭으로 이름 충돌 해결
- Go 모듈(module)로 의존성 관리
패키지 기본 개념
1. 패키지 선언
// 파일: math/operations.go
package math
// Add는 두 정수의 합을 반환합니다
func Add(a, b int) int {
return a + b
}
// 소문자로 시작하면 패키지 내부에서만 접근 가능
func multiply(a, b int) int {
return a * b
}
2. 패키지 사용
// 파일: main.go
package main
import (
"fmt"
"myproject/math"
)
func main() {
result := math.Add(10, 20)
fmt.Println(result) // 30
// math.multiply(2, 3) // 컴파일 에러: unexported
}
3. 여러 파일로 구성된 패키지
myproject/
├── main.go
└── calculator/
├── add.go
├── subtract.go
└── multiply.go
// calculator/add.go
package calculator
func Add(a, b int) int {
return a + b
}
// calculator/subtract.go
package calculator
func Subtract(a, b int) int {
return a - b
}
// calculator/multiply.go
package calculator
func Multiply(a, b int) int {
return a * b
}
모든 파일이 같은 calculator 패키지에 속하며, 함께 컴파일됩니다.
Import 방법
1. 기본 Import
import "fmt"
import "math"
import "strings"
// 또는 그룹화
import (
"fmt"
"math"
"strings"
)
2. 별칭 (Alias)
import (
"fmt"
m "math" // 별칭 사용
str "strings"
)
func main() {
fmt.Println(m.Pi) // 3.141592653589793
fmt.Println(str.ToUpper("hello"))
}
3. 빈 식별자 Import
import (
_ "database/sql"
_ "github.com/lib/pq" // init() 함수만 실행
)
// 패키지의 init() 함수는 실행되지만
// 패키지의 다른 함수는 사용하지 않음
4. 점(.) Import (권장하지 않음)
import (
. "fmt"
. "math"
)
func main() {
// 패키지 접두사 없이 사용
Println(Pi) // fmt.Println, math.Pi
// ⚠️ 이름 충돌 위험으로 권장하지 않음
}
5. 상대 경로 Import (권장하지 않음)
// ❌ 권장하지 않음
import "./utils"
import "../common"
// ✅ 절대 경로 사용 권장
import "myproject/utils"
import "myproject/common"
Init 함수
1. 기본 Init
package database
import "fmt"
var db *Database
func init() {
fmt.Println("Initializing database package")
db = &Database{}
db.connect()
}
2. 여러 개의 Init 함수
package config
import "fmt"
func init() {
fmt.Println("First init")
}
func init() {
fmt.Println("Second init")
}
func init() {
fmt.Println("Third init")
}
// 실행 순서: 선언 순서대로
// First init
// Second init
// Third init
3. Init 실행 순서
1. 임포트된 패키지의 변수 초기화
2. 임포트된 패키지의 init() 함수 실행
3. 현재 패키지의 변수 초기화
4. 현재 패키지의 init() 함수 실행
5. main() 함수 실행
예제:
// package a
package a
import "fmt"
var A = func() int {
fmt.Println("Variable A initialized")
return 1
}()
func init() {
fmt.Println("Package A init()")
}
// package b
package b
import (
"fmt"
"myproject/a"
)
var B = func() int {
fmt.Println("Variable B initialized")
return 2
}()
func init() {
fmt.Println("Package B init()")
}
// main.go
package main
import (
"fmt"
"myproject/b"
)
var Main = func() int {
fmt.Println("Variable Main initialized")
return 0
}()
func init() {
fmt.Println("Main package init()")
}
func main() {
fmt.Println("main() function")
}
출력:
Variable A initialized
Package A init()
Variable B initialized
Package B init()
Variable Main initialized
Main package init()
main() function
4. Init 활용 사례
// 데이터베이스 드라이버 등록
package main
import (
"database/sql"
_ "github.com/lib/pq" // init()에서 드라이버 등록
)
// 환경 설정 로드
package config
import (
"os"
"log"
)
var AppConfig *Config
func init() {
var err error
AppConfig, err = loadConfig("config.yaml")
if err != nil {
log.Fatal("Failed to load config:", err)
}
}
// 전역 변수 초기화
package cache
import "sync"
var (
globalCache *Cache
once sync.Once
)
func init() {
once.Do(func() {
globalCache = NewCache()
})
}
가시성 규칙
1. Exported vs Unexported
package library
// Exported: 대문자로 시작 (공개)
type Book struct {
Title string // Exported 필드
Author string // Exported 필드
pages int // unexported 필드
}
// Exported 함수
func NewBook(title, author string) *Book {
return &Book{
Title: title,
Author: author,
pages: 0,
}
}
// Exported 메서드
func (b *Book) GetPages() int {
return b.pages
}
// unexported 메서드
func (b *Book) validate() bool {
return b.Title != "" && b.Author != ""
}
// Exported 상수
const MaxBorrowDays = 14
// unexported 상수
const defaultCategory = "General"
// 다른 패키지에서 사용
package main
import "myproject/library"
func main() {
book := library.NewBook("Go Programming", "John Doe")
// ✅ Exported 접근 가능
fmt.Println(book.Title)
fmt.Println(book.GetPages())
fmt.Println(library.MaxBorrowDays)
// ❌ unexported 접근 불가
// fmt.Println(book.pages) // 컴파일 에러
// book.validate() // 컴파일 에러
// fmt.Println(library.defaultCategory) // 컴파일 에러
}
2. 구조체 필드 가시성
package user
type User struct {
ID int // Exported
Name string // Exported
email string // unexported
password string // unexported
}
func NewUser(id int, name, email, password string) *User {
return &User{
ID: id,
Name: name,
email: email,
password: hashPassword(password),
}
}
// Getter로 접근 제공
func (u *User) Email() string {
return u.email
}
// Setter로 검증 추가
func (u *User) SetEmail(email string) error {
if !isValidEmail(email) {
return errors.New("invalid email")
}
u.email = email
return nil
}
func hashPassword(pwd string) string {
// 해싱 로직
return pwd
}
func isValidEmail(email string) bool {
// 검증 로직
return true
}
Internal 패키지
Go 1.4부터 internal 디렉토리는 특별한 의미를 가집니다.
myproject/
├── go.mod
├── main.go
├── internal/
│ ├── database/
│ │ └── db.go
│ └── utils/
│ └── helpers.go
├── api/
│ └── handlers.go
└── models/
└── user.go
// internal/utils/helpers.go
package utils
func FormatString(s string) string {
return strings.ToUpper(s)
}
// api/handlers.go (같은 프로젝트 내)
package api
import "myproject/internal/utils"
func handler() {
// ✅ 같은 모듈 내에서 접근 가능
result := utils.FormatString("hello")
}
// 외부 프로젝트
package external
import "myproject/internal/utils" // ❌ 컴파일 에러!
// internal 패키지는 상위 디렉토리 내에서만 접근 가능
패키지 문서화
1. 패키지 문서
// Package calculator provides basic arithmetic operations.
//
// This package implements addition, subtraction, multiplication,
// and division for integer and floating-point numbers.
//
// Example usage:
//
// result := calculator.Add(10, 20)
// fmt.Println(result) // 30
//
package calculator
import "errors"
// ErrDivisionByZero is returned when attempting to divide by zero.
var ErrDivisionByZero = errors.New("division by zero")
// Add returns the sum of two integers.
//
// Example:
//
// sum := Add(5, 3) // returns 8
//
func Add(a, b int) int {
return a + b
}
// Divide returns the quotient of two numbers.
// It returns an error if the divisor is zero.
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivisionByZero
}
return a / b, nil
}
2. 예제 코드
// calculator_test.go
package calculator_test
import (
"fmt"
"myproject/calculator"
)
// Example 함수는 godoc에 예제로 표시됨
func ExampleAdd() {
result := calculator.Add(2, 3)
fmt.Println(result)
// Output: 5
}
func ExampleDivide() {
result, err := calculator.Divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result)
// Output: 5
}
Go 모듈 시스템
1. 모듈 초기화
# 새 모듈 생성
go mod init github.com/username/myproject
# go.mod 파일 생성됨
// go.mod
module github.com/username/myproject
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
)
2. 패키지 Import 경로
// 표준 라이브러리
import "fmt"
import "net/http"
// 자체 모듈의 패키지
import "github.com/username/myproject/internal/database"
import "github.com/username/myproject/api/handlers"
// 외부 모듈
import "github.com/gin-gonic/gin"
import "github.com/stretchr/testify/assert"
3. 버전 관리
# 특정 버전 설치
go get github.com/gin-gonic/gin@v1.9.1
# 최신 버전으로 업데이트
go get -u github.com/gin-gonic/gin
# 의존성 정리
go mod tidy
# vendor 디렉토리 생성
go mod vendor
패키지 구조 모범 사례
1. 프로젝트 레이아웃
myproject/
├── go.mod
├── go.sum
├── README.md
├── cmd/
│ ├── server/
│ │ └── main.go
│ └── cli/
│ └── main.go
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── database/
│ │ └── db.go
│ └── handlers/
│ └── user.go
├── pkg/
│ ├── models/
│ │ └── user.go
│ └── utils/
│ └── helpers.go
├── api/
│ └── router.go
├── scripts/
│ └── deploy.sh
└── tests/
└── integration_test.go
디렉토리 설명:
cmd/: 실행 가능한 애플리케이션 진입점internal/: 프로젝트 전용 패키지 (외부 접근 불가)pkg/: 외부에서 사용 가능한 라이브러리api/: API 정의 및 라우팅scripts/: 빌드/배포 스크립트tests/: 통합 테스트
2. 패키지 명명 규칙
// ✅ 좋은 예: 간결하고 명확
package user
package http
package json
// ❌ 나쁜 예: 너무 길거나 모호함
package usermanagement
package utilityhelpers
package misc
3. 순환 의존성 방지
// ❌ 순환 의존성 (피해야 함)
// package a imports package b
// package b imports package a
// ✅ 해결 방법 1: 인터페이스 사용
package models
type UserRepository interface {
GetUser(id int) (*User, error)
}
// ✅ 해결 방법 2: 공통 패키지 분리
package common
type User struct {
ID int
Name string
}
실전 예제
예제 1: HTTP 서버 패키지 구조
webserver/
├── go.mod
├── main.go
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── handlers/
│ │ ├── user.go
│ │ └── product.go
│ └── middleware/
│ └── auth.go
└── pkg/
└── models/
├── user.go
└── product.go
// main.go
package main
import (
"log"
"net/http"
"webserver/internal/config"
"webserver/internal/handlers"
"webserver/internal/middleware"
)
func main() {
cfg := config.Load()
mux := http.NewServeMux()
mux.HandleFunc("/users", handlers.UserHandler)
mux.HandleFunc("/products", handlers.ProductHandler)
handler := middleware.AuthMiddleware(mux)
log.Printf("Starting server on %s", cfg.Port)
log.Fatal(http.ListenAndServe(cfg.Port, handler))
}
// internal/config/config.go
package config
import "os"
type Config struct {
Port string
DBConnection string
}
func Load() *Config {
return &Config{
Port: getEnv("PORT", ":8080"),
DBConnection: getEnv("DB_CONN", "localhost:5432"),
}
}
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
// pkg/models/user.go
package models
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func (u *User) Validate() error {
if u.Name == "" {
return errors.New("name is required")
}
if u.Email == "" {
return errors.New("email is required")
}
return nil
}
예제 2: 계산기 라이브러리
// Package calculator implements basic arithmetic operations.
//
// This package provides functions for addition, subtraction,
// multiplication, and division with proper error handling.
package calculator
import "errors"
// ErrDivisionByZero is returned when dividing by zero.
var ErrDivisionByZero = errors.New("division by zero")
func init() {
// 초기화 로직 (필요시)
}
// Add returns the sum of two integers.
func Add(x, y int) int {
return x + y
}
// Subtract returns the difference of two integers.
func Subtract(x, y int) int {
return x - y
}
// Multiply returns the product of two integers.
func Multiply(x, y int) int {
return x * y
}
// Divide returns the quotient of two numbers.
// It returns an error if y is zero.
func Divide(x, y float64) (float64, error) {
if y == 0 {
return 0, ErrDivisionByZero
}
return x / y, nil
}
// main.go
package main
import (
"fmt"
calc "myproject/calculator" // 별칭 사용
)
func init() {
fmt.Println("Main package initialized")
}
func main() {
fmt.Println("Addition:", calc.Add(10, 5))
fmt.Println("Subtraction:", calc.Subtract(10, 5))
fmt.Println("Multiplication:", calc.Multiply(10, 5))
result, err := calc.Divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Division:", result)
// 0으로 나누기 테스트
_, err = calc.Divide(10, 0)
if err == calc.ErrDivisionByZero {
fmt.Println("Caught division by zero error")
}
}
예제 3: 데이터베이스 패키지
// internal/database/db.go
package database
import (
"database/sql"
"log"
_ "github.com/lib/pq"
)
var db *sql.DB
func init() {
var err error
connStr := "postgres://user:password@localhost/dbname?sslmode=disable"
db, err = sql.Open("postgres", connStr)
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
if err = db.Ping(); err != nil {
log.Fatal("Database is unreachable:", err)
}
log.Println("Database connected successfully")
}
// GetDB returns the database connection.
func GetDB() *sql.DB {
return db
}
// Close closes the database connection.
func Close() error {
return db.Close()
}
일반적인 실수
1. 순환 의존성
// ❌ 잘못된 예
// package user imports package order
// package order imports package user
// ✅ 해결: 인터페이스나 공통 패키지 사용
2. 너무 많은 패키지
// ❌ 과도한 분리
myproject/
├── add/
├── subtract/
├── multiply/
└── divide/
// ✅ 적절한 그룹화
myproject/
└── calculator/
└── operations.go
3. internal 남용
// ❌ 모든 것을 internal에
internal/
├── everything.go
└── ...
// ✅ 재사용 가능한 것은 pkg에
pkg/
├── models/
└── utils/
internal/
└── handlers/
4. init() 함수 남용
// ❌ init()에서 복잡한 로직
func init() {
// 파일 읽기, 네트워크 호출 등
// 에러 처리 어려움
}
// ✅ 명시적 초기화 함수
func Initialize() error {
// 에러 반환 가능
return nil
}
정리
- 패키지는 Go의 기본 모듈화 단위
- 디렉토리당 하나의 패키지 (테스트 제외)
- 대문자 시작: exported (공개), 소문자 시작: unexported (비공개)
init()함수는 패키지 로드 시 자동 실행 (선언 순서대로)- import 별칭으로 이름 충돌 해결
internal/디렉토리는 모듈 내부에서만 접근 가능- Go 모듈로 의존성 관리 (
go.mod) - 명확한 문서화로 사용성 향상
- 순환 의존성 방지
cmd/,internal/,pkg/구조 권장