이 포스트는 펠리너 헤라만스, ‘프로그래머의 뇌: 훌륭한 프로그래머가 알아야 할 인지과학의 모든 것’를 읽고 작성하였습니다.

책을 읽기 전

소제목에 ‘훌륭한 프로그래머가 알아야 할 인지과학의 모든 것’임을 보아 프로그래머들이 어떻게 생각하는지를 알려주려 하는 게 아닐까 했다.
그런데 막상 목차를 살펴보니, ‘Chapter 1 코딩 중 겪는 혼란에 대한 이해’를 제외하고는 크게 인지과학적인 내용이 없어 보였다(휴…).

아래에는 내가 이 책을 읽으면서 든 생각이나 정리할 것을 적어놓았다.

Part 1. 코드 더 잘 읽기

Chapter 1. 코딩 중 겪는 혼란에 대한 이해

코드가 초래하는 세 가지 종류의 혼란

  1. 지식의 부족
  2. 정보의 부족
  3. 처리 능력의 부족

책에서는 코딩 중 겪는 혼란을 위의 세 가지로 분류했는데, 지식의 부족에 대한 예는 다음과 같다.
아래의 코드는 APL에서 숫자 n을 이진수로 변환하는 코드다.

2 2 2 2 2 T n

난 처음에 이 코드를 보고 ? 했다. 이게 뭐지? T는 부호고 2 개수만큼 자리를 채우는 건가? 이런 일이 발생하는 건, 내가 APL에 대한 지식이 없기 때문이다.

정보의 부족으로는 자바에서의 ‘toBinaryString()’을 예로 들었다. 메서드 이름만 딱 봐도 이진수로 변환하는 역할을 하는 것 같은데, 이 메서드가 구체적으로 어떤 일을 수행하는지 이해하려면 메서드 내부를 더 살펴봐야 한다고 저자는 말한다. 이게 번역되는 과정에서 뉘앙스가 누락된 건지, 뭔가 이해가 잘 안 됐다. ‘메서드 이름으로는 기능을 추측할 수 있지만, 꼭 그게 정답이란 법은 없으니 그 메서드를 모르는 것과 같다. 따라서 그 메서드를 모르기 때문에 혼란이 생긴다.’의 말인 건가 했다.

처리 능력의 오류는 이미 많이 겪어 봤었다. 파이썬에서 numpy로 다차원 행렬을 다룰 때, 여러 연산을 거치며 바뀌는 행렬의 값을 일일이 다 기억하기는 어렵다. 따라서 나는 연산 결과의 shape만 주석으로 적어놓곤 했다.

위의 세 가지 혼란과 연관된 인지 과정은 다음과 같다.

코딩에 영향을 주는 인지 과정 지식의 부족 = LTM의 문제 정보의 부족 = STM의 문제 처리 능력의 부족 = 작업 기억 공간의 문제

자, LTM, STM, 작업 기억 공간이 뭔지를 알아야 할텐데, 우선 LTM은 장기 기억 공간이다. LTM은 기억하는 내용을 반 영구적으로 저장하는 곳으로, 지식이 없다는 것은 LTM에 해당 내용이 없다는 것을 뜻한다. 컴퓨터의 하드 드라이브와 비슷하다.
STM은 단기 기억 공간이다. 정보가 부족할 때는 STM에 해당 내용이 없다는 뜻이다. 정보를 수집할 때 단기 기억 장소에 일시적으로 저장하지만, 다른 정보를 찾는 과정에서 이미 수집해놓은 정보 중 일부는 잊어버린다. 컴퓨터의 캐시나 메인 메모리라고 할 수 있다.
작업 기억 공간은 우리가 사고할 때 사용하는 영역이다.

이 인지과정이 어떻게 코딩과 연결될까?

특정 프로그래밍 언어의 키워드나 문법, 프로그램을 작성할 때의 추상적인 알고리즘은 LTM에 저장된다. 그리고 내가 코드를 읽을 때 이게 무슨 언어인지, 이 키워드는 뭘 의미하는지를 LTM에서 인출한다.
STM에는 키워드, 변수명, 자료구조 등이 일시적으로 저장된다.
STM의 예시에서 재밌는 걸 하나 찾았는데,

public class BinaryCalculator {
    public static void mian(Integer n) {
        System.out.println(Integer.toBinaryString(n));
    }
}

여기서 부자연스러운 게 있을 것이다. 바로 main 메서드가 아니라 mian 메서드라고 명명된 것이다. 내가 수능 준비할 때 영어 지문으로 어디선가 “사람들은 오타를 무의식적으로 교정해서 읽는다.”와 같은 내용을 본 적이 있다. 저자에 따르면 이 과정은 오타를 읽을 때 오타가 STM에 저장되는데, LTM에 저장된 교정된 단어를 사용함으로써 일어난다. 즉, STM과 LTM은 독립적으로 일어나는 것이 아니다.
작업 기억 공간에서는 생각, 아이디어, 해결책 같은 것들이 만들어진다.

뭔가 프로그래밍에 있어서 묘하게 LTM과 STM의 구분이 모호한 감이 없잖아 있는데… 코드를 보는 시점에 따라 구분되는 것 같다. 내가 지금 보는 건 STM, 지금 보고 있지 않지만 기억하는 것은 LTM이라고 봐야할 것 같다.

