[IT-비교] Go 변수 비교와 상수 선택 가이드 – 입문자를 위한 실전 활용법

변수와 상수 개념을 시각화한 Go 프로그래밍 일러스트

데이터의 흐름을 결정하는 변수와 상수

협업 과정에서 동료가 작성한 코드를 리뷰하다가 당황스러운 순간을 마주한 적이 있으신가요? 시스템의 핵심 설정값이 변수로 선언되어 있어, 누군가 실수로 그 값을 변경하는 바람에 전체 서비스가 중단되는 사고가 발생하기도 해요. 이런 문제는 단순한 코딩 실수가 아니라, 데이터의 성격을 명확히 규정하지 못한 설계의 부재에서 비롯돼요.

Go 언어는 매우 엄격하고 명확한 언어예요. 컴파일 단계에서부터 변수와 상수의 역할을 분명히 나누어, 프로그램의 안정성을 높이도록 설계되어 있어요. 하지만 입문자 단계에서는 어떤 값을 var로 선언해야 할지, 아니면 const로 묶어두어야 할지 판단하는 것이 쉽지 않아요.

변수를 너무 남발하면 데이터가 어디서 어떻게 변했는지 추적하기 어려워지고, 반대로 상수를 너무 과하게 사용하면 코드의 유연성이 떨어져 요구사항 변경에 대응하기 힘들어요. 결국 변수와 상수를 적재적소에 사용하는 능력은 단순히 문법을 아는 것을 넘어, 견고한 소프트웨어를 만드는 설계 역량과 직결돼요.

이 글을 끝까지 읽고 나면 여러분은 다음의 내용을 완벽히 이해하게 돼요.

  • 변수와 상수의 근본적인 차이점과 Go 언어만의 동작 방식
  • 상황에 맞는 올바른 선언 방식과 데이터 타입 선택 기준
  • 실무에서 흔히 발생하는 실수와 이를 방지하는 프로덕션 전략
  • Go의 독특한 특징인 ‘타입이 없는 상수(Untyped Constant)’ 활용법

선택을 위한 사전 지식과 판단 기준

본격적으로 코드를 작성하기 전에, 우리가 다룰 두 개념의 기초 체력을 길러야 해요. Go에서 변수는 메모리 공간을 확보하여 값을 저장하고 언제든 변경할 수 있는 상태를 의미하며, 상수는 컴파일 시점에 값이 결정되어 프로그램 실행 중에는 절대 바꿀 수 없는 상태를 의미해요.

단순히 “값이 변하면 변수, 안 변하면 상수”라고 생각하면 위험해요. 데이터가 변하지 않더라도, 그 데이터가 프로그램 실행 중에 동적으로 계산되어야 하는 값이라면 변수로 취급해야 할 수도 있어요. 반대로 값이 고정되어 있더라도, 컴파일 타임에 미리 확정될 수 있는 값이라면 상수로 선언하여 성능과 안정성을 동시에 잡아야 해요.

💡 알아두기
Go의 컴파일러는 상수를 매우 효율적으로 처리해요. 상수는 실행 시점에 메모리를 찾아가는 과정을 거치지 않고, 컴파일 단계에서 코드에 직접 삽입되는 방식으로 최적화될 수 있기 때문이에요.

변수와 상수 결정 가이드라인

어떤 것을 선택할지 고민될 때, 아래의 비교 표를 기준으로 판단해 보세요. 이 기준만 잘 따라도 코드의 품질이 비약적으로 상승해요.

비교 항목 변수 (Variable) 상수 (Constant)
값의 변경 가능성 실행 중 자유롭게 변경 가능 절대 변경 불가 (컴파일 에러)
결정 시점 런타임 (프로그램 실행 중) 컴파일 타임 (빌드 시점)
사용 가능한 자료형 모든 데이터 타입 가능 기본 타입(숫자, 문자열, 불리언)만 가능
주요 용도 계산 결과, 사용자 입력, 상태값 설정값, 수학적 상수, 고정된 메시지

이 표에서 주목할 점은 상수의 자료형 제한이에요. Go에서는 구조체(struct)나 슬라이스(slice), 맵(map) 같은 복잡한 데이터 타입을 상수로 만들 수 없다는 점을 반드시 기억해야 해요. 이러한 복잡한 데이터는 반드시 변수로 선언해야 하며, 이것이 Go 설계의 핵심적인 규칙 중 하나예요.

Go 변수와 상수의 심층 활용법

이제 이론을 넘어 실제 코드 레벨에서 어떻게 이들을 다루는지 단계별로 살펴볼게요. Go의 독특한 문법적 특징을 이해하면 훨씬 더 효율적인 코드를 작성할 수 있어요.

