코딩할 때의 사고 과정을 되짚으며, 프로그래밍 지식의 상당 부분이 새로운 문제를 익숙한 ‘트릭’의 어휘로 환원하는 일임을 살펴본다.
2026년 2월 11일
메타 프로그래밍 글이다. 코딩할 때 내 사고 과정이 어떤지 들여다보고, 프로그래밍 “지식”이란 게 무엇인지 붙잡아 보려는 시도다. 결론부터 말하면, 그 지식의 상당 부분은 새로운 문제를 이미 알고 있는 트릭들의 어휘로 환원하는 일에 가깝다. 이 글은 개인적이고 서술적인 글이지, 여러분에게 이렇게 하라고 지시하는 처방적 글이 아니다.
출발점은 Ziggit에 올라온 질문이다. 배경을 설명하자면 Zig는 “앰비언트 IO(ambient IO)” 기능을 제거하는 과정에 있다. 현재는 std.process.getEnvVarOwned를 통해 어디서든 프로그램 환경에 접근할 수 있다. 다음 Zig 버전에서는 std.process.Environ.Map을 main에서부터 환경에 접근해야 하는 모든 루틴까지 계속 넘겨줘야 한다. 질문자의 경우 이전에는 readHistory 함수가 환경에서 히스토리 파일 경로를 찾아왔는데, 새 Zig에서는 이를 가장 잘 어떻게 모델링할지 고민하고 있다. 테이블 위에 올라온 선택지는 다음과 같다:
pub fn readHistory(
io: std.Io,
alloc: Allocator,
file: std.Io.File,
) ReadHistoryError!void;
pub fn readHistory(
io: std.Io,
alloc: Allocator,
maybe_environ_map: ?*std.process.Environ.Map,
) ReadHistoryError!void;
pub fn readHistory(
io: std.Io,
alloc: Allocator,
maybe_absolute_path: ?[]const u8,
maybe_environ_map: ?*std.process.Environ.Map,
) ReadHistoryError!void;
내 출발점은 대신 이런 형태일 것이다:
pub const HistoryOptions = struct {
file: []const u8,
pub fn from_environment(
environment: *const std.process.Environ.Map,
) HistoryOptions;
};
pub fn readHistory(
io: std.Io,
gpa: Allocator,
options: HistoryOptions,
) ReadHistoryError!void;
메타 프로그래밍 관점에서 내가 흥미롭게 느끼는 건, 이게 내게는 즉각적(생각을 거의 안 해도 됨)인 동시에, 분명히 예전에 축적해 둔 여러 사실 조각들로 분해 가능하다는 점이다. 아래는 내가 여기서 한 일을 해체한 것이고, 내가 그 일을 생각할 때 쓰는 언어적 “레이블”, 그리고 그것을 어디서 배웠는지다:
첫째, 나는 _그것_에 이름과 타입(HistoryOptions)을 부여함으로써 “추상화 수준을 올렸다”. 이건 드문 변환인데, 나는 이걸 스스로 배우고 스스로 이름 붙였다. 이름 짓기는 내 사고와 커뮤니케이션 과정에서 중요하다. “추상화 수준을 올립시다”는 내 코드 리뷰에서 단골 코멘트다.
둘째, 옵션의 모든 측면이 사용자 설정 가능하도록 해서 “미들레이어 실수(medlayer mistake)”를 피했다. Zig에서는 모든 필드가 public이라 이걸 하기가 쉽다. 나는 Josh Triplett의 GitHub 코멘트에서 미들레이어 실수에 대해 배웠다.
셋째, 추상화 레이어를 가로지르는 “지름길(shortcut)”로서 from_environment 편의 함수를 제공했다. 나는 “지름길” 격언을 Django Views — The Right Way.에서 배웠다. 이 글과 관련해서 말하자면, 나는 Django를 마지막으로 만진 뒤 10년이 지나서야 그 글을 읽었다. 객체 수준에서는 내게 쓸모가 없었다. 하지만 메타 수준에서는 그 글을 읽는 일이 여러 프로그래밍 트릭을 내게 굳히고 또 이름 붙여주었다. 그 울림은 How to Make a 💡?.에서도 볼 수 있다.
넷째, 나는 본능적으로 alloc을 “gpa”(“arena”의 반대 개념으로)로 이름을 바꿨다. 이 명명은 Zig 컴파일러에서 발견한 것이다.
다섯째, 설정 인자의 이름을 config, props, params가 아니라 “options”로 지었다. 이 명명 규칙은 TigerBeetle에서 배웠다.
여섯째, 시그니처가 “포지셔널 DI(positional DI)” 스킴을 따르도록 했다. 의존성인 인자들, 고유한 타입을 가진 리소스들은 위치 기반으로 주입되고(그리고 io나 gpa 같은 정식 이름을 가진다). 함수의 동작을 직접적으로 바꾸는 인자들(전이적으로 호출되는 하위 함수들에 영향을 주는 것과 대비되는 의미)은 이름 기반으로, Options 구조체로 전달한다.
구체적으로 말해, 내 스니펫이 이 문제를 푸는 올바른 방법이라고 주장하는 건 아니다! 나는 전체 맥락을 알 수 없기 때문에 전혀 확신할 수 없다. 다만 내가 실제로 이 문제를 풀고 있다면, 위의 스니펫이 추가 반복을 위한 초기 출발점이 될 거라는 뜻이다.
또한 내가 위의 여섯 가지를 왜 하는지 설명하지도 않는다. 나는 그저 이름을 붙이고 출처를 가리킬 뿐이다. 사실 그 _왜_를 제대로 설명하려면, 여섯 가지 각각에 대해 블로그 글 한 편씩이 필요할 것이다.
그리고 이것이 아마 내 사고 과정의 핵심 속성이다. 내게는 트릭 가방이 있고, 그 트릭들에는 이름이 붙어 있다. 내 머릿속에서 그 레이블은 실제 트릭(타이핑할 코드)뿐 아니라 그것에 대한 정당화(어떤 맥락에서 좋은 트릭인지)도 함께 가리킨다.
그리고 나는 이런 트릭을 정말 항상, 말 그대로 항상 쓴다! 포럼 댓글에 툭 답하는 것만으로도 나는 한 줌을 집어 들게 된다. 내 지식의 상당 부분은 코딩 격언집처럼 구조화되어 있다.
메타-메타: 나는 어떻게 저런 트릭들을 다 습득했을까? 나는 닥치는 대로 읽는다. 랜덤 커밋, 이슈, 열정적으로 토끼굴로 뛰어들기, 위키 여행. 여기서 핵심 기술은 격언을 마주쳤을 때 그게 격언임을 알아차리는 것이다. Ziggit을 읽는 것도 내 트릭 습득 루틴의 일부다. 트릭을 배운 뒤에는 기억한다. 여기서 “기억한다”는 건 적절한 순간에 능동적으로 떠올리는 능동 회상의 행위다. 이 회상이 도메인 간 “수평적 유전자 전달(horizontal gene transfer)”을 가능하게 한다. Django에서 지름길을 훔쳐 오고, 커널에서 미들레이어 실수를 가져오는 식이다. 소프트웨어 엔지니어링의 암묵지 도메인에 “수평적 유전자 전달”을 적용하는 것 자체가 수평적 유전자 전달이라는 점을 눈치챘는가? 새로운 도메인에 들어갈 때 나는 빠져 있는 트릭을 적극적으로 찾는다. 나는 Zig에 비교적 최근에 들어왔지만, 위의 트릭들은 모두 Zig 토박이이거나, 최소한 Zig에 맞게 적응된 것들이다.
가끔은 내가 직접 트릭을 “발명”하기도 한다. 예를 들면 “포지셔널 DI”는 내가 작년에야 말로 정식화해서 언어화한 것이다. 그렇다고 그 전에는 그런 방식으로 안 했다는 뜻은 아니다. 다만 그 활동이 의식 속에서 ‘의도적으로 할 수 있는 별개의 일’로 레이블링되어 있지 않았다는 의미다. 아이디어는 이미 있었고, 이제는 격언도 생긴 셈이다.