이 인지 과정들은 정보가 들어오면 필터를 거쳐 STM에 저장된다. 그리고 작업 기억 공간에 STM의 정보와 LTM의 정보가 같이 들어와 합쳐지고 처리된다.

Chapter 2. 신속한 코드 분석

코드를 분석하고 다시 코드를 작성하는 것은 생각보다 쉽지 않다. 나도 기능을 구현할 때 모르는 부분은 코드를 찾아보고, 그 코드를 보지 않고 재현하려 할 때 어려움이 있었는데, 이는 STM의 용량에 제한이 있기 때문이다. 저자는 이 문제를 해결하기 위해 여러 방식을 제안했다.

  1. 디자인 패턴의 사용
  2. 주석문 쓰기
  3. 표식 남기기

위의 세 방법은 코드에서의 청킹을 잘 할 수 있게 해준다. 코드에서의 청킹은 말 그대로 코드를 청크로 나누는 것이다. 저자는 더흐로트의 체스 실험으로 청크를 설명했다. 해당 실험은 전문가와 일반인 두 그룹이 체스 말의 위치를 어떻게 기억하는지를 연구한 것인데, 전문가 그룹은 LTM에 저장된 지식을 활용해서 말의 위치를 기억했다는 내용이었다. 여기서 LTM에 저장된 지식, 몇 개의 그룹으로 묶은 정보를 청크라고 한다.

그렇다면 청킹을 연습하려면 어떻게 해야할까?
저자는 청킹을 의도적으로 연습하기 위해서는 적극적으로 코드를 기억해내는 것을 훈련하면 좋다고 하며 다음의 단계를 제시했다.

  1. 코드 선정 - 어느 정도 익숙한 코드 베이스에서 메서드나 함수 혹은 밀접하게 연결되어 있는 코드 선정. 최대 50라인.
  2. 코드 파악 - 최대 2분 동안 코드 파악. 시간이 다 되면 코드는 보지 말 것.
  3. 코드 재현 - 종이나 IDE에 코드 재현.
  4. 회고
    1. 어느 부분을 쉽게 기억했는가?
    2. 부분적으로 기억한 코드가 있는가?
    3. 전체를 다 기억하지 못한 코드가 있는가?
    4. 기억하지 못한 라인들이 있다면 그 이유가 무엇인가?
    5. 기억하지 못한 라인에 본인이 익숙하지 않은 프로그래밍 개념이 들어 있지는 않은가?
    6. 기억하지 못한 라인에 본인이 익숙하지 않은 도메인 지식이 있지는 않은가?
  5. 다른 사람과 비교(생략 가능)

전체적으로 신속한 코드 분석을 위해서는 코드를 청킹하는 게 도움이 되고, 청킹을 어떻게 연습하는지를 알려주는 챕터 같았다.

Chapter 3. 프로그래밍 문법 빠르게 배우기

정보를 기억하는 두 가지 형태로 저장 강도와 인출 강도가 있는데, 저장 강도는 무언가를 LTM에 얼마나 잘 저장하고 있는지를 나타낸다.
인출 강도는 무언가를 얼마나 쉽게 기억할 수 있는지를 나타낸다.
프로그래밍 언어의 특정 문법을 기억하려고 할 때는 종종 저장 강도가 아닌 인출 강도에 있다.
매번 문법을 필요할 때마다 찾아보기만 하면 인출 강도를 강화할 수 없다(뜨끔했다). 그러다 인출 강도가 약해지고 외우는 대신 계속 찾아보는 악순환이 이어진다. 따라서 기억을 강화해야 한다.

책에서는 무엇이든 신속하게 학습할 수 있으면서 기억을 강화하는 방법 중 하나로 플래시카드를 소개한다. 플래시카드를 프로그래밍에 활용할 때에는 앞면에 개념을 적어놓고 뒷면에 해당하는 코드를 적는 방식을 사옹한다. 나의 경우에는 퀴즐렛으로 http 강의를 들으며 정리한 개념을 세트로 만들기로 했다. 세트를 만들면 이곳에 링크를 공개하도록 하겠다.

정보를 더 잘 기억하고 싶다면 그 정보를 정교화 하는 것도 도움이 된다고 한다. 정교화는 기억하고자 하는 내용을 기존 기억과 연관 지으면서 생각하는 것을 뜻한다(내가 제일 많이 하는 거다). 새로운 정보를 학습할 때 그 정보는 LTM에 저장하기 전에 스키마의 형태로 만들어지고, 정교화를 거치면서 그 정보가 LTM에 이미 저장되어 있는 스키마에 맞춰서 저장된다. 이는 인출 강도를 강화하는 데 도움을 준다고 한다.

Chapter 4. 복잡한 코드 읽는 방법

작업 기억 공간도 특정한 문제에 적용된 STM이기 때문에 용량 제한이 있는데, 이 용량을 인지 부하라고 한다. 너무 많은 요소가 있어 청크로 나뉘지 않는 문제를 풀려고 할 때 작업 기억 공간은 과부하 상태가 된다.

인지 부하의 종류로는 다음과 같은데,

부하 종류 설명
내재적 부하 문제 자체가 얼마나 복잡한지
외재적 부하 외부적 요인에 의해 문제에 추가된 것
본유적 부하 생각을 LTM에 저장하는 과정에서 일어나는 인지 부하

이러한 인지 부하를 줄이기 위한 기법으로 아래의 방법들이 있다.

  • 리팩터링
  • 생소한 언어 구성 요소를 다른 것으로 대치하기
  • 플래시카드에 코드 동의어 추가

