//
The Programmer's Brain · 펠리너 헤르만스
코드를 읽고 실행 결과를 예측하는 것과, 코드가 왜 그렇게 작성되었는지를 이해하는 것은 다른 차원의 활동이다. 전자는 표면적 지식이고 후자는 의도와 설계에 대한 지식이다.
2부는 이 두 가지 이해의 층위를 다루고, 코드를 더 깊이 파악하기 위한 도구로 변수 역할 프레임워크, 정신 모델, 개념적 기계를 소개한다.
이스턴 핀란드 대학교의 요르마 사야니에미 교수는 초보 프로그래머가 코드를 이해하기 어려운 주요 이유 중 하나로, 변수가 코드 내에서 수행하는 역할에 대한 스키마가 LTM에 없다는 점을 꼽았다. 그는 프로그램에서 변수가 반복적으로 취하는 11가지 역할을 정리했다.
| 역할 | 설명 | 예 |
|---|---|---|
| 고정값 (Fixed Value) | 초기화 후 값이 변경되지 않음 | const MAX_RETRIES = 3 |
| 스테퍼 (Stepper) | 루프 반복에 따라 단계적으로 변함 | for (let i = 0; ...) |
| 플래그 (Flag) | 특정 상황 발생 여부를 나타냄 | let isLoaded = false |
| 워커 (Walker) | 자료 구조를 순회하는 데 사용 | 연결 리스트의 current |
| 최근값 보유자 (Most-Recent Holder) | 계속 변하는 값의 최근 상태를 저장 | lastInput, previousValue |
| 목적값 보유자 (Most-Wanted Holder) | 조건을 만족하는 값을 찾을 때 사용 | bestMatch, maxScore |
| 모집자 (Gatherer) | 연산 결과를 누적 저장 | sum, result, output |
| 컨테이너 (Container) | 값 추가·삭제가 가능한 자료 구조 | 배열, 맵, 큐 |
| 추적자 (Follower) | 다른 변수의 이전 값을 추적 | 연결 리스트의 prev |
| 조직자 (Organizer) | 추가 처리를 위해 데이터를 재구성 | 정렬 전 임시 배열 |
| 임시 (Temporary) | 잠시만 사용하고 버리는 변수 | swap의 temp |
이 분류가 유용한 이유는 변수의 이름이 아니라 행동 패턴에 집중하기 때문이다. result라는 이름만으로는 그 변수가 누산기인지 최적값인지 알 수 없지만, 역할을 파악하면 코드의 의도를 더 빠르게 파악할 수 있다.
변수 역할 프레임워크는 팀 전체가 공유할 때 가장 효과적이다. 코드 리뷰에서 "이 변수는 Gatherer 역할인데 이름이 temp라서 혼란스럽다"와 같이 공통 언어로 피드백을 줄 수 있다. 다만 팀이 이 프레임워크를 모른다면, 프레임워크를 도입하는 비용 대비 효과를 현실적으로 평가해야 한다.
역할 프레임워크는 마이크로소프트 개발자 찰스 시모니가 고안한 앱 헝가리안(Apps Hungarian) 표기법과 개념적으로 유사하다. 앱 헝가리안은 변수명에 타입이 아닌 의미론적 정보를 접두어로 붙인다. cbItems의 cb는 "count of bytes"를 의미하고, usAddr의 us는 "unsafe string"을 의미한다.
오늘날 정적 타입 시스템이 강력한 언어에서는 불필요하지만, 역할 정보를 명명 규칙으로 인코딩하려는 아이디어 자체는 여전히 참고할 만하다.
콜로라도 대학교 낸시 페닝턴은 프로그램 이해를 두 가지 지식 유형으로 구분했다.
두 지식은 상호 보완적이다. 텍스트 구조 지식 없이는 계획 지식을 파악할 수 없지만, 텍스트 구조 지식만 갖고 있으면 "이 코드가 동작한다"는 것은 알지만 "왜 이렇게 작성되었는가"는 여전히 불투명하다.
브리검영 대학교의 조너선 실리토는 코드 이해의 네 단계를 정리했다.
낯선 코드베이스에 처음 접근할 때 이 단계를 의식하면 무작정 파일을 열어보는 것보다 체계적으로 이해를 쌓아갈 수 있다.
언어학 연구에서 텍스트를 깊이 이해하기 위한 전략들은 코드 읽기에도 유효하다.
| 전략 | 텍스트 읽기 | 코드 읽기 |
|---|---|---|
| 활성화 (Activation) | 관련 배경 지식 먼저 떠올리기 | 비슷한 기능의 코드나 패턴 연상하기 |
| 모니터링 (Monitoring) | 이해한 부분과 모른 부분 구분 | 파악된 부분과 추가 탐색이 필요한 부분 표시 |
| 중요도 결정 | 핵심 문장 파악 | 핵심 함수·클래스 식별 |
| 추론 (Inference) | 명시되지 않은 내용 유추 | 구현을 보지 않고 인터페이스에서 동작 추론 |
| 시각화 | 도표 그리기 | 의존 그래프, 시퀀스 다이어그램 |
| 질문 | 텍스트에 질문하기 | "이 함수는 왜 여기서 호출되는가?" |
| 요약 | 핵심 내용 압축 | 함수 동작을 한 문장으로 요약 |
fMRI 연구에 따르면 코드를 이해하려 할 때 활성화되는 뇌 영역 중 일부(BA21, BA44, BA47)는 텍스트를 읽을 때도 활성화된다. 이는 자연언어 읽기와 코드 읽기가 인지적으로 유사한 메커니즘을 공유한다는 근거다. 다만 이를 "언어 능력이 프로그래밍 능력을 예측한다"는 결론으로 확장하는 것은 과도한 해석으로 보인다.
스코틀랜드 철학자 케네스 크레이크가 1943년에 처음 소개한 개념으로, 외부 현실에 대해 두뇌 내부에서 형성되는 추상적인 표현이다. 복잡한 시스템을 이해할 때 우리는 그 시스템의 단순화된 내부 모델을 만들고, 이 모델을 통해 예측하고 추론한다.
프로그래밍에서 정신 모델은 코드가 어떻게 실행되는지에 대한 내부 표현이다. 정확하지 않더라도 동작하는 정신 모델이 있으면 버그를 찾고 새 기능을 예측하는 것이 가능하다.
개념적 기계는 코드가 실행되는 방식에 대해 추론할 때 사용하는 추상적 모델이다. 실제 컴퓨터 하드웨어의 모든 세부 사항을 포함하지 않고, 프로그래밍 언어 수준에서 유용한 정도로만 추상화된다.
예를 들어, 파이썬 개발자가 GIL(Global Interpreter Lock)을 이해하지 않고도 멀티스레딩 코드를 작성할 수 있는 것은, 언어 수준의 개념적 기계만으로도 대부분의 문제를 해결할 수 있기 때문이다. 반면 성능 문제가 발생하면 하위 계층의 모델이 필요해진다.
개념적 기계는 기존에 익숙한 스키마타를 새 언어 학습에 연결할 수 있게 해주는 다리 역할도 한다. 자바를 아는 개발자가 코틀린을 배울 때 JVM 계층의 개념적 기계를 공유하므로, 언어 문법 차이에만 집중하면 된다.
새로운 프로그래밍 언어를 배울 때 이미 알고 있는 언어의 지식은 양날의 검이다.
긍정적 전이 (Positive Transfer): 기존 지식이 새 언어 학습을 도울 때다. 자바를 알면 C#을 배우기 쉽다. 함수형 프로그래밍 개념을 알면 하스켈의 기본 구조를 더 빠르게 파악한다.
부정적 전이 (Negative Transfer): 기존 지식이 새 언어에 대한 오개념을 만들 때다. 파이썬을 주로 쓰던 개발자가 자바스크립트의 this를 파이썬의 self와 동일하게 생각하면 예기치 않은 버그를 만난다. C에서 포인터를 다루던 개발자가 자바의 참조 타입을 포인터처럼 취급하면 NullPointerException 이전에 개념적 혼란이 생긴다.
부정적 전이는 단순히 새 문법을 모르는 것보다 더 위험하다. 잘못 알고 있다는 사실을 모르기 때문이다. 새 언어를 배울 때 "이미 아는 언어와 어떤 점이 다른가"를 의식적으로 추적하는 습관이 오개념 형성을 예방한다.
2부를 통해 얻을 수 있는 핵심은 두 가지다. 첫째, 변수의 이름뿐 아니라 역할을 파악하는 것이 코드 이해를 가속한다. 이를 위한 공통 어휘가 변수 역할 프레임워크다. 둘째, 코드를 깊이 이해하려면 표면적 지식(텍스트 구조)에서 의도적 지식(계획)으로 나아가야 하며, 이 과정에서 정신 모델을 의식적으로 구성하는 것이 도움이 된다.
한편, 변수 역할 프레임워크나 코드 읽기 전략들의 실용적 한계도 있다. 팀 전체의 동의와 실천이 없으면 개인 수준에서 얻을 수 있는 효과는 제한적이다. 이 도구들을 일종의 분석 어휘로 가볍게 활용하되, 전면적인 팀 프로세스로 도입하는 데는 비용과 편익을 신중히 따져볼 필요가 있다.
이 글이 도움이 되셨나요?