ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Refactoring] 7. 뒤엉킨 변경
    클린코드 2022. 9. 10. 20:34

     

     

    코딩으로 학습하는 리팩토링 - 인프런 | 강의

    리팩토링은 소프트웨어 엔지니어가 갖춰야 할 기본적인 소양 중 하나입니다. 이 강의는 인텔리J와 자바를 사용하여 보다 실용적인 방법으로 다양한 코드의 냄새와 리팩토링 기술을 설명하고 직

    www.inflearn.com

    | 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.


    뒤엉킨 변경
    • 소프트웨어는 변경에 유연하게 대처할 수 있어야 한다.
    • 서로 다른 문제는 다른 모듈에서 해결하는 것이 좋다.
      • 응집도를 높이고 결합도를 낮추자.

    • 모듈의 책임이 분리되어 있을 수록 해당 문맥을 더 잘 이해할 수 있으며 다른 문제는 신경쓰지 않아도 된다.
    • 관련 리팩토링 기술
      • 단계 쪼개기 -> 서로 다른 문맥의 코드를 분리할 수 있다.
      • 함수 옮기기 -> 적절한 모듈로 함수를 옮길 수 있다.
      • 함수 추출하기 -> 여러 가지 작업이 하나의 함수에 모여 있을 경우 적용할 수 있다.
      • 클래스 추출하기 -> 별도의 클래스로 분리할 수 있다.

    Refactoring 1. 단계 쪼개기

    • 서로 다른 일을 하는 코드를 각기 다른 모듈로 분리한다. -> 그것과 관련있는 부분만 신경 쓸 수 있도록 하기 위해서
    • 여러 일을 하는 함수의 처리 과정을 각기 다른 단계로 구분할 수 있다.
      • ex) 전처리 -> 주요 작업 -> 후처리

    • 서로 다른 데이터를 사용한다면, 단계를 나누는데 있어 중요한 단서가 될 수 있다.
    • 중간 데이터를 사용하여 단계를 구분하고, 매개변수를 줄이는데 활용할 수 있다.
    Before
    public class PriceOrder {
    
        public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
            final double basePrice = product.basePrice() * quantity;
            final double discount = Math.max(quantity - product.discountThreshold(), 0)
                    * product.basePrice() * product.discountRate();
            final double shippingPerCase = (basePrice > shippingMethod.discountThreshold()) ?
                    shippingMethod.discountedFee() : shippingMethod.feePerCase();
            final double shippingCost = quantity * shippingPerCase;
            final double price = basePrice - discount + shippingCost;
            return price;
        }
    }
    
    public record Product(double basePrice, double discountThreshold, double discountRate) {
    }
    
    public record ShippingMethod(double discountThreshold, double discountedFee, double feePerCase) {
    }
    After
    public class PriceOrder {
    
        public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
            final PriceData priceData = calculatePriceData(product, quantity);
            final double price = applyShipping(priceData, shippingMethod);
            return price;
        }
    
        private static PriceData calculatePriceData(Product product, int quantity) {
            final double basePrice = product.basePrice() * quantity;
            final double discount = Math.max(quantity - product.discountThreshold(), 0)
                    * product.basePrice() * product.discountRate();
    
            final PriceData priceData = new PriceData(quantity, basePrice, discount);
            return priceData;
        }
    
        private static double applyShipping(PriceData priceData, ShippingMethod shippingMethod) {
            final double shippingPerCase = (priceData.basePrice() > shippingMethod.discountThreshold()) ?
                    shippingMethod.discountedFee() : shippingMethod.feePerCase();
            final double shippingCost = priceData.quantity() * shippingPerCase;
            final double price = priceData.basePrice() - priceData.discount() + shippingCost;
            return price;
        }
    }
    
    public record PriceData(int quantity, double basePrice, double discount) {
    }
    
    public record Product(double basePrice, double discountThreshold, double discountRate) {
    }
    
    public record ShippingMethod(double discountThreshold, double discountedFee, double feePerCase) {
    }

    => PriceData : 중간 데이터


    Refactoring 2. 함수 옮기기

    • 모듈화가 잘 되어 있는 소프트웨어는 최소한의 지식으로 프로그래밍이 가능하다.
    • 관련있는 함수나 필드가 모여있어야 더 쉽게 찾고 활용할 수 있다.
    • 함수를 옮겨야 하는 경우
      • 해당 함수가 다른 문맥(클래스)에 있는 데이터를 더 많이 참조하는 경우
      • 해당 함수를 다른 클라이언트(클래스)에서도 필요한 경우

    Before
    public class Account {
    
        private int daysOverdrawn;
    
        private AccountType type;
    
        public Account(int daysOverdrawn, AccountType type) {
            this.daysOverdrawn = daysOverdrawn;
            this.type = type;
        }
    
        public double getBankCharge() {
            double result = 4.5;
            if (this.daysOverdrawn() > 0) {
                result += this.overdraftCharge();
            }
            return result;
        }
    
        private int daysOverdrawn() {
            return this.daysOverdrawn;
        }
    
        private double overdraftCharge() {
            if (this.type.isPremium()) {
                final int baseCharge = 10;
                if (this.daysOverdrawn <= 7) {
                    return baseCharge;
                } else {
                    return baseCharge + (this.daysOverdrawn - 7) * 0.85;
                }
            } else {
                return this.daysOverdrawn * 1.75;
            }
        }
    }
    
    public class AccountType {
        private boolean premium;
    
        public AccountType(boolean premium) {
            this.premium = premium;
        }
    
        public boolean isPremium() {
            return this.premium;
        }
    }
    After
    public class Account {
    
        private int daysOverdrawn;
    
        private AccountType type;
    
        public Account(int daysOverdrawn, AccountType type) {
            this.daysOverdrawn = daysOverdrawn;
            this.type = type;
        }
    
        public double getBankCharge() {
            double result = 4.5;
            if (this.daysOverdrawn() > 0) {
                result += this.type.overdraftCharge(this.daysOverdrawn);
            }
            return result;
        }
    
        private int daysOverdrawn() {
            return this.daysOverdrawn;
        }
    
    }
    
    public class AccountType {
        private boolean premium;
    
        public AccountType(boolean premium) {
            this.premium = premium;
        }
    
        public boolean isPremium() {
            return this.premium;
        }
    
        double overdraftCharge(int daysOverdrawn) {
            if (isPremium()) {
                final int baseCharge = 10;
                if (daysOverdrawn <= 7) {
                    return baseCharge;
                } else {
                    return baseCharge + (daysOverdrawn - 7) * 0.85;
                }
            } else {
                return daysOverdrawn * 1.75;
            }
        }
    }

    => type의 isPremium()을 사용하므로 overdraftChange메서드를 Account Class 에서 AccountType Class로 이동


    Refactoring 3. 클래스 추출하기

    • 클래스가 다루는 책임이 많아질수록 클래스가 커진다.
    • 클래스를 쪼개는 기준
      • 데이터나 메서드의 일부가 매우 밀접한 경우
      • 일부의 데이터, 메서드가 같이 바뀌는 경우
      • 하위 클래스로 만들어 책임을 분산시킬 수 있는 경우

    Before
    public class Person {
    
        private String name;
    
        private String officeAreaCode;
    
        private String officeNumber;
    
        public String telephoneNumber() {
            return this.officeAreaCode + " " + this.officeNumber;
        }
    
        public String name() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String officeAreaCode() {
            return officeAreaCode;
        }
    
        public void setOfficeAreaCode(String officeAreaCode) {
            this.officeAreaCode = officeAreaCode;
        }
    
        public String officeNumber() {
            return officeNumber;
        }
    
        public void setOfficeNumber(String officeNumber) {
            this.officeNumber = officeNumber;
        }
    }
    After
    public class Person {
    
        private TelephoneNumber telephoneNumber;
        private String name;
    
        public Person(TelephoneNumber telephoneNumber, String name) {
            this.telephoneNumber = telephoneNumber;
            this.name = name;
        }
    
        public String name() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public TelephoneNumber getTelephoneNumber() {
            return telephoneNumber;
        }
    
    }
    
    public class TelephoneNumber {
        private String areaCode;
        private String number;
    
        public TelephoneNumber(String areaCode, String officeNumber) {
            this.areaCode = areaCode;
            this.number = officeNumber;
        }
    
        public String areaCode() {
            return areaCode;
        }
    
        public String number() {
            return number;
        }
    }

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

    [Refactoring] 9. 기능 편애  (0) 2022.09.12
    [Refactoring] 8. 산탄총 수술  (0) 2022.09.11
    [Refactoring] 6. 가변 데이터  (0) 2022.09.09
    [Refactoring] 5. 전역 데이터  (0) 2022.09.08
    [Refactoring] 4. 긴 매개변수 목록  (0) 2022.09.07

    댓글

Designed by Tistory.