리팩터링은 외부적으로 제공하는 기능은 유지한 채 코드의 내부 구조를 개성하는 것을 의미한다(리팩터링을 하면 집안 대청소를 마치고 깨끗해진 집을 보는 기분이 든다. 개운함). 그 중엔 유지 보수보다는 장기적으로 가독성이 높은 코드를 작성하도록 하는 인지적 리팩터링도 있다. 인지적 리팩터링은 때론 역 리팩터링을 수반할 수 있다. 또 다른 방법으로 메서드의 순서를 바꾸는 방법도 있다. 나는 개인적으로 연관이 있는 메서드끼리 이웃하게 코딩하는 것을 선호한다. 책에서는 예시로 어떤 메서드가 최초로 호출되는 위치로부터 가까이 정의되어 있다면 코드의 가독성이 좋아진다고 한다.

생소한 언어 구성 요소를 다른 것으로 대체한다? 바로 람다식과 리스트 컴프리헨션과 같은 것들을 for 나 while로 바꿔보는 것을 예시로 들 수 있다.

그리고 앞면에는 삼항 연산자나 람다 같은 고급 개념을 사용한 코드를 적고 뒷면에는 그에 해당하는 전통 방식의 코드를 적는 식으로 플래시카드를 활용할 수 있다.

코드를 집중해서 읽는 데 도움이 될 만한 보조 수단으로는

  • 의존 그래프 생성
  • 상태표 사용
  • 의존 그래프, 상태표 혼용

이 있다. 먼저 의존 그래프를 만드는 방식은 다음과 같다.

  1. 모든 변수를 원으로 표시한다.
  2. 비슷한 변수를 연결한다.
  3. 모든 메서드나 함수 호출을 1번과 다른 색의 원으로 표시한다.
  4. 메서드나 함수 호출을 정의와 연결한다. 딱 한 번만 호출되는 메서드는 인라인으로 리팩터링할 수 있는 대상이다.
  5. 클래스의 모든 인스턴스를 1번, 3번과 다른 색의 원으로 표시한다.
  6. 클래스와 그것의 인스턴스를 연결한다.

의존 그래프로는 코드의 흐름을 보여주고 코드를 읽는 보조 수단으로 사용할 수 있다. 또한 이를 코드의 구조에 대한 정보를 가진 레퍼런스로 활용할 수 있다.

상태표를 만드는 방식은 다음과 같다.

  1. 모든 변수를 나열한다.
  2. 테이블을 만들고 각 열에 하나의 변수를 기입한다.
  3. 코드의 실행 단계마다 행을 만든다.
  4. 코드를 각 단계별로 실행하고 그 단계에서 변수들의 값을 해당 열과 행에 적는다.

어디서 많이 들어본 방식이다. 바로 정처산기를 준비할 때 알고리즘 과목에서 순서도 문제를 풀면서 작성한 그 상태표다. 아마 다들 상태표를 보면 “아 이거?” 하면서 단 번에 알아볼 것이다.
개인적으로는 반복문이 많은 곳에서 꽤나 유용하게 썼던 것 같다.

Part 2. 코드에 대해 생각하기

Chapter 5. 코드를 더 깊이 있게 이해하기

코드에 대해 추론할 때는 변수가 중심적인 역할을 한다는 데에는 나도 동의한다. 그 변수를 요르마 사야니에미 교수가 다음의 11개 역할로 대부분의 변수를 설명할 수 있다고 한다.

  1. 고정값(fixed value) - 초기화를 통해 값이 할당된 이후 값이 변경되지 않는 변수
  2. 스테퍼(stepper) - 루프를 반복 실행하며 값이 단계적으로 변하는 변수, for문에서의 i 값
  3. 플래그(flag) - 무엇인가 발생했거나 어떤 경우에 해당하는지를 나타내는 변수
  4. 워커(walker) - 스테퍼처럼 자료구조를 순회하지만 루프가 시작되기 전에는 어떤 값을 가질지 알 수 없다. 포인터나 정수 인덱스가 그 예
  5. 최근값 보유자(most recently holder) - 어떤 값이 변할 때 최근에 변경된 값을 갖는 변수
  6. 목적값 보유자(most wanted holder) - 찾고자 하는 값 또는 조건에 부합하는 값을 갖는 변수
  7. 모집자(gatherer) - 데이터를 모으거나 모은 데이터에 대해 어떤 연산을 수행하여 얻은 값을 저장하는 변수
  8. 컨테이너(container) - 값을 새로 추가하거나 삭제할 수 있는 자료구조
  9. 추적자(follower) - 이전 값 또는 다음 값을 추적, 이전 값에 대한 포인터나 조회한 원소의 인덱스
  10. 조직자(organizer) - 다른 값을 저장하기 위한 목적으로 사용하는 변수
  11. 임시(temporary) - 잠시 사용하기 위한 변수

이걸 ‘변수 역할 프레임워크’라고 하는데, 이를 도입하면 코드를 이해하고 의사소통 하는 데 도움이 된다고 한다. 내 생각엔 이 프레임워크를 집단이 전부 알고 있어야 효과가 있을 것 같다.
저자는 각 변수 역할마다 아이콘을 지정해서 코드 내의 변수에 아이콘을 적어놓는다고 한다.

