[책요약] 읽기 좋은 코드가 좋은 코드다 2장 이름에 정보 담기

2020-04-23

2. 이름에 정보 담기

  • 변수, 함수, 클래스 등의 이름을 결정할 때는 항상 같은 원리가 적용된다.
  • 이름을 일종의 설명문으로 간주해야 한다.
  • 충분한 공간은 아니지만, 좋은 이름을 선택하면 생각보다 많은 정보를 전달할 수 있다.

이름에 정보를 담아내라

  • 프로그램에서 사용하는 대다수 이름이 tmp처럼 모호하다.
  • size 혹은 get처럼 그럴듯하게 보이는 이름조차 많은 정보를 담아내지 못한다.
  • 정보를 잘 표현하는 좋은 이름을 선택하는 방법
    • 특정한 단어 고르기
    • 보편적인 이름 피하기(혹은 언제 그런 이름을 사용해야 하는지 깨닫기)
    • 추상적인 이름 대신 구체적인 이름 사용하기
    • 접두사 혹은 접미사로 이름에 추가적인 정보 덧붙이기
    • 이름이 얼마나 길어져도 좋은지 결정하기
    • 추가적인 정보를 담을 수 있게 이름 구성하기

2.1 특정한 단어 고르기

  • 매우 구체적인 단어를 선택하여 ‘무의미한’ 단어를 피하는 것이다.
  • 예를 들어 ‘get’은 지나치게 보편적이다.
def GetPage(url):
    ...
  • get은 별다른 의미를 전달하지 않는다.
  • 로컬캐시, db, 인터넷 중 어디에서 페이지를 가져오는 것인가?
  • 만약에 인터넷에서 가져오는 것이라면 FetchPage() 혹은 DownloadPage()가 더 의미 있는 이름이 될 것이다.

  • 다음은 BinaryTree 클래스의 예이다.
class BinaryTree {
    int Size();
    ...
};
  • Size() 메소드는 무엇을 반환할까? 트리의 높이? 노드의 개수? 트리의 메모리 사용량?
  • 우리가 의도한 정보를 전달하지 못하는 문제가 있다.
  • Height(), NumNodes(), MemoryBytes() 등이 더 의미 있는 이름일 것이다.

  • 다음과 같은 Thread 클래스가 있다고 해보자
class Thread {
    void Stop();
    ...
};
  • Stop() 이라는 메소드명은 그런대로 괜찮지만, 정확히 무엇을 수행하는지에 따라서 더 의미있는 이름을 사용할 수 있다.
  • 다시는 되돌릴 수 없는 최종 동작을 수행한다면 Kill()이 더 확실한 의미를 전달할 것이다.
  • 만약 Resume()을 호출해서 다시 돌이킬 수 있는 동작이라면 Pause()가 더 좋을 것이다.

더 ‘화려한’ 단어 고르기

  • 유의어 색인집을 찾아보거나 동료에게 더 나은 이름을 묻는 일을 주저하면 안된다.
  • 영어는 매우 풍부한 언어이며, 선택할 수 있는 단어는 무궁무진한다.
  • 다음 표는 어떤 단어와 그 단어보다 상황에 더 적합할 수 있는 ‘화려한’ 단어를 예로 나열한 것이다.
단어 대안
send deliver, dispatch, announce distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new
  • 하지만 화려한 단어가 꼭 좋은 것은 아니다.
  • PHP는 문자열은 explode()하는 함수가 있다.
  • 이는 상당히 화려한 이름으로, 무언가를 조각으로 잘게 부서뜨리는 느낌을 잘 전달하지만, split()과 무엇이 다른가?
    • 두 함수는 서로 다르지만, 이름만으로는 무엇이 다른지 구별할 수 없다.

재치있는 이름보다 명확하고 간결한 이름이 더 좋다.

2.2 tmp나 retval 같은 보편적인 이름 피하기

  • tmp, retval, foo 같은 이름
    • ‘내 머리로는 이름을 생각해낼 수 없어요’라고 고백하면서 책임을 회피하는 증거에 불과하다
  • 이렇게 무의미한 이름이 아니라, 개체의 값이나 목적을 명확하게 설명하는 이름을 골라야 한다.
  • 다음은 retval을 이용하는 자바스크립트 코드이다.
