[클린 코드] 함수

함수

작게 만들어라

함수를 규칙은 작게 만드는 것

  • if문, while문 등에 들어가는 블록은 한 줄
  • 이 블록에서 다른 함수를 부른다.
  • 함수의 이름이 적절하다면 이해하기 훨씬 쉬워진다.

중첩 구조가 생길만큼 함수가 커지면 안된다는 의미

한 가지만 해라

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

  • 그 한가지를 잘해야 한다.
  • 예시 코드)
      public static String renderPageWithSetupsAndTeardowns(
      PageData pageData, boolean isSuite) throws Exception {
      if (isTestPage(pageData))
          includeSetupAndTeardownPages(pageData, isSuite);
          return pageData.getHtml();
      }
    
    1. 페이지가 테스트 페이지인지 판단
    2. 설정 페이지와 해제 페이지를 넣는다.
    3. 페이지를 HTML로 렌더링한다.
  • 지정된 함수 이름 아래 추상화 수준이 하나이다.
  • 의미를 유지하며 해당 코드를 더 줄이기는 불가능
  • 다양한 섹션으로 나누기 어려울 때까지 나누어야 한다.

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

함수가 한 가지 작업만 하기 위해선 함수 내 모든 문장의 추상화 수준이 동일해야함

  • 한 코드 내에서 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다.
  • 위에서 아래로 이야기처럼 읽히는 코드가 좋다: 내려가기 규칙

서술적인 이름을 사용하라

코드를 읽으며 짐작한 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드로 불러도 된다.

  • 한 가지만 하는 작은 함수에 좋은 이름을 붙이면 이 원칙을 달성할 수 있다.
  • 이름이 길어도 짧고 어려운 이름보다는 낫다.
  • 서술적인 이름을 사용한다면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
  • 모듈내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.

함수 인수

가장 이상적인 인수 개수는 0개

  • 4개 이상일 경우는 특별한 이유가 있어도 사용하지 않는 것이 좋다.

단항형식의 인수

함수에 인수를 1개 넘기는 경우

  1. 인수에 질문을 던지는 경우
  2. 인수를 뭔가로 변환해 반환하는 경우
  3. 이벤트 함수: 함수 호출을 이벤트로 해석해 입력 인수로 시스템 상태를 바꾸는 경우
    • 이경우가 아니라면 단항 함수는 가급적 피한다.

플래그 인수

함수로 bool값을 넘기는건 너무 추하다.

  • 하지말자.

이항 함수

이항함수가 적절한 경우

  1. 좌표계의 점을 넘기는 경우
    • 가능하면 단항함수로 바꾸어야 한다.

삼항 함수

신중히 고려해야 한다.

인수 객체

인수가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언할 가능성을 짚어본다.

  • 좌표계의 점 x, y를 따로 넘기는 것 보다 Point 객체로 넘기는 것이 낫다.

인수 목록

인수 개수가 가변적인 함수가 필요할 경우가 있다.

  • String.Format

동사와 키워드

함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름이 필수

  • 단항 함수는 함수와 인수가 동사/명사와 쌍을 이루어야 한다.
  • 함수 이름에 키워드(인수 이름)을 추가해 인수 순서를 기억할 필요가 없도록 한다.

부수 효과를 일으키지 마라

한 함수에서는 딱 한가지만 수행하도록 해야한다.

  • 예시

      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.initalize(): 함수가 일으키는 부수효과 - 의도치 않게 세션 정보가 날아갈 수도 있다.

출력 인수

출력인수는 일반적으로 피하는 것이 좋다.

  • 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.

명령과 조회를 분리하라

함수는 뭔가를 수행하거나 답하거나 둘 중 하나만 해야 한다.

  • 객체 상태를 변경하거나 객체 정보를 반환하거나
  • 둘 다 하면 혼란을 초래한다.

오류코드보다 예외를 사용하라

명령 함수에서 오류 코드를 반환하는 것은 명령/조회 분리 규칙을 미묘하게 위반한다

  • ex) if(deletePage(page) == E_OK)
    • 여러 단계로 중첩되는 코드를 야기
    • 오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야 한다는 문제

Try/Catch 블록 뽑아내기

try/catch 블록은 별도 함수로 뽑아내는 편이 좋다

  • 예시 코드

      public void delete(Page page) {
          try {
              deletePageAndAllReferences(page);
          } catch (Exception e) {
              logError(e);
          }
      }
    
      private void deletePageAndAllReferences(Page page) throws Exception { 
          deletePage(page);
          registry.deleteReference(page.name); 
          configKeys.deleteKey(page.name.makeKey());
      }
    
      private void logError(Exception e) { 
          logger.log(e.getMessage());
      }
    

오류 처리도 한 가지 작업

오류처리 하는 함수는 오류만 처리해야 한다.

반복하지 마라

중복은 소프트웨에에서 모든 악의 근원이다.

  • 만은 원칙과 기법이 중복을 없애거나 제어할 목적으로 나왔다.
  • 구조적 프로그래밍, AOP, COP 모두 어떤 면에서 중복 제거 전략 중 하나

구조적 프로그래밍

모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다.

  • 함수는 return 문이 하나여야 한다.
  • 루프 안에서 break, continue, goto를 사용하면 안된다.
  • 하지만 함수가 작다면 return, break, continue를 여러차례 사용해도 괜찮다.

함수를 어떻게 짜죠?

처음에는 길고 복잡하며 들여쓰기 단계도 많고 중복된 루프도 많다.

  • 이 코드를 빠짐없이 테스트하는 단위 테스트 케이스를 만든다.
  • 이후 코드를 다듬고 함수를 만들고 이름으 바꾸고 중복을 제거한다.
  • 메서드를 줄이고 순서를 바꾼다.
  • 전체 클래스를 쪼개기도 한다.
  • 이 와중에 코드는 항상 단위 테스트를 통과해야 한다.

처음부터 바로 짜지지 않는 것이 정상이다..

homebdy
homebdy 개발에 이제 막 발 담근 사람. 개발에 이제 막 발 담근 사람. 개발에 이제 막 발 담근 사람. 개발에 이제 막 발 담근 사람.
comments powered by Disqus