본문 바로가기

리뷰/책 리뷰

[IT/리뷰] 프로그래머의 뇌

728x90

프로그래머의 뇌 : 네이버 도서 (naver.com)

 

프로그래머의 뇌 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

사람의 뇌의 동작 방식과 프로그래밍을 직접적으로 매핑해 코드를 더 잘 읽고, 이해하고, 작성하는 방법 등에 대해 설명한다.

 

책을 처음 선택했을 때는 프로그래머들이 생각하는 방식에 대해 설명할 줄 알았다. 문제를 해결할 때 어떤 식으로 인지하고 해결할 수 있는지, 코드를 볼 때 어떤 식으로 읽고 이해하는지 등 사례 중심으로 이야기를 풀어갈 줄 알았다. 이 책에서도 사례들이 많이 나오기는 하지만 주로 연구 결과가 등장한다. 프로그래밍보다는 진짜 뇌 과학에 대해 더 설명이 많다. 아무래도 대상 독자가 개발자들이다 보니 뇌 과학 위주로 설명과 연구 결과를 통해 프로그래밍에 매핑시키는 방식인 것 같다.

 

책을 순서대로 읽었을 때 앞에 나왔던 개념들이 뒤에서 반복해서 사용되면서 이해하기 쉽게 설명한다. 딱딱할 수 있는 뇌 과학도 실제 프로그래밍에서는 어떻게 해야할지에 대해 가볍게 다루니 더 읽기 쉬웠다.

 

실제로 알고 있던 내용들도 있었지만 이유에 대해 몰랐던 것들도 많았는데 책에서의 설명 덕에 알아갈 수 있었다. 예를 들어 헝가리안 표기법이 좋지 않은 이유, 이름 틀의 장점, 업무 중단의 문제점 등이 있다.

 

코드를 읽기 좋게 작성하는 것과 성능 좋게 작성하는 것 중 읽기 좋게 작성하는 것이 더 좋다고 확신하게 되었다. 또, 어떤 코드가 더 읽기 좋게 작성하는 것인지 다시 생각해보게 되었다. 이 두 가지는 현재 회사에서는 일부 맞지 않다. 코딩 컨벤션이 있지만 잘 지켜지지 않는 경우들이 있고 시니어와 주니어 사이에 이름을 짓는 규칙도 다르고 더 중요하게 생각하는 것도 다르다. 한 번 간단한 세미나라도 해서 코딩 컨벤션을 새롭게 고치고 일관성있게 적용해나갈 필요가 있다고 생각한다.


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

  1. 지식의 부족
    • 언어에 대한 지식의 부족
  2. 정보의 부족
    • 메서드에 대한 정보의 부족
  3. 처리 능력의 부족
    • 코드의 모든 과정을 한 번에 이해하고 처리하기 힘든 처리 능력의 부족

 

프로그래머가 일하는 시간 중 코드를 읽고 분석하는 일은 생각보다 많다. 연구에 의하면 일하는 시간의 60%를 코드를 '이해'하는데 사용한다. 따라서 코드를 빨리 이해할 수 있으면 프로그래밍 기술이 향상되는 것과 같다.

 

"프로그램은 사람이 읽을 수 있도록 작성해야만 한다. 기계가 실행하는 것은 부차적인 일이다."
- <컴퓨터 프로그램의 구조와 해석>

이 말은 사실임에도 현실적으로 프로그래머들은 코드를 읽는 법보다 작성하는 법을 더 많이 연습한다. 코드를 읽는 연습은 거의 전무할 것이다.

코드를 읽는 목적은 다양하다. 기능을 추가하거나 버그를 발견하기 위해서 혹은 코드가 실행될 더 큰 시스템을 이해하기 위해서다. 한 가지 공통점은 코드에 존재하는 특정한 정보를 찾기 위해 읽는다는 점이다. 즉, 관련 정보를 신속히 찾는 능력을 향상하면 코드를 다시 찾아보는 횟수를 줄일 수 있다.

 

우리의 뇌는 다음 세 가지 영역으로 나눌 수 있다.

  • STM(Short Term Memory) : 컴퓨터 메모리에 해당
  • LTM(Long Term Memory) : 컴퓨터 디스크에 해당
  • 작업공간 : 컴퓨터 CPU에 해당

 

STM은 최대 6개까지 기억할 수 있다. 밀러의 이론에 의하면 문자를 6개까지 읽고 나면 앞에 읽었떤 문자를 잊어버려야 한다. 하지만 우리는 6개 넘는 문자들을 기억하고 처리할 수 있다.

이것이 가능한 이유는 청킹(chunking)을 이용하기 때문이다. 전혀 알지 못하는 문자로 이루어진 문장보다는 단어로 구성된 경우의 문장이 훨씬 기억하기 쉽다. 이는 문자 자체가 LTM에 저장된 정보이기 때문이다. 즉, LTM에 지식이 많으면 기억을 쉽게 할 수 있다.

이는 프로그래밍에서도 마찬가지다. 동일한 코드를 보더라도 전문가가 초보자 보다 처음보는 코드를 훨씬 더 잘 기억할 수 있다.

 

청크를 묶을 수 있는 코드를 작성하게 된다면 나중에 코드를 읽거나 다른 사람이 코드를 읽을 때 더 잘 이해할 수 있다. 그룹으로 나누기 쉬운(두뇌에서 처리하기 쉬운) 코드를 작성하는 방법에 대한 연구들로 몇 가지 방법이 제시됐다.

  • 디자인 패턴 사용
    • 카를스루 공과대학교 컴퓨터 과학 교수 발터 티히(Walter Tichy)가 제안
    • 디자인 패턴이 코드의 유지보수에 도움이 되는지 연구
    • 디자인 패턴 교육을 받은 경우 수정 시간 감소
    • 패턴의 종류에 따라 효과는 다양
  • 주석문 쓰기
    • "코드 자체가 이미 문서"이기 때문에 작성하지 않아야 한다는 논쟁 존재
    • 연구에 의하면 주석문이 포함된 코드를 읽는 시간이 더 오래 걸림
    • 개발자가 코드를 읽을 때 주석 또한 읽기 때문
    • 즉, 주석을 쓰는 것은 의미 없는 활동이 아님
    • 초급 개발자가 코드를 이해하는 데 도움이 되며, 주석에 따라 코드를 청킹하는 방식에도 도움
    • 고수준 주석과 달리 저수준 주석은 오히려 청킹 작업에 부담
  • 표식(beacon) 남기기
    • 코드가 무엇을 하는지 이해하는 데 도움이 되는 것으로, 라인이 될 수도 있고 라인의 일부가 될 수도 있음
    • 일반적으로는 특정 자료구조, 알고리즘, 접근 방식 등을 보여주는 라인
      • ex) 변수명 tree + 변수명 root  + 주석문 트리 + 변수명 right + 변수명 left --> 이진트리
    • 표식은 일반적으로 청크보다는 더 작은 코드의 일부분으로 여겨짐
    • 전문가와 달리 초급 개발자는 코드를 읽고 이해할 때 표식의 이용이 적음

프로그래밍 문법은 빠르게 배우는 것이 좋다. 많은 개발자들이 언어의 문법을 모르더라도 검색하면 되기 때문에 그리 중요하게 생각하지 않는다. 하지만 이것의 단점이 두 가지가 있다.

  1. 관련 내용을 미리 알고 있을 때(LTM에 지식이 있을 때)만 더 많은 코드를 쉽게 분리하고 기억하고 처리할 수 있다.
  2. 검색을 위한 업무 중단은 생각한 것보다 훨씬 더 나쁜 결과를 불러온다.

