ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CleanCode 요약] 3.함수
    개발서적 공부 2024. 1. 21. 14:53
    반응형

    3.1예시코드

    public static String testtableHtml(PageData pageData, boolean includeSuiteSetup) throws Exception{
        WikiPage wikiPage = pageData.getWigiPage();
        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("TearDown", wikiPage);
            if(teardown != null){
                WikiPagePath tearDownPath = wikiPage.getPageCrawler().getFullPath(tearDown);
                String tearDownPathName = 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){
                WikiPath pagePath = suiteTeardown.getPageCrawler().getFullPath(suiteTeardown);
                String pagePathName = PathParser.render(pagePath);
                buffer.append("!include-teardown.").append(pagePathName).append("\n");
            }
        }
        pageData.setContent(buffer.toString());
        return pageData.getHtml();
    }

     

     

    !!! 깨끗한 함수를 만드는 방법

     

       1.작게 만든다

       2.1가지 기능만 수행한다.

       3.함수당 추상화 수준은 하나로

    한가지 작업만 수행하기 위해서는 블록 내 추상화 수준이 동일해야 한다.

     

    3-1 코드를 1, 2, 3 규칙에 따라 간략화 한 코드

    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())
            includeTeardownPages(testPage,newPageContent, isSuite);
            pageData.setContent(newPageContent.toString());
        }
        return pageData.getHtml();
    }
    
    
    /*
     *  위 메서드를 더욱 간략화 하면 아래와 같이 표현할 수 있다.
     * 
    */
    public static String moreRefectored(PageData pageData, boolean isSuite) throws Exception{
        if(isTestPage(pageData)){
            includeSetupAndTearDownPages(pageData, isSuite);
        }
        return pageData.getHtml();
    }

     

       4. Switch

    여러 분기를 처리하는 Switch문을 사용해야 하는 경우,
    
    Switch문을 추상팩토리에 숨겨 "가급적" 노출시키지 않는 방식을 사용하는 것이 좋다.
    
    
    
    예시
    public Money calculatePay(Employee e)throws InvalidEmployeeType{
        
        switch(e.type){
            case COMMISSIONED:
                return calculateCommissionedPay(e);
            case HOURLY:
                return calculateSalariedPay(e);
            case SALARIED:
                return calculateSalariedPay(e);
            default:throw new InvalidEmployeeType(e.type);
        }
    }
    
    /*
     * 문제점 : 코드가 길고 새 직원유형을 추가할 때 마다 길어질 수 있음.
     * 한가지 작업만 수행하지 않음.
     * Single Responsibility Principle을 위반한다.
     * Open Closed Principle을 위반한다. 새 직원유형을 추가할 때 마다 변경이 일어나기 때문이다.
     * 
     * 가장 치명적인 문제는 위 함수구조와 동일한 함수가 무한정 존재할 수 있다는 것이다.
     * 
    */
    
    //-> 변경 후
    
    public abstract class Employee {
        public abstract boolean isPayday();
    
        public abstract Money calculatePay();
    
        public abstract void deliverPay(Money pay);
    }
    
    // --------------------------------------------------------
    public interface EmployeeFactory {
        public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
    }
    
    // --------------------------------------------------------
    public class EmployeeFactoryImpl implements EmployeeFactory {
        public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
            switch(r.type){
                case COMMISSIONED: 
                    return new CommissionedEmployee(r);
                case HOURLY: 
                    return new HourlyEmployee(r);
                case SALARIED:
                    return new SalariedEmployee(r);
                default:
                    throws new InvalidEmployeeType(r.type);
            }
        }
    }

     

       5.서술형 이름을 쓴다.

    길더라도 서술적인 이름이 짧고 어려운 이름과 장황한 주석이 함께하는 것보다 낫다.
    
    서술적인 이름은 설계를 명확하게 표현하는데 도움을 준다. 단 작명시에는 일관성이 있어야 한다.

       6.파라미터

    이상적인 파라미터 개수는 0개이다. 많아질 수록 바람직 하지 않다.
    
    파라미터를 1개 받아 처리하는 이벤트함수의 경우, 처리이벤트라는 목적이 분명히 드러나야 한다. 이름과 문맥에 주의한다.
    
    변환함수가 필요하다면 변환값은 return 으로 돌려받는 형식으로 한다. 변환 형태를 유지하기 때문에 이해하기 수월하다.
    
    파라미터로 boolean을 넘기는 것은 최대한 하지 않는다.
    
    불가피하게 파라미터가 2-3개, 그이상이 필요하다면
    
    클래스변수로 묶어 가급적 개수를 줄이는 방식으로 처리한다.
    
    이과정에서 이름을 붙여야 하므로 개념을 표현할 수 있기 때문이다.
    
    여러 파라미터를 사용한다면 함수명에 키워드를 추가하여 순서에 혼동이 없도록 한다.
    
    ex) assertExpectedEqualsActual(expected, actual)

       7.Side Effect 최소화하라.

    기대되는 동작 외 다른 동작을 수행시키지 말 것.

       8.명령과 조회는 따로따로

    독자 입장에서 혼란을 줄 수 있는 코드는 피한다.
    
    ex)
    
    public boolean set(String attribute, String value);
    
    if (set("username", "unclebob"){….}
    
    보다는
    
    if (attributeExsits("username"){
        setAttribute("username", "unclebob");
        …
    }
    
    과 같이 표현하는 것이 훨씬 직관적이다.

       9.오류코드보단 예외

    커스텀으로 설정한 오류코드보다는 try catch를 활용한 예외처리가 코드를 훨씬 명료하게 표현한다.
    
    하지만 try catch는 정상동작과 오류동작을 뒤섞어 혼란을 줄 여지가 있으므로 따로 별도함수로 분리하여 처리하는 편이 좋다.
    
    try{
        deletePage(page);
        registry.deleteReference(page.name);
        configKeys.deleteKey(page.name.makeKey());
    } catch (Exception e){
        logger.log(e.getMessage());
    }
    
    를 아래와 같이
    
    public void delete(Page page){
        try{
            deletePageAndReferences(page);
        } catch(Exception e){
            logError€;
        }
    }
    
    private void deletePageAndReferences(Page page) throws Exception{
        deletePage(page);
        registry.deleteReference(page.name);
        configKeys.deleteKey(page.name.makeKey());
    }
    
    private void logError(Exception e){
         logger.log(e.getMessage());
    }

     

       10.반복을 피하라

       11.How to?

    글쓰기처럼 생각을 먼저 기록한 후 읽기 좋게 다듬는다.
    
    즉, 우선 초안 로직을 작성한 후, 코드를 다듬고 함수를 분리하며 이름을 바꾸고 중복을 제거한다.
    메서드를 줄이고 순서를 바꾼다. 이러한 과정을 통해 코드를 정리하는 것이 핵심이다.

     

    궁극적인 목표는 시스템이라는 이야기를 풀어나가는 것이다.

    작성하는 함수가 분명하고 정확한 언어로 깔끔하게 맞아떨어져야만 이야기를 풀어가기 수월해진다는 것을 기억할 .

     

    서적에서 제시한 위 규칙을 총괄하여 예시 3-1 코드를 정리한 최종 코드

    public class SetupTeardownIncluder {
        private PageData pageData;
        private boolean isSuite;
        private WikiPage testPage;
        private StringBuffer newPageContent;
        private PageCrawler pageCrawler;
    
        public static String render(PageData pageData) throws Exception {
            return render(pageData, false);
        }
    
        private SetupTeardownIncluder(PageData pageData) {
            this.pageData = pageData;
            testPage = pageData.getWikiPage();
            pageCrawler = testPage.getPageCrawler();
            newPageContent = new StringBuffer();
        }
    
        private String render(boolean isSuite) throws Exception {
            this.isSuite = isSuite;
            if (isTestPage()) {
                includeSetupAndTeardownPages();
            }
            return pageData.getHtml();
        }
    
        private boolean isTestPage() throws Exception {
            return pageData.hasAttribute("Test");
        }
    
        private void includeSetupAndTearDownPages() throws Exception {
            includeSetupPages();
            includePageContent();
            includeTeardownPages();
            updatePageContent();
        }
    
        private void includeSetupPages() throws Exception {
            if (isSuite)
                includeSuiteSetupPage();
            includeSetupPage();
        }
    
        private void includeSuiteSetupPage() throws Exception {
            include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
        }
    
        private void includeSetupPage() throws Exception{
            include("Setup", "-setup")
        }
    
        private void includePageContent() throws Exception {
            newPageContent.append(pageData.getContent());
        }
    
        private void includeTeardownPages() throws Exception {
            includeTeardownPages();
            if (isSuite)
                includeSuiteTeardownPage();
        }
    
        private void includeTeardownPage() throws Exception {
            include("TearDown", "-teardown");
        }
    
        private void includeSuiteTeardownPage() throws Exception {
            include(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
        }
    
        private void updatePageContent() throws Exception {
            pageData.setContent(newPageContent.toString());
        }
    
        private void include(String pageName, String arg) throws Exception {
            WikiPage inheritedPage = findInheritedPage(pageName);
            if (inheritedPage != null) {
                String pagePathName = getPathNameForPage(inheritedPage);
                buildIncludeDirective(pagePathName, arg);
            }
        }
    
        private WikiPage findInheritedPage(String pageName) throws Exception {
            return PageCrawlerImpl.getIngeritedPage(pageName, testPage);
        }
    
        private String getPathNameForPage(WikiPage page) throws Exception {
            WikiPagePath pagePath = pageCrawler.getFullPath(page);
            return PathParser.render(pagePath);
        }
    
        private void buildIncludeDirective(String pagePathName, String arg) {
            new PageContent.append("\n !include")
                    .append(arg)
                    .append(" .")
                    .append(pagePathName)
                    .append("\n");
        }
    }
    반응형

    '개발서적 공부' 카테고리의 다른 글

    [Clean Code 요약] 2. 의미있는 이름  (0) 2024.01.21
    [Clean Code 요약] 1. 클린코드  (0) 2024.01.21

    댓글

Designed by Tistory.