본문 바로가기
개발잡담

「읽기 좋은 코드가 좋은 코드다」요약

by 행복한집사 2022. 6. 26.

최근에 이해하기 어렵지 않으면서 실무에 바로 도움이 될 수 있는 책을 읽은 것 같아서 짧게 요약해 봤다.

나같은 주니어라면 이 책이 정말 도움이 되지 않을까 싶었다. 코드를 짜면서 한 번씩은 고민해봤을 주제들이 다 있는 듯 하다.

반납하기 전에 급하게 요약한거라... 미학적으로 보기 좋지 않아도 이해 바란다. 나중에 더 예쁘게 고치겠다(대체 언제??)

 

요약 시작!

 

대전제: 1.코드는 이해하기 쉬워야 한다.

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

#적은 분량으로 코드를 작성하는 것이 좋은 목표긴 하지만, 이해를 위한 시간을 최소화하는 게 더 좋은 목표다.

 

Part One 표면적 수준에서의 개선

2. 이름에 정보 담기

#이름에 정보를 담아내라

*특정한 단어 고르기

 - BinaryTree 클래스에서 트리 노드의 개수를 반환하는 메서드는 size()보다 numNodes()가 낫다.

 - 유의어 색인집을 찾아보거나 동료에게 더 나은 이름을 묻는 일을 주저하면 안 된다.

*보편적인 이름 피하기(혹은 언제 그런 이름을 사용해야 하는지 깨닫기)

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

 - run_locally 라는 명령형 플래그 대신 extra_logging use_local_database로 쪼개는 게 의미가 더 명확하고 유연한 선택을 제공한다.

*추상적인 이름 대신 구체적인 이름 사용하기

*접두사 혹은 접미사로 이름에 추가적인 정보 덧붙이기

*이름이 얼마나 길어져도 좋은지 결정하기

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

 - 흔히 쓰는 약어와 축약형은 괜찮지만 본인만 알아볼 수 있는 축약형은 안 된다.

 - 불필요한 단어 제거하기

*추가적인 정보를 담을 수 있게 구성하기

 

3. 오해할 수 없는 이름들

#경계를 포함하는 한계값을 다룰 때는 minmax를 사용하라

#경계를 포함하는 범위에는 firstlast를 사용하라

#경계를 포함하고/배제하는 범위에는 beginend를 사용하라

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

- get으로 시작하는 메소드는 왠지 금방 값을 반환할 거 같은 느낌을 준다. 사실 값을 계산하는데 리소스가 많이 든다면 computeMean() 이런식으로 변환하는게 낫다.

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

 

4. 미학

#미학적으로 보기 좋은 코드가 사용하기 더 편리하다

#메소드를 활용하여 불규칙성을 정리하라

assert(ExpandFullName(database_connection, “Doug Adams”, &error == “Mr. Douglas Adams”);

assert(error == “”);

assert(ExpandFullName(database_connection, “Jake Brown”, &error== “Mr. Jake Brown III”);

assert(error == “no match found”);

 

위의 코드를

CheckFullName(“Doug Adams”, “Mr.Douglas Adams”, “”);

CheckFullName(“Jake Brown”, “Mr. Jake Brown III”, “”);

이렇게 변경 가능하다.

 

#도움이 된다면 코드의 열을 맞춰라

#의미있는 순서를 선택하고 일관성 있게 사용하라

- 가장 중요한 것에서 시작해서 가장 덜 중요한 것까지 순서대로 나열하라

# 선언문을 블록으로 구성하라

# 코드를 문단으로 쪼개라

# 일관성 있는 스타일은 올바른스타일보다 더 중요하다

 

5. 주석에 담아야 하는 대상

#주석의 목적은 코드를 읽는 사람이 코드를 작성한 사람만큼 코드를 잘 이해하게 돕는 데 있다.

#코드에서 빠르게 유추할 수 있는 내용은 주석으로 달지 말라

#나쁜 이름에 주석을 달지 마라 대신 이름을 고쳐라

#’감독의 설명을 포함할

#코드에 있는 결함을 설명하라

#상수에 대한 설명이 필요하면 포함해도 좋다

//합리적인 한계를 설정하라 그렇게 많이 읽을 수 있는 사람이 없다.

Const int MAX_RSS_SUBSCRIPTIONS = 1000;

#코드를 읽는 사람의 입장이 되어라

#사람들이 쉽게 빠질 것 같은 함정을 경고하라

#(주석) 쓰는 두려움을 떨쳐내라

 

6. 명확하고 간결한 주석 달기

#주석을 간결하게 하라

#모호한 대명사는 피하라

#엉터리 문장을 다듬어라

#함수의 동작을 명확하게 설명하라

#구체적인 용법을 설명해주는 입/출력 예를 사용하라

// : Strip(“ab”, “a”)“b”를 반환한다.

#코드의 의도를 명시하라

#정보 축약형 단어를 사용하라

//이 클래스는 데이터베이스에 대한 캐시 계층으로 기능한다.

 

 

Part Two 루프와 논리를 단순화하기

7. 읽기 쉽게 흐름제어 만들기

# 흐름을 제어하는 조건과 루프 그리고 여타 요소를 최대한 자연스럽게만들도록 노력하라. 코드를 읽다가 다시 되돌아가서 코드를 읽지 않아도 되게끔 만들어야 한다.

#부정이 아닌 긍정을 다루어라

If(!debug)가 아니라 if(debug)를 선호하라

#간단한 것을 먼저 처리하라

#더 흥미롭고, 확실한 것을 먼저 다루어라

#줄 수를 최소화하는 일보다 다른 사람이 코드를 읽고 이해하는 데 걸리는 시감을 최소화하는 일이 더 중요하다.

#함수 중간에서 반환하는 것은 완전히 허용되어야 한다. 함수 중간에서 반환하여 중첩을 제거하라.

#루프 내부에 있는 중첩 제거하기

 

8. 거대한 표현을 잘게 쪼개기

#드모르간의 법칙 사용하기

1) not (a or b or c ) == (not a) and (not b) and (not c)

2) not (a and b and c ) == (not a) or (not b) or (not c)

