2018년 9월 2일 일요일

[The Art of Readable Code, 읽기 좋은 코드가 좋은 코드다] 2. 이름에 정보 담기

표면적인 수준에서의 개선



"표면적 수준이란 좋은 이름을 짓고, 좋은 설명을 달고, 코드를 보기 좋게 정렬하는 따위를 의미한다."

책의 첫 단락은 표면적인 수준에서의 개선부터 시작합니다. 이런 수정은 코드를 통째로 바꾸거나 동작하는 방식을 변화시키지 않고 '그 자리에서' 곧바로 만들 수 있기에 첫 시작으로 매우 적절하다 생각합니다.

물론 가독성에 관련된 논의는 이 수준보다 더 나아가 많은 내용을 담고 있겠으나 이는 차차 살펴갈 것이며 먼저 1부에서는 폭넓게 적용할 수 있고, 그다지 많은 노력을 요구하지 않는 내용을 우선적으로 다룹니다.

이름에 정보 담기


변수, 함수, 혹은 클래스 등의 이름을 결정할 때는 항상 같은 원리가 적용합니다. 
"이름을 일종의 설명문으로 간주해야 한다."
충분한 공간은 아니지만, 좋은 이름을 선택하면 생각보다 많은 정보를 전달할 수 있다는 것이죠. 구체적으로는 아래의 여섯 가지 방법을 제안합니다.
  • 특정한 단어 고르기
  • 보편적인 이름 피하기 (혹은 언제 그런 이름을 사용해야 하는지 깨닫기)
  • 추상적인 이름 대식 구체적인 이름 사용하기
  • 접두사 혹은 접미사로 이름에 추가적인 정보 덧붙이기
  • 이름이 얼마나 길어져도 좋은지 결정하기
  • 추가적인 정보를 담을 수 있게 이름 구성하기
앞으로는 책에 나온 내용을 모두 다 소개하기 보다는 개중 제가 재미있었던 내용들을 좀 골라서 예시와 함께 알아보겠습니다. 

특정한 단어 고르기


매우 구체적인 단어를 선택하여 "무의미한" 단어를 피하자. 

예를 들어 "get"은 지나치게 보편적입니다.

def GetPage(url):
    ...

여기서 "get"보다는 메소드가 어디에서 페이지를 가져오는 지 알려줄 수 있게 FetchPage() 혹은 DownloadPage()와 같이 구체적으로 명명하는 것이 더 좋습니다.

사실 위 예시보다 다음 예시가 더 좋았는데요. 다음과 같이 BinaryTree 클래스에서

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

우리는 Size() 메소드가 반환하는 것이 무엇일 지 이름만 봐서는 알 수 없습니다. 트리의 높이, 노드의 개수, 혹은 트리의 메모리 사용량이 될 수도 있겠죠.  따라서 Height(), NumNodes(), 혹은 MemoryBytes() 등이 더 의미 있는 이름이라는 것에는 모두 동의하리라 생각합니다. 

같은 맥락에서 저자들은 thesaurus를 뒤져보고 더 나은 이름을 생각하기를 권합니다. 다만 너무 "재치" 있는 이름보다는 명확하고 간결한 이름이 더 좋습니다. 다음에 이어지는 내용들도 사실 같은 내용인데 예제들과 소소한 팁 위주로 살펴보곘습니다.

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


"변수값을 설명하는 이름을 사용하라"

예를 들어, 다음과 같이 Euclidean norm을 계산하는 자바스크립트 코드에서

var euclidean_norm = function (v) {
    var retval = 0.0;
    for (var = i = 0; i<v.length; i+=1)
        retval += v[i];
    return Math.sqrt(retval);
};

retval보다는 sum_squares라고 이름을 붙여준다면 변수의 목적을 바로 이해할 수 있으며 나중에 버그를 잡을 때도 용의합니다.

retval += v[i]; 부분이 sum_squares += v[i]; 였다면 훨씬 눈에 잘 띄었겠죠.

물론 아래와 같이 정말로 대상이 짧게 임시적으로만 존재하고, 임시적 존재 자체가 변수의 가장 중요한 용도일 때는 tmp와 같은 변수를 사용할 수 있겠습니다.

두 변수를 서로 교환하는 알고리즘 예:

if (right<left) {
    tmp = right;
    right = left;
    left = tmp;
}

같은 맥락으로 i, j, iter, it 같은 이름이 인덱스나 루프 반복자로 사용되는 것은 충분히 괜찮습니다. 다만 이 역시도 디버깅의 용이성을 위해서 아래와 같이 소속을 표현해준다면 더 좋겠죠.