프로그래밍 언어 문법을 포함해 무엇이든 빠르게 학습할 수 있는 방법 중 하나는 플래시 카드를 사용하는 것이다. 플래시 카드를 이용해 반복적으로 노출하고 외우다 보면 LTM에 해당 지식이 쌓이게 된다. 또한, 플래시 카드를 이용하면 검색을 하는 시간도 줄일 수 있다. 새로운 개념을 배우거나 모르는 개념을 검색할 때 하나씩 만드는 것이 도움이 될 것이다.

하지만 꾸준히 반복해야 LTM으로 지식이 쌓이게 된다. LTM에 쌓이더라도 오랜 시간이 지나면 망각할 수밖에 없다. 따라서 간격을 두고 반복해야만 한다.


코드가 너무 복잡해 완전히 이해하지 못하는 경우가 가끔 있다. 코드 읽는 연습을 하지 않아 이해되지 않는 코드를 다루는 방법을 잘 모를 수 있다. 다시 읽는 것이나 포기하라는 조언은 도움이 되지 않는다. 이때는 작업 기억 공간의 기저에 있는 인지 과정을 알면 좋다.

 

STM과 마찬가지로 작업 기억 공간도 한 번에 2~6개의 내용만 기억할 수 있다. 작업 기억 공간에서 이 용량을 인지 부하(cognitive load)라고 부른다. 너무 많은 요소로 청크가 나뉘지 않는 문제는 작업 기억 공간이 과부하(overload) 상태가 되는 것이다.

  • 내재적 부하 : 문제 자체의 복잡도
  • 외재적 부하 : 외부 요인에 의해 문제에 추가된 것
  • 본유적 부하 : 생각을 LTM에 저장하는 과정에서 발생

외재적 부하와 본유적 부하는 문제 자체가 복잡해지는 것은 아니다. 문제 해결 방법은 동일한데 추가로 외재적 업무를 수행할 때 발생하는 것이다.

 

인지 부하를 줄일 수 있는 방법으로는 아래의 것들이 있다.

  • 리팩터링(refactoring)
    • 일반적으로 리팩터링은 코드의 유지보수를 쉽게 하기 위한 목적
    • 코드 전체가 유지보수하기 좋아졌다고 해서 가독성까지 좋아지는 것은 아님
    • 탈국지화된 코드는 여러 곳에서 메서드 내부 구현을 일일이 찾아봐야하기 때문에 작업 기억 공간에 인지 부하 발생
    • 장기적으로 유지보수하기 좋은 코드보다는 가독성 높은 코드 작성의 리팩터링(인지적 리팩터링, cognitive refactoring)이 좋을 수 있음
    • 인지적 리팩터링은 역 리팩터링(reverse refactoring)을 수반할 수도 있음
      • 일반적인 이름의 메서드(ex. calculate / transfomr)는 기능을 알기 위해서 코드를 인라인으로 구현하는 것이 좋을 수 있음
  • 생소한 언어 구성 요소를 다른 것으로 대치
    • 혼란의 세 가지 요소(지식 부족, 정보 부족, 처리 능력 부족)를 극복하는 데 도움이 되는 기법
    • 람다(익명 함수)는 복잡한 경우 작업 기억 공간에 과부하가 발생
    • 익숙하지 않은 것은 작업 기억 공간에 외재적 인지 부하를 늘리기 때문에 익숙한 내용으로 대치

람다나 삼항 연산자는 코드 가독성을 높여주기 때문에 사용하는 것이 언제나 바람직하다고 생각할 수도 있다. 역 리팩터링을 반대할지도 모르겠지만 가독성이라는 것은 사람의 눈에 읽기 좋은 것을 말한다. 삼항 연산자에 익숙하다면 그것을 사용한 코드는 읽기 쉽다. 사람마다 가지고 있는 지식에 따라 다르기 때문에 좀 더 친숙한 형태로 바꿔서 이해하는 것이 필요하다. 자신의 코드베이스에 따라 코드를 이해하고 나면 가독성을 위해 수정한 코드를 되돌릴 수도 있다.

 

작업 기억 공간이 부족해 부하가 온다면 보조 수단을 활용하면 좋다. 대표적인 보조 수단으로는 의존 그래프(dependency graph)가 있다. 의존 그래프를 이용하려면 코드를 프린트하거나 PDF 파일로 변환한 후 디지털 상태로 주석을 달면 좋다. 다음의 순서대로 주석을 다는 것도 좋다.

  1. 모든 변수를 원으로 표시
  2. 비슷한 변수를 연결
  3. 모든 메서드나 함수 호출을 원으로 표시
  4. 메서드나 함수 호출을 정의와 연결
  5. 클래스 내 모든 인스턴스를 원으로 표시
  6. 클래스와 클래스의 인스턴스를 연결

 

작업 기억 공간이 부족할 때 사용할 수 있는 다른 방법으로는 상태표가 있다. 자신의 지식에 맞는 형태로 코드를 리팩터링 하고 의존 관계를 표시한 후에도 코드를 이해하기 어렵다면 처리 능력의 부족과 관련이 있을 가능성이 있다.

상태표는 코드의 구조보다 변수의 값에 중점을 둔다. 열은 변수를 나타내고 행은 해당 변수가 각 단계에서 갖는 값을 표시한다.

 

의존 그래프와 상태표는 각각 다른 측면에서 코드의 정보를 공한다. 의존 그래프는 코드의 구조를 보여주고 상태표는 코드에서의 계산 결과를 보여준다.


코드가 하는 일을 이해한 뒤에는 좀 더 깊이 생각하는 단계가 필요하다. 어떻게 작성되었는지, 새로운 기능은 어디에 추가해야 하는지, 다른 설계를 적용한다면 어떤 것이 가능한지 등에 대해서다. 작성자의 아이디어, 생각, 결정에 대해 추론할 수 있는 깊은 수준에서의 생각을 세 가지 살펴본다.

이전 내용들은 스키마타, 즉 기억이 두뇌에서 어떻게 구성되어 있는지 살펴본 것이다. 기억은 따로 저장되는 것이 아니라 다른 기억들과 연결돼 있다. LTM에 저장된 기억으로 작업 기억 공간에서 청크를 만들고 청크는 코드를 생각하는데 도움이 되기 때문이다.

 

첫 번째는 '변수 역할' 프레임워크다. 코드를 추론할 때 변수가 중심적인 역할을 한다. 즉, 적절한 변수명은 표식(beacon)으로 사용될 수 있고 깊이 있는 이해에 도움을 준다.

이스턴 핀란드 대학교 요르마 사야니에미(Jorma Sajaniemi) 교수에 의하면 변수를 이해하기 어려운 이유는 대부분의 개발자가 변수를 연관 지을 좋은 스키마를 각자 자신들의 LTM에 갖고 있지 않기 때문이라고 한다. 변수나 정수처럼 너무 많은 것을 포함하는 청크 단위를 사용하거나 number_of_customer 같은 구체적인 변수명처럼 너무 적은 것을 포함하는 청크 단위를 사용하는 경향이 있다고 한다. 적절한 중간 단위의 청크가 필요한데, 이를 변수 역할(role of variables)이라는 프레임워크를 만드는 계기가 됐다. 변수 역할은 프로그램에서 변수가 하고자 하는 바를 나타낸다.