STEP 1. 변수의 선언과 제로 값(Zero Value)의 이해

Go에서 변수를 선언하는 방법은 크게 두 가지예요. 명시적 선언단축 선언이죠. 명시적 선언은 var 키워드를 사용하며, 타입을 직접 지정해 줘요. 반면, 단축 선언은 :=를 사용하여 타입을 컴파일러가 스스로 추론하게 만들어요.

여기서 매우 중요한 개념이 바로 제로 값(Zero Value)이에요. Go는 변수를 선언할 때 별도의 값을 할당하지 않아도, 해당 타입의 기본값으로 자동 초기화해 줘요. 예를 들어, 정수형은 0, 불리언은 false, 문자열은 “”(빈 문자열)이 들어가요. 이는 초기화되지 않은 변수로 인해 발생할 수 있는 예측 불가능한 오류를 원천적으로 차단하는 아주 강력한 기능이에요.

하지만 주의할 점이 있어요. 단축 선언(:=)은 함수 내부에서만 사용할 수 있어요. 패키지 레벨(함수 밖)에서 변수를 선언할 때는 반드시 var 키워드를 사용해야 한다는 규칙을 잊지 마세요.

STEP 2. 상수의 불변성과 컴파일 타임 최적화

상수는 프로그램이 실행되는 동안 값이 변해서는 안 되는 모든 곳에 사용되어야 해요. 예를 들어, API 통신을 위한 타임아웃 시간, 데이터베이스 연결 최대 개수, 혹은 수학 계산에 쓰이는 원주율(Pi) 같은 값들이죠. 상수를 사용하면 코드를 읽는 다른 개발자에게 “이 값은 절대 바뀌지 않으니 안심하고 사용하세요”라는 강력한 메시지를 전달할 수 있어요.

또한, 상수는 컴파일러가 최적화를 수행할 수 있는 힌트가 돼요. 컴파일러는 상수의 값을 미리 알고 있기 때문에, 실행 중에 불필요한 메모리 참조를 줄이고 계산 과정을 단순화할 수 있어요. 이는 대규모 트래픽을 처리하는 고성능 시스템에서 미세하지만 결정적인 차이를 만들어내요.

STEP 3. Go의 마법, 타입이 없는 상수(Untyped Constant)

Go 언어의 가장 매력적인 부분 중 하나는 바로 타입이 없는 상수 개념이에요. 일반적인 변수는 선언할 때 특정 타입(int, float64 등)이 고정되지만, 상수는 선언할 때 타입을 생략할 수 있어요.

타입이 없는 상수는 컴파일 시점에 아주 높은 정밀도를 유지하며, 실제 사용될 때 비로소 해당 타입으로 변환돼요. 예를 들어, 다음과 같은 시나리오를 생각해 보세요.

  • const Pi = 3.14159265358979323846
  • 이 상수는 타입을 지정하지 않았기 때문에, float32 변수에 넣으면 float32 정밀도로 맞춰지고, float64 변수에 넣으면 float64 정밀도로 맞춰져요.

만약 상수에 미리 타입을 지정해 버렸다면, 다른 타입의 변수와 계산할 때마다 번거로운 타입 변환(Type Casting)을 거쳐야 했을 거예요. 하지만 타입 없는 상수를 사용하면 Go의 엄격한 타입 시스템을 유지하면서도, 코드의 유연성을 극대화할 수 있어요. 이것이 바로 Go가 수학적 계산이나 정밀한 데이터 처리에 강점을 보이는 이유예요.

STEP 4. 실전 시나리오: 설정 관리 전략

실제 프로덕션 환경에서 변수와 상수를 어떻게 배분하는지 예시를 통해 알아볼까요? 서비스의 환경 설정(Configuration)을 관리한다고 가정해 봅시다.

💡 알아두기
실무에서는 환경 변수(Environment Variable)를 통해 값을 읽어오는데, 이때 읽어온 값은 런타임에 결정되므로 반드시 변수에 저장해야 해요.

다음은 권장되는 설계 패턴이에요.

  1. 상수(const) 영역: 프로그램 로직 자체에 녹아 있는 불변의 규칙. (예: 최대 재시도 횟수, 기본 페이지 크기)
  2. 변수(var) 영역: 외부 설정 파일이나 환경 변수로부터 읽어온 값. (예: DB 접속 주소, API 키, 타임아웃 설정값)