헝가리안 표기법에 대해서도 다뤘는데, 나는 개인적으로 헝가리안 표기법을 곧잘 쓰는 편이었다. 대학 다닐 때 C#을 전공으로 했던지라 버튼은 btn, 그리드뷰는 grv와 같은 접두사를 많이 썼다. 물론 타입별 접두사도 많이 썼다. Int 형이면 i, String 형이면 s를 변수 앞에 붙였다. 그런데 오늘날에는 보통 권장되지 않는다고 한다(이상하게 파이썬이나 자바를 할 때는 변수가 하는 일에 좀 더 집중해서 변수명을 지었다). 헝가리안 표기법도 시스템 헝가리안, 앱 헝가리안이 있는데, 내가 아는, 변수명에 변수의 타입을 나타내는 방식은 시스템 헝가리안이다. 앱 헝가리안은 단지 변수의 타입이 아니라, 배열의 길이를 나타내기 위해 lX와 같은 접두사를 쓰는 것처럼 구체적인 의미를 갖는다.

드디어 프로그램에 대해 깊이 있는 지식을 얻는 방법에 대해 보자면, 프로그램 이해의 단계는 다음과 같이 이루어진다.

  1. 초점을 찾는다.
  2. 초점으로부터 지식을 확장한다.
  3. 관련된 개체로부터 개념을 이해한다.
  4. 여러 개체에 걸쳐 있는 개념을 이해한다.

여기서 초점은 자바의 main()이나 웹 어플리케이션에서 onLoad() 같은 진입점이라고 한다(따라서 나는 앞으로 초점을 프로그램의 진입점으로 바꿔서 쓰겠다). 진입점은 코드를 어디서부터 읽을지에 대한 기준이므로 상당히 중요하다.

진입점을 찾으면 역할이 있는 관련 개체(변수, 메서드, 클래스)를 다 원으로 표시하고 비슷한 변수는 연결한다. 표시한 코드는 슬라이스라고 부르며, 어떤 한 라인 X의 슬라이스는 ‘라인 X와 관련 있는 모든 코드 라인’으로 정의한다. 슬라이스를 집중해서 살펴보면 프로그램의 어디에서 데이터가 사용되는지 이해하는 데 도움이 된다.

다음으로 표시한 슬라이스 내에서 여러 번 호출한 메서드가 있는지, 어떤 부분이 메서드 호출을 많이하는지 등을 찾아본다.

마지막으로 코드에 있는 서로 다른 개념들을 고수준에사 이해한다. 예로 코드에 있는 자료구조뿐만 아니라 해당 자료구조에 적용된 연산과 그 연산에 대한 제약도 이해한다. 또는 예외 처리에 대해서도 이해한다.

흥미로웠던 게 텍스트를 읽는 것과 코드를 읽는 것은 유사하다고 한다. 생각해보면 나는 책이나 글을 읽을 때 제목이나 목차를 먼저 읽고 글이 무슨 내용일지를 미리 추측하고 글을 읽는다. 그리고 코드를 볼 때에는 모듈 내의 메서드명이나 클래스명을 먼저 쭉 훑어보고 “대충 이 모듈은 이런 역할을 하는 개체들이 모여있겠구나.” 하며 자세히 분석한다. 어찌보면 비슷한 패턴이다.

참고로 초급 프로그래머와 숙련된 프로그래머가 코드를 읽는 방식에 대한 실험 결과도 있었다. 초급 프로그래머는 약 75%가 순차적으로, 나머지가 콜 스택 흐름에 따라 코드를 읽었다. 반면 숙련된 프로그래머는 초급 프로그래머보다 더 많은 비율이 콜 스택 흐름에 따라 코드를 읽었다고 한다. 근데 콜 스택 흐름에 따라 코드를 안 읽으면 코드가 유기적으로 연결되는 게 아니라 뚝뚝 끊기는 느낌이 날텐데…

결과적으로 텍스트 이해 전략에서 비롯된 코드 읽기 전략은 다음과 같다.

  1. 기존 지식의 활성화 - 그 코드가 무엇에 대한 것인지 파악
  2. 모니터링 - 현재 무엇을 읽고 있는지, 이해는 하고 있는지를 추적. 이해되는 라인과 이해되지 않는 라인 표시
  3. 코드에서 중요한 라인 결정 - 프로그램이 실행될 때 가장 중요한 영향을 끼치는 코드를 찾아 표시
  4. 변수명의 의미를 추론하기 - 코드를 한 줄씩 따라가면서 모든 식별자(변수, 클래스, 메서드, 함수)의 이름 나열
  5. 시각화 - 연산 테이블로 식별자 이름, 식별자와 관련된 연산 나열
  6. 질문하기
    1. 코드에서 다섯 가지의 중심 개념은 무엇인가? 이 중심 개념이 식별자, 테마, 클래스 혹은 주석문 내의 정보로 나타나는가?
    2. 중심 개념을 찾기 위해 어떤 전략을 사용했는가? 예를 들어 메서드 이름, 문서 또는 변수명을 살펴봤다거나 아니면 시스템에 대해 이미 가지고 있는 지식을 활용했는가?
    3. 코드에서 발견되는 가장 중심적인 컴퓨터 과학의 중심 개념 다섯 가지는 무엇인가? 알고리즘, 자료구조, 가정, 사용된 기술 등이 이에 해당할 수 있다.
    4. 코드 작성자가 내린 결정 사항이 무엇인가? 예를 들면 특정 버전의 알고리즘을 구현하기로 한 결정, 특정 디자인 패턴을 사용하기로 한 결정, 특정 라이브러리나 API를 사용하기로 한 결정 등 말이다.
    5. 그런 결정을 내리는 데 상정한 가정은 무엇인가?
    6. 그 결정의 효과는 무엇인가?
    7. 그 결정의 잠재적 위험 요소는 무엇인가?
    8. 다른 해결책으로는 어떤 것이 있을까?
  7. 코드 요약 - 코드의 목적, 가장 중요한 라인, 가장 관련 있는 도메인 개념, 가장 관련 있는 프로그래밍 구성 요소, 코드 작성 시 내린 결정

