Menu
Coddy logo textTech

코드 생성 개요

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 84번째.

코드 생성(Code generation)은 런타임에 타입을 검사하는 대신 컴파일 타임에 Go 소스 코드를 생성하는 리플렉션(reflection)의 대안입니다. 이 접근 방식은 반복적인 상용구 코드(boilerplate code)를 줄이면서도 타입 안정성과 더 나은 성능을 제공합니다.

Go는 코드 생성을 자동화하기 위해 go generate 명령을 제공합니다. 소스 파일에 특수한 주석 지시어를 배치하면, go generate를 실행할 때 지정된 도구가 실행됩니다:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

go generate ./...를 실행하면 stringer 도구가 호출되어 Status 타입에 대한 String() 메서드를 포함하는 새 파일이 생성됩니다. 생성된 코드는 프로그램과 함께 컴파일되는 일반적인 Go 코드입니다.

Go 생태계에서 흔히 사용되는 코드 생성 도구로는 열거형(enum) 문자열 메서드를 위한 stringer, 인터페이스로부터 테스트 목(mock)을 생성하기 위한 mockgen, 그리고 Protocol Buffer 정의로부터 코드를 생성하기 위한 protoc 등이 있습니다. 많은 ORM과 API 프레임워크도 타입 안전한 데이터베이스 쿼리나 클라이언트 코드를 생성하기 위해 코드 생성을 사용합니다.

리플렉션(reflection)에 비해 핵심적인 장점은 생성된 코드가 컴파일러에 의해 검사된다는 점입니다. 오류는 런타임이 아니라 빌드 타임에 발견됩니다. 트레이드오프(trade-off)는 추가적인 빌드 단계와 저장소에서 관리해야 할 생성된 파일들입니다. 여러 타입에 걸쳐 반복적인 구현이 필요한 OOP 패턴의 경우, 코드 생성은 종종 안전성과 편의성 사이에서 최상의 균형을 제공합니다.

challenge icon

챌린지

쉬움

열거형(enum)과 유사한 타입을 위한 String() 메서드를 생성하는 코드 생성기를 만들어 봅시다! 이 환경에서는 실제 go generate를 실행할 수는 없지만, 타입 정의를 기반으로 Go 소스 코드를 프로그래밍 방식으로 생성하는 코드 생성 도구의 역할을 시뮬레이션하게 됩니다.

코드는 다음 두 파일로 구성됩니다:

  • generator.go: 코드 생성 로직을 작성합니다.

    Name (문자열)과 Values (상수 이름을 나타내는 문자열 슬라이스)를 가진 열거형 타입 정의를 나타내는 EnumType 구조체를 만듭니다.

    String() 메서드에 대한 Go 소스 코드를 생성하는 GenerateStringMethod(e EnumType) string 함수를 만듭니다. 생성된 코드는 다음 패턴을 따라야 합니다:

    func (e TypeName) String() string {
        switch e {
        case Value1:
            return "Value1"
        case Value2:
            return "Value2"
        default:
            return "Unknown"
        }
    }

    또한 iota를 사용하여 상수 선언을 생성하는 GenerateConstants(e EnumType) string 함수를 만듭니다:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    마지막으로, 이 코드가 생성된 코드임을 나타내는 헤더 주석과 함께 두 출력물을 결합하는 GenerateFullCode(e EnumType) string을 만듭니다:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: 생성기를 사용하여 다양한 열거형 타입에 대한 코드를 생성합니다.

    열거형 타입 이름을 읽은 다음, 값의 개수와 각 값의 이름을 읽습니다. 이 정보를 생성기에 전달하고 생성된 전체 코드를 출력합니다.

다음 입력이 제공됩니다:

  • 1행: 열거형 타입 이름
  • 2행: 값의 개수
  • 다음 행들: 각 값의 이름

예를 들어, 다음과 같이 입력되면:

Status
3
Pending
Active
Completed

출력은 다음과 같아야 합니다:

// Code generated by enum generator. DO NOT EDIT.

const (
    Pending Status = iota
    Active
    Completed
)

func (e Status) String() string {
    switch e {
    case Pending:
        return "Pending"
    case Active:
        return "Active"
    case Completed:
        return "Completed"
    default:
        return "Unknown"
    }
}

그리고 다음과 같이 입력되면:

Priority
2
Low
High

출력은 다음과 같아야 합니다:

// Code generated by enum generator. DO NOT EDIT.

const (
    Low Priority = iota
    High
)

func (e Priority) String() string {
    switch e {
    case Low:
        return "Low"
    case High:
        return "High"
    default:
        return "Unknown"
    }
}

이 연습은 stringer와 같은 도구 뒤에 숨겨진 핵심 개념을 보여줍니다. 즉, 런타임에 리플렉션을 사용하는 대신 컴파일 타임에 반복적이지만 타입 안전한 코드를 생성하는 것입니다. 여러분의 생성기는 파일로 저장되어 프로젝트의 나머지 부분과 함께 컴파일될 수 있는 유효한 Go 코드를 생성합니다.

치트 시트

코드 생성은 런타임에 타입을 검사하는 대신 컴파일 타임에 Go 소스 코드를 생성하여, 상용구 코드를 줄이면서 타입 안전성과 더 나은 성능을 제공합니다.

go generate 명령은 특수한 주석 지시어를 사용하여 코드 생성을 자동화합니다:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

go generate ./...를 실행하면 지정된 도구(예: stringer)가 호출되어, 프로그램과 함께 컴파일되는 생성된 코드가 포함된 새 파일을 만듭니다.

일반적인 코드 생성 도구는 다음과 같습니다:

  • stringer - 열거형(enum) 타입에 대한 String() 메서드를 생성합니다
  • mockgen - 인터페이스로부터 테스트 모의 객체(mock)를 생성합니다
  • protoc - 프로토콜 버퍼(Protocol Buffer) 정의로부터 코드를 생성합니다

리플렉션(reflection) 대비 주요 장점:

  • 생성된 코드는 컴파일러에 의해 검사됩니다
  • 오류가 런타임이 아닌 빌드 타임에 발견됩니다
  • 더 나은 성능

트레이드오프(절충점)로는 추가적인 빌드 단계와 저장소에서 관리해야 할 생성된 파일들이 있습니다.

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 열거형 타입 이름 읽기
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// 값의 개수 읽기
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// 각 값의 이름 읽기
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: 읽어온 데이터로 EnumType 생성

	// TODO: GenerateFullCode를 사용하여 전체 코드를 생성하고 출력
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