클린코드 TIL : 3장. 함수

Devil😈·2024년 1월 29일
1

CleanCode

목록 보기
3/10
post-thumbnail

클린코드에 나오는 코드를 Dart로 수정해보았습니다

작게 만들어라

중첩 구조가 생길만큼 함수가 커져서는 안된다. 들여쓰기가 1단, 2단 수준이어야 한다. 그렇다면 함수는 얼마나 작아야 하는가?

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

함수가 확실히 한 가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.

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

Switch 문

본질적으로 switch문은 N가지를 처리한다. switch문을 피하기는 어렵지만 다형성을 이용해서 각 switch문을 저차원 클래스에 숨기고 반복하지 않는 방법은 있다.

  1. 아래와 같은 추상 함수를 만든다
abstract class Employee {
  bool isPayday();
  Money calculatePay();
  void deliverPay(Money pay);
}
  1. EmployeeFactory 인터페이스도 abstract 클래스를 사용해서 구현한다
abstract class EmployeeFactory {
  Employee makeEmployee(EmployeeRecord r);
}
  1. Employee 인터페이스를 거쳐 호출하므로 switch 문을 숨길 수 있다.
class EmployeeFactoryImpl implements EmployeeFactory {
  @override
  Employee makeEmployee(EmployeeRecord r) {
    switch (r.type) {
      case EmployeeType.COMMISSIONED:
        return CommissionedEmployee(r);
      case EmployeeType.HOURLY:
        return HourlyEmployee(r);
      case EmployeeType.SALARIED:
        return SalariedEmployee(r);
      default:
        throw InvalidEmployeeType(r.type);
    }
  }
}

인수 (Arguments)

  • 이상적인 인수의 개수는 0개다
  • 다음은 1개고
  • 그 다음은 2개다
  • 3개는 가능한 피하는 편이 좋다
  • 4개 이상은 특별한 이유가 필요하다. (특별한 이유가 있어도 사용하면 안된다)

단항 함수

다음과 같은 경우에 많이 사용한다. 이 경우가 아니면 단항 함수는 가급적 피한다.

  1. 인수에 질문을 던지는 경우
    boolean fileExists("MyFile")

  2. 인수를 뭔가로 변환해 결과를 반환하는 경우
    InputStream fileOpen("MyFile") : String형 파일 이름을 InputStream으로 변환한다 (Java)

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

플래그 인수

쓰지마라. 함수로 bool 값을 넘기는 것은 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이다. (Flag가 참이면 이거 하고, 거짓이면 저거 해라)

이항 함수, 삼항 함수

인수의 순서를 헷갈릴 가능성이 크니 사용하지 말 것을 강조하고 있는데, 여전히 복잡하지만 named parameter를 사용하면 이런 문제는 없지 않을까?

단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다

  • 예를 들면 write(name)
    이거보다는 writeField(name)이 더 낫다

함수 이름에 인수 이름을 넣으면 인수 순서를 기억할 필요가 없어진다

  • assertExpectedEqualsActual(expected, actual)

그러니깐.. Dart에서는 named parameter를 쓰라고

부수 효과를 일으키지 마라

부수 효과(Side Effects), 즉 부작용(副作用)이다. 아래 Dart 코드는 userName과 password를 확인해서 올바르면 true를 반환하는데, 부수 효과로 Session.initialize()를 호출하고 있다. 함수 이름은 checkPassword인데 세션을 초기화하고 있으므로 사용자 인증 과정에서 기존 세션 정보를 지워버릴 위험이 있다.

class UserValidator {
  final Cryptographer cryptographer;

  UserValidator(this.cryptographer);

  bool checkPassword(String userName, String password) {
    User user = UserGateway.findByName(userName);
    if (user != User.nullUser) {
      String codedPhrase = user.getPhraseEncodedByPassword();
      String phrase = cryptographer.decrypt(codedPhrase, password);
      if (phrase == "Valid Password") {
        Session.initialize();
        return true;
      }
    }
    return false;
  }
}

명령과 조회를 분리하라

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

원래 함수는 하나만 하는 거라며? 굳이 안써도 되는 원칙아닌가 싶음

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

  • if - else로 오류 코드를 뽑아내는 코드 보다는 try-catch 가 낫다
  • try-catch 블록도 추하니까 최대한 별도 함수로 뽑아내는 편이 좋다
    아래와 같은 형식이다. 오류를 처리하는 함수는 오류만 처리해야 한다. (함수는 한 가지만 해야 한다)
void delete(Page page) {
  try {
    deletePageAndAllReferences(page);
  } catch (e) {
    logError(e);
  }
}

void deletePageAndAllReferences(Page page) {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
}

void logError(e) {
  logger.log(e.toString());
}

좋은 함수를 어떻게 만드나?

먼저 쓰고 읽기 좋게 다듬는다. 처음에는 대충 쓰고 이름을 바꾸고 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 이 모든 과정에서 단위 테스트를 통과해야 한다.

profile
얼굴셋 손여섯

0개의 댓글