- 위아래 if문은 뜻이 똑같다.

if(!(file_exists && !is_protected))

if(file_exists && is_protected)

 

#더 우아한 접근방법 발견하기

Ex) 주어진 범위의 양쪽 경계값이 other의 범위에 속하는지 확인하는 OverlapsWith()함수를 구현하는 2가지 방법

1) boolean OverLapsWith(Range other){

return (begin >= other.begin && begin < other.end) ||

        (end > other.begin && end <= other.end) ||

       (begin <= other.begin && end >= other.end);

}

2) boolean OverLapsWith(Range other){

if(other.end <= begin) return false; // 우리가 시작하기 전에 끝난다.

if(other.begin >= end) return false; // 우리가 끝난 후에 시작한다.

 

  return true; // 마지막 가능성만 남았다. 즉 겹친다.

}

2번째 방법이 더 간단!

 

9. 변수와 가독성

#불필요한 임시 변수들, 중간 결과담는 변수 삭제

#변수의 범위를 좁혀라

#많은 메소드를 정적 static으로 만들라

#커다란 클래스를 여러 작은 클래스로 나누라

#정의를 사용하는 곳에 가깝게 옮겨라

#값을 한 번만 할당하는 변수를 선호하라

(변수값이 달라지는 곳이 많아지는 것보다 변수의 숫자가 더 많아지는 게 나은 듯)

 

PART THREE 코드 재작성하기

10. 상관없는 하위문제 추출하기

#순수한 유틸리티 코드를 빼내서 만든다.

#util 패키지에 담아놓을 수 없는 특정한 프로젝트를 위한 기능이더라도 따로 빼는 것이 나으면 따로 빼서 같은 클래스에 담아놓아도 괜찮다.

# 자신의 필요에 맞춰서 인터페이스의 형태를 바꾸기

# 지나치게 추출하는 건 지양하기

 

11. 한 번에 하나씩

#한 번에 하나의 작업만 수행하게 코드를 구성해야 한다.

(이전 코드는 너무 길어서개선된 코드 일부만 적어놓는다)

void UpdateCounts(HttpDownload hd){

//작업: 읽고자 하는 각각의 값을 위한 기본값을 정의한다.

String exit_state = “unknown”;

String http_response = “unknown”;

 

//작업: HttpDownload에서 각각의 값을 하나씩 읽는다.

if(hd.has_event_log() && hd.event_log().has_exit_stage()){

  exit_state = ExitStateTypeName(hd.event_log().exit_state());

}

if(hd.has_http_headers() && hd. http_headers().has_response_code()){

  http_response = StringPrintf(“%d”, hd.http_headers().reponse_code()));

}

 

//작업: count[]를 갱신한다.

counts[“Exit State”][exit_state]++;

counts[“Http Response”][http_response]++;

}

 

12. 생각을 코드로 만들기

할머니에게 설명할 수 없다면 당신은 제대로 이해한 게 아닙니다

- 알버트 아인슈타인

#논리를 명확하게 설명하기

#라이브러리를 알면 도움이 된다.

#자신의 문제를 쉬운 말로 설명할 수 없으면 해당 문제는 무언가 빠져 있거나 아니면 제대로 정의되지 않은 것이다. 어떤 프로그램을 혹은 어떤 생각이라도 말로 설명하는 행위는 문제의 틀을 제대로 잡는 데 도움을 준다.

 

13. 코드 분량 줄이기

#필요한 기능이 아니면 그 기능을 구현하려고 애쓰지 마라

- 코드베이스에 새로운 무게를 더하는 데 얼마나 많은 시간이 필요한지 잊지마라

#요구사항에 질문을 던지고 질문을 잘게 나누어 분석하라

- 복잡한 LRU 캐시를 구현하는 대신 단순하게 가장 최근 1개 항목만 저장하는 캐시를 구현해서 약 90%의 실행속도 향상을 이룰 수도 있다

#코드베이스를 최대한 작고 가볍게 유지하라

#자기 주변에 있는 라이브러리에 친숙해져라

- 매일 15분씩 자신의 표준 라이브러리에 있는 모든 함수/모듈/형들의 이름을 읽어라. 코드를 직접 작성하는 대신 잠깐 전에 API에서 보았던 뭔가 비슷한데..”라고 생각나면 우선 존재하는 라이브러리를 사용하는 습관을 가질 수 있다.

- 코딩 대신 유닉스 도구를 활용도 가능

 

PART FOUR 선택된 주제들

14. 테스트와 가독성

#읽거나 유지보수하기 쉽게 테스트를 만들어라

#최소한의 테스트 구문 만들기

- 테스트 값을 넣기 쉽게 이를 위한 메서드를 작성

#테스트하기 어려운 코드의 특징

1) 전역변수 사용

2) 코드가 많은 외부 컴포넌트 사용

3) 코드가 비결정적인 행동을 한다

#테스트하기 쉬운 코드의 특징

1) 클래스들이 내부 상태를 거의 가지고 있지 않다

2) 클래스/함수가 한 번에 하나의 일만 수행

3) 클래스가 다른 클래스에 의존하지 않고 서로 상당히 떨어져 있다

4) 함수들이 간단하고 잘 정의된 인터페이스를 가지고 있다

#지나치게 테스트에 집착하면 안 된다.

- 테스트를 가능하게 하려고 실제 코드의 가독성을 희생시키면 안 된다

댓글