어느 코드든 변수들이 하는 역할은 비슷하다. 샤이니에미는 다음의 11개 역할로 대부분의 변수를 설명할 수 있다고 주장한다.

  • 고정값(fixed value)
    • 초기화된 값이 할당 이후 변하지 않는 변수
  • 스태퍼(stepper)
    • 루프를 반복할 때 단계적으로 변하는 변수
  • 플래그(flag)
    • 이벤트가 발생했거나 이벤트가 어떤 경우에 해당하는지를 나타내는 변수
  • 워커(walker)
    • 스테퍼와 유사하지만 자료구조를 순회하는 변수
    • 스테퍼는 미리 정해진 값을 따라 반복하지만 워커는 루프가 시작되기 전에 어떤 값을 가지게 될지 알 수 없음
    • 언어에 따라 포인터나 정수 인덱스를 사용할 수 있으며 보통 스택이나 트리 순회에 사용
  • 최근값 보유자(most recent holder)
    • 값이 변할 때 가장 최근에 변경된 값을 갖는 변수
  • 목적값 보유자(most wanted holder)
    • 반복할 때 목적(찾고자 하는 조건에 부합)에 해당하는 변수
  • 모집자(gatherer)
    • 데이터를 모으거나 모은 데이터에 연산을 수행해 얻은 값을 저장하는 변수
  • 컨테이너(container)
    • 추가하고 삭제할 수 있는 자료구조에 대항하는 변수
  • 추적자(follower)
    • 알고리즘에서 이전 값 혹은 다음 값을 추적할 때 사용하는 변수
  • 조직자(organizer)
    • 추가적인 처리를 위해 변수 값을 변환해야 하는 경우가 있는데, 이렇게 다른 값을 저장하기 위한 목적으로 사용하는 변수
    • 종종 임시 변수로 사용
  • 임시(temporary)
    • 잠시만 사용하는 변수

역할은 특정한 프로그래밍 패러다임에 제한되지 않고 모든 패러다임에 나타난다. 예를 들어, 모집자 변수는 함수형 언어에서도 나타날 수 있다. 물론 객체 지향 프로그래밍 언어에서도 모두 확인이 가능하다.

 

이 역할들은 새로운 개념이라기보다는 변수에 대해 논의할 때 사용 가능한 새로운 용어 정도로 생각하면 된다. 팀원간 의사소통에 도움이 될 정도다. 역할에 친숙해지면 소스 코드를 머릿속으로 처리하는 데 도움이 될 뿐만 아니라 성과가 뛰어났다는 연구 결과도 있다. 성과 향상에 효과적인 이유 중 한 가지는 몇 개의 역할이 하나로 묶여서 프로그램의 한 타입을 특정 짓는다는 것이다.

예를 들어 앞서 프린트하거나 PDF 파일에 디지털로 주석을 다는 보조 도구를 설명했다. 이 보조 도구를 사용할 때 역할별로 아이콘을 지정해 주석으로 표시한다면 더욱 강력한 보조 도구로 활용 가능하다.

 

변수 역할 프레임워크를 보면 헝가리안 표기법(Hungarian Notation)이 떠오르기도 한다. 헝가리안 표기법은 변수 타입을 변수 명에서 나타내는 것인데, 이는 타입 시스템이 없던 언어(BCPL, Basic Combined Programming Language)에서 시작한 방식이다. 게다가 당시에는 인텔리센스 기능이 있는 IDE도 없이 에디터에서 변수 타입을 파악해야 했다. 마이크로소프트에서 일했던 찰스 시모니(Charles Simonyi)의 논문에서 제안되었는데, 그의 명명 규약은 마이크로소프트의 소프트웨어 표준이 되었다.

찰스 시모니의 방식은 당시 변수 타입을 추가함으로써 코드 가독성을 높일 수 있었다. 하지만 변수명도 길어지고 타입 변경이 많은 변수에 영향을 미쳤다. 오늘날에는 에디터에서 변수 타입을 쉽게 확인 가능하고 변수명이 길어지는 단점으로 권장되지는 않는다.

사실 변수명에 타입을 나타내는 것이 시오니가 제안한 전부가 아니다. 변수명에 타입을 나타내는 방식은 시스템 헝가리안 표기법(Systems Hungarian Notation)이라 한다. 하지만, 시오니가 제안한 것은 본질적으로 더 의미론적인 앱 헝가리안 표기법(Apps Hungarian Notation)이다.

앱 헝가리안 표기법은 접두어에 변수 타입이 아닌 좀 더 구체적인 의미를 추가한다. 예를 들어, 인스턴스의 개수를 나타내는 변수는 c(count)를 붙이고 배열의 길이를 나타내는 변수는 l(length)를 붙이는 방식이다. 시모니가 참여했던 엑셀을 예로 들면 row, col 같은 것을 붙이는 경우로 볼 수 있다. 이런 앱 헝가리안 표기법이 훨씬 가독성 측면에서 좋다.

사실 윈도우즈 팀도 앱 헝가리안 아니라 시스템 헝가리안 표기법을 사용했다. 여러 가능성이 있지만 저자는 소수의 개발자가 잘못된 표기법을 쓰기 시작하면서 퍼져나간 것이 아닌가 추측한다.

 

프로그램에 깊이 있는 지식을 얻기 위해서는 코드 작성자의 목적과 의사 결정 내용을 이해하는 것이 필요하다. 콜로라도 대학교 심리학과 교수 낸시 페닝턴(Nancy Pennington)은 이해를 여러 층위로 분석했다. 페닝턴은 프로그래머가 소스 코드를 이해하는 두 가지 층위를 제시했다. 텍스트 구조 지식(text structure knowledge)과 계획 지식(plain knowledge)이다.

텍스트 구조 지식은 키워드가 하는 일이나 변수의 역할같은 표면적 이해와 관련 있다. 계획 지식은 프로그램을 작성할 때 계획한 것이 무엇인지, 무엇을 달성하려 했는지를 나타낸다. 코드의 목적은 변수의 역할만을 통해서 알 수 있는 것이 아니고 코드가 어떤 구조로 어떻게 연결되어 있는지 살펴봄으로써도 알 수 있다.

 

프로그램에 대한 계획 지식을 갖는다는 것은 코드의 각 부분이 다른 부분과 어떤 방식으로 관련되어 있는지 이해하는 것을 의미한다. 코드 이해의 바탕에 깔려 있는 이론을 토대로 흐름을 빨리 파악할 수 있을만한 방법을 제시한다.

브리검영 대학교 교수인 조너선 실리토(Jonathan Sillito)는 코드 이해 방법을 4단계로 정의했다.

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

코드를 읽을 때 초점은 중요한 개념이다. 초점으로는 main(), onLoad()와 같은 함수들, 즉 진입점에 해당된다. 초점을 바탕으로 어디서부터 읽기 시작해야 할지 알아야 한다. 어디서부터 시작해야 할지 알기 위해서는 프레임워크에서 코드가 어떻게 서로 연결되어 있는지 이해해야 한다.

 

