본문 바로가기
책 리뷰/클린코드

클린코드 - 3장

by eess 2024. 8. 26.

🔖 3장 : 함수

읽은 날짜 : 2024.08.26

지은이 : 로버트 C. 마틴

출판사 : 인사이트

 

기억하고 싶은 내용

의도를 분명히 표현하는 함수를 만드는 방법

1. 작게 만들어라

  • 길이가 짧을수록 좋다.

 

2. 한 가지 일만 해라

  • 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.

 

3. 하나의 함수 내의 추상화 수준을 동일하게 만들어라

  • 코드는 위에서 아래로 이야기처럼 읽혀야 한다.
  • 위에서 아래로 읽을 때 추상화 수준이 한 번에 한 단계씩 낮아지면 읽기 쉬워진다.

 

4. Switch 문을 추상 팩토리에 숨겨라

  • 각 case 조건에 대한 구현을 캡슐화하고, 이를 추상 팩토리를 사용하여 동적으로 선택할 수 있도록 한다.

 

5. 서술적인 이름을 사용해라

  • 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
  • 서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.

 

6. 이상적인 함수 매개변수의 개수는 2개 이하 [참고]

  • 매개변수로 플래그를 사용하지 말자.
    • 플래그를 사용하는 것 자체가 함수가 한 가지 이상의 역할을 하고 있다는 뜻이다.
    • boolean 기반으로 함수가 실행되는 코드가 나뉜다면 함수를 분리하자.
  • 매개변수가 2~3개 필요하다면 클래스 변수로 선언해 개념을 묶는다.
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Pointer center, double radius);
  • 매개변수가 1개인 함수는 함수와 매개변수가 동사-명사 쌍을 이루면 이해하기 쉽다.
    • write(name) ➡️ writeField(name)
  • 함수 이름에 매개변수 이름을 넣으면 매개변수 순서를 기억하지 않아도 돼서 이해하기 쉽다.
    • assertEquals(expected, actual) ➡️ assertExpectedEqualsActual(expected, actual)

 

7. 부수 효과를 조심해라

  • 예측 가능하고, 외부 상태에 의존하지 않도록 하기 위해 다음을 조심하자. [참고1] [참고2]
    • 함수로 넘어온 매개변수를 수정하지 않는다.
    • 함수 내부에서 전역 변수를 수정하지 않는다.
// Bad
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

// Good
const addItemToCart = (cart, item) => {
  return [...cart, { item, date : Date.now() }];
};
  • 특정 상황에서만 호출하게 되지 않도록 한 가지 일만 수행하게 한다.
    • 예를 들어 checkPassword()라는 함수가 있다면, 사용자의 비밀번호를 확인하고 true/false를 반환할 것을 기대한다.
    • 이외에 비밀번호가 유효한 경우 세션을 초기화하는 코드가 포함되어 있다면, 이름만 보고 호출했을 때 세션 초기화에 대한 부수 효과가 발생한다.

 

8. 명령과 조회를 분리하라

  • 함수는 상태를 변경하거나 정보를 반환하거나 둘 중 하나만 해야 한다.

 

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

  • 오류 코드를 반환하면 호출자는 오류 코드를 곧바로 처리해야 한다는 문제가 있다.
  • 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리될 수 있다.
  • 그러나 try/catch 블록은 정상 동작과 오류 처리를 뒤섞는다. 별도의 함수로 추출하면 가독성이 올라간다.
  • 오류 처리도 한 가지 작업만 해야 한다.
// Bad
if (deletePage(page) == E_OK) {
  if (registry.deleteReference(page.name) == E_OK) {
    if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
      logger.log("page deleted");
    } else {
      logger.log("configKey not deleted");
    }
  } else {
    logger.log("deleteReference from registry failed");
  }
} else {
  logger.log("delete failed");
  return E_ERROR;
}

// Good
try {
  deletePageAndAllReferences(page);
}
catch (Exception e) {
  logError(e)
}

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

private void logError(Exception e) {
  logger.log(e.getMessage());
}

 

오늘 읽은 소감

'진짜 목표는 시스템이라는 이야기를 풀어나가는 데 있다'는 말이 크게 와닿았습니다. 

위에서 아래로 이야기처럼 읽히면서, 한 가지 일을 잘 해내는 함수를 작성하는 것이 중요하다는 것을 알게 되었습니다. 

또한, 당장 함수를 다 분리해내지 못하더라도, 이름을 의미있게 짓고 함수의 크기를 작게 만드려고 하다보면 가독성은 저절로 올라간다는 점을 이해하게 되었습니다.

 

궁금한 내용 & 잘 이해되지 않는 내용

