ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로버트 C.마틴의 클린코드(Clean Code) - 3) 함수
    개발 공부/Clean Code 2020. 12. 3. 20:15

     

     

    프로그래밍에서 기본이 되는 함수.

    혹시나 여태까지 정말 아무 생각 없이 함수를 작성하고 있지는 않았을까?

     

    과연 우아하고 깨끗한 함수란 무엇인가?

     

    3장 "함수"에서 그 해결책을 조금이나마 제시해주었습니다.

    차근차근 보시죠.

     

     

    작게 만들어라!


    함수를 만드는 첫째 규칙은 '작게!'다. 함수를 만드는 둘째 규칙은 '더 작게!'다.

     

    함수는 무조건 작게 만드는 게 좋다. 얼마나? 200줄? 100줄? 50줄????

    최소한 함수는 15줄 이내로 만드는 것이 베스트다.

     

    또한 함수에서는 들여 쓰기(indent)가 1단 혹은 2단을 넘어가서는 안된다.

    중첩 구조가 생길 만큼 함수가 커져서는 안 된다는 뜻이다.

     

    예를 들면, 아래 renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) 메서드는 너무 길다.

    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(); 
      }

     

    일단 if 문 안에 문장들이 너무 많다. 이를 하나로 묶어서 아래처럼 if 문의 블록을 한 줄로 줄일 수 있다.

    public static String renderPageWithSetupsAndTeardowns(
      PageData pageData, boolean isSuite) throws Exception { 
      if (isTestPage(pageData)) 
         includeSetupAndTeardownPages(pageData, isSuite); 
      return pageData.getHtml(); 
    }

    훨씬 깔끔하지 않은가?

    함수 하나의 길이가 길어지는 것보다, 차라리 여러 개의 함수로 잘게 쪼개서 함수의 개수를 늘리는 것이 더 낫다.

     

     

    한 가지만 해라!


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

     

    함수는 한 가지 일만을 해야 한다. 과연 한 가지란 어디까지인가? 저자는 함수에서의 한 가지를 다음과 같이 설명한다.

    지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.

     

    우리가 위에서 봤던 바로 이 코드

    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(); 
      }

    이 함수는 한 가지 일을 하고 있지 않다. 추상화 수준이 둘이다.

     

    함수가 하는 두 가지 일을 다음과 같이 나눠볼 수 있다.

    // 첫 번째 일
    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()); 
    } 

     

    그러므로 다음과 같이 함수가 한 가지 일만 하도록 줄여야 한다. 아래는 아까도 등장했던 함수이다.

    public static String renderPageWithSetupsAndTeardowns(
      PageData pageData, boolean isSuite) throws Exception { 
      if (isTestPage(pageData)) 
         includeSetupAndTeardownPages(pageData, isSuite); 
      return pageData.getHtml(); 
    }

    이제 renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite ) 메서드는 한 가지 일만 처리한다.

    첫 번째 일이었던 boolean isTestPage = pageData.hasAttribute("Test");isTestPage(pageData)라는 함수로 따로 분리하였다.

     

    따라서 함수가 '한 가지'만 하는지를 알려면 꼭 이 점을 명심하자.

    단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 것이다.

     

    함수를 작성했다면, 코드를 보면서 혹시나 다른 함수로 추출해 낼 수 있는 코드가 있지 않은지 잘 살펴보아야 한다.

    만약 그렇다면 그 함수는 한 가지 일만 처리하고 있는 것이 아니다. 꼭꼭 명심하자.

     

     

    내려가기 규칙


    코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.

    즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.

     

    위에서 사용했던 코드를 다시 가져와 보았다.

    public static String renderPageWithSetupsAndTeardowns(
      PageData pageData, boolean isSuite) throws Exception { 
      if (isTestPage(pageData)) 
         includeSetupAndTeardownPages(pageData, isSuite); 
      return pageData.getHtml(); 
    }
    
    public static boolean isTestPage( PageData pageData ) {
      return pageData.hasAttribute("Test");
    }

    renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite ) 메서드가 먼저 작성되고

    그다음에 여기서 사용되는 isTestPage( PageData pageData ) 메서드를 작성함으로써, 위에서 아래로 읽으면 자연스럽게 코드가 이해되는 구조이다. 또한 내려가면서 추상화 수준도 한 단계 씩 낮아지는 것을 확인할 수 있다.

     

    renderPageWithSetupsAndTeardowns에서 추상적으로 작성했던 isTestPage를 아래에서 구체적으로 표현했다고 생각하면 될 듯하다.

     

     

    함수 인수(Parameter)


    함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다. 3개(삼항)는 가능한 피하는 편이 좋다. 4개 이상(다항)은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안 된다.

     

     

    코드를 읽는 사람에게는 includeSetupPageInto(new PageContent) 보다, includeSetupPage()가 이해하기 더 쉽다.

    최선은 입력 인수가 없는 경우이며, 차선은 입력 인수가 1개뿐인 경우다.

     

    [함수에 인수 1개를 넘기는 이유]

     1. 인수에 질문을 던지는 경우

    boolean fileExists("MyFile")

     

     2. 인수를 뭔가로 변환해 결과를 반환하는 경우

    InputStream fileOpen("MyFile")

     

    이 두 가지 경우가 아니라면 단항 함수는 가급적 피하는 것이 좋다.

     

    [이항 함수]

    인수가 2개인 함수는 인수가 1개인 함수보다 이해하기 어렵다.

     

    하지만 인수 2개가 마치 한 몸인 것처럼 역할을 하는 경우에는 이항 함수가 좋다.

    Point point = new Point(0, 0);

     

    만약 아래처럼 선언하면 이게 도대체 뭔가 싶을 것이다.

    Point point = new Point(0);

     

    [삼항 함수]

    인수가 3개인 함수는 인수가 2개인 함수보다 훨씬 더 이해하기 어렵다.

    그래서 삼항 함수를 만들 때는 신중히 고려해야 한다. 

     

     

    부수 효과를 일으키지 마라!


    부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 것도 하니까.

     

    아래 코드에서 부수 효과를 발견할 수 있겠는가?

    public class UserValidator { 
      private Cryptographer cryptographer; 
      
      public boolean checkPassword(String userName, String password) { 
        User user = UserGateway.findByName(userName); 
        if (user != User.NULL) { 
        String codedPhrase = user.getPhraseEncodedByPassword(); 
        String phrase = cryptographer.decrypt(codedPhrase, password); 
          if ("Valid Password".equals(phrase)) { 
            Session.initialize(); 
            return true; 
          } 
        } 
        return false; 
      } 
    }

     

    여기서 함수가 일으키는 부수 효과는 Session.initialize(); 호출이다.

    위 코드에 적힌 checkPassword(String userName, String password) 함수는 이름 그대로 암호를 확인한다.

    이름만 봐서는 세션을 초기화한다는 사실이 드러나지 않는다. 그래서 함수 이름만 보고 함수를 호출하는 사용자는 사용자를 인증하면서 기존 세션 정보를 지워버릴 위험에 처한다.

     

    즉, checkPassword 메서드는 특정 상황에서만 호출이 가능하다. 세션을 초기화해도 괜찮은 경우에만 호출이 가능한 것이다. 그러므로 checkPassword에는 부수 효과가 숨겨져 있는 것이다.

     

    따라서 위 메서드의 이름은 checkPasswordAndInitializeSession 이 더 낫다.

    댓글

Designed by Tistory.