의미있는 이름(2)

확실하게 이름짓기

책너두 5기 5일차

로버트 C. 마틴의 클린코드 p.35 ~ p.45

내용 정리

2장 의미있는 이름

의미 있는 맥락을 추가하라

state만 보고는 주소인지, 상태인지 알 수없다.

addrstate면 주소에서 ‘주’를 뜻한다는 것을 파악할 수 있다.

예시)

맥락이 불분명한 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void printGuessStatistics(char candidate, int count) {
    String number;
    String verb;
    String pluralModifier;
    if (count = 0) {
        number = "no";
        verb = "are";
        pluralModifier = "s";
    } else if ( count == 1) {
        number = "1";
        verb = "is";
        pluralModifier = "";
    } else {
        number = Integer.toString(count);
        verb = "are";
        pluralModifer = "s";
    }
    String guessMessage = String.format(
    "There %s %s %s%s", verb, number, candidate, pluralModifier
    );
    print(guessMessage);
}

GuessStatisticsMessage 클래스를 만든 후 세 변수를 넣는다.

개선된 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class GuessStatisticsMessage {
    private String number;
    private String verb;
    private String pluraModifier;

    public String make(char candidate, int count) {
        createPluralDependentMessageParts(count);
        return String.format(
        "There %s %s %s%s",
        verb, number, candidate, pluralModifier);
    }

    private void createPluralDependentMessageParts(int count) {
        if (count = 0) {
            thereAreNoLetters();
        } else if (count = 1) {
            thereIsOneLetter();
        } else {
            thereAreManyLetters(count);
        }
    }

    private void thereAreManyLetteres(int count) {
        number = Integer.toString(count);
        verb = "are";
        pluralModifier = "s";
    }

    private void thereIsOneLetter() {
        number = "1";
        verb = "is";
        pluralModifier = "";
    }

    private void thereAreNoLetters() {
        number = "no";
     	verb = "are";
        pluralModifier = "s";
    }
}

함수를 쪼개서 알고리즘이 명확해졌고 맥락도 분명해졌다.

불필요한 맥락을 없애라

GSD라는 서비스를 만든다고 모든 클래스 이름에 GSD를 붙이는 것은 현명하지 못하다. 불필요한 맥락이다.

마치면서

비록 요즘 이름과 관련된 암기는 도구에게 맡기고, 문장이나 문단처럼 읽히는 코드가 아니라면 자료구조 처럼 읽히는 코드를 짜는데 집중해야 하지만 그럼에도 명명법은 중요하고 가치있다.

3장 함수

함수 만드는 법과 관련된 챕터이다.

다음 코드를 3분내에 이해해보자

코드 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public static String testableHtml(
    PageData pageData,
    boolean includeSuiteSetup
) throws Exception {
    WikiPage wikiPage = pageData.getWikiPage();
    StringBuffer buffer = new StringBuffer();
    if (pageData.hasAttribute("Test")) {
        if (includeSuiteSetup) {
            WikiPage suiteSetup =
                PageCrawlerImpl.getInheritedPage(
            SuiteResponder.SUITE_SETUP_NAME, wikiPage
            );
           if (suiteSetup != null) {
               WikiPagePath pagePath =
                   suiteSetup.getPageCrawler().getFullPath(suiteSetup);
               string pagePathName = PathParser.render(pagePath);
               buffer.append("!include -setup .")
               		 .append(pagePathName)
               		 .append("\n");
           }
        }
        WikiPage setup =
            PageCrawlerImpl.getInheritedPage("SetUp", wikiPAge);
        if (setup != null) {
            WikiPagePath setupPath =
                wikiPage.getPageCrawler().getFullPath(setup);
            String setupPathName = PathParser.render(setupPath);
            buffer.append("!include -setup .")
            	  .append(setupPathName)
            	  .append("\n");
        }
    }
    buffer.append(pageData.getContent());
    if (pageData.hasAttribute("Test")) {
        WikiPage teardown =
            PageCrawlerImpl.getInheritedPage("TeaerDown", wikiPage);
        if (teardown != null) {
            WikiPagePath tearDownPath =
                wikiPage.getPageCrawler().getFullPath(teardown);
            String tearDownPatName = PathParser.render(tearDownPath);
            buffer.append("\n")
            	  .append("!include -teardown .")
            	  .append(tearDownPathName)
            	  .append("\n");
        }
        if (includeSuiteSetup) {
            WikiPage suiteTeardown =
                PageCrawlerImpl.getInheritedPage(
                	SuiteResponder.SUITE_TEARDOWN_NAME,
            		wikiPage
            	);
            if (suiteTeardown != null) {
                WikiPagePath pagePath =
                    suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);
                String pagePathName = PathParser.render(pagePath);
                buffer.append("!include =teardown .")
                	  .append(pagePetName)
                	  .append("\n");
            }
        }
    }
    pageData.setContent(buffer.toString());
    return pageData.getHtml();
}