SRP(Single Responsibility Principle)

  • "한 가지 이유로만 변경되어야 한다."
    • 클래스나 모듈은 단 하나의 변경 이유만을 가져야 합니다. 즉, 클래스가 변경되어야 하는 이유는 딱 하나여야 합니다.
    • 책임은 '왜 변경되는가'에 따라 구분되어야 하며, 변경되는 이유가 여러 개라면 클래스가 여러 개의 책임을 가지고 있다고 판단됩니다.

OCP(Open-Closed Principle)

  • "소프트웨어의 확장에는 열려 있고, 수정에는 폐쇄적이어야 한다."
    • 새로운 기능이나 동작을 추가할 때에는 기존 코드를 변경하지 않고 확장할 수 있어야 합니다. 모듈이나 클래스는 새로운 요구사항이나 기능이 추가될 때에 대응하여 확장 가능해야 합니다.
    • OCP는 주로 다형성을 활용하여 달성됩니다. 즉, 인터페이스와 추상화를 통해 새로운 기능을 추가하거나 변경할 수 있도록 설계되어야 합니다.

추상 팩토리

  • 객체 생성에 관련된 인터페이스를 이용하여 여러 종류의 연관된 객체를 생성할 수 있도록 하는 디자인 패턴. 구체적인 클래스의 인스턴스를 생성하는 것이 아니라, 관련된 여러 종류의 객체들을 생성하기 위한 인터페이스를 만드는 것입니다.
  • 언제 사용할까?
    • 여러 종류의 연관된 객체를 생성해야 할 때
    • 객체의 생성과 조립이 서로 관련되어 있을 때
  • 구성 요소
    • AbstractFactory : 객체 생성에 관련된 인터페이스를 정의한다. 일반적으로는 여러 종류의 "생성 메서드"를 제공합니다.
    • ConcreteFactory : AbstractFactory를 구현한 클래스. 특정한 종류의 객체를 생성하는 책임이 있습니다.
    • AbstractProduct : 생성되는 객체들에 대한 인터페이스를 정의한다. 여러 종류의 제품에 대한 공통의 인터페이스를 제공합니다.
    • ConcreteProduct : AbstractProduct를 구현한 클래스. 구체적인 객체의 생성과 구현을 담당합니다.
    • Client : 추상 팩토리를 이용하여 객체를 생성하고 사용하는 클라이언트 코드. 클라이언트는 구체적인 팩토리와 제품 클래스를 몰라도 됩니다.

명령 / 조회 분리(Command Query Separation, CQS)

  • 함수는 명령(command) 또는 조회(query) 중 하나만 수행해야 한다는 원칙.
  • command는 시스템의 상태를 변경하는 작업을 수행하고, query는 시스템의 상태를 읽고 반환하는 작업을 수행합니다.
  • 함수의 의도가 명확해지므로 가독성은 올라가지만, 작은 규모의 프로그램에서는 오버 엔지니어링이 될 수 있습니다.
  • CQS(Command Query Separation) vs CQRS(Command Query Responsibility Segregation)
    • CQS는 함수 레벨에서 적용하는 원칙이고, CQRS는 시스템 차원에서 명령과 조회를 분리하는 아키텍처를 의미합니다.
    • 둘 다 작은 규모의 프로그램, 시스템에서 오버엔지니어링이 될 수 있습니다.

AOP(Aspect Oriented Programming)

  • 코드의 횡단 관심사(cross-cutting concerns)를 모듈화하고 분리하기 위한 프로그래밍 패러다임 중 하나입니다.
  • 횡단 관심사는 여러 모듈이나 클래스에 영향을 미치는 공통적인 부분(e.g. 로깅, 보안, 트랜잭션 관리 등)을 말합니다.
  • Spring에서는 AOP, AspectJ 등의 AOP 프레임워크가 있고, NestJS에서는 Interceptor, Custom Decorator 등이 있습니다.
  • 클린 코드 책에서는 중복을 없애기 위한 방법 중 하나로 언급되었습니다.

COP(Component Oriented Programming)

  • 소프트웨어를 구성하는 주요 단위를 "컴포넌트"로 보고, 이러한 컴포넌트들을 조립하여 전체 소프트웨어를 구축하는 프로그래밍 패러다임입니다.
  • 각각의 컴포넌트는 재사용 가능하고 독립적으로 개발될 수 있는 단위입니다.

 

참고

clean-code-javascript

💠 추상 팩토리(Abstract Factory) 패턴 - 완벽 마스터하기

명령-조회 분리(CQS) - 간단하고 강력한 디자인 패턴

 

 

 

'책 리뷰 > 클린코드' 카테고리의 다른 글

클린코드 - 6장  (0) 2024.09.04
클린코드 - 5장  (1) 2024.08.29
클린코드 - 4장  (0) 2024.08.27
클린코드 - 2장  (0) 2024.08.19
클린코드 - 1장  (0) 2024.08.14