jongfeel / BookReview

Reviews the IT or any books slowly and steady.
MIT License
4 stars 0 forks source link

CHAPTER 2 이름에 정보 담기 #859

Closed jongfeel closed 3 months ago

jongfeel commented 3 months ago

CHAPTER TWO Packing Information into Names

image

이름을 일종의 설명문으로 간주해야 한다. 좋은 이름을 선택하면 생각보다 많은 정보를 전달할 수 있다.

핵심 아이디어 이름에 정보를 담아내라

특정한 단어 고르기

매우 구체적인 단어를 선택해서 '무의미한' 단어를 피한다.

def GetPage(url);

여기서 get은 별다른 의미가 없다. 인터넷에서 가져오는 페이지라면 Fetch나 Download가 더 의미있는 이름이 될 것이다.

class BinaryTree {
    int Size();
   ...
}

Size()는 트리의 높이, 노드의 개수, 트리의 메모리 사용량 중 무엇인지 알기 어렵다. 문제는 이 이름이 우리가 의도한 정보를 전달하지 못한다는 데 있다. Height(), NumNodes(), MemoryBytes() 등이 더 의미 있는 이름일 것이다.

class Thread {
    int Stop();
   ...
}

Stop()은 그런대로 괜찮지만, 정확히 무엇을 수행하는지에 따라 더 의미 있는 이름을 사용할 수 있다. Kill() 이라는 이름을 쓰면 다시는 되돌릴 수 없는 최종 동작일 것이고 Resume()을 호출해서 다시 돌이킬 수 있는 동작이라면 Pause()가 더 좋을 것이다.

더 '화려한' 단어 고르기

image

유의어 색인집 찾기나 동료에게 물어보자. 선택할 수 있는 단어는 많다. 아래는 '화려한' 단어를 예로 나열한 것이다.

Word Alternatives
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

핵심 아이디어 재치 있는 이름보다 명확하고 간결한 이름이 더 좋다.

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

tmp, retval, foo 같은 이름은 "내 머리로는 이름을 생각해 낼 수 없다"라고 고백하면서 책임을 회피하는 증거에 불과하다. 무의미한 이름 보다는 값이나 목적을 정확하게 설명하는 이름을 골라야 한다.

반환되는 값의 이름을 생각하기 어려워 그냥 retval 이름을 쓰고 싶은 유혹이 있다. 하지만 retval 이라는 이름은 "저는 사실 반환되는 값이랍니다" 라는 정보 이외에 아무 의미도 없다.

예로 제곱한 값을 모두 더한 값을 담아야 한다는 걸 표현한다면, sum_squares 라는 이름이 좋다. 이 이름은 목적을 직접 나타내므로 나중에 버그를 잡는 데 도움이 될 수 있다.

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

tmp

두 변수 값을 서로 바꾸는 로직에서는 임시로 한 변수의 값을 담고 있어야 하는 데 tmp 라는 이름이 더 적절할 수 있다. tmp는 변수가 임시저장소 이외에 다른 용도가 없다는 사실을 잘 전달하기 때문이다.

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

루프 반복자

i, j, iter, it 같은 이름은 인덱스, 루프반복자로 사용된다. 보편적인 이름이고 "나는 반복자랍니다" 라는 의미를 충분히 전달한다.

i, j, k를 쓰는 3중 루프에는 인덱스를 잘못 쓰거나 헷갈릴 수 있으므로 club_i, members_i, users_i 혹은 ci, mi, ui 같은 이름을 사용하면 좋다.

그러면 첫 문자가 일치하는 걸 보고 인덱스를 올바르게 썼는지를 알 수 있다.

if (clubs[ci].members[mi] == users[ui])

조언 tmp, it, retval 같은 보편적인 이름을 사용하려면, 꼭 그렇게 해야 하는 이유가 있어야 한다.

몇 초라도 좋은 이름을 생각하려고 고민하는 습관을 들이면 '작명을 위한 내공'이 빠르게 쌓이는 것을 느낄 수 있다.

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

image

예로 서버가 어떤 TCP/IP 포트를 사용할 수 있는지 검사하는 ServerCanStart()라는 메소드는 추상적이다. 구체적인 이름으로 CanListenOnPort()라고 지으면, 수행하는 일을 직접 설명하는 방식이다.

예: DISALLOW_EVIL_CONSTRUCTOR

구글 코드베이스의 예다. C++ 프로그래머는 클래스의 복사 생성자copy constructor나 할당 연산자assignment operator를 생략하면 기본값을 사용하게 된다. 편리한 기능이긴 하지만 잘 모르는 상태로 동작하기에 메모리 누수나 다른 종류의 문제를 일으키기 쉽다.

구글은 매크로를 이용해서 '사악한' 생성자가 만들어지는 걸 막는 정책을 사용하기로 했다.

