🔖 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)
- 소프트웨어를 구성하는 주요 단위를 "컴포넌트"로 보고, 이러한 컴포넌트들을 조립하여 전체 소프트웨어를 구축하는 프로그래밍 패러다임입니다.
- 각각의 컴포넌트는 재사용 가능하고 독립적으로 개발될 수 있는 단위입니다.
참고
💠 추상 팩토리(Abstract Factory) 패턴 - 완벽 마스터하기
명령-조회 분리(CQS) - 간단하고 강력한 디자인 패턴