독일 컴퓨터 과학 교수 야네트 지크문트(Janet Siegmund)는 프로그래밍에 대해 fMRI 장치를 이용한 연구를 진행했다. 피실험자들에게 잘 알려진 알고리즘을 구현한 자바 코드를 읽도록 시켰다. 이때 변수명은 일부로 의미를 파악하기 힘든 이름으로 대치해 변수명으로부터 기능을 유추하기보다는 프로그램의 흐름을 이해하는 인지적 노력을 하도록 유도했다.

실험 결과는 인간 언어 이해와 관련된 부분이 활성화되었다는 점이다. 변수명을 통해 의미 파악이 안 되도록 했기에 이 발견은 흥미로웠다. 인간의 언어로 된 텍스트를 읽을 때 하는 일을 뇌가 한 것이다.

 

워싱턴 대학교 샨텔 프랫(Chantel Prat) 교수는 인지 능력과 프로그래밍 사이 연관성에 대한 연구를 수행했다. 프로그래밍 능력뿐만 아니라 수학, 언어, 추론 등의 영역에서 능력을 평가했다. 프로그래밍 능력 점수와 다른 인지 능력 점수로 관련성을 파악하기 위함이었다.

실험 결과로 발견된 것은 수학적 능력과의 연관성은 적었으며, 오히려 언어 능력과의 연관성이 더 높았다. 프로그래밍 능력별 수학적 능력은 분산이 2%로 저조했지만 언어 능력은 17%의 분산으로 나타났다. 흔히 개발자들은 수학 능력을 중시한다고 하지만 언어 능력이 더 중요했다는 것이었다.

가장 예측성, 즉 연관성이 높았던 항목은 작업 기억 공간 용량과 추론 능력이었다. 이것은 분산이 34%나 되었다.

 

앞서 코드를 이해하기 위한 인지적 능력은 일반적인 언어를 읽을 때와 유사한 것을 살펴보았다. 이는 자연언어 텍스트 이해에 대한 연구 결과도 코드 읽기에 적용할 수 있음을 말한다. 효과적인 텍스트 이해 전략은 다음 7개 범주로 나뉜다.

  • 활성화 - 관련된 것들을 적극적으로 생각해서 이미 갖고 있는 지식을 활성화
  • 모니터링 - 텍스트에서 자신이 이해한 것을 관찰하고 기록
  • 중요도 결정 - 어느 부분이 중요한지 결정
  • 추론 - 명시적으로 주어지지 않은 사실 유추
  • 시각화 - 깊이 있는 이해를 위해 텍스트에 대한 도표 생성
  • 질문 - 텍스트에 대해 질문
  • 요약 - 텍스트를 짧게 요약

사람들은 문제를 풀 때 모델을 만든다. 실재를 간단하기 표현하기 위한 것으로, 문제에 대해 생각하고 해결하는 데 도움을 주기 위함이다. 앞서 설명했던 내용 중에서는 상태표와 의존 그래프가 대표적인 예가 될 수 있다. 문제를 풀 때 코드의 모델을 명시적으로 사용하는 것에는 두 가지 장점이 있다.

  • 모델은 프로그램에 대한 정보를 다른 사람과 공유할 때 유용하다.
  • 문제를 풀 때 두뇌에서 한 번에 처리할 수 있는 한계에 도달하는 인지 부하를 줄일 수 있다.

 

문제에 대해 생각할 때 두뇌의 외부에서 만들어지지 않은 모델을 사용할 수도 있다. 이를 정신 모델(mental model)이라 한다. 정신 모델은 다른 것보다 더 사고에 도움이 된다. 저자가 생각하는 정신 모델에 대한 적절한 정의는 '풀어야 할 문제에 대해 추론하기 위해 사용할 수 있는 작업 기억 공간 내의 추상화'다.

정신 모들의 예는 트리 순회에 대해 생각해보는 것이다. 코드와 컴퓨터에는 우리가 순회하는 실제 트리가 존재하지 않는다. 트리 구조는 사실 메모리 내 값일 뿐이다. '한 노드의 자식 노드'라고 생각하는 것이 '요소가 가리키는 다른 요소'라고 생각하는 것보다 쉽게 추론하는 데 도움을 준다.

 

인지 과정과 정신 모델 사이의 연관을 보면 각자 훈련할 수 있는 방법이 있을 것이다. 정신 모델은 작업 기억 공간과 LTM 양쪽 모두 위치해있다.

 

프린스턴 대학교 심리학과 교수 필립 존슨-레어드(Philip Johnson-Laird)는 두뇌가 추론을 하는 동안 정신 모델이 사용되고 작업 기억 속에 존재한다고 했다. 피실험자에게 '칼은 포크 왼쪽에 있다'와 '포크는 접시 왼쪽에 있다'처럼 테이블 세팅에 대한 문장들이 주어진 뒤 다른 일을 하게 했다. 처음 들었던 세팅과 가장 유사한 세팅을 맞추도록 연구를 진행했다.

2개는 완전히 다른 서술이고, 1개는 원래의 서술, 1개는 유추된 서술(접시는 칼의 오른쪽에 있다)의 선택지가 주어졌을 때 원래의 서술과 유추된 서술이 더 높은 순위가 되었다. 이로써 참가자들은 테이블 설정에 대한 모델을 만들어 정답을 고르는 데 사용했다는 결론이 도출되었다.

 

R&D 기업인 볼트 베라넥 뉴먼(Bolt Beranek and Newman Inc.)의 연구원 데드레 겐트너(Dedre Gentner)와 앨버트 스티븐스(Albert Stevens)는 LTM에 정신 모델이 저장되며 필요할 때 기억해 낼 수 있다고 주장했다. 일반적으로 유리컵에 우유를 따를 때 어떻게 흐르는지에 대한 정신 모델, 팬케이크 반죽을 그릇에 부을 때 반죽과 우유와 다른 움직임을 가질 것이라는 정신 모델 등이 있을 수 있다.

프로그래밍에 적용하자면 트리를 탐색하는 것에 대한 추상적인 표현을 저장할 수 있다. 루트부터 자식 노드를 확인하는 BFS 혹은 DFS 방식과 같은 것이 LTM에 저장된 정신 모델이라 볼 수 있다. 한 번도 사용해본 적 없는 프로그래밍 언어로 작성된 트리 탐색 코드도 이전에 저장한 정신 모델을 통해 이해할 수 있다.

 

정신 모델은 일반적이고 모든 영역에서 찾아볼 수 있다. 프로그래밍 언어에서 이와 유사한 개념을 사용하는데 개념적 기계(Notational Machine)라는 것이다. 개념적 기계는 오직 컴퓨터가 코드를 실행하는 방법에 대해 추론할 때만 사용한다.

프로그램이 작동하는지 이해하고자 할 때 대부분의 경우 컴퓨터가 물리적으로 동작하는 세부 사항에는 관심이 없다. 즉 전기를 사용해 비트를 저장하는 방식에는 관심이 없으며 프로그래밍 언어 수준의 높은 개념적 수준에서 일어나는 일에 관심이 있다. 실제 물리적 기계와 그 기계가 추상적 수준에서 수행하는 작업의 차이를 나타내기 위해 개념적 기계라는 용어를 사용한다.