이렇게 분리하면, 코드를 수정하지 않고도 환경 변수만 바꿔서 서버의 동작을 조절할 수 있으면서도, 시스템의 핵심 규칙은 상수로 보호받는 완벽한 구조를 갖출 수 있어요.

STEP 5. 변수 섀도잉(Shadowing) 주의하기

마지막으로 초보 개발자들이 가장 많이 빠지는 함정인 변수 섀도잉에 대해 이야기할게요. 동일한 이름의 변수를 서로 다른 스코프(Scope)에서 다시 선언할 때 발생하는 현상이에요.

함수 상단에서 선언한 변수를 함수 내부의 짧은 블록(if문이나 for문) 안에서 :=를 사용하여 다시 선언하면, 바깥쪽 변수가 바뀌는 것이 아니라 안쪽에서만 유효한 새로운 변수가 만들어져요. 이는 버그를 추적하기 매우 어렵게 만들며, 의도치 않은 결과를 초래해요. 항상 변수의 이름이 중복되지 않도록 주의 깊게 설계해야 해요.

자주 하는 실수와 해결법

코드를 작성하다 보면 누구나 실수를 해요. Go의 엄격한 규칙 때문에 발생하는 대표적인 실수들과 그 해결책을 정리해 드릴게요.

상수 값에 연산을 시도하거나 재할당하는 경우
왜 발생하는가: 상수를 변수처럼 생각하고 값을 바꾸려 할 때 발생해요.
✅ 해결법: 값이 변해야 한다면 반드시 var를 사용하세요. 상수는 오직 ‘고정된 값’을 위해 존재합니다.

함수 밖(패키지 레벨)에서 :=를 사용하는 경우
왜 발생하는가: 단축 선언은 함수 내부에서만 허용되는 문법이기 때문이에요.
✅ 해결법: 패키지 수준의 전역 변수는 반드시 var 키워드로 선언하세요.

사용하지 않는 변수를 방치하는 경우
왜 발생하는가: Go 컴파일러는 사용되지 않는 변수가 있으면 컴파일 오류를 발생시켜요.
✅ 해결법: 사용하지 않는 변수는 즉시 삭제하거나, 디버깅 용도라면 언더스코어(_)를 사용해 무시하세요.

변수 섀도잉으로 인해 엉뚱한 값을 참조하는 경우
왜 발생하는가: 같은 이름의 변수를 블록 내부에서 :=로 새로 만들었기 때문이에요.
✅ 해결법: 변수 이름을 더 구체적으로 짓거나, 기존 변수에 값을 대입할 때는 := 대신 =를 사용하세요.

상수로 슬라이스나 맵을 만들려고 하는 경우
왜 발생하는가: Go에서 상수는 기본 타입(Primitive types)만 지원하기 때문이에요.
✅ 해결법: 복잡한 구조의 데이터는 무조건 var로 선언하여 변수로 관리해야 합니다.

자주 묻는 질문

Q. 함수 호출 결과를 상수로 만들 수 있나요?

아니요, 불가능해요. 상수는 반드시 컴파일 시점에 값이 확정되어 있어야 해요. 함수는 프로그램이 실행되어야 결과를 알 수 있는 런타임 요소이므로, 함수 결과값은 반드시 변수에 담아야 해요.

Q. 상수를 선언할 때 타입을 지정하는 게 좋은가요, 안 하는 게 좋은가요?
특별한 이유가 없다면 타입을 생략하는 것이 더 유연해요. 앞서 설명한 ‘타입이 없는 상수’의 이점을 활용할 수 있기 때문이죠. 다만, 명확한 타입 규제가 필요한 특수한 경우에는 타입을 지정하는 것이 안전할 수 있어요.

Q. var:=의 성능 차이가 있나요?
실행 성능 면에서는 거의 차이가 없어요. 하지만 코드의 가독성과 안정성 측면에서 차이가 커요. 함수 내부에서는 :=가 간결하지만, 함수의 의도를 명확히 전달해야 할 때는 var를 사용하는 것이 더 좋을 때가 많아요.

Q. 상수는 메모리를 사용하지 않나요?
상수는 메모리 공간을 점유하기보다는 컴파일러에 의해 코드 속에 직접 녹아들어 가는 경우가 많아요. 따라서 변수보다 메모리 효율성과 접근 속도 면에서 유리한 것이 사실이에요.

Q. 모든 고정값은 상수로 만들어야 하나요?
꼭 그렇지는 않아요. 만약 그 값이 나중에 설정 파일 등을 통해 외부에서 변경되어야 할 가능성이 조금이라도 있다면, 상수가 아닌 변수로 설계하는 것이 확장성 면에서 유리해요.

댓글 남기기