#define DISALLOW_EVIL_CONSTRUCTORS(ClassName)
    ClassName(const ClassName); \
    void operator=(const ClassName&);

class ClassName {
    private:
        DISALLOW_EVIL_CONSTRUCTORS(ClassName);
    ...
}

이 매크로는 private:에 두었으므로 두 메소드는 우연히 사용될 수 없다.

하지만 DISALLOW_EVIL_CONSTRUCTORS라는 이름은 별로 좋지 않다. evil 이라는 단어는 논쟁의 여지가 있는 강한 표현이고, 매크로가 disallow 하는 대상이 무엇인지 드러나지 않는다. 사실 operator=() 를 금지하는데 constructor가 아니므로 헷갈린다.

이 이름은 여러해 동안 사용되다가 덜 자극적이고 더 구체적인 새로운 이름으로 대체되었다.

#define DISALLOW_COPY_AND_ASSIGN(ClassName) ...

예: --run_locally

직접 작성한 cli 프로그램에서 이 옵션을 쓰면 디버깅 정보를 출력하는 대신 동작 속도는 느려진다. 주로 노트북 같은 로컬 컴퓨터에서 테스트를 수행할 때 사용하고 원격 서버에서 동작할 때는 성능이 중요하므로 이 플래그를 사용하지 않는다.

이 이름에는 몇 가지 문제가 있다.

--run_locally는 실제 내용보다는 주로 사용되는 환경을 나타내는 방식으로 지어졌으므로 --extra_logging 이라는 이름이 더 직접적이고 명확하다.

로컬 데이터베이스를 설정하고 사용하는 다른 작업도 해야 한다면 --run_locally 플래그에 로컬 데이터베이스 설정을 추가하는 것 보다는 다른 플래그를 만드는 것이 낫다. --use_local_database라는 두 번째 플래그를 만들면 플래그 2개를 사용해야 하지만 의미는 훨씬 더 명확하다.

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

image

변수 이름은 작은 설명문이다. 사용자가 반드시 알아야 하는 변수와 관련한 중요한 정보를 추가적인 '단어'로 만들어서 이름에 붙이는 게 좋다. 16진수 문자열을 담고 있는 변수라면 변수 이름을 id가 아닌 hex_id로 하는 편이 더 나을 것이다.

단위를 포함하는 값들

변수가 시간의 양이나 바이트 수와 같은 측정치를 담고 있다면, 변수명에 단위를 포함시키는 게 도움이 된다.

자바스크립트에서 new Date().getTime()은 초가 아니라 밀리초를 반환하므로 두 시간의 차이를 계산하면 밀리초 단위로 나온다. 따라서 변수에 _ms 라는 이름을 붙여주면 문제가 명확해진다.

아래 표는 단위를 포함하지 않는 함수의 인수와 단위를 포함하는 더 나은 함수의 인수를 보여준다.

Function parameter Renaming parameter to encode units
Start(int delay) delay → delay_secs
CreateCache(int size) size → size_mb
ThrottleDownload(float limit) limit → max_kbps
Rotate(float angle) angle → degrees_cw

다른 중요한 속성 포함하기

어떤 변수에 위험한 요소 혹은 나중에 놀랄만한 내용이 있다면 추가적인 정보를 붙이는 방법을 사용할 필요가 있다.

아래 표는 이름에 추가적인 정보를 나타내야 하는 여러 가지 상황을 보여준다.

Situation Variable name Better name
A password is in “plaintext” and should be encrypted before further processing password plaintext_password
A user-provided comment that needs escaping before being displayed comment unescaped_comment
Bytes of html have been converted to UTF-8 html html_utf8
Incoming data has been “url encoded” data data_urlenc

모든 변수에 unescaped_나 _utf8 같은 정보를 담을 필요는 없다. 이런 테크닉은 누군가 변수를 잘못 이해했을 때에만 중요한 의미가 있다. 변수의 의미를 제대로 이해하는 것이 중요하다면 그 의미를 드러내는 정보를 변수의 이름에 포함시켜야 한다.

지금까지의 설명은 헝가리헌 표기법인가?

헝가리안 표기법은 마이크로소프트 내부에서 널리 사용하는 표기법으로
모든 변수의 타입을 이름 앞에 붙여 넣는 방식이다.

이 책에서 설명하는 건 헝가리언 표기법보다 더 넓고 비공식적인 시스템이다.
어떤 변수의 중요한 속성이 있다면 변수명에 포함시키는 방법이다.
이 방법을 '잉글리쉬 표기법'이라고 부를 수도 있다.

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

image

이름이 지나치게 길면 안 된다는 제한이 암묵적으로 있다. 이걸 또 그대로 받아들이면 단어 하나 혹은 문자 하나로 된 이름을 사용할 수도 있다. 변수명을 d, days, days_since_last_update 중에서 어떤 걸로 할지 어떻게 결정하는게 좋을까