개념적 기계는 불완전하더라도 프로그래밍 언어의 실행에 대해서는 일관되고 정확하게 추상화된 것이다. 정신 모델은 잘못되거나 일관되지 않을 수도 있는 것과 차이가 있다. 개념적 기계는 컴퓨터가 작동하는 방식을 설명한 것으로, 프로그래밍 언어에 대해 더 많이 배울수록 정신 모델은 점점 개념적 기계에 가까워진다.

 

프로그래밍 개념에 대한 설명과 이해를 목적으로 개념적 기계를 사용할 때 어떤 세부사항을 숨기고 있는지 의식적으로 생각해보면 좋다.

개념적 기계는 단점이 있지만 프로그래밍에 관해 생각할 때 효과적인 수단이다. 효과적인 개념적 기계는 프로그래밍 개념을 일상생활의 개념과 연관 짓는데 일상생활의 개념은 강한 스키마타가 이미 형성되어 있기 때문이다.

스키마타는 LTM이 정보를 저장하는 방식이다. 상자에 대해 사람들이 갖고 있는 생각은 강한 연관성을 갖는 개념일 가능성이 매우 높다. 물건을 상자에 넣고, 꺼내고, 확인하기 위한 행동은 사람들에게 익숙하다. 이때 변수를 상자로 생각하면 추가적인 인지 부하가 발생하지 않는다.


우리는 새로운 프로그래밍 언어를 배울 때 LTM에 저장된 기존 프로그래밍 언어와 관련된 지식을 이용한다. 이를 학습 전이(transfer of learning)라 한다. 학습 전이는 우리의 사고 활동 없이도 일어난다. 하지만 이런 학습 전이가 항상 도움만 되는 것은 아니다.

 

자동화된 기술의 전이와 의식적인 기술의 전이는 차이가 있다. 자동화된 기술 전이는 저도 전이(low-orad transfer)라고 한다. 새 편집기에서 아무 생각 없이 Ctrl + C, Ctrl + V를 사용하는 것을 예로 들 수 있다. 반대로 복잡한 작업이 전이되는 것을 고도 전이(high-road transfer)라고 한다. 고도 전이는 그러한 상황이 발생하고 있음을 인지하는 경우가 많다. 예를 들어 대부분의 프로그래밍 언어에서 변수를 사용하기 전에 먼저 선언을 하기 때문에 새로운 언어를 배울 때도 변수 선언 먼저 해야 한다고 생각하는 경우다.

영역 간의 거리에 따라 전이의 형태를 나눌 수도 있다. 미적분학과 대수학, C#과 자바처럼 가까운 영역 사이 지식이 전이되는 것을 근거리 전이(near transfer)라 한다. 라틴어와 논리학, 자바와 프롤로그 같이 먼 영역 간에 일어나는 전이를 원거리 전이(far transfer)라고 한다. 당연히 원거리 전이가 근거리 전이보다 일어날 가능성이 훨씬 낮다.

 

또 다른 범주로 두 가지의 전이를 나눌 수 있다. 긍정적 전이(positive transfer)와 부정적 전이(negative transfer)다. 네덜란드의 다익스트라(Edsger Dijkstra)는 '정신이 손상되기' 때문에 베이직을 가르치는 것을 금지하자는 것으로 유명하다. 다익스트라의 베이직에 대한 언급은 일리가 있다. 잘못된 가정은 부정적 전이를 일으킬 수 있다.

 

하지만 전이는 일어나기 어렵고 대부분의 사람에게 자동으로 일어나지는 않는다. 체스는 학습 전이의 원천이라고 거론되지만 체스 지식이 논리적 추론 기술과 기억력, 일반 지능을 높여주지는 않는다. 프로그래밍에서도 마찬가지로 논리적 추론에 대한 기술이나 일반적 지능의 향상을 가져와주지는 않는다.

여기서 중요한 점은 하나의 프로그래밍 언어를 숙달하더라도 새로운 언어를 배우는 데 항상 도움이 되는 것은 아니라는 것이다. 새로운 언어를 배우기 시작했다면 이미 습득한 언어와는 근본적으로 다른 언어를 선택하는 것이 중요하다.

 

버그는 근본적인 이유로 발생하기도 한다. 작업 중인 코드에 대해 잘못된 가정을 할 때 일어날 수 있고, 코드의 다른 곳에서 인스턴스가 초기화될 거라고 생각하거나 선택한 메서드가 맞다고 확신하는 등의 오개념(오해, misconception)의 문제일 가능성이 있다.

오개념은 강한 확신 속에 있는 잘못된 사고방식이다. 때로는 교정하기가 어려울 수 있다. 지적만으로는 충분하지 않으며 잘못된 생각을 바꾸려면 잘못된 사고방식을 새로운 사고방식으로 바꿀 필요가 있다.

 

이미 알고 있는 프로그래밍 언어 때문에 생긴 오개념을 현재 학습 중인 새로운 언어에 맞는 정신 모델로 대체하는 과정을 개념 변화(conceptual change)라고 한다. 기존의 개념은 근본적으로 바뀌거나 대체되거나 새로운 지식에 동화된다. 기존 스키마에 새로운 지식을 추가하는 것이 아닌 지식 자체를 바꾼다는 것이 개념 변화가 다른 유형의 학습과 다른 점이다.

이미 학습한 LTM에 쌓인 지식을 변경하는 것은 일반적인 학습보다 더 어렵다. 따라서 새로운 프로그래밍 언어를 배울 때는 이전 프로그래밍 언어에 대한 기존의 지식을 떨쳐내기 위해 많은 에너지를 소비해야 한다.

 

오개념에 대해 우리가 할 수 있는 것이 많지 않다. 부정적 전이를 피할 수는 없지만 도움이 될만한 몇 가지 방법이 있다.

  • 자신이 옳다고 확신하더라도 여전히 틀릴 수 있음을 알아야 한다.
  • 흔하게 발생하는 오개념을 의도적으로 연구한다.
  • 같은 프로그래밍 언어를 같은 순서로 학습한 다른 프로그래머의 조언을 구한다.

좋은 이름은 문법적으로 정의할 수 있다. 영국 오픈 대학교 강사 사이먼 버틀러(Simon Butler)는 다음과 같은 명명 규칙 목록을 만들었다.

  • 비정상적인 대문자 사용
    • 대문자를 올바르게 사용하자
    • ex) paGecoUnter
  • 연속된 두 개의 밑줄
    • 연속된 여러 밑줄을 가져서는 안 된다.
    • ex) page__counter
  • 사전 등재 단어
    • 식별자는 단어로 만들되 약어는 원래 명칭보다 더 자주 사용되는 경우에만 사용해야 한다.
    • ex) page_countr
  • 단어의 수
    • 사용되는 단어는 두 개에서 네 개 사이여야 한다.
    • ex) page_counter_convertedP_and_normalized_value
  • 너무 많은 단어
    • 사용되는 단어는 네 개를 초과하면 안 된다.
  • 짧은 이름
    • c, d, e, g, i, in, inOut, j, k, out 등과 같은 것을 제외하고 8글자 보다 작으면 안 된다.
    • ex) P, page
  • 열거형 식별자 선언 순서
    • 분명한 이유 없이는 열거형은 알파벳 순서로 선언한다.
    • ex) ACE, EIGHT, FIVE, FOUR, JACK, KING..
  • 외부 밑줄
    • 밑줄로 시작하거나 끝나서는 안 된다.
    • ex) __page_counter_
  • 식별자 인코딩
    • 헝가리안 표기법 등으로 식별자 이름에 타입 정보를 나타내서는 안 된다.
    • ex) int_page_counter
  • 긴 이름
    • 긴 식별자 이름은 가능한 한 피해야 한다.
    • ex) page_counter_converted_and_normalized_value
  • 명명법 규약 이상
    • 대문자와 소문자를 표준적이지 않은 방법으로 섞어서는 안 된다.
    • ex) Page_counter
  • 숫자를 나타내는 식별자 이름
    • 숫자만을 나타내는 단어나 수를 사용해서는 안 된다.
    • FIFTY

