[책요약] 읽기 좋은 코드가 좋은 코드다 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
- 이러한 이름들은 적어도 우리에게는 모두 뜻이 잘 통한다.
- 새로운 기능을 구성 파일에 더하는 사람이 바로 우리 자신이기 때문이다.
- 하지만 이 기능을 모르는 사람이 이 코드를 읽으면 이름을 어떻게 생각할지를 신중하게 고려할 필요가 있다.
- 코드를 읽은 사람이 이름이 뜻하는 바를 잘못 해석할 가능성을 염두에 두면서 각각의 이름을 분석해 보자.
- template을 사용할 때
experiment_id: 101 tempate: 100
- 템플릿이라는 이름에는 두 가지 문제가 있다
- 의미가 분명하지 않다.
- 내가 템플릿이야
- 나는 다른 템플릿을 사용하고 있어
- ‘template’라는 표현은 종종 구체적인 의미를 갖기 전에 반드시 ‘채워져야’ 하는 추상적인 무엇이라는 사실을 의미한다.
- 그렇기 때문에 어떤 사람은 템플릿을 사용하는 실험이 ‘진짜’ 실험이 아니라고 생각할 수 있다.
- 의미가 분명하지 않다.
- 결론적으로 template이라는 이름은 지나치게 모호하다.
- 템플릿이라는 이름에는 두 가지 문제가 있다
-
reuse는 어떨까?
experiment_id: 101 reuse: 100
- reuse 는 괜찮은 단어다.
- 하지만 어떤 사람은 이 단어를 보고 ‘이 실험은 최대한 100번까지 재사용 될 수 있구나’ 라고 생각할지도 모르낟.
- 그러면 이름을 reuse_id로 바꾸어보자.
- 하지만 이 경우에도
reuse_id:100
이라는 표현을 ‘재사용을 위한 나의 id 는 100이야’라는 의미로 받아들일 수 있다.
-
copy는 어떨까?
experiment_id: 101 copy: 100
- copy는 좋은 단어다.
- 하지만
copy:100
은 마치 ‘이 실험을 100번 복사하라’나 ‘어떤 무엇의 100번째 복사물이다’라는 말처럼 들린다. - 이 용어가 다른 실험을 가리킨다는 사실을 명확하게 하려면, 이름을 copy_experiment처럼 바꾸는 것이 좋다.
- 지금까지 살펴본 이름 중에서는 이 이름이 가장 좋다.
-
그렇지만 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