Chapter 6. 코딩 문제 해결을 더 잘하려면

저자는 모델을 사용해서 코드에 대해 생각해볼 것을 추천한다. 모델은 실재를 간단하게 표현한 것으로, 프로그램에 대한 정보를 다른 사람과 공유할 때 유용하고, 문제를 풀 때 도움이 된다.

그 중에서도 두뇌의 바깥에서 만들어지지 않은 모델인 정신 모델이 있다. 나는 이걸 내 뇌속에서 만들어 낸, ‘내가 정의한 자신만의 개념’이라고 이해했다(이걸 이해해기까지 계속 그 챕터만 읽었고 사실 저게 맞는 표현인지도 모르겠다). 코드에 대해 생각할 때 정신 모델, 자신만의 개념을 효율적으로 사용하는 방법은 다음과 같다.

  1. 국지적 모델을 만든다(localized를 국지적이라고 번역한 것 같은데, 나는 이걸 모듈 마다의 모델을 만든다고 이해했다). 상태표나 의존 그래프 등이 있다.
  2. 코드에서 관련된 모든 객체와 객체 간의 관계를 나열한다.
  3. 시스템에 대한 질문을 만들고 이 질문의 답을 사용해서 모델을 개선한다.
    1. 시스템에서 가장 중요한 요소(클래스, 객체, 페이지)는 무엇인거? 모델에 그것들이 포함되었는가?
    2. 이 중요한 요소들 사이의 관계는 무엇인가?
    3. 프로그램의 주요 목표는 무엇인가?
    4. 목표가 핵심 요소 및 그 관계와 어떻게 관련되어 있는가?
    5. 일반적인 사용 사례는 무엇인가? 모델이 그것을 보여주는가?

정신 모델과 더불어서 개념적 기계라는 것이 있다. 개념적 기계는 컴퓨터가 코드를 실행하는 방법에 대해 추론할 때 사용하는 모델이다.
예시로 아래와 같은 코드가 있다고 하자.

double celsius = 10;
double fahrenheit = (9.0 / 5.0) * celsius + 32;

사람은 fahrenheit의 값을 구하기 위한 연산을 위의 표기 그대로 받아들이겠지만, 컴퓨터는 연산을 스택 구조로 표현한다. 우리는 포인터가 ‘가리킨다’라고 표현하지만, 실제로 컴퓨터에서는 포인터 변수에 ‘메모리의 주소가 저장되어 있다’ 라고 하는 것, 이게 개념적 기계이다. 그리고 하나의 개념적 기계는 다른 개념적 기계를 구성하는 데 사용될 수도 있는데, 변수가 여러 개 쌓여 배열을 구성하는 것이 그 예이다.
정신 모델과 개념적 기계의 확실한 차이점이, 개념적 기계는 위처럼 컴퓨터가 작동하는 방식을 설명한다는 것이다. 개념적 기계를 내재화해서 쉽게 사용할 수 있으면 개념적 기계는 정신 모델이 된다.

개념적 기계는 프로그래밍에 관해 생각할 때에는 일반적으로 효과적인 수단이다. 이는 프로그래밍 개념을 일상생활의 개념과 연관 짓기 때문이다.
예를 들어 변수에 대해 ‘변수는 값을 담아두는 상자이다’라고 해보자. 우리는 ‘상자는 물건을 담아두고, 나중에 꺼낼 수 있다’라는 개념을 알고 있기 때문에, 위의 변수의 비유를 잘 이해할 수 있을 것이다. 즉, 개념을 설명할 때에는 듣는 사람이 친숙한 비유를 고르는 것이 중요하다. 의사가 환자에게 병에 대해 설명할 때, 의학적인 용어를 써서 설명하기보다는 환자가 이해하기 쉽도록 비유를 들고 용어를 풀어서 설명하는 것처럼.

Chapter 7. 생각의 버그

이 챕터에서는 전이와 오개념, 개념 변화에 대해 다루고 있다.

