ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [클린코드] 함수
    Java/클린코드 2022. 2. 7. 23:57
    • 클린코드 3장(함수) 을 읽어보며.
      • 작게 만들어라!
      • 한 가지만 해라
      • 함수당 추상화 수준은 하나
      • 이야기 처럼 읽여야 한다 (내려가기 규칙)
      • 서술적인 이름을 사용해라 (하지만 동사)
      • 함수 인수는 가능한 적게!
      • 명령과 조회를 분리해라
      • 오류 코드 보다는 예외를 사용하기 (오류처리도 한가지 작업)
      • 반복하지 말자
      • 단위 테스트를 작성해가면서 첫 장황한 함수를 개선해나가자

     

    • 함수를 작성할때는.. SOLID 방식을 사용하자
      • SRP (단일 책임 원칙) : 한 클래스는 하나의 책임만 가져야 한다.
      • OCP (개방-폐쇄 원칙) : 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야한다.
      • LSP (리스코프 치환 원칙) : 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
      • ISP (인터페이스 분리 원칙) : 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
      • DIP (의존성 역전 원칙) : 상위 모델은 하위 모델에 의존하면 안된다. 둘 다 추상화에 의존해야 한다. 추상화는 세부 사항에 의존해서는 안된다. 세부 사항은 추상화에 따라 달라진다.

     

    • 간결한 함수 작성하기
      • 작게 쪼갠다
      • 타입에 대한 처리는 Factory 에서만
      • 인수의 갯수는 0~2개, 3개 이상은 객체를 인자로 넘기기

     

    • 안전한 함수
      • 부수 효과(Side Effect) 없는 함수

     

    • 함수 리팩터링
      1. 기능을 구현하는 서투른 함수를 작성한다.
      2. 테스트 코드를 작성한다.
      3. 리팩터링을 한다.

     

    예시

    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);
        }
    }

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

    [클린코드] 오류 처리  (0) 2022.03.01
    [클린코드] 객체와 자료구조  (0) 2022.02.23
    [클린코드] 형식 맞추기  (0) 2022.02.15
    [클린코드] 주석  (0) 2022.02.11
    깨끗한 코드 그리고 의미있는 이름  (0) 2022.02.06
Designed by Tistory.