Go는 프로그램 실행 시 패키지, 변수 및 함수 초기화에 대한 엄격한 규칙을 가지고 있습니다. 주요 메커니즘은 init 함수 실행 및 전역 변수 초기화입니다. 이러한 과정에 대한 올바른 이해는 오류와 예상치 못한 효과를 방지하는 데 중요합니다.
문제의 역사:
Go는 초기에 선언, 초기화 및 이후 코드 실행 단계의 엄격한 분리 방식을 도입했습니다. C/C++와 같은 언어에서는 전역 변수의 생성자가 흔히 사용되는 반면, Go는 초기화 순서가 결정적이지만 몇 가지 ню안이 있습니다.
문제:
전역 변수의 초기화 또는 init 호출이 패키지 간의 상호 의존적이거나 순환적인 상황으로 이어지기 쉬운 함정에 빠질 수 있습니다. 이는 추적하기 어렵고 프로그램이 개발자가 예상하는 것과 다르게 동작할 수 있습니다, 특히 숨겨진 의존성이나 초기 상태의 밀폐화가 있을 때.
해결책:
Go의 패키지는 의존성에 따라 초기화됩니다: 먼저 의존성을 초기화하고 그 다음에 패키지를 초기화합니다. 먼저 패키지 수준 변수가 초기화되고 (원본 파일에서의 발생 순서에 따라), 그 다음에 init() 함수가 호출됩니다. 하나의 파일에서 여러 개의 init()를 선언할 수 있습니다. 하나의 패키지 내 파일 간의 초기화 순서는 정의되어 있지 않습니다(이는 오류를 초래할 수 있습니다).
코드 예:
// a.go package main import "fmt" func init() { fmt.Println("a.go에서 초기화됩니다") } // b.go package main import "fmt" func init() { fmt.Println("b.go에서 초기화됩니다") }
이 init 함수의 실행 결과는 동일 디렉토리 내 파일 간에 예측할 수 없지만 항상 main() 함수 이전에 발생합니다.
주요 특징:
하나의 패키지에 있는 다른 파일 간 init 함수 실행 순서에 의존할 수 있나요?
아니요! Go는 하나의 패키지 내 다른 파일의 init 함수 간 실행 순서를 보장하지 않습니다. 특정 순서에 대한 기대는 잡기 어려운 오류와 비즈니스 로직의 붕괴로 이어질 수 있습니다.
init 함수 실행 시 전역 변수가 초기화되지 않을 수 있나요?
아니요 — 패키지의 모든 전역 변수는 이 패키지의 모든 init 함수보다 선언 순서에 따라 엄격히 실행됩니다. 예외는 패키지 간의 교차 초기화일 뿐입니다(아래 참고).
패키지 간 init의 순환 의존성을 어떻게 피할 수 있나요?
Go는 패키지 수준에서 순환 임포트를 허용하지 않지만(이는 컴파일 타임 오류), 간접 초기화의 함정에 빠질 수 있습니다: A는 B에 의존하고, B는 C에 의존하며, C(전역 변수 또는 init를 통해)가 A의 코드를 호출합니다. 이러한 경우 init/전역 생성자 호출 순서가 명확하지 않을 수 있습니다.
서비스 초기화 논리가 서로 다른 파일의 여러 init 함수에서 수행되었습니다. 하나의 init이 다른 init의 결과에 의존하여 다른 서버에서의 빌드 및 실행 중 우연한 동작을 초래합니다.
장점:
단점:
모든 상태와 초기화가 main()에서 명시적으로 호출됩니다. init 함수는 시작 추적 및 간단한 검사를 위해서만 사용됩니다.
장점:
단점: