[책요약] 읽기 좋은 코드가 좋은 코드다 3장 오해할 수 없는 이름들

2020-04-30

3장 오해할 수 없는 이름들

핵심 아이디어: 본인이 지은 이름을 ‘다른 사람들이 다른 의미로 해석할 수 있을까?’라는 질문을 던져보며 철저하게 확인해야 한다.

3.1 예: Filter()

  • 데이터베이스가 반환한 결과를 수정하는 코드
results = Database.all_objects.filter('year <= 2011')
  • results는 어떤 데이터를 담고 있는가?
    • year <= 2011 인 객체들인가?
    • year <= 2011 이 아닌 객체들인가?
  • 여기서는 filter의 의미가 모호하여 문제가 생긴다. 대상을 ‘고르는’ 것인지 아니면 ‘제거하는’ 것인지 불분명하다.
  • 이런 이름은 의미를 오해할 가능성이 크므로 사용하지 않는게 최선이다.
  • 대상을 ‘고르는’ 기능을 원한다면 select()가
  • 대상을 ‘제거하는’ 기능을 원한다면 exclude()가 더 낫다

3.2 예: clip(text, length)

  • 어떤 문단의 내용을 오려내는 함수가 있다고 해보자
# 텍스트의 끝을 오려낸 다음 '...'을 붙인다.
def Clip(text, length):
    ...
  • 여기서 Clip()이 동작하는 방식을 다음 두 가지로 이해할 수 있다
    • 문단의 끝에서부터 거꾸로 length만큼 제거한다.
    • 문단을 처음부터 최대 length만큼 잘라낸다.
  • 문단을 처음부터 length만큼 잘라내는 두 번째 방법을 뜻하는 듯하지만 확실하지는 않다.
  • 함수명을 처음부터 Truncate(text, length)로 짓는 것이 좋을 것이다.
  • 파라미터 이름 length도 비난 받을 만 하다. max_length로 하면 의미를 더욱 뚜렷하게 만들어 줄 것이다.
  • max_length라는 이름도 여러 의미로 해석될 수 있다.
    • 바이트의 수
    • 문자의 수
    • 단어의 수
  • 앞 장에서 보았듯이, 이름에 단위가 덧붙여져야 하는 예다.
  • ‘문자의 수’를 나타내고자 하므로 변수명은 max_char가 되어야 한다.

3.3 경계를 포함하는 한계값을 다룰 때는 min과 max를 사용하라

  • 쇼핑카트 애플리케이션은 고객이 한번에 10개 이상이 품목을 구매하지 못하게 해야 한다.
CART_TOO_BIG_LIMIT = 10

if shopping_car.nun_items() >= CART_TOO_BIG_LIMIT:
    Error('Too many items in cart.')
  • 이 코드는 경계선의 값이 1만큼 벗어나게 하는 전형적인 버그를 포함한다. >=을 >으로 고치면 버그를 잡을 수 있다.
  • 혹은 CART_TOO_BIG_LIMIT 값을 11로 설정해도 된다.
  • 하지만 더 근본적인 문제는 CART_TOO_BIG_LIMIT이라는 이름이 모호하다는데 있다.
  • 그 이름이 ‘그 수까지’를 의미하는지 아니면 ‘해당 수를 포함하면서 그 수까지’를 의미하는지 분명하지 않다.

조언: 한계를 설정하는 이름을 가장 명확하게 만드는 방법은 제한받는 대상의 이름 앞에 max_나 min_을 붙이는 것이다.

  • 이 경우에는 이름을 MAX_ITEMS_IN_CART로 하자. 그러면 코드는 간단하고 명확해진다.
MAX_ITEMS_IN_CART = 10

if shopping_car.nun_items() > MAX_ITEMS_IN_CART:
    Error('Too many items in cart.')

3.4 경계를 포함하는 범위에는 first와 last를 사용하라

  • 다음은 ‘그 수까지’와 ‘해당 수를 포함하면서 그 수까지’의 경우가 불분명한 코드의 예이다.
print interger_range(start=2, stop=4)
# 이 코드는 [2,3]과 [2,3,4] 중에서 무엇을 출력하는가?
  • start는 그럴듯한 변수명인데, stop은 여러 가지 방식으로 해석 될 수 있다.
  • 이 예처럼, 경계의 양 끝 점이 포함된다는 의미에서 경계를 포함하는 범위에는 first/last가 좋은 선택이다.
set.PrintKeys(first='bart', last='maggie')
  • stop과 달리 last라는 이름은 그것이 포함된다는 의미가 뚜렷하다.
  • first/last뿐만 아니라 min/max도 해당 문맥에서 ‘의미있게 들린다면’ 경계를 포함하는 범위에서 사용될 수 있다.

3.5 경계를 포함하고/배제하는 범위에는 begin과 end를 사용하라

  • 실전에서 범위의 한쪽 끝이 포함되지만 다른 한쪽 끝은 포함되지 않는 범위가 종종 편리하게 사용된다.
  • ‘포함/배제가’ 동시에 일어나면 begin/end를 사용하는 전형적인 프로그래밍 관행이 있다.

3.6 불리언 변수에 이름 붙이기

  • 불리언 변수 혹은 불리언값을 반환하는 함수에 이름을 붙일 때는 true와 false가 각각 무엇을 의마하는지 명확해야 한다.
  • 다음은 위험한 예다.
bool read_password = true;
  • 위 코드는 두 가지 상반된 해석이 가능하다.
    • 우리는 패스워드를 읽을 필요가 있다.
    • 패스워드가 이미 읽혔다.
  • 이 경우에는 ‘read’라는 단어를 사용하지 않는 것이 최선이다. 대신 need_password 혹은 user_is_authenticated 를 사용한다.
  • 일반적으로 is, has, can, should와 같은 단어를 더하면 불리언값의 의미가 더 명확해진다.
  • 예를 들어 SpaceLeft()같은 함수는 숫자값을 반환할 것 처럼 보인다. 만약 불리언값을 반환한다면 HasSpaceLeft()가 더 적합할 것이다.
  • 이름에서는 의미를 부정하는 용어를 피하는 것이 좋다.
  • 아래와 같은 이름 대신
bool disable_ssl = flase
  • 다음과 같은 이름을 사용하면 더 간결하고 읽기 좋다
bool use_ssl = true;

3.7 사용자의 기대에 부응하기

  • 사용자가 어떤 이름의 의미를 이미 특정한 방식으로 이해해서 실제로 다른 의미가 있음에도 오해를 초래할 때가 있다.
  • 이런 경우에는 ‘굴복하고’ 그것이 일반적인 의미를 갖도록 하는 게 좋다.

예: get*()

  • 프로그래머들은 대개 get으로 시작되는 이름의 메소드는 ‘가벼운 접근자’로서 단순히 내부 멤버를 반환한다고 관행적으로 생각한다.
  • 때문에 이러한 관행에 어긋나는 방식으로 이름을 지으면 혼란을 초래할 것이다.
  • 다음은 따라 하면 안되는 자바로 작성된 사례다.
public class StatisticsCollector {
    public void addSample(double x) { ... }
    
    public double getMean() {
        // 모든 샘플을 반복한 다음 total / num_samples를 반환한다.
    }
    
    ...
}
  • 이 경우 getMean()은 과거 데이터를 순차적으로 짚어가면서 동적으로 중앙값을 계산한다.
  • 만약 데이터의 분량이 많으면 이러한 계산은 상당히 오래 걸릴 것이다.
  • 하지만 이러한 사실을 모르는 프로그래머는 이 메소드의 계산을 간단한 것으로 간주하고 부주의하게 getMean()을 호출할지도 모른다.
  • 따라서 이러한 메소드명을 computeMean()처럼 고쳐서 시간이 제법 걸리는 연산이라는 사실을 명확하게 드러내는 게 좋다.

3.8 예: 이름을 짓기 위해서 복수의 후보를 평가하기

  • 좋은 이름을 정할 때 마음속으로 떠올리는 후보가 여럿이게 마련이다.
  • 어느 하나를 최종적으로 선택하기 전에 각 이름의 장점을 따져보게 마련이다.
  • 다음의 예는 이러한 과정을 설명하고 있다.
  • 방문자가 많은 웹사이트는 종종 어떤 변화가 비즈니스에 도움이 되는지 ‘실험’한다.
  • 다음은 이와 같은 실험을 구성하는 데 사용되는 구성 파일의 예이다.
experiment_id: 100
description: 'increase font size to 40pt'
traffic_fraction: 5%
  • 각 실험은 15개 정도의 속성/값 짝으로 되어있다.
  • 불행히도 비슷한 종류의 다른 실험을 정의하려면 여기 적힌 줄 대부분을 그대로 복사해서 붙여넣어야 한다.
experiment_id: 101
description: 'increase font size to 13pt'
[다른 줄은 experiment_id 100 동일하다]
  • 이러한 상황을 개선하려고 하나의 실험이 다른 실험의 속성을 읽을 수 있게 만들었다고 해보자(상속)