이 명명 규약은 대부분 문법과 관련 있다.

 

다른 관점에서 좋은 이름에서 중요한 것은 일관성이다. 알라마니스는 좋은 명명 방식의 가장 중요한 측면은 코드베이스 전반에 걸쳐 비슷하게 명명하는 것이라 주장했다.

 

버틀러는 문법적 지침을 따르면 올바른 이름을 지을 수 있다는 것이다. 알리미나스는 이름의 품질에 대해 고정된 규칙이나 지침을 정하지 않았다. 하지만 코드베이스는 주도적이어야 하며 나쁘더라도 일관적인 편이 좋지만 일관적이지 않은 것보다 낫다는 입장이다. 결과적으로는 코드를 읽는 사람에 따라 선호되는 것은 달라진다.

버틀러의 관점은 이름을 처리할 때 인지 부하를 낮출 수 있다. 알라마니스의 관점은 청킹을 지원하기 좋다.

 

그럼 이제 어떤 종류의 이름이 더 이해하기 쉬운지 알아보자. 사전에 등재된 단어를 결합해 이름을 만들어야 한다는 의견에 관해 본 적이 있을 것이다.

독일 파사우 대학교 연구원 요하네스 호프마이스터(Johannes Hofmeister)는 전문 개발자들을 대상으로 버그를 찾는 실험을 했다. 버그를 찾을 때 식별자 이름의 의미나 형태가 중요한지에 대한 관심을 가졌다. 식별자가 글자, 약자, 단어로 구성된 세 가지 유형의 프로그램을 제시했고 문법 오류와 의미 오류 모두 찾도록 했다.

단어인 프로그램을 읽을 때 글자나 약어에 비해 분당 19% 더 많은 결함을 발견했으며, 글자나 약어로 된 프로그램에서는 속도의 차이가 없었다.

 

단어로 이루어진 변수가 코드 이해에 도움이 된다지만 긴 변수 이름을 사용하는 것은 단점이 될 수 있다. 로리는 연구에서 전문 개발자에게 동일한 변수에 대해 세 가지 유형의 식별자를 가진 코드(단어, 약어, 단일 문자)를 파악하고 기억하도록 했다. 세 식별자 중 한 가지를 사용한 코드를 보여준 뒤 코드를 시야에서 보이지 않게 한 후 참가자들에게 코드를 말로 설명하고 변수 이름을 물어봤다.

단어로 구성된 식별자는 다른 두 가지 방법보다 이해하기 쉽다는 결론이 나왔다. 단어 식별자를 사용한 코드 요약이 단일 문자 식별자를 이용한 코드 요약보다 거의 1점(5점 만점) 높은 평가를 받았다.

하지만 단어 식별자의 단점도 밝혀냈다. 코드 요약을 조사하면서 변수 이름이 길수록 기억하기 어렵고 기억하는 데 더 많은 시간이 걸린다는 사실을 발견했다. 영향을 많이 준 것은 길이 자체가 아니라 음절의 수였다. 인지적 관점에서 이름이 길면 STM에서 더 많은 청크를 사용할 수 있고 각 음절에 대해 청크를 만들 가능성이 높다.

그러면서 로리는 식별자에 접두사나 접미사를 붙이는 명명 규약을 조심할 것으로 충고했다. 이러한 방식이 정보를 추가해서 얻는 이득보다 이름을 기억하기 어려워 발생하는 손실이 더 큰지 조심히 확인해야 한다.

 

로욜라 대학교 메릴랜드의 컴퓨터 과학 교수 데이브 빙클리(Dave Binkley)는 캐멀 케이스 변수와 스네이크 케이스 변수에 대한 이해도 차이를 조사했다. 프로그램에 적응하는 속도와 정확도에 영향을 미치는지 조사했다. 연구 참가자들에게 변수를 설명하는 문장을 보여준 뒤, 변수 이름에 관한 선택지 중 하나를 선택하도록 했다. 그중 하나가 실험 참가자가 봤던 문장을 나타냈고 나머지는 비슷한 모양새지만 다른 뜻의 문장이었다. (ex. extendListAsTable, expandAliasTable, expandAliasTitle, expandAliasTable 등)

빙클리의 연구 결과는 프로그래머와 비프로그래머 모두에게서 캐멀 케이스가 더 높은 정확도를 갖는 것을 확인했다. 캐멀 케이스일 때가 스네이크 케이스 보다 51.5% 더 높은 정확도를 보였다. 그러나 답을 선택하는 데 들어간 시간은 캐멀 케이스가 0.5초 더 길었다.

또 하나 확인된 사항은 캐멀 케이스에 대한 교육을 많이 받은 프로그래머들은 변수 이름이 캐멀 케이스일 때 교육받지 않은 프로그래머보다 더 빨리 답을 찾았다는 점이다. 다른 스타일을 사용할 때 부정적인 영향을 많이 받았다는 사실이 드러났다. 인지 처리 과정에 빗대어보면 식별자 이름을 캐멀 케이스로 자주 작성하면 이름을 청킹하고 의미 파악하는 일을 더 잘하게 되는 것이다.

마지막의 내용은 코드베이스에서 변수 이름이 캐멀 케이스냐 스네이크 케이스냐를 변경하는 것은 현명하지 않다는 것을 말한다. 코드베이스 전체의 일관성이 중요한 측면이다. 새로운 명명 규약을 결정할 때라면 캐멀 케이스를 선택하는 것이 더 낫다는 것 뿐이다.

 

앞서 명명 규약을 만들었던 버틀러는 명명 지침 위반을 감지하는 툴을 만들었다. 이를 이용해 명명 지침을 위반한 코드 위치를 찾아냈다. 그런 다음 버틀러는 잘못된 이름의 위치를 파인드버그(FindBugs, 정적 분석을 통한 잠재적 버그 위치 탐색 툴)을 이용해 버그의 위치와 비교했다. 흥미로운 점은 명명 규약을 위반한 곳과 코드 품질이 나빴던 위치는 비슷했다. 즉 잘못된 명명 방식은 읽고 이해하고 유지보수하기 어려운 코드가 아니라 잘못된 코드일 가능성이 높다는 것이다.

물론 버그 위치와 나쁜 이름 위치의 상관관계가 인과관계를 의미하지는 않지만 버그와 좋지 않은 이름 모두 초보 프로그래머나 실력 없는 개발자가 작성한 코드 결과일 수도 있다. 혹은 복잡한 문제를 해결하느라 버그가 발생한 것일 수도 있다.

따라서 잘못된 이름을 고치는 것은 버그를 고치거나 예방하는 것까지는 아닐지라도 코드를 개선하고 버그 발생 가능성이 있는 위치를 찾는 데 도움이 될 수는 있다.

 

