Study/클린코더스 강의

클린코더스 2강 - Function

voider 2021. 2. 28. 18:01

💡백명석 님의 클린 코더스 강의를 듣고 요약한 자료입니다.

목차

1, 2강 OOP
3, 4강 Function
5강 Function Structure
6강 Form

Function

함수는 한 가지 일만 해야 한다.

한 가지 일만 하는 함수를 만들기 위해서는 indentation, while, mested, if등이 없어야 한다.

잘 지어진 서술적인 긴 이름을 갖는 많은/작은 함수들로 유지해야 한다.

The First Rule of Functions

  1. 함수는 더 이상 작을 수 없을 만큼 작아야 한다.

따라서 큰 함수를 보면 클래스로 추출할 생각을 해야 한다.

  • 블록이 적어야 한다.--> 이 말은 블록 안의 또다른 블록은 계속 함수로 추출해야 한다는 뜻.
  • if-else, while등 내부 블록은 한 줄이어야 한다.
  • Indenting은 최대한 적게. 함수는 중첩 구조를 가질 만큼 크면 안 된다. 길어야 한 두 단계여야 한다.

함수를 작게 만들어야 하는 이유

  1. 읽기 쉬워진다.
  2. 이해하기 쉬워진다.
  3. 함수가 의도를 잘 드러낸다.
  • 1이 가능하면 2-3은 따라오는 것이니 1이 가장 중요하다.

예제로 Refactoring 해보기

리팩토링 전 FitnessExample

public class FitnessExample {
    public 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 = wikiPage.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("TearDown", wikiPage);
            if (teardown != null) {
                WikiPagePath tearDownPath = wikiPage.getPageCrawler().getFullPath(teardown);
                String tearDownPathName = PathParser.render(tearDownPath);
                buffer.append("!include -teardown .").append(tearDownPathName).append("\n");
            }
            if (includeSuiteSetup) {
                WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage);
                if (suiteTeardown != null) {
                    WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteTeardown);
                    String pagePathName = PathParser.render(pagePath);
                    buffer.append("!include -teardown .").append(pagePathName).append("\n");
                }
            }
        }

        pageData.setContent(buffer.toString());
        return pageData.getHtml();
    }
}

리팩토링 후 FitnessExample

매우 길었던 testableHtml()메서드를 클래스로 추출했다. 그 다음 하나의 메서드에서 모두 처리하던 것을 여러 메서드로 분산했다. 그럼으로써 코드는 길어졌지만 훨씬 읽기 쉽고 복잡하지 않은 코드가 되었다.

public class FitnessExample {
    public String testableHtml(PageData pageData, boolean includeSuiteSetup) throws Exception {
        return new TestableHtmlBuilder(pageData, includeSuiteSetup).invoke();
    }

    private class TestableHtmlBuilder {
        private PageData pageData;
        private boolean includeSuiteSetup;
        private WikiPage wikiPage;
        private StringBuffer buffer;

        public TestableHtmlBuilder(PageData pageData, boolean includeSuiteSetup) {
            this.pageData = pageData;
            this.includeSuiteSetup = includeSuiteSetup;
            buffer = new StringBuffer();
            wikiPage = pageData.getWikiPage();
        }

        public String surround() throws Exception {
            if (isTestPage()) {
                includeSetUps();
                buffer.append(pageData.getContent());
                includeTearDowns();
            }
            pageData.setContent(buffer.toString());
            return pageData.getHtml();
        }

        private boolean isTestPage() throws Exception {
            return pageData.hasAttribute("Test");
        }

        private void includeTearDowns() throws Exception {
            includeInherited("teardown", "TearDown");
            if (includeSuiteSetup)
                includeInherited("teardown", SuiteResponder.SUITE_TEARDOWN_NAME);
        }

        private void includeSetUps() throws Exception {
            if (includeSuiteSetup)
                includeInherited("setup", SuiteResponder.SUITE_SETUP_NAME);
            includeInherited("setup", "SetUp");
        }

        private void includeInherited(String setup1, String suiteSetupName) throws Exception {
            WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(suiteSetupName, wikiPage);
            if (suiteSetup != null) {
                includePage(setup1, suiteSetup);
            }
        }

        private void includePage(String teardown1, WikiPage suiteTeardown) throws Exception {
            WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteTeardown);
            String pagePathName = PathParser.render(pagePath);
            buffer.append("!include -" + teardown1 + " .").append(pagePathName).append("\n");
        }
    }

🧐 tip 메서드 여러 곳에서 사용하는 변수를 Field Variable로 선언하면 메서드에서 또 다시 메서드를 추출할 때 파라미터를 달고 다니는 일을 하지 않아도 된다.

하지만 리팩토링 후에도 testableHtml()는 하나 이상의 일을 하는 것처럼 보인다.

  1. 페이지가 테스트 페이지인지 결정하고
  2. 테스트 페이지라면 setups, teardowns를 include하고
  3. HTML로 페이지를 렌더링한다.

이 세 가지 일을 하는 메서드의 이름은 surround다. surround는 이 세가지 동작의 추상화된 이름이다. 이 세 가지 일을 surround라는 하나의 일로 추상화해버렸기 때문에 세 가지 일을 한다고 볼 수 없다.

이 부분이 잘 이해가 가지 않는다면 로그인을 생각해보면 좋다.

  1. 아이디와 비밀번호를 입력 받는다.
  2. DB에 있는 정보를 대조한다.

하는 일은 두 가지지만 로그인이라는 하나의 일로 추상화할 수 있다.


Function Part2 주요 내용

One thing?

함수는 한 가지 일만 해야 하고 그 한 가지를 잘 해야한다.

도대체 그 한 가지는 무엇인가요?

호출하는 입장에서는 그것이 한 가지다.

하지만 그것을 읽는 사람 입장에서는 한 가지 일이 아니다.

코드에 대해 말할 때 가장 존중받아야 하는 사람은 그것을 읽을 혹을 읽게 될 사람이다.

읽기 쉬운 코드를 작성하는 일은 어려운 일이다. 하지만 나만 조금 어려우면 그걸 읽게 될 수많은 사람들이 편해진다. 남을 배려해서 코드를 쓸 줄 알아야 한다. 남 좋은 일 하는 것 같지만 결국 나에게 도움이 되는 일이다.

함수를 어떻게 작게 만들 것인가?

  • 주요 섹션을 함수로 추출
  • 서로 다른 추상화 레벨로 분리
    • 함수가 하나 이상의 추상화 레벨을 다루면 이 함수는 한 가지 이상의 일을 하는 것

하지만 추상화 레벨이란 것은 불분명하다.

Extract Till you drop

  • 더 이상 extract할 수 없을 때까지 extract하라.
  • extract할 코드를 가진 함수는 한 가지 이상의 일을 하는 것이다.
  • 4줄 이내의 함수로만 구성된 클래스
  • if, while 문 등에서 {}가 보이면 extract 대상
  • {}는 extract할 기회

'Study > 클린코더스 강의' 카테고리의 다른 글

클린코더스 6강 - Form  (0) 2021.03.30
클린코더스 5강 Function Structure #1  (0) 2021.03.10
클린 코더스 1, 2강 - OOP  (0) 2021.02.27