변수의 용도에 따라 그때그때 판단해야 하는데 몇 가지 도움이 될 만한 조언이 있다.

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

변수의 타입, 초기값, 어떻게 사라지는 지 등에 대한 정보는 몇 줄 안에서 파악이 가능하므로 짧은 이름을 써도 상관 없다.

if (debug) {
    map<string, int> m;
    LookupNamesNumbers(&m);
    Print(m);
}

여기서 m이 바로 위에 선언된 지역 변수가 아닌 클래스 멤버이거나 전역 변수일 때 같은 코드를 쓰면 가독성이 떨어질 것이다.

    LookupNamesNumbers(&m);
    Print(m);

이때의 이름은 큰 범위를 가지므로 이름에 의미를 분명하게 만들기 위한 정보를 충분히 포함해야 한다.

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

긴 이름을 사용하면 안 되는 이유는 많지만 "긴 이름은 입력하기 어렵다"는 변명은 더 이상 성립하지 않는다. IDE 마다 단어 완성 기능이 있으므로 자신이 쓰고 있는 IDE에서 이 기능을 지원하는 명령어가 뭔지 확인해 본다.

Editor Command
Vi Ctrl-p
Emacs Meta-/ (hit ESC, then /)
Eclipse Alt-/
IntelliJ IDEA Alt-/
TextMate ESC

약어와 축약형

예로 BackEndManager -> BEManager라는 이름을 사용하는데 이런 축약이 나중에 일어날지 모르는 혼란을 무릅쓸만한 가치가 있을까?

경험상 특정 프로젝트에 국한된 의미를 가진 약어는 좋은 생각이 아니다. 프로젝트에 새로 합류한 사람에게 비밀스럽고 위협적인 모습으로 다가오며 시간이 흐르면 이름을 만든 장본인에게도 비밀스럽고 위협적인 모습을 지니게 된다.

규칙은 이렇다. 팀에 새로 합류한 사람이 이름이 의미하는 바를 이해할 수 있다면 그 이름은 괜찮은 것이다.

불필요한 단어 제거하기

정보를 손실하지 않으면서 이름에 포함된 단어를 제거할 수도 있다. ConvertToString() 이라는 이름 대신 ToString()이라고 짧게 써도 실질적인 정보는 사라지지 않는다.

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

underscores, dashes 그리고 대문자를 잘 이용하면 이름에 더 많은 정보를 담을 수 있다. 예로 다음은 구글 오픈소스 프로젝트에서 쓰는 C++ formatting conventions 이다.

static const int kMaxOpenFiles = 100;
class LogReader {
  public:
    void OpenFile(string local_file);
  private:
    int offset_;
    DISALLOW_COPY_AND_ASSIGN(LogReader);
};

문법적 차이가 드러나게 서로 다른 개체의 이름에 각자 다른 포맷팅 방식을 적용하는 방법은 코드를 더 읽기 쉽게 해준다. 이 예에서 사용된 방식은 클래스명을 CamelCase라고 쓰고 변수명을 lower_separated라고 썼다.

offset_ 처럼 밑줄로 끝나는 관습은 멤버 변수를 다른 변수와 구별할 수 있어서 편리함을 준다.

다른 포맷팅 관습

더글라스 크락포드의 < 더글라스 크락포트의 자바스크립트 핵심 가이드 > 에서는 new와 함께 호출되는 함수인 '생성사'를 대문자로 표시하고 다른 평범한 함수는 소문자로 표시할 것을 제안했다.

var x = new DatePicker(); // DatePicker() is a "constructor" function
var y = pageHeight(); // pageHeight() is an ordinary function

jQuery의 경우 변수 앞에 $을 붙이는 관습이 있다.

var $all_images = $("img"); // $all_images is a jQuery object
var height = 250; // height is not

$all_images 변수는 jQuery의 결과를 담는 객체라는 사실이 뚜렷해진다.

HTML / CSS 에서 id는 underscore로 구분하고 class는 dash로 구분하는 방법도 있다

<div id="middle_column" class="main-content"> ...

이런 관습은 실제로 사용할지는 팀의 결정에 달려 있다. 이렇게 하면 코드를 읽는 사람은 이름만으로도 많은 정보를 추출할 수 있다.

Summary

jongfeel commented 3 months ago

다른 포맷팅 관습 에서

new와 함께 호출되는 함수인 '생성사'를 대문자로 표시하고 다른 평범한 함수는 소문자로 표시할 것을 제안하는 설명을 하면서 더글라스 크락포드의 < 더글라스 크락포트의 자바스크립트 핵심 가이드 > 책을 언급 http://aladin.kr/p/IFxv3