더 나은 이름을 선택하는 방법으로 페이텔슨은 이름 틀을 제시했다. 이름 틀은 변수 이름의 요소가 일반적으로 결합되는 패턴이다. 개발자들이 변수에 같은 이름을 선택하는 경우는 드물지만 다른 개발자들이 선택한 이름을 이해한다는 것에서 발견된 것이다. 예를 들어 다음의 변수명은 다른 개발자들이 작성되었어도 동일하게 이해한다.

  • max_benefit
  • max_benefit_per_month
  • max_benefit_num
  • max_monthly_benefit
  • benefits
  • benefits_per_month
  • max_num_of_benefit
  • max_acc_benefit
  • max_allowed_benefit
  • ...

동일한 코드 베이스에서는 서로 같은 이름 틀을 사용하는 것이 좋다. 이름을 이해하는데 정신 에너지를 쏟는 것은 옳지 못하다. 게다가 변수 이름이 비슷한 경우 동일한 이름틀을 사용해야 LTM에서 관련 정보를 쉽게 찾아온다. 예를 들어 max_interest_amount라는 변수는 max_benefit_amount라는 변수와 같은 이름틀이지만 interest_maximum이라는 변수와는 다른 이름 틀이기 때문에 LTM에서 기억을 찾기 힘들다.

 

더 나은 변수명을 짓기 위해 페이텔슨은 3단계 모델을 제시한다.

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

개념은 도메인별로 많이 다르며 어떤 차원을 포함할 것인지 결정하는 것이 중요할 수도 있다. 이름을 선택할 때 고려해야 할 주요 사항은 이름의 의도이다. 이름을 설명하기 위한 주석문이나 이름 가까이 주석문이 있다면 주석문의 문구가 변수 이름에 포함되어야 할 수도 있다.

각 개념을 나타낼 단어를 선택할 때 종종 올바른 단어를 선택하는 것은 간단하며 명백할 것이다. 그러나 동의어가 같은 의미인지 미묘한 차이를 나타내는지에 대해 개발자들은 혼란스러워하는 경우도 있었고 이는 다양성으로 문제를 일으킬 수 있다. 이때는 프로젝트 어휘 사전으로 동의어를 정리해놓는 것이 좋을 수 있다.

단어들을 사용해 이름을 구성할 때는 코드베이스의 이름 틀에서 선택하는 것이 좋다. 다만 고려해볼 사항은 최대한 자연어에 맞춰 이름 틀을 사용하는 것이다. 예를 들어 '점수 최대'가 아닌 '최대 점수'가 자연스럽기 때문에 points_max 대신 max_points가 낫다.

페이텔슨의 단계는 반드시 순서대로 실행될 필요는 없다. 때로는 표현하고자 하는 개념을 고려하지 않아도 변수 이름에 사용할 단어가 생각날 수도 있다.


코드를 읽기 힘들게 하는 요소로 코드 스멜(code smell)이라는 개념이 있다. 코드 스멜은 작동은 하지만 개선할 부분이 있는 코드를 의미한다.(<Refactoring>, 마틴 파울러)

마틴 파울러는 다양한 코드 스멜과 함께 이를 해소하기 위한 전략을 함께 목록화해서 설명했고, 이를 리팩터링(refactoring)이라 한다. 리팩터링은 코드 스멜을 해소하는 것 말고도 일반적인 의미의 코드 개선을 뜻하는 용어로 쓰이게 되었다.

코드 스멜 설명
긴 메서드 메서드는 여러 가지 다른 일을 수행하느라 라인이 길어져서는 안 된다.
많은 인수 메서드는 인수감 낳으면 안 도니다.
스위치 문 스위치 문은 길면 안 된다. 다형성을 통해 해결할 수 있어야 한다.
다른 것처럼 보이나 같은 클래스 처음에는 다른 것처럼 보이지만 유사한 필드와 메서드를 갖는 클래스가 있으면 안 된다.
원시 타입 집착 클래스에서 원시 데이터 타입의 과도한 사용은 피해야 한다.
미완성 라이브러리 클래스 메서드를 라이브러리 클래스가 아닌 임의의 클래스에 추가해서는 안 된다.
너무 큰 클래스 너무 많은 메서드와 필드를 가지고 있어 클래스의 추상화를 불명확하게 해서는 안 된다.
게으른 클래스 클래스가 하는 일이 너무 적으면 존재할 이유가 없다.
데이터 클래스 클래스는 데이터만 가져서는 안 되고 메서드도 가져야 한다.
임시 필드 클래스는 불필요한 임시 필드를 가져서는 안 된다.
데이터 그룹 같이 사용하는 데이터는 같은 클래스나 구조체에 저장되어야 한다.
산재한 수정 일반적으로 코드 수정은 한 클래스의 한 부분에서만 이루어져야 한다.
클래스의 여러 부분을 수정해야 한다면 코드 구조가 잘못된 것이다.
기능 이전 클래스는 다른 클래스에 광범위하게 연관되지 않아야 한다.
부적절한 연관 클래스 A의 많은 메서드가 클래스 B에 의해 참조되면 그 메서드들은 B로 옮겨야 한다.
중복 코드 또는 코드 클론 같거나 비슷한 코드가 코드베이스 여러 군데에서 중복돼서는 안 된다.
주석문 주석문은 코드가 무엇을 하는지가 아니라 왜 거기 있는지를 설명해야 한다.
메시지 체인 메시지 호출이 연속해서 꼬리에 꼬리를 무는 방식으로 이루어져서는 안 된다.
미들맨 클래스가 자신이 하는 일은 없이 위임을 많이 하면 이 클래스가 굳이 존재할 이유가 있는가?
평행 상속 한 클래스의 서브 클래스를 만들 때마다 다른 클래스의 서브 클래스도 만들어야 한다면 두 클래스의 기능은 하나의 클래스로 합쳐야 한다.
상속 거절 클래스가 자신이 사용하지 않는 것을 상속받는다면 상속은 필요 없는 일일지도 모른다.
샷건 수술 코드 수정은 한 클래스에 대해서만 이루어져야 한다. 하나의 사항에 대해 여러 클래스를 수정해야 한다면 코드 구조에 문제가 있으므로 수정할 여러 부분을 하나의 클래스로 묶어야 한다.
추측에 근거한 일반성 만일의 경우를 대비한 코드를 추가하지 말고 필요한 기능만 추가하라.

 

코드 스멜은 이전의 작업 및 코드 작성에 대한 개인적인 경험을 바탕으로 한다. 파울러가 연관 짓지는 않았지만 많은 코드 스멜은 인지 기능과 관련이 있다. 작업 기억 공간과 LTM에 대한 것을 바탕으로 코드 스멜이 포함된 코드의 효과를 설명할 수 있다.

예를 들어 긴 매개변수 목록이나 복잡한 스위치 문은 작업 기억 공간의 인지 부하를 불러올 수 있다. 신의 클래스나 긴 메서드는 효율적인 청킹을 불가능하게 한다. 코드 중복은 청킹이 잘못된 형태다.

 

코드 스멜은 구조적 안티패턴(structural antipattern) 문제가 있는 코드다. 코드가 잘 작성되었으나 파악하기 힘든 구조로 만들어졌다는 것을 의미한다. 그러나 일부 코드에는 개념적 안티패턴도 있을 수 있다. 개념적 안티패턴은 짧은 메서드와 깔끔한 클래스지만 혼동되는 이름을 갖는 경우다. 이 경우에는 언어적 안티패턴(linguistic antipattern)으로 설명 가능하다.

