Fall in IT.

Go언어에서 구조체의 필드는 Public? Private? 어떤게 맞을까? 본문

카테고리 없음

Go언어에서 구조체의 필드는 Public? Private? 어떤게 맞을까?

D.Y 2025. 3. 29. 09:30
반응형

Go 언어로 개발을 하다 보면, 구조체(struct)를 설계할 때 필드를 공개(public)로 해야 할지, 비공개(private)로 해야 할지 고민되는 경우가 많다. 캡슐화를 위해 private으로 설계했다가, 실제 사용 시 불편해지는 경험도 흔하죠. 그렇다면 Go에서는 어떤 기준으로 필드의 접근 범위를 설정하는 것이 좋을까?

 

이 글에서는 Go 언어의 철학에 기반해, 구조체 필드를 언제 public으로 두고 언제 private으로 설정하는 것이 적절한지를 설명하고자 한다.


Go 언어의 철학: 실용성과 단순성

Go 언어는 "실용적이고 단순한 언어"를 목표로 설계되었습니다. 창시자 중 한 명인 Rob Pike는 다음과 같이 말했다:

"불필요한 복잡성을 피하라"

"명확성이 미묘한 기교보다 낫다"

 

 

이 철학은 구조체 필드의 공개 여부를 결정할 때도 중요한 기준이 된다.


기본 가이드라인

✅ 권장되는 방식:

  • 실제로 비공개로 유지해야 할 명확한 이유가 있는 필드만 private으로 설정
  • 유효성 검사나 부가 로직이 필요한 경우에만 setter 메서드 사용
  • 단순 데이터 구조체는 그냥 public 필드 사용

❌ 피해야 할 방식:

  • 모든 필드를 무조건 private으로 설정
  • 단순 getter/setter 남용
  • 과도한 캡슐화

표준 라이브러리 예시

Go 표준 라이브러리에서도 대부분의 구조체는 필드를 public으로 노출한다:

// time 패키지
 type Time struct {
     wall uint64
     ext  int64
     loc  *Location
 }

// http 패키지
 type Request struct {
     Method string
     URL    *url.URL
     Header Header
     Body   io.ReadCloser
     // ... 등등
 }

 

이처럼 불필요한 캡슐화는 하지 않고, 필요한 경우에만 필드를 숨긴다.


예시: ExcelRow 구조체 설계

type ExcelRow struct {
    // 대부분의 필드는 단순 데이터이므로 public 유지
    IssueNumber string
    PageID      string
    Path        string

    // 특별한 규칙이나 검증이 필요한 필드만 private + setter
    status string
}

// 상태값 검증이 필요한 경우에만 setter 사용
func (r *ExcelRow) SetStatus(status string) error {
    if !isValidStatus(status) {
        return fmt.Errorf("invalid status: %s", status)
    }
    r.status = status
    return nil
}

언제 private 필드가 필요할까? (예시 포함)

✅ 유효성 검증이 필요한 경우

type BankAccount struct {
    accountHolder string
    balance       float64
}

func (b *BankAccount) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("입금액은 0보다 커야 합니다")
    }
    b.balance += amount
    return nil
}

✅ 내부 상태가 서로 연관된 경우

type Cache struct {
    data        map[string]string
    lastUpdated time.Time
}

func (c *Cache) Set(key, value string) {
    c.data[key] = value
    c.lastUpdated = time.Now()
}

✅ 동시성 제어가 필요한 경우

type Counter struct {
    count int64
    mu    sync.Mutex
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

✅ 불변성을 보장해야 하는 경우

type Configuration struct {
    environment string
    apiKey      string
}

func NewConfiguration(env, key string) (*Configuration, error) {
    if env == "" || key == "" {
        return nil, errors.New("environment와 apiKey는 필수입니다")
    }
    return &Configuration{
        environment: env,
        apiKey:      key,
    }, nil
}

func (c *Configuration) Environment() string {
    return c.environment
}

✅ 내부 계산 결과를 캐시하는 경우

type Circle struct {
    radius float64
    area   float64
}

func (c *Circle) SetRadius(r float64) {
    c.radius = r
    c.area = math.Pi * r * r
}

func (c *Circle) Area() float64 {
    return c.area
}

public으로도 충분한 경우

📦 단순 데이터 전송 객체 (DTO)

type UserDTO struct {
    ID        string
    Name      string
    Email     string
    CreatedAt time.Time
}

⚙️ 설정값 구조체

type Config struct {
    Host     string
    Port     int
    Username string
    Password string
}

이러한 구조체들은 다음과 같은 특징이 있어 굳이 private으로 만들 이유가 없다.

  • 단순 데이터 저장 용도
  • 유효성 검사가 필요 없음
  • 필드 간 연관 관계 없음
  • 동시성 제어 필요 없음

✅ 결론

Go 언어는 불필요한 추상화나 캡슐화를 지양하며 실용성과 단순함을 우선합니다. 

  • 단순한 데이터 구조체는 public 필드로 설계하는게 좋다.
  • 유효성 검사, 불변성, 동시성 제어 등이 필요한 경우에만 private 필드 + 메서드를 사용하는게 좋다.
반응형
Comments