Java - 함수

<클린코드>

Posted by Yan on September 13, 2021

함수 인수

  • 함수에서 이상적인 파라미터 개수는 0개. 적으면 적을 수록 좋고, 3개는 가능한 피하는 편이 좋다.
  • 4개 이상은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안된다.
  • 테스트 관점에서도 인수가 많을 수록 어렵다. 갖가지 인수 조합으로 함수를 검증하는 테스트 케이스를 작성해야 한다.
  • 인수가 3개를 넘어가면 인수마다 유효한 값으로 모든 조합을 구성해 테스트하기가 상당히 부담스러워진다.

단항 형식

  • 함수에 인수를 1개 넘기는 이유로 가장 흔한 경우
    • 인수에 질문을 던지는 경우 boolean fileExists("MyFile")
    • 인수를 뭔가로 변환해 결과를 반환하는 경우 InputStream fileOpen("MyFile")

이벤트

  • 이벤트 함수는 입력 인수만 있고 출력 인수는 없다.
  • 프로그램은 함수 호출을 이벤트로 해석해 입력 인수로 시스템 상태를 바꾼다.
  • passwordAttemptFailedNtimes(int attempts)

피해야하는 단항 함수

  • void includeSetupPageInto(StringBuffer pageText) -> 변환 함수에서 출력 인수를 사용하면 혼란을 일으킨다. 입력인수를 변환하는 함수라면 변환 결과는 반환값으로 돌려준다.
  • StringBuffer transform(StringBuffer in)StringBuffer transform(StringBuffer in)보다 좋다.

이항 함수

  • 이항 함수는 단항 함수보다 이해하기 어렵다
  • 이항함수가 적절한 경우 : Point p = new Point(0,0)
  • 이항함수는 인자의 순서를 기억해야 되는 것이 불편하다. assertEquals(Expected, actual)

삼항 함수

  • 삼항 함수는 이항 함수보다 이해하기 어렵다.
  • assertEquals(message, expected, actual) -> 매번 함수를 볼 때마다 주춤하게 만든다.

출력 인수

  • 일반적으로 우리는 인수를 함수의 입력으로 해석한다.

명령과 조회를 분리하라

  • 함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안된다.
  • 잘못된 예) public boolean set(String attribute, String value); : 이름이 attribute인 속성을 찾아 값을 value로 설정한 후 성공하면 true, 실패하면 false를 반환하는 함수
    • 사용하면 if(set("username", "Dua"))이런 식으로 쓰게 된다 -> set이라는 단어가 모호해서 어떤 동작을 하는지 헷갈림
    • ‘username을 Dua로 설정하고 성공하면 true’가 아닌, ‘username이 Dua로 설정되어 있다면’ 확인하는 코드 같아보이기도 함
    • 해결책은 명령과 조회를 분리해 혼란을 애초에 뿌리뽑는 방법
      1
      2
      3
      
      if (attributeExists("username")){
      setAttribute("username", "Dua");
      }
      

try-catch블록 뽑아내기

  • try/catch블록은 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다.
  • try/catch블록을 별도 함수로 뽑아내는 편이 좋다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void delete(Page page) {
  try {
    deletePageAndAllRefernces(page);
  } catch (Exception e) {
    logError(e);
  }
}

private void deletePageAndAllReference(Page page) throws Exception {
  deletePage(page);
  registry.deleteRefernce(page.name);
  configKey.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
  logger.log(e.getMessage());
}
  • delete함수는 모든 오류를 처리함
  • 실제로 페이지를 제거하는 함수는 deletePageAndAllReference
  • deletePageAndAllReference 함수는 예외를 처리하지 않음
  • 정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하는게 쉬워진다.

오류 처리도 한 가지 작업이다.

  • 함수는 한 가지 작업만 해야 한다.
  • 오류 처리도 한 가지 작업에 속한다.
  • 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.

Error.java 의존성 자석

  • 오류코드를 반환한다 = 클래스든 열거형 변수든 어디선가 오류 코드를 정의한다는 뜻

의존성 자석

1
2
3
4
5
6
7
8
public enum Error {
  OK,
  INVALID,
  NO_SUCH,
  LOCKED,
  OUT_OF_RESOURCES,
  WAITING_FOR_EVENT;
}
  • 다른 클래스에서 Error enum을 import해 사용
  • Error enum이 변하면 Error enum을 사용하는 클래스를 전부 다시 컴파일하고 다시 배치해야 함 -> Error클래스 변경이 어려워짐
  • 재컴파일/재배치가 번거롭기에 새 오류 코드를 추가하는 대신 기존 오류코드를 재사용함
  • 오류코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생됨 -> 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다. = OCP

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

그 예로,

  • 자료에서 중복을 제거할 목적으로 관계형 데이터베이스에 정규 형식을 만들었다.
  • 객체지향 프로그래밍은 코드를 부모 클래스로 몰아 중복을 없앤다.
  • 구조적 프로그래밍, AOPAspect Oriented Programming, COPCommponent Oriented Programming 모두 어떤 면에서는 중복 제거 전략이다.