Code Readability

15 March 2016

더 나은 개발자로서 더 적은 버그를 양산하고, 자신의 코드를 더 자랑스러워하며, 주변 사람들이 사용하기를 원하는 코드를 만드는 방법.

핵심 아이디어

  • 코드는 이해하기 쉬워야 한다.
  • 코드는 다른 사람이 그것을 이해하는 데 들이는 시간을 최소화하는 방식으로 작성되어야 한다.

이름에 정보 담기

구체적인 단어 선택

매우 구체적인 단어를 선택하여, 이름에 정보를 담아낸다. 지나치게 보편적이여서 별다른 의미를 담지 못하는 단어는 피한다.

  • getPage() -> fetchPage(), downloadPage()
  • size -> height, numNodes, memoryBytes
  • stop() -> kill(), pause()
  • send() -> deliver(), dispatch(), announce(), distribute(), route()
  • find() -> search(), extract(), locate(), recover()
  • start() -> launch(), create(), begin(), open()
  • make() -> create(), setup(), build(), generate(), compose(), add(), new()

보편적인 이름 피하기

temp, returnValue, foo 같은 이름은 “내 머리로는 이름을 생각해낼 수 없어요”라고 고백하며 책임을 회피하는 증거에 불과하다. 이렇게 무의미한 이름 말고, 값이나 목적을 정확하게 설명하는 이름을 고른다.

  • 물론 의도적으로 의미를 전달하기 위하여 보편적인 이름을 쓰는 경우도 있다. 두 변수를 swap 하는 코드에서, 임시 저장소 용도로 사용되는 변수의 이름으로는 temp가 적합하다. 변수의 목적 자체가, 임시저장소 이외는 다른 용도가 없다는 사실이 잘 전달되기 때문이다. temp라는 이름은 대상이 임시적으로 짧은 시간동안에만 존재하고, 임시적 존재 자체가 변수의 가장 중요한 용도일때에 한해서 사용한다.
  • 정말 좋은 이름이 떠오르지 않을 때, foo 같은 무의미한 이름을 사용하며 앞으로 전진할 수도 있다. 하지만 다만 몇 초라도 좋은 이름을 생각하려고 고민하는 습관을 들이자.

추상적이거나 모호한 이름 대신 구체적인 이름 사용

  • serverCanStart() -> canListenOnPort()
  • disallow_evil_constructors() -> disallow_copy_and_assgin()

변수에 중요한 정보 담기

반드시 알아야 하는 변수와 관련된 중요한 정보를 이름에 추가하는 것이 좋다.

  • id -> hexId
  • start -> start_ms
  • limit -> max_kbps
  • html -> html_utf8
  • text -> raw_text

적당한 길이

  1. 좁은 범위에서만 사용되는 변수의 이름에 많은 정보를 담을 필요는 없다. 모든 정보가 쉽게 한눈에 보이므로 짧은 이름을 사용해도 상관 없다. 단 클래스의 멤버이거나 전역 변수인 경우 타입이나 목적이 드러나지 않는 변수 이름을 쓴다면 코드 가독성이 떨어질 것이다.
  2. ConvertToString() -> Convert를 뺴고 ToString() 으로 써도 의미는 충분히 전달된다.

오해가 생길만한 이름 피하기

의미를 명확하게

  • filter -> select / exclude
  • length -> max_length -> max_chars / max_words / max_bytes

경계를 포함하는 한계 값에는 max_ , min_ 사용

limit은 경계를 포함하는지 여부가 애매하다. max, min은 경계를 포함하는 것이 명확하다.

경계를 포함하는 범위에는 first, last 사용

boolean 변수에는 is, has, can, should와 같은 단어를 사용

기대에 부응하기

  • get~는 getter와 같은 단순 접근자로 보인다. 복잡하고 오래 걸리는 계산을 한다면 compute 등을 사용하여 시간이 제법 걸리는 연산이라는 사실을 명확하게 드러낸다.
  • 함수의 이름이 size() 라면, 사용자는 O(1)라고 생각할 것이다.

테스트와 Readability

좋지 않은 테스트 코드

  1. 테스트코드가 너무 길고, 중요하지 않은 자세한 내용이 많다. 테스트가 하는 일을 한 문장으로 표현해보고, 이 문장의 흐름대로 코드를 작성해보자.
  2. 새로운 테스트를 추가하기 쉽지 않다. 추가하려면 많은 부분을 copy & paste 해야 한다. 이는 코드를 더 길고 중복되게 만든다.
  3. 테스트 실패시 출력되는 에러 메시지가 별로 도움이 되지 않는다.
  4. 한 번에 여러가지를 테스트 하려고 한다.
  5. 테스트의 입력 값들이 심플하지 않다. 예를 들어 -99998.7 같은 입력은 그 값이 특별히 중요한 의미를 갖지 않음에도 불필요하게 시선을 끈다. 더 간단한 음수값을 사용해도 충분하다.
  6. 테스트의 입력 값들이 코드를 꼼꼼하게 실행시키지 않는다. 예를 들어, 입력 값이 0인 경우를 다루지 않는다.
  7. 입력값이 null 이거나, 매우 큰 수이거나 하는 등의 비정상적인 값을 가지는 입력에 대해 테스트하지 않는다.
  8. 테스트 메소드의 이름이 아무 의미가 없다.

