
변수와 상수, 왜 매번 고민하게 될까요
Go 언어로 코드를 작성하다 보면 아주 단순하지만 결정하기 어려운 순간이 찾아와요. “이 값을 var로 선언할까, 아니면 const로 고정할까?”라는 질문이죠. 처음에는 아무렇게나 써도 프로그램이 돌아가니까 큰 문제가 없다고 느껴질 수 있어요.
하지만 시간이 지나 코드가 복잡해지면 상황은 달라져요. 실수로 바꿔서는 안 될 설정값을 프로그램 중간에 수정해버려서 시스템 전체가 오작동하거나, 반대로 값이 바뀌어야 하는 데이터를 상수로 고정해버려 버그를 만드는 일이 빈번하게 발생하거든요. 이런 사소한 선택의 차이가 나중에는 유지보수가 불가능한 코드를 만드는 원인이 되기도 해요.
단순히 문법을 아는 것과, 어떤 상황에서 어떤 도구를 꺼내 들어야 하는지 아는 것은 완전히 다른 문제예요. 숙련된 개발자는 코드를 쓰기 전에 이미 이 값이 변할 가능성이 있는지, 아니면 프로그램이 끝날 때까지 변하지 않을 값을 담을 것인지 머릿속으로 결정하고 시작해요.
이 글을 끝까지 읽고 나면 여러분은 더 이상 변수와 상수 사이에서 방황하지 않게 될 거예요. 상황에 맞는 최적의 선택 기준을 갖추게 되고, 읽기 좋고 안전한 Go 코드를 작성할 수 있는 능력을 갖추게 된답니다. Go 변수 선택 가이드를 통해 실전 감각을 익혀보세요.
이 글에서 함께 살펴볼 내용이에요
- 변수와 상수의 본질적인 차이와 동작 원리
- 상황별 최적의 선택을 위한 판단 기준
- 실전 프로덕션 환경에서 바로 쓰는 코드 패턴
- 초보 개발자가 자주 빠지는 함정과 해결 방법
기본 개념 이해와 선택을 위한 기준점
본격적으로 코드를 작성하기 전에, 우리가 사용할 도구들의 특징을 명확히 짚고 넘어가야 해요. 무작정 타이핑을 시작하기보다, 각 도구가 가진 성질을 이해하면 설계 단계부터 실수가 줄어들거든요.
변수(Variable)의 성질
변수는 말 그대로 변할 수 있는 값을 담는 바구니예요. 프로그램이 실행되는 동안 그 안에 담긴 내용물이 언제든지 교체될 수 있죠. 사용자의 입력값, 계산 결과, 반복문에서 증가하는 숫자 등이 모두 변수에 해당해요. Go에서는 var 키워드를 사용하거나, 함수 내부에서 := 짧은 선언식을 사용하여 간단하게 만들 수 있어요.
상수(Constant)의 성질
상수는 이름 그대로 절대 변하지 않는 값을 의미해요. 한 번 결정되면 프로그램이 끝날 때까지 그 값을 유지해야 하죠. 상수는 컴파일 단계에서 이미 값이 결정되어야 하며, 실행 중에 값을 바꾸려고 시도하면 컴파일 에러가 발생해요. 이는 프로그래머의 실수를 컴파일러가 미리 잡아준다는 아주 강력한 장점이 돼요.
Go에서 상수는 단순히 값을 고정하는 것을 넘어, 컴파일러가 코드를 최적화할 수 있는 단서를 제공해요. 상수를 적절히 사용하면 성능 면에서도 이득을 볼 수 있답니다.
어떤 것을 선택할지 결정하는 기준
결정하기 어렵다면 아래의 비교 표를 머릿속에 넣어두세요. 코드를 작성할 때마다 이 기준을 떠올리면 판단이 훨씬 빨라질 거예요.
| 비교 항목 | 변수 (var) | 상수 (const) |
|---|---|---|
| 값의 변경 가능성 | 언제든 변경 가능 | 변경 절대 불가 |
| 결정 시점 | 프로그램 실행 중 (Runtime) | 컴파일 단계 (Compile-time) |
| 주요 용도 | 데이터 처리, 사용자 입력 | 설정값, 수학적 상수, 고정된 규칙 |
| 메모리 할당 | 메모리 주소를 가짐 | 값 자체가 코드에 직접 포함됨 |
결론적으로, 값이 변할 여지가 1%라도 있다면 변수를 사용하고, 절대로 변하지 않을 것이라고 확신한다면 상수를 사용하세요. 이 단순한 원칙이 코드의 안전성을 결정지어요.
실전에서 적용하는 단계별 활용 전략
이제 이론을 넘어 실제로 코드를 어떻게 짜야 하는지 구체적으로 살펴볼게요. 단순히 문법을 나열하는 게 아니라, 실제 개발 현장에서 마주치는 상황들을 중심으로 단계를 나누어 보았어요.
STEP 1. 변수의 다양한 선언 방식 마스터하기
Go에서는 상황에 따라 변수를 선언하는 방식이 달라요. 이 방식을 적절히 섞어 써야 코드가 깔끔해져요.
가장 기본적인 방식은 var 키워드와 타입을 명시하는 거예요. 주로 함수 밖의 전역 변수를 선언하거나, 초기값을 명확히 지정하고 싶을 때 사용해요. 예를 들어, var score int = 100처럼 말이죠.
두 번째는 타입 추론(Type Inference)을 활용하는 방식이에요. var score = 100이라고 쓰면 Go가 알아서 정수형으로 판단해줘요. 타입을 일일이 적기 귀찮을 때 아주 유용해요.
세 번째는 함수 내부에서 가장 많이 쓰이는 := 단축 선언식이에요. score := 100처럼 쓰면 코드 줄 수가 확 줄어들죠. 하지만 주의할 점이 있어요. 이 방식은 반드시 함수 안에서만 사용할 수 있고, 선언과 동시에 초기화가 이루어져야 한다는 점이에요. 전역 공간에서 이 방식을 쓰면 컴파일 에러가 나니까 조심해야 해요.
STEP 2. 상수의 강력한 힘, 컴파일 타임 결정 활용하기
상수는 단순히 ‘고정된 값’ 그 이상이에요. Go의 상수는 매우 독특한 특징이 있는데, 바로 타입이 없는 상수(Untyped Constant)라는 점이에요.
우리가 const Pi = 3.14159라고 선언하면, 이 값은 특정 타입(float64 등)에 묶여 있지 않아요. 덕분에 이 상수는 다양한 타입의 변수에 아주 유연하게 들어갈 수 있답니다. 만약 const Pi float64 = 3.14라고 타입을 딱 정해버리면, 나중에 다른 타입의 변수에 넣을 때 매번 형 변환을 해줘야 하는 번거로움이 생겨요.
상수를 선언할 때는 가능하면 타입을 명시하지 않는 것이 Go의 유연함을 극대화하는 방법이에요. 컴파일러가 가장 적절한 타입을 알아서 찾아줄 수 있도록 맡겨두세요.
STEP 3. 프로덕션 환경을 위한 변수와 상수 설계 패턴
실제 서비스를 만들 때는 어떤 값을 무엇으로 정의할지 설계가 필요해요. 아래 시나리오를 통해 감을 잡아보세요.
시나리오: API 통신 모듈 개발
- API 서버의 주소(URL): 프로그램이 실행되는 동안 절대 바뀌지 않죠? ➡️
const API_URL = "https://api.example.com" - 사용자의 로그인 시도 횟수: 로그인을 할 때마다 숫자가 올라가야 해요. ➡️
var loginAttempts int = 0 - 최대 로그인 허용 횟수: 시스템 보안 규칙으로 정해진 값이에요. ➡️
const MaxAttempts = 5 - 데이터베이스 연결 타임아웃 시간: 설정 파일에서 읽어와야 하므로 실행 중에 바뀔 수 있어요. ➡️
var timeout time.Duration
이처럼 ‘설정(Configuration)’과 ‘상태(State)’를 구분하는 것이 핵심이에요. 설정은 상수로, 상태는 변수로 관리하는 패턴을 몸에 익히세요.
STEP 4. 복합 타입(Slice, Map) 사용 시의 주의사항
이 부분이 초보자들이 가장 많이 실수하는 지점이에요. 슬라이스(Slice)나 맵(Map) 같은 복합 타입은 상수로 만들 수 없어요.
많은 분이 const myMap = map[string]int{"a": 1} 같은 코드를 짜려고 시도하지만, Go 컴파일러는 이를 허용하지 않아요. 왜 그럴까요? 상수는 컴파일 타임에 값이 완전히 결정되어야 하는데, 슬라이스나 맵은 메모리 내부의 구조를 통해 동적으로 데이터를 관리하는 복잡한 객체이기 때문이에요.
맵이나 슬라이스를 ‘고정된 값’처럼 쓰고 싶더라도, 문법적으로는 반드시
var를 사용하여 변수로 선언해야 해요. 대신, 해당 변수를 외부에서 수정하지 못하도록 캡슐화(Encapsulation)하는 전략을 사용하는 것이 올바른 방향이에요.STEP 5. 실전 코드 예제와 실행 흐름 확인
이해를 돕기 위해 작은 예제 코드를 구성해 봤어요. 이 코드가 어떻게 동작하는지 상상하며 읽어보세요.
package main
import "fmt"
// 상수는 패키지 수준에서 선언 가능
const MaxUsers = 100
func main() {
// 변수는 함수 내부에서 선언
var currentUsers = 10
fmt.Println("현재 사용자 수:", currentUsers)
fmt.Println("최대 허용 사용자 수:", MaxUsers)
// 사용자 추가 상황 (변수 값 변경)
currentUsers += 5
fmt.Println("사용자 5명 추가 후:", currentUsers)
// 만약 MaxUsers = 200 이라고 쓰면?
// -> 컴파일 에러 발생! (상수는 변경 불가)
}
위 코드에서 MaxUsers는 시스템의 한계를 나타내는 절대적인 규칙이에요. 반면 currentUsers는 프로그램이 돌아가면서 계속 변하는 데이터죠. 이렇게 역할을 명확히 나누면 코드를 읽는 다른 개발자도 “아, MaxUsers는 건드리면 안 되는 규칙이구나”라고 즉시 이해할 수 있어요.
자주 하는 실수와 해결법 및 FAQ
코드를 짜다 보면 의도치 않은 에러를 만나거나, 논리적인 결함을 발견할 때가 있어요. 가장 흔히 발생하는 문제들을 정리했으니 꼭 확인해 보세요.
자주 하는 실수와 해결법
❌ 상수에 값을 다시 할당하려고 할 때
왜 발생하는가: 상수는 컴파일 단계에서 값이 고정되도록 약속된 도구예요. 이를 바꾸려 하면 컴파일러가 즉시 제동을 겁니다.
✅ 해결법: 값이 변해야 하는 상황이라면 처음부터 var를 사용하여 변수로 선언하세요.
❌ 변수 섀도잉(Variable Shadowing) 문제
왜 발생하는가: 같은 이름의 변수를 다른 스코프(예: if문 내부)에서 다시 선언하면, 기존 변수가 가려져 버려요. 의도치 않게 엉뚱한 값이 업데이트될 수 있죠.
✅ 해결법: 변수 이름을 명확하게 짓고, 동일한 이름이 중복되지 않도록 주의 깊게 스코프를 관리하세요.
❌ 상수를 복합 타입으로 만들려 할 때
왜 발생하는가: 앞서 언급했듯 맵이나 슬라이스는 컴파일 타임에 그 구조를 확정할 수 없어요.
✅ 해결법: var로 선언하되, 외부에서 접근할 수 없는 패키지 내부 변수로 두고 접근용 함수(Getter)만 노출하는 방식을 사용하세요.
❌ 모든 것을 :=로만 해결하려고 할 때
왜 발생하는가: 단축 선언은 편리하지만, 타입 명시가 필요하거나 전역 변수가 필요한 상황에서는 사용할 수 없어요.
✅ 해결법: 전역 변수는 var로, 명확한 타입 정의가 필요한 경우에는 var name type = value 형식을 사용하세요.
❌ 타입 추론에만 너무 의존할 때
왜 발생하는가: 의도하지 않은 타입(예: float64 대신 int)으로 추론되어 나중에 형 변환 에러를 일으킬 수 있어요.
✅ 해결법: 데이터의 성격이 명확하다면 var를 통해 타입을 명시적으로 선언하는 것이 가장 안전해요.
자주 묻는 질문
Q. var와 :=는 언제 무엇을 쓰는 게 가장 좋나요?
함수 안에서 간단하게 변수를 만들고 바로 값을 대입할 때는 :=가 가장 효율적이고 읽기 좋아요. 하지만 변수의 타입을 명확히 보여줘야 하거나, 초기값 없이 선언만 먼저 해야 할 때는 var를 쓰는 것이 정석이에요.
Q. 상수를 선언할 때 타입을 꼭 써줘야 하나요?
아니요, 굳이 쓰지 않아도 돼요. 오히려 타입을 쓰지 않는 ‘타입 없는 상수’로 만드는 것이 Go의 유연함을 활용하는 데 훨씬 유리해요. 컴파일러가 상황에 맞춰 적절한 타입을 알아서 적용해 주기 때문이죠.
Q. 0이나 빈 문자열도 상수로 만들 수 있나요?
상수는 리터럴 값(숫자, 문자열 등)이어야 해요. const MyZero = 0처럼 직접적인 값은 가능하지만, 다른 변수의 값을 상수로 가져오는 것은 불가능해요.
Q. 전역 변수를 사용하는 것이 나쁜 습관인가요?
전역 변수는 프로그램의 상태를 예측하기 어렵게 만들기 때문에 가급적 피하는 게 좋아요. 꼭 필요한 경우라면 최소한의 범위에서만 사용하고, 웬만하면 함수 인자로 데이터를 전달하는 방식을 권장해요.
핵심 요약과 다음 단계
오늘 배운 내용을 한눈에 정리해 드릴게요. 이 내용만 기억해도 여러분의 Go 코드는 한층 더 견고해질 거예요.
- 상수(const)는 변하지 않는 설정값과 규칙에 사용하세요.
- 변수(var)는 데이터의 흐름이나 계산 결과처럼 변하는 값에 사용하세요.
- 상수는 타입을 명시하지 않는 방식이 유연성 면에서 가장 좋아요.
- 슬라이스나 맵 같은 복합 타입은 상수로 만들 수 없으니 주의하세요.
- 코드의 안전성을 위해 변할 수 없는 값은 반드시 상수로 고정하세요.
- 함수 내부에서는
:=를 활용하되, 전역 범위에서는var를 사용하세요.
이제 이론은 충분히 익혔어요. 이제는 직접 손을 움직여야 할 때예요. 오늘 배운 내용을 바탕으로 다음 단계를 실천해 보세요.
오늘 바로 실행해 볼 일
- 지금 작성 중인 코드에서
var로 선언된 값 중, 절대 변하지 않을 값(예: API 키, 최대 제한 수 등)을 찾아const로 바꿔보세요. :=를 남발하고 있지는 않은지 점검하고, 타입이 중요한 곳에는var를 사용해 보세요.
이번 주 목표
- 작은 Go 프로젝트를 하나 만들어서, 모든 설정값과 상태값을 구분하여 설계해 보세요.
- 다른 사람의 Go 오픈소스 코드를 보며, 그들은 어떤 기준으로 변수와 상수를 나누었는지 관찰해 보세요.
더 깊이 있는 Go 프로그래밍 실력을 쌓고 싶다면, Go 변수와 상수의 모든 것: 완전 가이드 글도 함께 읽고 실력을 완성해 보세요. 여러분의 즐거운 코딩 생활을 응원해요!