언어적 안티패턴은 워싱턴 주립 대학교 교수 베네러 아나우도바(Venera Arnaoudova)에 의해 정의됐다. 언어적 안티패턴을 코드의 언어적 요소와 역할 사이의 불일치로 묘사했다. 언어적 요소란 메서드의 입출력 정의, 설명 문서, 속성 이름, 데이터 타입, 주석문 등을 포함하는 자연어 부분으로 정의한다. 즉 언어적 안티패턴의 예로는 initial_element라는 변수가 배열이나 리스트의 원소가 아닌 인덱스를 갖는다는 경우, isValid라는 변수가 불리언 값이 아닌 정수 값을 갖는 경우 등이 있을 수 있다.

아나우도바의 6가지 언어적 안티패턴은 다음과 같다.

  • 이름이 나타내는 것보다 더 많은 일을 하는 메서드
  • 이름이 나타내는 것보다 더 적은 일을 하는 메서드
  • 이름과 정반대의 일을 하는 메서드
  • 개체에 포함된 것보다 더 많은 것을 가지고 잇는 것처럼 보이는 식별자 이름
  • 개체에 포함된 것을 누락하는 식별자 이름
  • 개체에 포함된 것과 반대되는 식별자 이름

네덜란드 델프트 공과대학교 교수 리니 판솔링언(Rini van Solingen)은 90년대 중반부터 프로그래머의 업무 방해나 중단에 관해 연구했다. 연구한 두 조직에서 유사한 결과를 얻었다. 업무 중단이 두 조직 모두 흔하게 발생했고 각 중단 시간은 15~20분 정도였다. 개발자 업무의 약 20%가 업무 중단에 쓰였다. 오늘날에는 슬랙과 같은 메시지 앱 사용의 증가로 업무 중단이 더 흔해졌다.

 

프로그래밍 작업에서는 워밍업이 필요하다. 선단과학기술대학원의 나카가와 다카오는 2014년도 연구를 통해 프로그램의 이해 과제에서 워밍업과 냉각 단계가 있었다고 밝혀냈다. 워밍업과 냉각 단계 사이에 가장 힘든 작업이 수행된다는 것을 시사했다.

파닌은 연구를 통해 업무 중단 후 발생하는 상황을 살펴본 결과 생산성에 상당한 지장을 초래했다고 주장했다. 프로그래머가 업무 중단 이후 다시 코드 작성 작업을 시작하는 데 약 25분 소요됐다. 메서드를 작성하다가 중단된 경우 1분 이내에 작업을 재개할 수 있는 경우는 10%에 불과하다.

개발자들이 원래 하던 코딩 작업으로 돌아가려 할 때 작업 기억 공간이 원래 작업하던 코드에 대한 중요한 정보를 잃어버린 상태다. 프로그래머들은 원래 작업으로 돌아가기 위해 의도적으로 노력해야 했다. 이들은 작업을 재개하기 전 코드의 여러 위치를 찾아다닌 경우가 많았고 업무 중단해야 할 때 의미없는 무작위 문자를 코드에 추가해 컴파일 오류를 유발하는 것을 확인했다. 파닌은 이를 장애물 경고(roadblock reminder)라고 불렀다. 개발자들의 최후의 방법으로는 현재 버전과 마스터 브랜치 차이를 diff 하기도 했지만 실제 차이를 발견하기는 번거로운 일이다.

 

중단에 잘 대비하기 위해서는 워밍업 단계를 줄이면 된다. 나카가와의 연구 결과는 이해 활동에 준비하는 시간이 있음을 보여주는데 코드의 정신 모델을 구축하는 데 이해 활동에 가장 많은 시간을 사용할 가능성이 높다. 따라서 코드의 정신 모델이 남아있으면 워밍업 단계를 줄일 수 있다. 정신 모델에 대한 메모를 주석문으로 남기는 것도 도움이 된다.

일부 개발자들은 주석문을 광범위하게 사용하는 것을 바람직하지 않은 것으로 여기는데 코드만으로는 프로그래머의 사고 과정을 설명하지 못하며 정신 모델을 적절히 표현하지 못한다.

"주석문의 배후에 놓인 전반적인 아이디어는, 설계자의 마음속에는 있었지만 코드로 표현할 수 없었던 정보를 포착하는 것이다."
- <A Philosophy of Software Design>, 존오스터하우트(John Osterhout)

결정에 대한 과정을 문서화하면 다른 사람이 코드를 읽을 때 유용할 뿐만 아니라 자신의 정신 모델을 일시적으로 저장하는 데도 도움이 되며 작업 재개에도 도움이 된다. <맨먼스 미신>에서 주석문은 항상 존재하기 때문에 프로그램 이해 과정에서 가장 중요하다고 말한다.

업무 중단 상황에서 시간 여유가 있다면 코드에 대한 최신 정신 모델을 주석문의 태로 브레인 덤프하는 것이 아주 유용할 수 있다.

 

워밍업 단계를 줄이는 두 번째 방법으로는 미래 기억(prospective memory)이 있다. 미래 기억은 미래에 무언가를 할 것에 대한 기억이다. 계획 및 문제 해결과 관련이 있다. 간단한 예로는 집에 오는 길에 우유를 사는 것을 기억하는 등의 것이다.

개발자들은 자신의 미래를 잊지 않기 위해 TODO 주석문을 달곤 한다. 하지만 이러한 TODO 주석문은 오래 작업되지 않거나 해결되지 않은 채 남을 가능성이 많다. 깃허브에서 TODO 검색만으로 1억 3700만 개의 결과가 나오기도 했다.

TODO 주석문 외에는 주석문이나 의도적 컴파일 오류, 스티커 메모, 이메일 등의 방법이 있다.

 

중단으로부터 보호할 수 있는 세 번째 방법으로는 하위 목표(subgoal) 라벨 붙이기가 있다. 문제를 어떤 식으로 작게 나눌 수 있는지 명시적으로 기록하는 것이다. 하위 목표를 상상하거나 기억하기는 어렵지 않지만 업무 중단 이후 다시 코드로 돌아간 경우에는 기억하는 것이 번거로울 수 있다.


새로운 개발자 팀원이 왔을 때 보통 다음과 같은 과정으로 진행되면서 적응 지원(onboarding)이 잘 안 되는 경우가 있다.

  1. 선임 개발자가 새 팀원에게 많은 정보를 줌으로써 인지 부하를 유발한다. 예를 들어 팀원, 코드베이스 도메인, 워크플로, 코드베이스 모두 한 번에 소개한다.
  2. 소개가 끝난 후 선임 개발자는 새 팀원에게 질문을 하거나 과제를 준다. 선임 개발자 입장에서는 아주 간단한 일로 여긴다. 예를 들어 작은 버그를 고치거나 작은 기능을 추가한다.
  3. 도메인이나 프로그래밍 언어 혹은 두 가지 모두 청크의 부족과 자동화 기술 부족으로 인지 부하가 높아지고 새 팀원은 적응에 어려움을 겪는다.

새 팀원은 인지 부하로 인해 새로운 코드베이스에 효과적으로 프로그래밍할 수 없고 새로운 정보를 유지할 수도 없다. 이는 선임 개발자와 새 팀원 모두에게 절망을 준다.

728x90