Java/클린코드
[클린코드] 함수
chjs93
2022. 2. 7. 23:57
- 클린코드 3장(함수) 을 읽어보며.
- 작게 만들어라!
- 한 가지만 해라
- 함수당 추상화 수준은 하나
- 이야기 처럼 읽여야 한다 (내려가기 규칙)
- 서술적인 이름을 사용해라 (하지만 동사)
- 함수 인수는 가능한 적게!
- 명령과 조회를 분리해라
- 오류 코드 보다는 예외를 사용하기 (오류처리도 한가지 작업)
- 반복하지 말자
- 단위 테스트를 작성해가면서 첫 장황한 함수를 개선해나가자
- 함수를 작성할때는.. SOLID 방식을 사용하자
- SRP (단일 책임 원칙) : 한 클래스는 하나의 책임만 가져야 한다.
- OCP (개방-폐쇄 원칙) : 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야한다.
- LSP (리스코프 치환 원칙) : 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
- ISP (인터페이스 분리 원칙) : 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
- DIP (의존성 역전 원칙) : 상위 모델은 하위 모델에 의존하면 안된다. 둘 다 추상화에 의존해야 한다. 추상화는 세부 사항에 의존해서는 안된다. 세부 사항은 추상화에 따라 달라진다.
- 간결한 함수 작성하기
- 작게 쪼갠다
- 타입에 대한 처리는 Factory 에서만
- 인수의 갯수는 0~2개, 3개 이상은 객체를 인자로 넘기기
- 안전한 함수
- 부수 효과(Side Effect) 없는 함수
- 함수 리팩터링
- 기능을 구현하는 서투른 함수를 작성한다.
- 테스트 코드를 작성한다.
- 리팩터링을 한다.
예시
SRP 단일책임 원칙
각 매장마다 할인 계산을 호출하는 로직이 Controller 에 있습니다.
@RestController
public class MarketQueryController {
@GetMapping("/api/calc/sail")
public Long calcSail(@requestParam MarketType marketType,
@RequestParam Long price) {
if (marketType == MarketType.COSTCO) {
CostcoPolicy policy = new CostcoPolicy();
return policy.calculate(price);
}
if (marketType == MarketType.TRADERS) {
TradersPolicy policy = new TradersPolicy();
return policy.calculate(price);
}
return null;
}
}
public class CostcoPolicy {
public Long calculate(Long price) {
MarketRule rule;
if (price < 50_000) {
rule = new MarketRule(0.6);
} else if (price < 200_000) {
rule = new MarketRule(0.5);
} else if (price < 600_000) {
rule = new MarketRule(0.4);
} else if (price < 900_000) {
rule = new MarketRule(0.5);
} else {
rule = new MarketRule(0.9);
}
return rule.calcSail(price);
}
}
위 코드는 코스트코 가격 정책에 의해서 할인율이 다르게 계산되는 함수입니다.
위 함수에서는 가격 조건에 따라 할인율이 다르게 적용되고, 할인이 계산이 되는 두가지 책임이 존재하므로 분리하면 다음과 같습니다.
public class CostcoPolicy {
public Long calculate(Long price) {
MarketRule rule = createMarketRule(price);
return rule.calcSail(price);
}
private MarketRule createMarketRule(Long price) {
MarketRule rule;
if (price < 50_000) {
rule = new MarketRule(0.6);
} else if (price < 200_000) {
rule = new MarketRule(0.5);
} else if (price < 600_000) {
rule = new MarketRule(0.4);
} else if (price < 900_000) {
rule = new MarketRule(0.5);
} else {
rule = new MarketRule(0.9);
}
return rule;
}
}
가격에 따라서 세일적용하는 부분과 세일을 계산하는 로직을 분리할 수 있습니다.
OCP (개방-폐쇄 원칙)
코스트코 정책처럼 트레이더스 가격 정책이 따로 있습니다.
public class CostcoPolicy {
public Long calculate(Long price) {
MarketRule rule = createMarketRule(price);
return rule.calcSail(price);
}
private MarketRule createMarketRule(Long price) {
MarketRule rule;
if (price < 50_000) {
rule = new MarketRule(0.6);
} else if (price < 200_000) {
rule = new MarketRule(0.5);
} else if (price < 600_000) {
rule = new MarketRule(0.4);
} else if (price < 900_000) {
rule = new MarketRule(0.5);
} else {
rule = new MarketRule(0.9);
}
return rule;
}
}
public class TradersPolicy {
public Long calculate(Long price) {
MarketRule rule = createMarketRule(price);
return rule.calcSail(price);
}
private Long calculate(Long price) {
MarketRule rule;
if (price < 50_000) {
rule = new MarketRule(0.6);
} else if (price < 200_000) {
rule = new MarketRule(0.5);
} else if (price < 600_000) {
rule = new MarketRule(0.4);
} else if (price < 900_000) {
rule = new MarketRule(0.5);
} else {
rule = new MarketRule(0.9);
}
return rule.calcMaxMarket(price);
}
}
위 두가지 클래스의 공통 되는 부분을 인터페이스로 묶어서 계산에 대한 기능을 공통화를 할 수 있습니다.
public interface MarketPolicy {
MarketRule createMarketRule(Long price);
default Long calculate(Long price) {
MarketRule rule = createMarketRule(price);
return rule.calcSail(price);
}
}
public class CostcoPolicy implements MarketPolicy {
public MarketRule createMarketRule(Long price) {
MarketRule rule;
if (price < 50_000) {
rule = new MarketRule(0.6);
} else if (price < 200_000) {
rule = new MarketRule(0.5);
} else if (price < 600_000) {
rule = new MarketRule(0.4);
} else if (price < 900_000) {
rule = new MarketRule(0.5);
} else {
rule = new MarketRule(0.9);
}
return rule;
}
}
public class TradersPolicy implements MarketPolicy {
public Long calculate(Long price) {
MarketRule rule;
if (price < 50_000) {
rule = new MarketRule(0.6);
} else if (price < 200_000) {
rule = new MarketRule(0.5);
} else if (price < 600_000) {
rule = new MarketRule(0.4);
} else if (price < 900_000) {
rule = new MarketRule(0.5);
} else {
rule = new MarketRule(0.9);
}
return rule.calcMaxMarket(price);
}
}
계산 로직을 인터페이스로 공통화 함으로서 각 매장마다의 정책에 대해서는 확장이 가능하며 계산에 대해서는 변경 영향이 없도록 바꿀 수 있습니다.
DIP (의존성 역전 원칙)
처음 Controller 에서 타입과 정책에 의존적인 로직을 해결할 수 있습니다.
@RestController
public class MarketQueryController {
@GetMapping("/api/calc/sail")
public Long calcSail(@requestParam MarketType marketType,
@RequestParam Long price) {
if (marketType == MarketType.COSTCO) {
CostcoPolicy policy = new CostcoPolicy();
return policy.calculate(price);
}
if (marketType == MarketType.TRADERS) {
TradersPolicy policy = new TradersPolicy();
return policy.calculate(price);
}
return null;
}
}
public class MarketPolicyFactory {
public static MarketPolicy of(MarketType marketType) {
switch (marketType) {
case RENT:
return new CostcoPolicy();
case PURCHASE:
return new TradersPolicy();
default:
throw new IllegalArgumentException("해당 marketType에 대한 정책이 존재하지 않습니다.");
}
}
}
MarketPlicy 인터페이스를 구현하는 Factory 클래스를 생성하면 다음과 같이 리팩토링할 수 있습니다.
@RestController
public class MarketQueryController {
@GetMapping("/api/calc/sail")
public Long calcSail(@requestParam MarketType marketType,
@RequestParam Long price) {
MarketPolicy policy = MarketPolicyFactory.of(marketType);
return policy.calculate(price);
}
}