이 코드를 리팩토링한 다음 코드도 3분동안 파악해보자

코드 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite
) throws Exception {
    boolean isTestPage = pageData.hasAttribute("Test");
    if (isTestPage) {
        WikiPage testPage = pageData.getWikiPage();
        StringBuffer newPageContent = new StringBuffer();
        includeSetupPages(testPage, newPageContent, isSuite);
        newPageContent.append(pageData.getContent());
        includeTeardownPage(testPage, newPageContent, isSuite);
        pageData.setContent(newPageContent.toString());
    }
    return pageData.getHtml;
}

나의 답 : wikipedia에서 어떤 조건에 따라 해당 화면을 불러오거나 조건이 만족되지 않으면 새로운 페이지를 만드는 것 같다.

함수가 설정(setup)페이지와 해제(teardown)페이지를 테스트 페이지에 넣은 후 해당 테스트 페이지를 HTML로 렌더링한다는 것을 유추할 수 있다. 파악하기 쉬워진 이유가 뭘까?

작게 만들어라!

정확한 근거나 증거를 대기는 곤란하지만 함수는 작으면 작을수록 좋다.

함수는 하나만 표현해야 한다. 이를 바탕으로 위의 코드 2ㅇ도 더 줄일 수 있다.

코드 3

1
2
3
4
5
6
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
    if (isTestPage(pageData))
        includeSetupAndTeardownPages(pageData, isSuite);
    return pageData.getHtml();
}

블록과 들여쓰기

가급적 if, else, while에 들어가는 블록은 한줄로 제한하라. 이를 바탕으로 바깥을 감싸는 함수(enclosing function)도 작게 하라. 중첩구조랄 최대한 지양하라.

한 가지만 해라!

코드 1

  1. 버퍼 생성
  2. 페이지 가져오기
  3. 상속된 페이지 검색
  4. 경로 렌더링
  5. 문자열 덧붙이기
  6. HTML 생성

코드 3

  1. 설정 페이지와 해제 페이지를 테스트 페이지에 넣기

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.

그렇다면 코드 3은

  1. 설정인지 해제인지 페이지 파악
  2. 판단한 페이지를 넣는다.
  3. 페이지를 HTML로 렌더링한다.

로 이루어져 3가지 일을 하지 않을까? 추상화 수준이 지정된 함수 이름 아래에서 하나기 때문에 한 가지 작업만 한다. 이 함수를 TO 문단으로 기술할 수 있다.

TO RenderPageWithSetupsAndTeardowns, 페이지가 테스트 페이지인지 확인한 후 테스트 페이지라면 설정 페이지와 해제 페이지를 넣는다. 테스트 페이지든 아니든 페이지를 HTML로 렌더링한다.

코드 2 : 추상화 수준이 2. 따라서 코드 3으로 축소 가능했던 것

함수가 여러 가지 일을 하는 지 확인하는 또 다른 방법 : 의미 있는 이름으로 다른 함수를 추출할 수 있는지 확인하는것!

읽고 나서

점차 내 실력이 늘어갈 수록 함수를 적게 만들어서 다양한 기능을 구현할 수 있게 하는 것이 중요하다고 생각했다. 또한 다른 사람의 코드를 보면서 굉장히 긴 함수를 보고 이해를 못할 때 갈 길이 참 멀다는생각을 많이 했는데 좋을 게 하나도 없는 코드였다. 어려운 것을 쉬워 보이게 하는 것이 가장 잘하는 것이고, 어렵고 다양한 미사여구보다 누구나 알아들을 수 있게 표현하는 사람이 달변가인것처럼 코드도 마찬가지인 것 같다.

새로 배운 단어

FitNesse

웹 기반의 일반 통합 테스트 프레임워크. 소프트웨어 개발자와 이해 관계자들이 협력하여 소프트웨어 요구사항을 적어 놓은 ‘위키’페이지를 사용하여 테스트 케이스를 생성하고 실행하는데 사용. 시스템 전체의 기능을 테스트하는데 개발자와 비개발자 모두 사용

JUnit

Java 프로그래밍 언어에 대한 단위 테스트를 작성하는 프레임워크. 개발자가 코드의 개별 부분이 의도한 대로 작동하는지 확인하는데 사용

TO

LOGO 언어에서 사용하는 키워드. 파이썬에서 def와 똑같다.

추상화

복잡성을 관리하고 이해하기 쉽게 만드는 핵심적인 개념. 복잡한 시스템, 디자인, 객체, 함수 등을 단순화하고, 필요한 기능에만 초점을 맞추기 위해서이다. 함수에서 추상화수준이 높다는 것은 여러 가지 작업을 수행한다는 뜻이고, 낮다는 것은 하나의 작업만 수행한 다는 것을 의미한다. 추상화 수준이 낮으면 단순하고, 재사용성이 높으며, 테스트가 용이하다.