(i, j, k) -> (club_i, members_j, users_i) or (ci, mj, ui)

활용 예:
if (clubs[ci].members[ui] == users[mi]) # 버그! 처음 문자가 일치 하지 않는다.

따라서 표편적인 이름이 항상 나쁜 것은 아니지만, 이를 사용하려면 꼭 그렇게 해야하는 이유가 있어야 합니다. 


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


단위(sec, millisecond, kg 등)를 포함하거나 다른 중요한 속성(unsafe, utf_8 등)이 있을 때는 변수에 그런 내용을 추가해주면 좋습니다. 

start -> start_ms, elapsed -> elapsed_ms
html -> html_utf-8 # html의 바이트가 UTF-8으로 변환되었다. 

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


만일 변수가 좁은 scope (예: 끽해야 몇 줄 안의 함수 scope)에서 사용된다면 멤버 변수가 "m"과 같이 매우 짧은 이름을 사용해도 별 문제가 없으나 이 변수의 scope이 클래스나 전역으로 넓어지면 가독성이 매우 떨어지게 되므로 상황에 따라 잘 사용하라고 하는군요. 

게다가 요즘은 긴 이름을 입력하는 것이 자동완성 기능으로 매우 편해져서 그리 주저할 일이 아닙니다. 그렇기 때문에 약어와 축약형은 매우 보편적인 경우(string-> str과 같이)를 제외하고는 지양하는 편이 좋겠습니다. 

이에 좀 더 더한다면 ConvertToString()에서 ToString()과 같이 불필요한 단어를 제거해서 간결하게 만드는 등의 팁이 있으나 앞의 내용들이 더 핵심에 가까운 것으로 보입니다. 

이로써 이름에 정보를 넣는 방법에 대해 요약해보았습니다. 

책에서 다음 장은 의미를 오해하기 쉬운 이름들에 대한 팁입니다만 사실 오늘 소개한 내용에 어느 정도 포함되는 것 같습니다. 다음 글에서는 미학(Aesthetics) 즉 "눈을 편하게" 하는 코드에 대해 정리하겠습니다. 





[PR12-Video] 71. Categorical Reparameterization with Gumbel Softmax


TensorFlowKR facebook comunity에서 모인 12명의 paper readers (PR12)가 읽어주는 Deep learning paper awesome list 100선 by Terry Um.

#71. Categorical Reparameterization with Gumbel Softmax


이 리뷰에서는 NIPS 2016 workshop에 같이 발표되었고 최근 ICLR 2017에 발표된 두 편의 논문을 리뷰하겠습니다. 재미있는 점은 이 두 편의 논문들이 똑같은 아이디어를 바탕으로 정확히 같은 수식을 사용하여 arXiv에도 고작 하루 차이로 올라왔다는 것입니다. 아이디어가 공중에 떠다닌다는 말이 정말 맞는가 싶습니다. 즐겁게 들어주시면 감사하겠습니다.


(추신) 24분 부분에 질문 주신 부분에 대해 답이 미진한것 같아 끝나고 곰곰히 생각해본 답글을 여기에 추가합니다.  둘 다 categorical dist를 만드는데 다른 방법을 사용할 뿐이라는것이 맞는 답인것 같습니다. 우리가 nn으로부터 샘플링을 하고 싶으면 logit을 받아서 softmax를 통과시켜서 확률값을 얻어서 이를 바탕으로 분포에 값을 넣어주고 그 분포로부터 샘플을 뽑는 방법이 있겠구요 (이 방법이 준범님이 말씀하신 보통의 방식인 것 같습니다. 결국 마지막 단에서 softmax하여 확률 값을 주니까요) 다만 샘플링을 하지 않고 확률값 자체를 라벨과 빼서 에러를 계산하는데 사용되는 것이라 백프롭에서는 문제가 없는것 같습니다. 자기자신으로 1이니까 그렇다고 생각하는데 혹 이상하면 말씀주세요. 그리고 두번째 방법이 logit에 검벨에서 뽑은 노이즈를 더하여 argmax를 통과시켜서 값을 얻으면 그 자체가 discrete categorical dist에서 나온 샘플입니다. 여기서 argmax를 softmax로 relaxation한 것이 gumbel softmax trick이구요 그래서 이렇게 복잡하게 과정을 거친 이유는 말씀드린 바와 같이 미분이 가능하게 해서 중간에 node가 껴있을때 gradient를 계산하기 위해서인 것으로 이해하면 되지 않을까 싶습니다.