var euchildean_norm = function(v) {
    var retval = 0.0;
    for (var i = 0; i < v.length; i += 1)
        retval += v[i] * v[i];
    return Math.sqrt(retval);
};
  • 반환되는 값의 이름을 생각하기 어려울 때면 그냥 retval라는 이름을 이용하고 싶은 유혹이 만만치 않다.
  • 하지만 retval 이라는 이름은 ‘저는 사실 반환되는 값이랍니다.’ 라는 정보 이외에는 아무것도 담지 않는다.
  • 더 좋은 이름은 변수의 목적이나 담고 있는 값을 설명해주어야 한다.
  • 위의 예제에서는 변수는 v를 제곱한 값을 모두 더한 값을 담고 있다.
  • 이 상황에서 더 좋은 이름은 sum_squares다
  • 이런 이름은 변수의 목적을 직접적으로 나타내므로 나중에 버그를 잡는 데 도움이 될 수도 있다.
  • 예를 들어 실수로 루프의 내부가 다음과 같이 작성되었다고 하자
retval += v[i];
  • 만약 변수명이 sum_squares였다면 다음 처럼 버그는 더 명백하게 드러날 것이다.
sum_squares += v[i];  // 더해야 하는 '제곱'은 어디에 있다는 말인가? 버그다!

retval이라는 이름은 정보를 제대로 담고 있지 않다. 대신 변수값을 설명하는 이름을 사용하라.

  • 하지만 오히려 보편적인 이름이 필요한 의미를 전달해주는 경우도 있다. 그러한 예를 살펴보자.

tmp

  • 두 변수를 서로 교환하는 전형적인 알고리즘을 생각해보자.
if (right < left) {
    tmp = right;
    right = left;
    left = tml;
}
  • 이 경우는 tmp라는 이름이 완벽하다
  • 변수의 목적 자체가 코드 몇줄에서만 사용하는 임시저장소 역할로 제한되어 있다.
  • 이때 tmp라는 이름은 코드를 읽는 사람에게 변수가 임시저장소 이외에 다른 용도가 없다는 사실을 잘 전달한다.
  • 하지만 다음 코드에서 사용된 tmp는 게으름의 산물에 불과하다
String tmp = user.name();
tmp += ' ' + user.phone_number();
tmp += ' ' + user.email();

...
    
template.set('user_info', tmp);
  • 이 변수도 삶의 주기가 짧긴 하지만, 변수의 주된 목적은 임시저장소의 역할로 국한되지 않는다.
  • 이럴 땐 user_info 같은 이름이 더 적절하다.

  • 다음은 이름에 tmp라는 단어가 필요하긴 하지만, 이름 전체가 아니라 일부분이 되어야 하는 경우이다
tmp_file = tempfile.NamedTeporaryFile();
...
SaveData(tmp_file, ...);
  • 대상이 파일 객체이므로 이름을 tmp가 아니라 tmp_file로 하였다.
  • 만약 단순히 tmp로 했으면, 파일인지, 파일 이름인지, 파일에 기록되는 데이터 자체를 의미하는 지 알 수 없다.

tmp라는 이름은 대상이 짧게 임시적으로만 존재하고, 임시적 존재 자체가 변수의 가장 중요한 용도일 때에 한해서 사용해야 한다.

루프반복자

  • i, j, iter, it 같은 이름은 흔히 인덱스나 루프반복자로 사용된다.
  • 이런 이름은 보편적이지만 ‘나는 반복자랍니다’ 라는 의미를 충분히 전달한다.
  • 하지만 이러한 이름을 다른 목적으로 사용하면 혼동을 초래 할 것이다. 그렇게 하지 말아야 한다.

  • i, j, k보다 루프 반복자에 더 좋은 이름이 있다.