좋은 테스트 코드

  1. 테스트 코드는 읽기 쉬워야 한다. 이는 다른 프로그래머들이 테스트 코드를 수정하거나 추가하는 것을 쉽게 만든다.
  2. 각 테스트의 최상위 수준은 최대한 간결해야 한다. 덜 중요한 세세한 사항은 숨기고, 더 중요한 내용 즉 테스트가 수행하는 핵심 내용이 눈에 띄게 해야 한다. 이상적으로는 각 테스트의 입출력이 한 줄의 코드로 설명될 수 있어야 한다.
  3. 한 번에 한 가지만 테스트한다.
  4. 테스트에 실패하면 디버깅에 도움이 될 만한 에러메시지를 출력한다.
  5. 코드의 구석구석을 철저하게 검사하는 가장 명확하고 간단한 입력을 사용한다. (예: -1, 0, 1, “”, 1e100, null)
  6. 무엇이 테스트되는지 분명하게 드러나는 테스트 메소드 이름을 사용한다. Test1() 대신 Test_메소드이름_상황()과 같은 형태의 이름을 사용하라.

테스트하기 어려운 코드의 특징

  1. 전역변수 사용
    • 테스트 관점: 테스트할 때마다 모든 전역변수를 초기화해야 한다.
    • 설계 관점: 각각의 함수를 별도로 고려할 수 없다. 모든게 제대로 작동하는지 알려면 프로그램 전체를 생각해야 한다.
  2. 많은 외부 컴포넌트 사용
    • 테스트 관점: 처음에 설정할 것이 너무 많아서 테스트를 작성하기 어렵고, 귀찮다.
    • 설계 관점: 외부 시스템과의 디펜던시가 있다.
  3. 코드가 비결정적(non-deterministic)인 행동을 가짐
    • 테스트 관점: 테스트가 변덕스럽고 안정적이지 못하다. 대부분의 경우 성공하는 테스트라면, 어쩌다 한 번 실패한 경우 무시하게 된다.
    • 설계 관점: 프로그램이 race condition이나 재현하기 어려운 버그를 가지고 있을 확률이 높다. 프로그램을 읽을 때 논리를 따라가기 어렵다. 발생한 버그를 추적하여 수정하기가 매우 어렵다.

테스트하기 적합한 코드의 특징

  1. 클래스들이 내부 상태를 거의 가지고 있지 않다.
    • 테스트 관점: 메소드를 테스트하기 전에 설정할 일이 거의 없다. 감추어져 있는 상태가 별로 없기 때문에 테스트 작성이 수월하다.
    • 설계 관점: 소수의 내부 상태를 가지는 클래스는 이해하기 더 쉽다.
  2. 클래스/메소드가 한 번에 하나의 일만 수행한다.
    • 테스트 관점: 테스트 코드 더 적고 간단해진다.
    • 설계 관점: 더 작고 간단한 컴포넌트는 더 잘 모듈화되어 있고, 시스템간 느슨하게 연결되어 있다.
  3. 클래스가 다른 클래스에 의존하지 않고 서로 상당히 떨어져 있다.
    • 테스트 관점: 각 클래스가 독립적으로 테스트된다.
    • 설계 관점: 시스템이 병렬적으로 개발될 수 있다. loose coupling으로 변경에 용이하다.
  4. 함수들이 간단하고 잘 정의된 인터페이스를 가지고 있다.
    • 테스트 관점: 테스트 대상이 잘 정의되어 있다. 간단한 인터페이스는 테스트를 위해서 더 적은 일을 요구한다.
    • 설계 관점: 프로그래머가 인터페이스를 쉽게 배울 수 있어 해당 인터페이스는 재사용될 가능성이 높아진다.

지나친 테스트

  • 테스트를 가능하게 하려고 실제 코드의 가독성을 희생시키는 경우, 뭔가 잘못된 것이다.
  • test coverage를 100%으로 만들기 위해 집착하지 말자. 코드의 90%를 테스트하는 노력이 나머지 10%를 테스트하는 노력보다 더 적을 수 있다. 그 10%는 어쩌면 버그로 인한 비용이 별로 높지 않기 때문에 굳이 테스트할 필요가 없는 경우일 수도 있다.

Reference: 책 - 읽기 좋은 코드가 좋은 코드다

Bash Shell - 변수 변경자 (variable modifier) 블로그 단장하기