Lobsters

GDScript: The Good, Bad, and Ugly Parts

GDScript: 좋은 점, 나쁜 점, 그리고 아쉬운 점 — TypeScript에서 포팅해본 개발자의 시선

동일한 시스템을 TypeScript와 GDScript로 각각 구현한 개발자가 GDScript를 심층 분석한 글입니다. 게임 로직 전용 언어로서의 강점(네이티브 벡터 타입, 시그널, GC 없는 메모리 관리)을 높이 평가하면서도, 미완성에 가까운 정적 타이핑 시스템과 함수형 프로그래밍 지원 부재를 약점으로 지적합니다.

본문

저자는 하나의 시스템을 먼저 TypeScript로 구현한 뒤 같은 시스템을 Godot에서 동작하도록 GDScript로 포팅한 경험을 바탕으로, GDScript라는 언어 자체에 대한 상세한 인상을 정리합니다. UnityScript나 ActionScript에 대한 기억 때문에 GDScript를 '장난감 언어'로 오해하기 쉽지만, 실제로는 기능은 간결하면서도(Lua에 가까운 수준) 결코 미숙하지 않으며, Godot를 쓴다면 GDScript를 시도해보는 것이 가장 좋은 선택이라고 주장합니다.

The Good — 게임을 위해 설계된 언어

GDScript는 게임 로직 구현이라는 단일 목적을 위해 설계된 드문 고수준 언어입니다. Lua(산업 자동화), C#(엔터프라이즈), C++ 등 다른 게임 개발 언어들이 원래 다른 문제를 위해 만들어진 것과 대조적입니다. 이 덕분에 네이티브 Vector2·Vector3 타입, 정수와 부동소수점의 구분, 실용적인 PRNG, 복잡한 분기 로직에 유용한 match 문, packed array, lerp()·smoothstep()·wrap() 같은 게임 개발 표준 라이브러리 함수를 갖추고 있습니다. 어노테이션을 통한 에디터 연동, preload()·load()를 통한 리소스 로딩, 일급 개념으로서의 시그널도 개발 속도를 높여줍니다.

특이하게도 GC가 없는 고수준 언어로, 수동 메모리 관리와 참조 카운팅을 조합합니다. 대부분의 객체는 씬 트리 노드이고 노드는 트리를 떠날 때 자식과 함께 스스로 해제되므로 90%의 경우 메모리를 신경 쓸 필요가 없다고 설명합니다. 노드가 아닌 객체는 참조 카운팅이 기본이며, 순환 참조 문제는 weakref()로 완화할 수 있습니다. GC의 예측 불가능한 성능 특성과 스터터링은 게임에서 특히 바람직하지 않다는 점도 지적합니다. C#은 언어 기능은 더 많지만 엔진과의 밀결합과 GC 부재라는 이점을 잃습니다. 덤으로 Godot 디버거를 그대로 쓸 수 있는 것도 장점입니다.

The Bad — 미완성의 정적 타이핑

가장 큰 문제는 타입 시스템입니다. 타입 힌트를 쓸 수는 있지만 사실상 미완성이라고 평가합니다. 예컨대 map()은 항상 무타입 배열을 반환하므로 Array[int].map(...)의 결과를 Array[String]에 대입하면 타입 오류가 발생하고, Array(map결과, TYPE_STRING, "", null) 같은 장황한 수동 캐스팅이 필요합니다. 근본 원인은 함수가 구체적 타입을 갖지 않고 모두 제네릭 Callable 객체라는 점으로, TypeScript의 (n: number) => string 같은 함수 타입 개념이 없습니다. Array[Array[int]] 같은 중첩 타입 지정도 불가능하고, TypeScript식 타입 narrowing도 훨씬 둔하며, 유니온·판별 유니온이 없어 원시 타입 유니온은 전부 Variant가 됩니다. 저자는 대부분의 맥락에서 typed collection을 쓰지 말고 packed array를 쓰라고까지 권합니다. 다만 GDScript는 엔진과 운명공동체라 하위 호환성에 얽매이지 않으므로, Godot 5에서 언어를 깨는 변경이 가능해 '수정 가능성'은 높다고 봅니다.

The Ugly — 문법과 함수형 스타일의 마찰

취향 문제로 분류되는 항목들입니다. 들여쓰기 기반 문법 자체를 원칙적으로 싫어하며, 여러 줄짜리 콜백(ary.map(func (e: String) -> int: ...))은 '순수한 고어'라고 표현합니다. 람다는 JS 화살표 함수 대비 거의 두 배 길고, 구조분해 할당, Lua식 다중 할당, 스프레드 연산자(func.callv(args)로 대체)가 없으며, 함수 값은 반드시 .call()·.callv()로만 호출해야 하고 내부 함수 정의도 불가능합니다. { ...oldStruct, id } 같은 불변 객체 갱신 패턴과도 궁합이 나쁘고, flatmap()·zip()조차 없어 함수형 스타일 전반이 마찰적입니다. ++/-- 연산자가 없는 것도 Lua에서 물려받은 잔재인데 정작 주석은 #이라 모순이라고 지적합니다. 다만 이 불만들은 타입 시스템만큼 치명적이지는 않다고 봅니다.

Lobsters 반응

  • @LesleyLai — 이 글의 'ugly' 항목 상당수는 GDScript가 영감을 받은 Python과 동일한 특성입니다. GDScript는 Python처럼 괄호와 세미콜론 대신 들여쓰기를 쓰는 배우기 쉬운 언어로 설계됐다는 배경이 중요합니다. 저자가 TypeScript 출신에 함수형 스타일을 선호하기 때문에 이런 점들을 불편하게 느꼈을 것이고, Python 배경의 사용자라면 대수롭지 않게 여겼을 부분이라고 지적합니다.

원문: Lobsters / 번역·요약: Trawling