for (int i = 0; i < clubs.size(); i++)
    for (int j = 0; j < clubs[i].members.size(); j++)
        for (int k = 0; k < users.size(); k++)
            if (clubs[i].members[k] == users[j])
                ....
        
  • if 구문에서 잘못된 인덱스를 사용하고 있다.
  • 이런 종류의 버그는 따로 떼어놓고 보면 잘못된게 없어 보여서 좀처럼 찾기 힘들다.
  • 이럴 때는 더 명확한 의미를 드러내는 이름을 사용하면 도움이 된다.
    • club_i, members_i, users_i

2.3 추상적인 이름보다 구체적인 이름을 선호하라

  • 변수나 함수 혹은 다른 요소에 이름을 붙일 때, 추상적인 방식이 아니라 구체적인 방식으로 묘사하라
  • 예를 들어 서버가 어느 tcp/ip 포트를 사용할 수 있는지 검사하는 ServerCanStart()라는 내부 메소드가 있다고 하자.
  • 이때 ServerCanStart()라는 이름은 다소 추상적이다.
  • 이보다 더 구체적인 이름은 CanListenOnPort()이다.
  • 이 이름은 해당 메소드가 수행하는 일을 직접적으로 설명한다.

예: –run_locally

  • 우리가 작성한 프로그램에서 –run_locally라는 명령행 플래그 옵셥을 사용한 적이 있다
  • 이 플래그가 선택되면 프로그램은 디버깅 정보를 출력한다. 대신 동작 속도는 다소 느려진다.
  • –run_locally라는 이름이 어떻게 탄생되었는지 어느 정도 이해는 되지만 이 이름에는 몇가지 문제점이 있다.
    • 팀에 새로 합류한 사람은 무엇을 위한 플래그인지 알 수 없다. 프로그램을 로컬 컴퓨터에서 실행할 때는 (대충 짐작으로) 플래그를 사용하겠지만, 왜 필요한지는 알지 못한다.
    • 때로는 프로그램을 원격컴퓨터에서 실행할 때도 디버깅 정보를 출력할 수 있다. 원격으로 실행되는 프로그램에 –run_locally 라는 플래그를 전달하는 우스운 광경으로 인하여 혼란이 초래된다.
    • 로컬 컴퓨터에서 성능검사를 수행할 때 로깅 기능으로 인한 성능저하를 원하지 않는 경우도 있다. 이때는 –run_locally라는 플래그를 사용하지 않을 것이다.
  • 문제는 –run_locally라는 이름이 실제 내용보다 주로 사용된느 환경을 나타내는 방식으로 지어졌다는 점이다.
  • 이보다는 –extra_logging이라는 이름이 더 직접적이고 명확하다.
  • 하지만 –run_locally 플래그가 추가적인 로깅 이외에 다른 일도 해야 한다면 어떻게 할까?
  • 예를 들어 로컬 데이터베이스를 설정하고 사용하는 일을 수행한다고 하자.
  • 이 경우 –run_locally라는 이름이 추가적인 로깅과 로컬 데이터베이스 설정이라는 두 가지 동작을 모두 포괄할 수 있으므로 매력적으로 보인다.
  • 하지만 이름을 선택하는 이유가 모호하고 간접적이므로 좋은 이름이라 볼 수 없다.
  • –use_local_database 라는 두 번째 플래그를 만드는 것이 더 좋은 방법이다.

2.4 추가적인 정보를 이름에 추가하기

  • 앞에서 언급한 바와 같이 변수의 이름은 작은 설명문이다.
  • 충분한 공간은 아니지만 이름안에 끼워 넣은 추가 정보는 변수가 눈에 보일 때마다 전달된다.
  • 따라서 사용자가 반드시 알아야 하는 변수와 관련한 중요한 정보를 추가적인 ‘단어’로 만들어서 이름에 붙이는 것이 좋다.
  • 예를 들어 16진수 문자열을 담고 있는 변수가 있다고 해보자
string id; // example: 'af84ef845cd8'
  • 사용자가 id의 내용을 기억해야 된다면 변수명을 hex_id로 하는 편이 더 나을 것이다.