전이에 대한 특성으로 왜 두 번째 프로그래밍 언어가 첫 번째보다 쉬운지를 예를 들어 설명하는데, 이는 학습 도중 전이 덕분이라고 한다.
학습 도중 전이는 LTM에 저장된 정보를 사용해서 새로운 내용을 쉽게 배우는 과정을 뜻한다. 또한 정교화는 학습 도중 전이가 많이 일어나는 데 도움이 되는데, 관련 정보를 LTM으로부터 명시적으로 검색하면 현재 하고 있는 작업에 도움이 될 만한 관련 정보를 인출할 가능성이 높기 때문이다.
이걸 듣고 떠오른 예시가 내가 C#을 배우고 그 다음으로 자바를 배웠을 때였다. C#에서 클래스를 배우고 자바에서의 클래스를 봤을 때, “어, 이거 내가 아는 그 클래스 맞나? C#처럼 쓰면 되는 건가?” 하고 연관지으면서 학습을 했었다.
그 밖에 학습 도중 전이 말고도 학습 전이가 있다. 학습 전이는 완전히 낯선 상황에 이미 알고 있는 내용을 적용할 때 일어난다. 내가 객체 지향 언어를 중점으로 사용해 온 상태에서 C언어를 한다고 했을 때, “이런 경우에는 객체 쓰면 편한데, C언어에는 클래스 같은 거 없나?” 하고 생각하는 것이다.
학습 전이와 학습 도중 전이가 명확하게 구분되지 않는 감이 있는데, LTM을 검색한다는 점에서 유사하기 때문이다. 사실 정확한 구분은 나도 잘 모르겠다.

지식 전이를 잘 하기 위해 여러 요인들이 필요한데, 그 요인들은 아래와 같다.

  • 숙달 - 지식과 관련한 작업을 얼마나 잘 숙달했는지
  • 유사성 - 두 작업 간의 공통점
  • 배경 - 작업을 실행하는 환경이 얼마나 비슷한지
  • 중요 특성 - 어떤 지식을 연관지어야 하는지를 분명하게 아는지
  • 연관 - 두 작업이 비슷하다고 얼마나 강하게 느끼는지
  • 감정 - 작업에 대해 어떻게 느끼는지