experiment_id: 101
the_other_experiment_id_I_want_to_reuse: 100
[필요에 따라서 다른 속성을 변경하라]
  • 여기에서 질문은 다음과 같다.
  • the_other_experiment_id_I_want_to_reuse는 어떤 이름을 가져야 하는가?
  • 고려할 만한 이름
    • template
    • reuse
    • copy
    • inherit
  • 이러한 이름들은 적어도 우리에게는 모두 뜻이 잘 통한다.
    • 새로운 기능을 구성 파일에 더하는 사람이 바로 우리 자신이기 때문이다.
  • 하지만 이 기능을 모르는 사람이 이 코드를 읽으면 이름을 어떻게 생각할지를 신중하게 고려할 필요가 있다.
  • 코드를 읽은 사람이 이름이 뜻하는 바를 잘못 해석할 가능성을 염두에 두면서 각각의 이름을 분석해 보자.

  1. template을 사용할 때
     experiment_id: 101
     tempate: 100
    
    • 템플릿이라는 이름에는 두 가지 문제가 있다
      1. 의미가 분명하지 않다.
        • 내가 템플릿이야
        • 나는 다른 템플릿을 사용하고 있어
      2. ‘template’라는 표현은 종종 구체적인 의미를 갖기 전에 반드시 ‘채워져야’ 하는 추상적인 무엇이라는 사실을 의미한다.
        • 그렇기 때문에 어떤 사람은 템플릿을 사용하는 실험이 ‘진짜’ 실험이 아니라고 생각할 수 있다.
    • 결론적으로 template이라는 이름은 지나치게 모호하다.
  2. reuse는 어떨까?

     experiment_id: 101
     reuse: 100
    
    • reuse 는 괜찮은 단어다.
    • 하지만 어떤 사람은 이 단어를 보고 ‘이 실험은 최대한 100번까지 재사용 될 수 있구나’ 라고 생각할지도 모르낟.
    • 그러면 이름을 reuse_id로 바꾸어보자.
    • 하지만 이 경우에도 reuse_id:100이라는 표현을 ‘재사용을 위한 나의 id 는 100이야’라는 의미로 받아들일 수 있다.
  3. copy는 어떨까?

     experiment_id: 101
     copy: 100
    
    • copy는 좋은 단어다.
    • 하지만 copy:100은 마치 ‘이 실험을 100번 복사하라’나 ‘어떤 무엇의 100번째 복사물이다’라는 말처럼 들린다.
    • 이 용어가 다른 실험을 가리킨다는 사실을 명확하게 하려면, 이름을 copy_experiment처럼 바꾸는 것이 좋다.
    • 지금까지 살펴본 이름 중에서는 이 이름이 가장 좋다.
  4. 그렇지만 inherit를 생각해보자.

     experiment_id: 101
     inherit: 100
    
    • ‘상속’이라는 단어는 대부분의 프로그래머에게 친숙하며, 프로그래머는 상속이 이루어진 다음에 추가적인 수정이 일어난다는 사실도 잘 알고 있다.

  • 여기에서 우리는 다른 실험으로부터 상속하고 있음을 상기하자.
  • 따라서 이름을 inherit_from 혹은 inherit_from_experiment_id로 향상시킬 수 있다.
  • 일반적으로 copy_experiment나 inherit_from_experiment_id가 실제로 일어나는 일을 잘 설명해주고 오해를 불러일으킬 소지가 적으므로 가장 좋은 이름이다.

요약

  • 언제나 의미가 오해되지 않는 이름이 최선의 이름이다.
  • 여러분이 작성한 코드를 읽는 사람은 그 이름을 다른 뜻이 아닌 여러분이 원래 의도했던 대로 이해해야 한다.
  • 하지만 불행히도 영어에는 filter, length, limit처럼 프로그래밍에서 사용하기에는 의미가 모호한 단어가 많다.
  • 어떤 이름을 정하기 전에 항상 최악의 경우를 가정하고 이름의 의미가 잘못 이해되는 가능성을 고려해봐야 한다.
  • 최선의 이름은 이러한 오해가 쉽게 일어나지 않는다.
  • 어떤 값의 상한과 하한을 정할 때 max_와 min_을 이름 앞에 붙이면 좋은 접두사 역할을 한다.
  • 경계를 포함한다면 first와 last가 좋은 이름이다.
  • 경계의 시작만 포함하고 마지막은 배제하는 범위라면, begin과 end가 가장 널리 사용되는 이름이므로 최선이다.
  • 불리언값 이름을 정할 때는 불리언이라는 사실을 명확히 드러내기 위해서 is나 has와 같은 단어를 사용한다.
  • disable_ssl 처럼 의미를 부정하는 단어는 피하는게 좋다.
  • 사람들이 특정한 단어를 일반적으로 생각하는 사실에 유의할 필요도 있다.
  • 예를 들어 사람들은 get() 혹은 size()와 같은 함수가 가벼운 메소드라고 기대할지도 모른다.

Comments