단위를 포함하는 값들

  • 변수가 시간의 양이나 바이트의 수와 같은 측정치를 담고 있다면, 변수명에 단위를 포함시키는 게 도움이 된다.
  • 다음은 웹페이지를 로딩하는 시간을 측정하는 자바스크립트 코드이다.
var start = (new Date()).getTime(); // 페이지의 맨 위
...
var elapsed = (new Date()).getTime() - start; // 페이지의 맨 아래
console.log('load time was: ' + elapsed + ' seconds');
  • 이 코드는 특별한 오류를 발생시키지는 않지만 getTime()이 초가 아니라 밀리초를 반환하기 때문에 잘못된 결과를 출력한다.
  • 변수에 _ms 를 추가하면 모든 문제가 명확해진다.
함수 인수 단위를 포함하게 재작성
Start(int delay) delay -> delay_secs
CreateCache(int size) size -> size_mb
ThrottleDownload(float limit) limit -> max_kbps
Rotate(float angle) angle -> degrees_cw

다른 중요한 속성 포함하기

  • 이름에 추가적인 정보를 붙이는 기술은 단위를 포함하는 값에 국한되지만은 않는다.
  • 어떤 변수에 위험한 요소 혹은 나중에 놀랄만한 내용이 있다면 언제든지 이 방법을 사용할 필요가 있다.
  • 예를 들어 대부분의 보안 취약점은 프로그램이 전달받는 일부 데이터가 아직 불안전하다는 사실을 제대로 인식하지 못할 때 발생한다.
  • 이러한 부분은 untrustedUrl이나 unsafeMessageBody와 같은 변수명을 사용하여 보완하는 게 좋다.
  • 안전하지 않은 입력을 안전하게 만드는 함수를 호출한 다음에, 동일한 데이터의 내용을 trustedUrl이나 safeMessageBody와 같은 변수명에 담는다.
상황 변수명 더 나은 이름
패스워드가 ‘plaintext’에 담겨있고, 추가적인 처리를 하기 전에 반드시 암호화 되어야 한다. password plaintext_password
사용자에게 보여지는 설명문이 화면에 나타나기 전에 이스케이프 처리가 되어야 한다. comment unexcaped_comment
html의 바이트가 UTF-8로 변환되었다. html html_utf8
입력데이터가 ‘url encoded’되었다. data data_urlenc
  • 그렇다고 모든 변수에 unescaped_ 혹은 _utf8 같은 추가 적인 정보를 담을 필요는 없다.
  • 누군가 변수를 잘못 이해했을 때, 심각한 결과를 낳을 가능성이 있을 때만 중요한 의미가 있다.

2.4 이름은 얼마나 길어야 하는가?

  • 좋은 이름을 선택할 때, 이름이 지나치게 길면 안된다는 제한이 암묵적으로 존재한다.
  • 다음과 같은 이름을 사용하려는 사람은 아무도 없을 것이다.
    • newNavigationControllerWrappingViewControllerForDataSourceOfClass
  • 이름이 길면 길수록 기억하기도 어렵고, 화면도 많이 차지한다.
  • 반면 오직 단어 하나 문자 하나로 된 이름을 사용할지도 모른다.
  • 변수명을 d, days, day_since_last_update 중에 어떤 걸로 할지 어떻게 결정하는가?
  • 정확한 용도에 따라 그때그때 판단해야 한다.

좁은 범위에서는 짧은 이름이 괜찮다.

  • 좁은 범위에서는 변수의 타입이 무엇인지, 초기값이 무엇인지 등의 정보가 쉽게 한눈에 보이므로 짧은 이름을 사용해도 상관없다.
if (debug) {
    map<string, int> m;
    LookUpNamesNumbers(&m);
    Print(m);
}
  • 여기서 m은 아무런 정보를 담고 있지 않지만, 필요한 모든 정보를 코드에서 쉽게 확인 할 수 있으므로 문제 될 게 없다.
  • 하지만 큰 범위를 갖는다면, 의미를 분명하게 만들기 위한 정보를 충분히 포함해야 한다.