전이의 형태도 여러 종류가 있다.

  • 고도 전이 - 자동화된 기술 전이 (ex. 새 편집기에서 당연하게 Ctrl + C로 복사)
  • 저도 전이 - 복잡한 작업 전이 (ex. 다른 언어에서도 변수 초기화 전에 선언)
  • 근거리 전이 - 가까운 영역 사이에서의 전이 (ex. C#과 자바)
  • 원거리 전이 - 서로 먼 영역 사이에서의 전이 (ex. 영어와 컴퓨터공학)

전이라고 해서 전부 새로운 것을 배울 때 도움이 되는 긍정적 전이인 것만은 아니다. 오히려 새로운 것을 배울 때 방해가 되는 부정적 전이도 있다.
부정적 전이의 예시로는 내가 C에서 함수를 선언할 때, 당연하게 “함수의 프로토타입을 선언하지 않아도 main에서 알아서 해주겠지” 한 것이다. C#에서는 상관이 없었지만, C언어에서는 프로토타입을 정의해야 main 함수가 맨 위에 있어도 내가 정의한 함수를 인식할 수 있다. 그 밖에도 프로토타입을 정의하면, 보기 편하고 함수 내에서 다른 함수를 호출할 때에도 불편함이 사라진다. 그 밖에도 C언어에서 함수 선언할 때 public, function 키워드를 쓴다거나 했던 일화가 있었다.

이제 오개념에 대해 적을 건데, 오개념은

  1. 사실과 다르다.
  2. 서로 다른 상황에서 일관되게 유지된다.
  3. 확신에 사로잡혀 있다.

의 조건이 갖춰져야 성립된다. 오개념의 예시로 책에서는 “고추의 씨앗이 가장 매운 부분이다”를 예시로 한다. 그 내용은 다음과 같다.

  1. 고추의 씨앗은 맵지 않다.
  2. 만약 어떤 종류의 고추의 씨앗이 맵다면, 모든 종류의 고추의 씨앗은 맵다.
  3. 고추의 씨앗이 맵다고 믿으며, 고추의 씨를 제거하고 요리한다.

프로그래밍에서의 오개념의 예시로는 temperature와 같은 변수는 값을 변경할 수 없고, 하나의 값만 가질 수 있다는 가정이 있다.

그러한 프로그래밍 언어에 의해 생긴 오개념을 현재 학습 중인 새로운 언어에 맞는 정신 모델로 대체하는 과정을 개념 변화라고 한다. 개념 변화는 기존 스키마에 있던 지식 자체를 바꾼다는 점에서 다른 유형의 학습과 구별된다. 아마 고정관념이 생기는 것은 개념 변화가 제대로 이루어지지 않아서 일어나는 현상같다.

새로운 프로그래밍 언어를 배울 때, 오개념을 방지하기 위해 몇 가지 방법들이 있다.

  1. 틀릴 수 있다는 것을 알고, 열린 마음을 유지하자.
  2. 흔한 오개념에 대해 의도적으로 연구하자.
  3. 같은 프로그래밍 언어를 같은 순서로 학습한 다른 프로그래머들에게 조언을 구하자.

새로운 코드베이스에서의 오개념을 방지하기 위한 방법은 다음과 같다.

  1. 두 명이 한 쌍으로 또는 더 큰 그룹으로 함께 프로그래밍한다.
  2. 항상 코드를 실행해보거나 테스트를 돌려봄으로써 코드에 대해 자신이 세운 가정이 맞는지 확인한다.
  3. 오개념을 발견한 경우, 문서(사내 위키)를 관련 위치에 추가하여 자신과 다른 사람이 동일한 오개념에 빠지는 것을 방지한다.

Part 3. 좋은 코드 작성하기

Chapter 8. 명명을 잘하는 방법

사실 이 챕터가 제일 공감가는 챕터였다.
우선 나는 나만의 명명 규칙이 있다.

  1. 변수 이름은 발음할 수 있어야 한다. 약어는 누구나 알 법한 것이 아니라면 웬만해서는 사용하지 않는다. (ex. stdNm -> studentName)
  2. 변수의 단어는 3개를 넘어가지 않는다.
  3. boolean 타입의 변수는 의문문으로 작성한다. (ex. is_valid, is_checked 등)
  4. 변수는 명사, 함수/메서드는 동사로 작성한다.

지금 당장 떠오르는 건 이 정도인데, 이 네 가지를 베이스로 최대한 그 언어에 맞게 조금씩 변형해서 쓰는 편이다.

이렇게 명명에 신경쓰는 이유가 나는 사람들이 내 코드를 봤을 때, 변수나 함수의 이름만 읽어도 무슨 역할을 하는지 알 수 있었으면 한다.
마찬가지로 책에서도 명명이 중요한 이유로

  • 이름은 코드베이스의 상당 부분을 차지한다
  • 코드 리뷰 시 이름의 역할이 중요하다
  • 이름은 문서화의 가장 쉬운 형태이다
  • 이름이 코드를 이해하는 데 도와주는 역할을 할 수 있다

를 제시했다.

그렇다면 더 나은 이름을 선택하기 위해 어떤 방법이 있을까? 저자가 소개한 방법 중, 나는 페이텔슨의 3단계 모델이 인상깊었기 때문에 그 모델을 적겠다.
페이텔슨의 3단계 모델의 내용은 이렇다.

  1. 이름에 포함할 개념을 선택한다.
  2. 각 개념을 나타낼 단어를 선택한다.
  3. 이 단어들을 사용하여 이름을 구성한다.

예를 들어, 주문 정보를 가져오는 메서드를 작성한다고 하자. 그러면

  1. 주문 정보, 가져오다
  2. orderInfo, get
  3. getOrderInfo

의 식이 될 것이다.

Chapter 9. 나쁜 코드와 인지 부하를 방지하는 두 가지 프레임워크

나쁜 코드와 인지 부하를 방지하는 두 가지 프레임 워크 중에 첫 번째로 코드 스멜이 있다.
코드 스멜은 흔히 우리가 생각하는 작동은 하지만 개선의 여지가 있는 코드를 의미한다. 예로는 매우 긴 메서드나 지나치게 복잡한 if문 등이 있다.
중복 코드는 코드를 제대로 청킹하기 어렵게 하고, 너무 많은 매개변수는 작업 기억 공간을 많이 차지하는 식으로 인지 부하를 높인다.

두 번째는 언어적 안티패턴이다.
언어적 안티패턴은 코드의 언어적 요소와 그 역할 사이의 불일치로 묘사한다. 예시로 getNumber() 메서드는 누가 봐도 숫자를 가져오는 메서드로 생각하겠지만, 실질적으로는 “가나다”를 반환한다. 또는 is_valid = -1234 라는 경우가 해당된다.
이는 구현된 코드와 다른 방식으로 그 코드의 의미를 특정할 수 있어서, 잘못된 청킹으로 이어질 수 있다.

Chapter 10. 복잡한 문제 해결을 더 잘하려면

프로그래밍에 있어서 문제 해결 능력을 높이는 요소에는 자동화가 있다.
자동화는 어떤 기술을 여러 번 연습한 후에 아무 생각 없이 할 수 있을 정도가 된 것을 뜻한다. 예를 들어, 내가 42서울에서 피평가자에게 피드백을 남기는데, 피평가자가 나에게 말을 걸면 나는 그 순간 “아 무슨 말을 쓰려고 했더라?” 하고 잊어버리며 피드백 작성을 멈춘다. 이건 피드백 남기기가 자동화되지 않은 것이다. 반대로 내가 컴퓨터로 받아적기를 하는데, 옆에서 누가 말을 걸거나, 유튜브를 틀어놔도 잘 받아적으면 그 기술은 자동화 된 것이다. 느낌이 멀티태스킹이 되는지에 조금 엮여있는 것 같았다.

그래서 기술을 어떻게 자동화를 시킬 수 있는지에 대해 책에서는 아래의 방법을 제시한다.

  • 터치 타이핑, 관련 단축키 암기하기
  • 연습하고자 하는 기술이 필요한, 유사하지만 다른 프로그램을 많이 작성해보기
  • 이미 작성된 프로그램을 수정해보기
  • 개발 작업 시 풀이된 예제 활용하기
  • 깃허브 탐구
  • 소스 코드에 대한 책 또는 블로그 게시물 읽기ㅎ

마지막에 적은 개발 작업 시 풀이된 예제 활용하기에서의 풀이된 예제는 풀이법, 또는 알고리즘이라고 생각하면 될 것 같다. 예를 들자면 내가 요즘 LeetCode에서 문제들을 풀고 있는데, 그곳에서 푼 방법을 개발할 때에도 활용하는 것이다.

Part 4. 코딩에서의 협업

Chapter 11. 코드를 작성하는 행위

프로그래밍 중 이루어지는 다양한 활동에는 다음과 같은 활동이 있다.

  • 검색 - 코드 베이스를 살펴보고 특정 정보를 검색하는 행위
  • 이해 - 코드를 읽고 실행해봄으로써 그 기능을 이해하는 행위
  • 전사 - 단순 코딩
  • 증가 - 검색 + 이해 + 전사
  • 탐구 - 테스트 실행, 기존 코드 읽기, 새 계획에 맞게 코드를 리팩터링 하기 등의 행위

그리고 이 챕터에서는 프로그래머의 업무 중단에 대해 다룬다. 나의 경우, 뭔가를 시작해서 집중 상태에 들어가면 끝을 봐야 하는 성격이라, 중간에 누가 방해하는 것을 정말 싫어한다(여담으로 졸업작품 때는 밥 안 먹고 화장실 안 가고 14시간 줄코딩을 한 적도 있다). 그래서 집에서 코딩할 때와 방해받기 싫을 때에는 문을 아예 닫아놓고, 게임을 한다든가의 방해받아도 상관 없을 때에는 방문을 열어놓는다.

흔히들 하던 작업이 중단되면 엄청난 짜증과 함께 “내가 뭐하고 있었지?” 하며 혼란스러운 경우가 있다. 그런 중단에 잘 대비하기 위해 메모, 그림, 주석 등을 이용하여 정신 모델을 저장하면 원래 하던 작업으로 빠른 복귀가 가능하다. 또는 하위 목표 라벨을 붙이는 방법도 있다. 하위 목표 라벨은 코드를 짜기 전에 기능의 각 단계를 주석문으로 미리 적어두는 것이다.

Chapter 12. 대규모 시스템의 설계와 개선

인지적 차원은 코드베이스를 조사하기 위한 서로 다른 방법을 나타내며, 그 종류는 다음과 같다.

  • 오류 경향성 - 일부 프로그래밍 언어에서는 다른 언어보다 실수를 저지르기 쉽다.
  • 일관성 - 비슷한 것들은 서로 얼마나 유사한가? 이름은 항상 동일한 방식으로 만들어지는가?
  • 분산성 - 프로그래밍 구성 요소가 얼마나 많은 공간(라인 수)를 차지하는가?
  • 숨겨진 의존성 - 참조 항목(메서드, 변수 등)이 얼마나 가시적으로 나타나는가?
  • 잠정성 - 자신이 무엇을 만들고 있는지 생각하는 게 얼마나 쉬운가?
  • 점도 - 특정 시스템을 변경하는 것이 얼마나 어려운가?
  • 점진적 평가 - 주어진 시스템에서 부분적인 작업을 확인하거나 실행하는 것이 얼마나 쉬운가?
  • 역할 표현력 - 여러 가지 다른 부분의 역할을 얼마나 쉽게 알 수 있는가?
  • 매핑 근접성 - 프로그래밍 언어 또는 코드가 문제의 해결 영역에 얼마나 가까운가?
  • 힘든 정신 활동
  • 보조 표기법 - 프로그래머가 공식 규격에는 없는 의미를 코드에 추가할 가능성
  • 추상화
  • 가시성 - 시스템의 다른 부분을 얼마나 쉽게 볼 수 있는가?

위의 특정 차원을 개선하기 위해 코드베이스를 변경하는 것을 설계 기동이라고 한다. 설계 기동을 통해 프레임워크의 인지 차원에 따라 기존 코드베이스의 설계를 개선할 수 있다.

Chapter 13. 새로운 개발자 팀원의 적응 지원

일단 전문가와 초보자의 차이를 알아 둘 필요가 있다. 전문가는 코드에 대해 추상적으로 추론할 수 있고, 코드 자체를 언급하지 않고도 코드에 대해 생각할 능력을 가지고 있다. 초보자는 코드의 세부 사항에 집중하는 경향이 있고, 세부 사항에서 벗어나는 데 어려움을 겪는다. 전문가는 숲을 보고 초보자는 나무를 본다는 말 같다.

새로운 개념을 배울 때 추상적인 용어와 구체적인 예를 모두 배울 필요가 있으며, 새로 배운 개념을 기존 지식과 연결할 시간이 필요하다.

새 팀원이 수행할 프로그래밍 활동은 한 번에 한 개로 제한해야 한다. 그래야 인지 과부하를 막을 수 있다.

적응 지원 과정에서 새 팀원의 LTM, STM, 작업 기억 공간을 지원하기 위해 관련 정보를 준비해야 한다.

책을 읽은 후

인지과학이라는 분야는 처음 접했던지라 인지과학의 개념을 주로 설명하는 Part1, Part2는 조금 어려웠다. 그래도 내 나름의 방식으로 정의하며 이해할 수 있었다. 인지과학의 관점에서 프로그래밍의 과정을 바라보니 “아, 그래서 그런 일이 있었구나.”와 같은 생각이 자주 들었다. 재밌는게 방금 쓴 말도 LTM과 관련된 활동의 결과 아닌가?

기본적으로 전공할 때, 교수님께서 “변수 이름 그냥 a, b로 적지 마라. 그러면 감점시킨다.”, “중복해서 쓰지 말고 추상화해서 클래스로 써라.” 등의 말씀을 해주셨는데, 그때 당시에는 하라니까 한다의 의미가 강했다면 이제는 “이런 문제가 있으니까 하지 말아야겠다.”의 식이 되었다. 코딩할 때 하면 안 되는 것들의 이유를 깨달을 수 있는 책이었다. 그리고 내가 코드를 짤 때의 스타일과도 비교할 수 있는 부분이 재밌었다.

앞으로도 개발하다가 이 책의 내용과 연관짓는 일이 많아질 것 같다. 그만큼 인상 깊었고, 코드를 어떻게 잘 다룰 수 있는지에 대한 방법을 많이 배울 수 있었다.

댓글남기기