프로그램의 기본적인 문법과 데이터 종류를 익혔다면, 이제 그 데이터들을 담고 관리하는 가장 핵심적인 도구인 변수(Variable)에 대해 깊이 알아볼 차례입니다. 변수는 프로그래밍에서 데이터를 저장하고, 이름을 붙여 필요할 때마다 참조하거나 변경할 수 있게 해주는 가장 기본적인 구성 요소입니다.
이 글에서는 Lua의 변수 사용법, 특히 변수의 유효 범위(Scope)를 결정하는 중요한 개념과 Lua의 유연한 타입 시스템을 효과적으로 다루는 방법을 자세히 탐구합니다. 이 내용을 이해하면 더 견고하고 효율적인 코드를 작성하는 강력한 기반을 다질 수 있습니다.
변수: 데이터에 이름표를 붙이는 기술
변수를 가장 쉽게 비유하자면, ‘이름표가 붙은 데이터 보관함’입니다. 우리는 이름표(변수 이름)를 통해 보관함(메모리 공간)에 어떤 데이터(값)가 들어있는지 쉽게 확인하고, 필요하면 다른 데이터로 교체할 수 있습니다.
Lua에서 변수를 사용하면 다음과 같은 명확한 이점을 얻습니다.
- 데이터 재사용: 한 번 저장된 값을 여러 곳에서 이름만으로 쉽게 가져와 사용할 수 있습니다.
- 가독성 향상:
userScore
라는 이름은 그냥 숫자100
보다 코드의 의도를 훨씬 명확하게 전달합니다. - 유지보수 용이성: 값이 바뀌어야 할 때, 변수에 할당된 값만 한 번 수정하면 그 변수를 사용하는 모든 곳에 즉시 반영됩니다.
변수를 사용하는 방법은 간단합니다. 변수 이름을 정하고 할당 연산자(=
)를 사용해 값을 저장(할당)하면 됩니다.
local score = 100
local playerName = "Alice"
-- 변수의 값은 언제든지 변경할 수 있습니다.
score = score + 50 -- 기존 score 값에 50을 더해 다시 할당
print(score) -- 출력: 150
전역 변수와 지역 변수: 변수의 활동 무대
위 예제에서 사용된 local
키워드는 변수가 활동할 수 있는 유효 범위, 즉 스코프(Scope)를 지정합니다. Lua의 변수는 스코프에 따라 두 종류로 나뉩니다.
전역 변수 (Global Variables)
local
키워드 없이 선언된 변수는 기본적으로 전역 변수가 됩니다. 이름처럼 프로그램의 어디에서든 접근할 수 있는 ‘공용’ 변수입니다.
-- local 키워드 없이 선언하면 전역 변수가 됩니다.
globalMessage = "이것은 전역 메시지입니다."
function printGlobal()
print(globalMessage) -- 함수 안에서도 자유롭게 접근 가능
end
printGlobal() -- 출력: 이것은 전역 메시지입니다.
-- 선언되지 않은 전역 변수에 접근하면 오류 대신 nil 값을 반환합니다.
print(anotherGlobal) -- 출력: nil
지역 변수 (Local Variables)
local
키워드를 사용하여 선언된 변수입니다. 지역 변수는 자신이 선언된 코드 블록(함수, 제어문, do...end
블록 등) 안에서만 유효합니다. 해당 블록을 벗어나면 더 이상 접근할 수 없습니다.
local localMessage = "이것은 지역 메시지입니다."
if true then
local blockVariable = "블록 안에서만 유효한 변수"
print(localMessage) -- 상위 블록의 지역 변수는 접근 가능
print(blockVariable) -- 출력: 블록 안에서만 유효한 변수
end
-- print(blockVariable) -- 오류 발생! if 블록 밖에서는 blockVariable에 접근할 수 없습니다.
왜 항상 local
을 사용해야 할까요?
특별한 이유가 없는 한, 모든 변수는 local
키워드로 선언하는 것이 Lua 프로그래밍의 가장 중요한 원칙입니다. 그 이유는 명확합니다.
- 성능: 지역 변수는 전역 변수보다 접근 속도가 훨씬 빠릅니다. Lua 인터프리터가 지역 변수를 더 효율적으로 처리하기 때문입니다.
- 이름 충돌 방지: 서로 다른 코드 블록에서 같은 이름의 지역 변수를 사용해도 서로 전혀 영향을 주지 않습니다. 전역 변수를 남용하면, 나도 모르게 다른 곳에서 사용 중인 중요한 변수의 값을 덮어쓰는 심각한 버그를 유발할 수 있습니다.
- 가독성 및 유지보수: 변수의 사용 범위가 명확해져 코드를 이해하고 추적하기 쉬워집니다.
- 메모리 관리: 지역 변수는 자신이 속한 블록의 실행이 끝나면 더 이상 필요 없는 데이터로 간주되어, 메모리를 더 효율적으로 관리하는 데 도움이 됩니다.
실수로 local
을 빠뜨려 의도치 않은 전역 변수를 만드는 것은 디버깅하기 매우 어려운 문제의 원인이 되므로 항상 주의해야 합니다.
변수를 다루는 실용적인 기술들
이름 규칙과 다중 할당
변수 이름은 그 안에 담긴 데이터의 의미를 잘 나타내도록 짓는 것이 좋습니다. 일반적으로 여러 단어를 조합할 때 playerName
처럼 중간에 대문자를 섞는 카멜 케이스(camelCase)나 player_name
처럼 밑줄을 사용하는 스네이크 케이스(snake_case)가 널리 쓰입니다.
또한 Lua는 한 번에 여러 변수에 값을 할당하는 다중 할당(Multiple Assignment)을 지원하여 코드를 간결하게 만들어 줍니다.
local x, y, z = 10, 20, 30
-- 값의 개수가 변수보다 많으면 남는 값은 무시됩니다.
local a, b = 100, 200, 300 -- a는 100, b는 200이 됨
-- 값이 부족하면 해당 변수는 nil로 채워집니다.
local p, q, r = 5, 8 -- p는 5, q는 8, r은 nil이 됨
-- 변수의 값을 서로 맞바꿀 때 매우 유용합니다.
local tempA = 1
local tempB = 2
tempA, tempB = tempB, tempA -- tempA와 tempB의 값을 한 줄로 교환
print(tempA, tempB) -- 출력: 2 1
유연함의 미학: 동적 타이핑과 타입 다루기
Lua는 동적 타입 언어입니다. 이는 변수의 타입이 고정되어 있지 않고, 실행 중에 할당되는 값에 따라 언제든지 변경될 수 있음을 의미합니다.
local myVar = 10 -- 현재 myVar는 number 타입
print(type(myVar)) -- 출력: number
myVar = "이제 문자열" -- string 타입으로 변경됨
print(type(myVar)) -- 출력: string
이러한 유연성은 개발 속도를 높여주지만, 예상치 못한 타입의 값이 변수에 할당되어 오류를 일으킬 수도 있습니다. 따라서 필요할 때 변수의 타입을 확인하고 제어하는 것이 중요합니다.
type()
함수: 변수나 값의 타입을 문자열로 반환하여 현재 타입을 확인할 수 있습니다.
if type(data) == "number" then
print("숫자 처리:", data + 1)
elseif type(data) == "string" then
print("문자열 처리:", string.upper(data))
end
assert()
함수: 특정 조건을 검사하고, 만약 조건이 거짓(false 또는 nil)이면 오류를 발생시켜 프로그램을 중단시킵니다. 함수의 인자나 중요한 변수의 타입을 강제할 때 유용합니다.
local function process_user(user_table)
-- user_table이 테이블이 아니면 오류를 발생시키고 메시지를 출력
assert(type(user_table) == "table", "user_table은 테이블이어야 합니다.")
print("사용자 처리:", user_table.name)
end
타입 변환: 자동 처리와 수동 제어
Lua는 특정 연산에서 필요에 따라 자동으로 타입을 변환(Type Coercion)하기도 합니다.
- 문자열 → 숫자: 산술 연산(
+
,-
,*
등)을 할 때, 숫자 형태의 문자열은 자동으로 숫자로 변환됩니다."10" + 5
는15
가 됩니다. - 숫자 → 문자열: 문자열 연결 연산자(
..
)를 사용하면 숫자가 자동으로 문자열로 변환됩니다."점수: " .. 100
은"점수: 100"
이 됩니다.
이러한 자동 변환은 편리하지만, 때로는 명시적으로 타입을 변환해주는 것이 더 안전하고 코드의 의도를 명확하게 합니다.
tostring(value)
: 어떤 값이든 문자열로 변환합니다.tonumber(value)
: 문자열을 숫자로 변환합니다. 변환이 불가능하면nil
을 반환합니다.
local strVal = "42"
local numVal = tonumber(strVal)
if numVal then
print("변환된 숫자:", numVal + 8) -- 출력: 변환된 숫자: 50
else
print("숫자로 변환 실패")
end
nil의 특별한 역할
nil
은 단순히 ‘값이 없음’을 의미하는 것을 넘어, 변수 관리에서 특별한 역할을 합니다. 변수에 nil
을 할당하는 것은 해당 변수를 사실상 ‘삭제’하는 것과 같은 효과를 가집니다.
local config = { host = "localhost", port = 8080 }
-- config 테이블에서 port 필드를 제거하고 싶을 때
config.port = nil
print(config.port) -- 출력: nil (테이블에 더 이상 port 키가 없음)
더 이상 사용하지 않는 전역 변수에 nil
을 할당하면, 해당 변수가 차지하던 메모리를 시스템이 회수할 수 있도록 돕는 좋은 습관입니다.
지금까지 Lua의 변수와 타입 시스템의 핵심적인 내용들을 살펴보았습니다. 데이터를 담는 그릇인 변수, 변수의 유효 범위를 정하는 스코프(특히 local
의 중요성), 값에 따라 타입이 유연하게 변하는 동적 타이핑, 그리고 타입을 확인하고 변환하는 방법까지 이해했습니다.
이러한 변수와 타입에 대한 깊이 있는 이해는 여러분이 작성하는 코드의 안정성, 성능, 가독성을 모두 높이는 데 결정적인 역할을 할 것입니다.