긴 이름 입력하기 - 더 이상 문제가 되지 않는다.

  • 긴 이름을 사용하면 안 되는 이유는 많다.
  • 하지만 ‘긴 이름은 입력하기 어렵다’는 변명은 더 이상 성립하지 않는다.
  • 프로그램이 문서 편집기에는 ‘단어 완성 기능’이 있다.

약어와 축약형

  • 때때로 짧은 이름을 위해서 약어나 축약형을 사용한다.
  • 예를 들어 BackEndManager 대신 BEManager라는 이름을 사용한다.
  • 경험에 비추어보면 특정 프로젝트에 국한된 의미를 가진 약어 사용은 좋은 생각이 아니다.
  • 이런 이름은 프로젝트에 새로 합류한 사람에게 비밀스럽고 위협적인 모습으로 다가온다.
  • 우리의 규칙은 이렇다.
  • 팀에 새로 합류한 사람이 이름이 의미하는 바를 이해할 수 있을까? 그렇다면 괜찮은 이름이다.
  • 예를 들어 evaluation 대신 eval을 사용하고, document대신 doc를 사용하고, string 대신 str을 사용하는 경우는 꽤 흔하다.
  • 그렇기 때문에 새로온 프로그래머도 FormatStr()이라는 함수를 보면 의미하는 바를 이해할 수 있을 것이다.
  • 하지만 BEManager의 의미는 이해할 수 없다.

불필요한 단어 제거하기

  • 경우에 따라서 아무런 정보를 손실하지 않으면서 이름에 포함된 단어를 제거할 수도 있다.
  • 예를 들어 ConverToString()이라는 이름 대신 ToString()이라고 짧게 써도 실질적인 정보는 사라지지 않는다.
  • 마찬가지로 DoServerLoop() 대신 ServerLoop()라고 해도 의미는 충분히 명확하다.

2.5 이름 포맷팅으로 의미를 전달하라

  • 밑줄과 대시, 그리고 대문자를 잘 이용하면 이름에 더 많은 정보를 담을 수 있다.
  • 다음은 구글 오픈소스 프로젝트에서 사용되는 포맷틴 관습을 따르는 c++코드이다.
static const int kMaxOpenFiles = 100;

class LogReader {
    public:
        void OpenFile(string local_file);
    private:
        int offset_;
        DISALLOW_COPY_AND_ASSIGN(LogReader);
}
  • 문법적 차이가 드러나게 서로 다른 개체의 이름에 각자 다른 포맷팅 방식을 적용하는 방법은 코드를 더 읽기 쉽게 해준다.
  • 이 예에서 사용된 포맷팅 방식은 클래스명을 CamelCase라고 쓰고 변수명을 lower_separated라고 썼는데, 상당히 흔한 관습이다.

요약

  • 특정한 단어를 사용하라
    • 예를 들어 Get 대신 Fetch나 Download를 사용하는 것이 더 나을 수 있다.
  • 꼭 그래야 하는 이유가 없다면 tmp나 retval과 같은 보편적인 이름의 사용을 피하라.
  • 대상을 자세히 묘사하는 구체적인 이름을 이용하라
    • ServerCanStart()는 CanListenOnPort()에 비해 의미가 모호하다
  • 변수명에 중요한 세부 정보를 덧붙여라
    • 예를 들어 밀리초의 값을 저장하는 변수 뒤에 _ms를 붙이거나 이스케이핑을 수행하는 변수의 앞에 raw_ 를 붙이는 것이다.
  • 사용 범위가 넓으면 긴 이름을 사용하라
    • 여러 페이지에 걸쳐서 사용되는 변수의 이름을 하나 혹은 두 개의 짧은 문자로 구성해 의미를 아라보기 힘들게 짓지 말라.
    • 다만 적은 분량(좁은 범위)에서 잠깐 사용되는 변수명은 짧은수록 더 좋다.
  • 대문자나 밑줄 등을 의미 있는 방식으로 활용하라
    • 예를 들어 클래스 멤버를 로컬 변수와 구분하기 위해서 뒤에 ‘_‘를 붙일 수 있다.

Comments