(paper1) Categorical Reparameterization with Gumbel Softmax and
(paper2) The Concrete Distribution: A Continuous Relaxation of Discrete Random Variables

Paper1: https://arxiv.org/abs/1611.01144
Paper2: https://arxiv.org/abs/1611.00712
슬라이드: https://www.slideshare.net/thinkingfactory/pr12-categorical-reparameterization-with-gumbel-softmax

다음에 또 다른 주제로 뵈어요~!

다른 분들의 발표도 보고 싶다면: PR12 딥러닝 논문읽기 모임

다음 읽을거리






2018년 9월 1일 토요일

[The Art of Readable Code, 읽기 좋은 코드가 좋은 코드다] Intro. 코드는 이해하기가 쉬워야 한다.

많은 분들이 그러실텐데 저 역시도 항상 좋은 코드란 어떤 것인지 알고 싶었습니다. 이런 고민을 듣고 최근 회사 동료인 전상혁님이 "The Art of Readable Code"라는 책을 추천해주시기에 책을 도서관에서 빌려 읽고 있는데 정말 많이 배우고 있습니다. 책의 내용이 좋아서 한 권 사서 두고두고 읽으려 합니다.

이런 내용들을 코드에 직접 적용하면서 체득하는 것이 가장 좋겠지만 당장 단기간에 이뤄질 수 있는 일은 아니기에, 일단은 좋은 내용들이 머리에 좀 더 오래 남기를 바라며 책 내용을 정리해서 올리고자 합니다.

나중에 이 글을 찾은 분 혹은 미래의 나 스스로에게 초심자의 입장에서 어떤 점들이 도움이 되었는지를 보여줄 수 있을거라 기대합니다.

이 책은 무엇에 대한 것인가?


이 책은 매우 읽기 편한 코드를 작성하는 방법을 설명하는데요. C++, 파이썬, 자바스크립트, 자바 등을 포함한 다양한 언어로 작성된 코드를 예로 들며 설명해줍니다. 중간중간 껴있는 삽화들도 매우 재치있고 각 장의 주제와 연관되어 있어 이해를 도와줍니다.

재밌는 점은 언어들을 다 알지 못하더라도 책을 읽는 데는 별 어려움이 없다는 것입니다.
저자들이 얘기하기론 "코드의 가독성"이라는 개념 자체가 언어로부터 독립적이기 때문이라고 하지만 제가 보기엔 여기서 저자들의 내공이 드러나는 것이 아닌가 싶습니다.

크게 아래와 같이 4부로 나누어
  1. 표면적인 수준에서의 개선
  2. 루프와 로직를 단순화하기
  3. 코드를 재작성하기
  4. 선택된 주제들
여러 측면에서 코드를 이해하기 쉽게 만드는 방법을 설명해줍니다. 


가독성의 기본 정리

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

분량이 적다고 항상 좋은 것이 아닙니다. 좋은 예로 주석도 사실은 "코드를 더하는 행위"지만 코드를 더 빨리 이해하게 도와줍니다. 적은 분량으로 코드를 작성하는 것이 좋은 목표긴 하지만, 이해를 위한 시간을 최소화하는 것이 더 좋은 목표입니다.

또 다른 예로,

return exponent >=0 ? mantissa * (1 <<exponent) : mantissa / (1 << -exponent);

라는 코드보다는

if (exponent >=0) {
    return mantissa * (1 << exponent);
} else {
    return mantissa / (1 << -exponent);
}

이렇게 바꾼 코드가 앞서보다 간결하진 않지만 더 이해하기 쉽습니다.

이해를 위한 시간은 코드의 효율성, 아키텍처, 테스트의 용이성과 같은 다른 목표와 충돌할까봐 걱정할 수도 있으나, 저자들의 경험에 따르면 대다수의 경우 이러한 조건은 거의 아무런 방해가 되지 않다고 합니다.

가장 기본적인 대원칙은 코드를 "읽기 쉽게" 만드는 원리가 적용될 때마다 의심의 여지가 생기면 언제나 가독성의 기본 정리가 다른 어떤 규칙보다 앞선다는 점입니다.

"이 코드는 이해하기 쉬운가?"


만일 정리가 되지 않을 코드를 고치고 싶을 때는 먼저 뒤로 한 걸음 물러나서 스스로에게 물어보는 것이 중요합니다: "이 코드는 이해하기 쉬운가?". 만약 그렇다면 다른 코드로 건너뛰어도 별 상관이 없습니다.