Skip to content

영양성분 계산 로직 리팩터링

koo995 edited this page Dec 7, 2024 · 8 revisions

결과 정리

비즈니스 로직을 전략 패턴과 팩토리 패턴을 적용하여 구현. 하지만 코드 리뷰를 통해 이 접근 방식이 비즈니스 로직을 이해하기 어렵게 만들고, 요구사항에 비해 복잡하다는 피드백을 받음.

총 3번의 리팩터링을 거쳐, 공통 부분을 추상화하고 Value Object를 생성. 이를 통해 11개의 클래스와 340줄의 코드를 5개의 클래스와 235줄의 코드로 줄여 더 간결하고 이해하기 쉬운 구조로 개선.

요구사항 화면 정의서

요구사항 분석

새로운 식품을 저장하는 경우

  • 요구사항 첫번째 이미지에 따르면 새로운 식품을 등록할 때, 서빙 사이즈, 기본 서빙 단위, 전체 중량이 존재한다. 이는 특정 식품의 영양성분표를 가지고 위의 이미지처럼 새로운 식품을 저장할 수 있다.

    예를 들어, 어떤 사용자는 특정 제품의 1봉지에 대한 영양성분을 저장할 수 있고, 총 사이즈에 해당하는 4봉지에 해당하는 영양성분을 저장할 수 있다.

등록된 식품에 대한 섭취를 저장하는 경우

  • 요구사항 두번째 이미지에 따르면 등록된 식품에 대한 섭취 다이어리를 작성할 때, 섭취식품의 정보를 무게(g)로 선택하거나 식품 저장 시 등록된 기본 단위(?개, ?컵, ?잔, ?인분)를 선택하냐에 따라서 영양정보를 알맞게 계산해 줘야 한다.

    예를 들어, 사용자가 사과를 섭취했을 때, 사과 1"개" 단위로 섭취할 수 있거나 257"g"처럼 무게단위로 섭취할 수 있다.
    또한, 영양성분의 알맞은 계산을 위해서 소수점 2자리 까지 표현하더라도 불필요한 소수점 표현은 없애야 할 필요가 있다. 예를 들어, 칼로리는 245.55kcal, 250kcal 와 같이 표현되어야지 250.10kcal, 251.0 처럼 불필요한 소수점 자리가 있으면 보기 안 좋다.

클래스 정의 V1

등록된 식품의 영양성분은 NutritionFacts 라는 클래스가 나타낸다. 그리고 등록된 기본 단위로 섭취했을 때 계산된 영양성분을 전달하는 NutritionFactsPerOneServing, gram단위로 섭취했을 때 계산된 영양성분을 전달하는 NutritionFactsPerGram클래스를 정의하였다.

NutritionFacts

식품에 대한 영양정보에 해당한다.

@ToString
@Getter
@NoArgsConstructor
public class NutritionFacts {
    // 새로운 식품 등록 페이지에서 입력된 정보들
    private BigDecimal productTotalCalories; // 식품의 총 칼로리
    private BigDecimal productTotalCarbohydrate;
    private BigDecimal productTotalProtein;
    private BigDecimal productTotalFat;
    
    // 식품의 서빙사이즈. 제품에 따라서 3인분 or 6개가 합쳐진 영양성분이 표시될 수 있으니까 서빙 제공량을 저장한다.
    // 기본 단위로 계산할 때 이 값을 나누어 1에 해당하는 기본 단위에서 사용자의 섭취 양을 계산해줘야한다.
    private BigDecimal productServingSize; 
    private String productServingUnit; // 식품의 기본 섭취 단위
    private BigDecimal productTotalWeightGram; // 전체 중량

    @Builder
    public NutritionFacts(BigDecimal productTotalCalories, BigDecimal productTotalCarbohydrate, BigDecimal productTotalProtein, BigDecimal productTotalFat, BigDecimal productServingSize, String productServingUnit, BigDecimal productTotalWeightGram) {
        this.productTotalCalories = productTotalCalories;
        this.productTotalCarbohydrate = productTotalCarbohydrate;
        this.productTotalProtein = productTotalProtein;
        this.productTotalFat = productTotalFat;
        this.productServingSize = productServingSize;
        this.productServingUnit = productServingUnit;
        this.productTotalWeightGram = productTotalWeightGram;
    }
    
    // 전체 영양성분에서 그램 당 영양성분을 전달함. (각 칼로리와 영양성분을 전체 중량으로 나눠서 1g당 영양성분을 얻는다.)
    public NutritionFactsPerGram calculateNutritionFactsPerGram() {
        return NutritionFactsPerGram.builder()
                .productCaloriesPerGram(stripIfNecessary(
                        productTotalCalories.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
                .productCarbohydratePerGram(stripIfNecessary(
                        productTotalCarbohydrate.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
                .productProteinPerGram(stripIfNecessary(
                        productTotalProtein.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
                .productFatPerGram(stripIfNecessary(
                        productTotalFat.divide(productTotalWeightGram, SCALE, ROUNDING_MODE)))
                .build();
    }

    // 전체 영양성분에서 기본 단위 당 영양성분을 전달함. (서빙사이즈가 4와 같이 식품 낱개가 아닌 모두 합한 정보가 저장되어 있을 수 있으니, 서빙 사이즈 값을 나눠서 1에 해당하는 사이즈로 영양성분을 전달한다.)
    public NutritionFactsPerOneServing calculateNutritionFactsPerOneServingUnit() {
        return NutritionFactsPerOneServing.builder()
                .productCaloriesPerOneServing(stripIfNecessary(
                        productTotalCalories.divide(productServingSize, SCALE, ROUNDING_MODE)))
                .productCarbohydratePerOneServing(stripIfNecessary(
                        productTotalCarbohydrate.divide(productServingSize, SCALE, ROUNDING_MODE)))
                .productProteinPerOneServing(stripIfNecessary(
                        productTotalProtein.divide(productServingSize, SCALE, ROUNDING_MODE)))
                .productFatPerOneServing(stripIfNecessary(
                        productTotalFat.divide(productServingSize, SCALE, ROUNDING_MODE)))
                .build();
    }

    // 불필요한 소수점을 제거하기 위한 메서드
    private BigDecimal stripIfNecessary(BigDecimal value) {
        BigDecimal strippedValue = value.stripTrailingZeros();
        return strippedValue.scale() <= 0 ? strippedValue.setScale(0) : strippedValue;
    }
}

NutritionFactsPerGram

@Getter
@ToString
public class NutritionFactsPerGram {
    private BigDecimal productCaloriesPerGram;

    private BigDecimal productCarbohydratePerGram;

    private BigDecimal productProteinPerGram;

    private BigDecimal productFatPerGram;

    @Builder
    private NutritionFactsPerGram(BigDecimal productCaloriesPerGram, BigDecimal productCarbohydratePerGram, BigDecimal productProteinPerGram, BigDecimal productFatPerGram) {
        this.productCaloriesPerGram = productCaloriesPerGram;
        this.productCarbohydratePerGram = productCarbohydratePerGram;
        this.productProteinPerGram = productProteinPerGram;
        this.productFatPerGram = productFatPerGram;
    }
}

NutritionFactsPerOneServing

@Getter
@ToString
public class NutritionFactsPerOneServing {
    private BigDecimal productCaloriesPerOneServing;

    private BigDecimal productCarbohydratePerOneServing;

    private BigDecimal productProteinPerOneServing;

    private BigDecimal productFatPerOneServing;

    @Builder
    private NutritionFactsPerOneServing(BigDecimal productCaloriesPerOneServing, BigDecimal productCarbohydratePerOneServing, BigDecimal productProteinPerOneServing, BigDecimal productFatPerOneServing) {
        this.productCaloriesPerOneServing = productCaloriesPerOneServing;
        this.productCarbohydratePerOneServing = productCarbohydratePerOneServing;
        this.productProteinPerOneServing = productProteinPerOneServing;
        this.productFatPerOneServing = productFatPerOneServing;
    }
}

이제 다이어리 작성시 계산기 클래스를 통해 섭취한 단위와 양에 따른 영양성분을 계산한다.

NutritionCalculationStrategy

사용자가 선택한 단위의 연산을 위한 전략 인터페이스를 정의한다.

public interface NutritionCalculationStrategy {
    CalculatedNutrition calculate(NutritionFacts nutritionFacts, ProductIntakeInfo productIntakeInfo);
}

CalculatedNutrition

계산된 영양성분을 표현할 객체.

@EqualsAndHashCode
@Getter
@ToString
public class CalculatedNutrition {
    private BigDecimal calories;
    private BigDecimal carbohydrate;
    private BigDecimal protein;
    private BigDecimal fat;

    @Builder
    public CalculatedNutrition(BigDecimal calories, BigDecimal carbohydrate, BigDecimal protein, BigDecimal fat) {
        this.calories = calories;
        this.carbohydrate = carbohydrate;
        this.protein = protein;
        this.fat = fat;
    }
}

GramBasedNutritionCalculationStrategy

gram 단위로 영양성분을 계산할 전략을 구현.

public class GramBasedNutritionCalculationStrategy implements NutritionCalculationStrategy {

    // quantity는 섭취한 양에 해당한다. 사용자가 정의한 단위 (ex. 개, 컵, 장)인 경우 앞의 갯수, gram단위인 경우 중량.
    // 여기서는 gram 단위로 계산하니까 중량에 해당한다.
    @Override
    public CalculatedNutrition calculate(NutritionFacts nutritionFacts, BigDecimal quantity) {

        // NutritionFacts 에서 그램단위의 영양성분을 가져온다.
        NutritionFactsPerGram nutritionFactsPerGram = nutritionFacts.calculateNutritionFactsPerGram();
        return CalculatedNutrition.builder()
                .calories(stripIfNecessary(
                        nutritionFactsPerGram.getProductCaloriesPerGram().multiply(quantity)))
                .carbohydrate(stripIfNecessary(
                        nutritionFactsPerGram.getProductCarbohydratePerGram().multiply(quantity)))
                .protein(stripIfNecessary(
                        nutritionFactsPerGram.getProductProteinPerGram().multiply(quantity)))
                .fat(stripIfNecessary(
                        nutritionFactsPerGram.getProductFatPerGram().multiply(quantity)))
                .build();
    }

    private BigDecimal stripIfNecessary(BigDecimal value) {
        BigDecimal strippedValue = value.stripTrailingZeros();
        return strippedValue.scale() <= 0 ? strippedValue.setScale(0) : strippedValue;
    }
}

DefaultNutritionCalculationStrategy

기본 단위로 영양성분을 계산할 전략을 구현.

public class DefaultNutritionCalculationStrategy implements NutritionCalculationStrategy {

    // quantity는 섭취한 양에 해당한다. 사용자가 정의한 단위 (ex. 개, 컵, 장)인 경우 앞의 갯수, gram단위인 경우 중량.
    // 여기서는 기본 단위로 계산하니까 갯수에 해당한다.
    @Override
    public CalculatedNutrition calculate(NutritionFacts nutritionFacts, BigDecimal quantity) {

        // NutritionFacts 에서 기본 단위의 영양성분을 가져온다.
        NutritionFactsPerOneServing nutritionFactsPerOneServing = nutritionFacts.calculateNutritionFactsPerOneServingUnit();
        BigDecimal quantity = productIntakeInfo.getQuantity();
        return CalculatedNutrition.builder()
                .calories(stripIfNecessary(
                        nutritionFactsPerOneServing.getProductCaloriesPerOneServing().multiply(quantity)))
                .carbohydrate(stripIfNecessary(
                        nutritionFactsPerOneServing.getProductCarbohydratePerOneServing().multiply(quantity)))
                .protein(stripIfNecessary(
                        nutritionFactsPerOneServing.getProductProteinPerOneServing().multiply(quantity)))
                .fat(stripIfNecessary(
                        nutritionFactsPerOneServing.getProductFatPerOneServing().multiply(quantity)))
                .build();
    }

    private BigDecimal stripIfNecessary(BigDecimal value) {
        BigDecimal strippedValue = value.stripTrailingZeros();
        return strippedValue.scale() <= 0 ? strippedValue.setScale(0) : strippedValue;
    }
}

NutritionCalculationStrategyFactory

선택된 단위에 맞는 전략을 만들어내는 팩토리 클래스.

@RequiredArgsConstructor
@Component
public class NutritionCalculationStrategyFactory {
    private static final String GRAM = "gram";

    // servingUnit은 사용자가 선택한 단위.
    // gram을 선택했으면 그램으로 계산하는 전략. 이외에는 지정된 기본 단위로 계산하는 전략.
    public NutritionCalculationStrategy getStrategy(String clientChoiceServingUnit) {
        if (GRAM.equals(clientChoiceServingUnit)) {
            return new GramBasedNutritionCalculationStrategy();
        } else {
            return new DefaultNutritionCalculationStrategy();
        }
    }
}

NutritionCalculator

영양성분을 계산하는 계산기 클래스.

@RequiredArgsConstructor
@Component
public class NutritionCalculator {
    private final ProductRepository productRepository;
    private final NutritionCalculationStrategyFactory strategyFactory; // 전략 팩토리
    
    // 사용자가 다이어리에 작성하기 위해 선택한 productId, clientChoiceServingUnit, quantity
    public CalculatedNutrition calculate(Long productId, String clientChoiceServingUnit, BigDecimal quantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new BusinessException(INVALID_PRODUCT_ID));
        NutritionFacts nutritionFacts = product.getNutritionFacts();
        
        // 사용자가 선택한 단위(clientChoiceServingUnit)에 해당하는 계산 전략을 가져온다.
        NutritionCalculationStrategy strategy = strategyFactory.getStrategy(clientChoiceServingUnit);
        
        // 선택한 단위에 맞는 전략으로 영양성분을 계산한다.
        return strategy.calculate(nutritionFacts, quantity);
    }
}

V1 코드의 문제점.

  • 불필요한 소수점을 없애기 위한 stripIfNecessary메서드가 여러 클래스에 중복되어있다. 만약 영양성분을 표시할 단위가 추가되거나 식품의 영양성분 중에서 식이섬유와 같은 항목이 추가된다면, 변경에 유연하지 못한 구조로 인해 수정해야 할 클래스가 너무 많은 문제점을 가지고 있다.

  • 사용자의 섭취단위에 따른 영양성분을 계산하는 단순한 로직인데 NutritionCalculator 가 이용할 클래스만 총 8개이다.

    NutritionCalculationStrategyFactory, DefaultNutritionCalculationStrategy, GramBasedNutritionCalculationStrategy, CalculatedNutrition, NutritionCalculationStrategy 총 5개의 클래스가 필요하며, CalculatedNutrition, NutritionFactsPerOneServing, NutritionFactsPerGram 클래스들은 비슷한 정보를 담는 구조인데 3개의 클래스로 나뉘어져 있다.
    따라서 하나로 추상화를 적용할 필요가 있다.

  • 만약 clientChoiceServingUnit 의 값으로 gram과 기본 설정된 단위(예시. 봉지)가 아닌 개, 장, 인분 등이 입력되더라도 잘못된 단위가 선택되었음을 검증할 방법이 없이 모두 기본 설정 단위(봉지)로 계산된다.

클래스 정의 V2

영양성분을 표현하는 Nutrition VO 도입. 해당 클래스에서 소수점 처리, 필요한 영양성분, 그리고 영양성분간의 계산을 위한 모든 로직을 관리한다.
따라서 영양성분이 추가되더라도(예를 들어 식이섬유, 나트륨) Nutrition 클래스 안에서 변경하면 된다. 그리고 CalculatedNutrition, NutritionFactsPerOneServing, NutritionFactsPerGram 와 같은 비슷한 클래스들을 하나의 클래스로 추상화를 하였다.

Nutrition

@Getter
@EqualsAndHashCode
@ToString
public class Nutrition {
    public static final int SCALE = 2;
    public static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP;

    private final BigDecimal calories;
    private final BigDecimal carbohydrate;
    private final BigDecimal protein;
    private final BigDecimal fat;

    @Builder
    private Nutrition(BigDecimal calories, BigDecimal carbohydrate, BigDecimal protein, BigDecimal fat) {
        this.calories = stripIfNecessary(calories);
        this.carbohydrate = stripIfNecessary(carbohydrate);
        this.protein = stripIfNecessary(protein);
        this.fat = stripIfNecessary(fat);
    }

    public Nutrition add(Nutrition nutrition) {
        return Nutrition.builder().
                calories(stripIfNecessary(this.calories.add(nutrition.calories))).
                carbohydrate(stripIfNecessary(this.carbohydrate.add(nutrition.carbohydrate))).
                protein(stripIfNecessary(this.protein.add(nutrition.protein))).
                fat(stripIfNecessary(this.fat.add(nutrition.fat))).
                build();
    }

    public Nutrition multiply(BigDecimal amount) {
        return Nutrition.builder().
                calories(stripIfNecessary(this.calories.multiply(amount))).
                carbohydrate(stripIfNecessary(this.carbohydrate.multiply(amount))).
                protein(stripIfNecessary(this.protein.multiply(amount))).
                fat(stripIfNecessary(this.fat.multiply(amount))).
                build();
    }

    public Nutrition divide(BigDecimal amount) {
        return Nutrition.builder().
                calories(stripIfNecessary(this.calories.divide(amount, SCALE, ROUNDING_MODE))).
                carbohydrate(stripIfNecessary(this.carbohydrate.divide(amount, SCALE, ROUNDING_MODE))).
                protein(stripIfNecessary(this.protein.divide(amount, SCALE, ROUNDING_MODE))).
                fat(stripIfNecessary(this.fat.divide(amount, SCALE, ROUNDING_MODE))).
                build();
    }

    public Nutrition subtract(Nutrition nutrition) {
        return Nutrition.builder().
                calories(stripIfNecessary(this.calories.subtract(nutrition.calories))).
                carbohydrate(stripIfNecessary(this.carbohydrate.subtract(nutrition.carbohydrate))).
                protein(stripIfNecessary(this.protein.subtract(nutrition.protein))).
                fat(stripIfNecessary(this.fat.subtract(nutrition.fat))).
                build();
    }

    private BigDecimal stripIfNecessary(BigDecimal value) {
        BigDecimal scaledValue = value.setScale(SCALE, ROUNDING_MODE);
        BigDecimal strippedValue = scaledValue.stripTrailingZeros();
        return strippedValue.scale() <= 0 ? strippedValue.setScale(0) : strippedValue;
    }

    public static Nutrition empty() {
        return Nutrition.builder()
                .calories(BigDecimal.ZERO)
                .carbohydrate(BigDecimal.ZERO)
                .protein(BigDecimal.ZERO)
                .fat(BigDecimal.ZERO)
                .build();
    }

    public static Nutrition of(BigDecimal calories, BigDecimal carbohydrate, BigDecimal protein, BigDecimal fat) {
        return Nutrition.builder().
                calories(calories).
                carbohydrate(carbohydrate).
                protein(protein).
                fat(fat).
                build();
    }
}

불필요한 전략과 팩토리 클래스 제거, 기존 클래스 리팩토링.

NutritionCalculationStrategyFactory, NutritionCalculationStrategy, GramBasedNutritionCalculationStrategy, DefaultNutritionCalculationStrategy 제거. NutritionFacts, NutritionCalculator 리팩토링

NutritionFacts

@ToString
@Getter
@NoArgsConstructor
public class NutritionFacts {
    private static final String GRAM = "gram";

    // Nutrition VO을 활용하여 칼로리, 탄수화물, 단백질, 지방정보를 포함한다.
    private Nutrition totalNutrition;

    private BigDecimal productServingSize;

    private String productServingUnit;

    private BigDecimal productTotalWeightGram;

    @Builder
    public NutritionFacts(Nutrition totalNutrition, BigDecimal productServingSize, String productServingUnit, BigDecimal productTotalWeightGram) {
        this.totalNutrition = totalNutrition;
        this.productServingSize = productServingSize;
        this.productServingUnit = productServingUnit;
        this.productTotalWeightGram = productTotalWeightGram;
    }
    
    // 매개변수로 받은 사용자가 선택한 단위(clientChoiceServingUnit)의 값이 gram이면 그램당 계산하고 아니면 기본단위로 계산한다.
    public Nutrition calculate(String clientChoiceServingUnit, BigDecimal quantity) {
        if (clientChoiceServingUnit.equals(GRAM)) {
            return totalNutrition.divide(productTotalWeightGram).multiply(quantity);
        }
        return totalNutrition.divide(productServingSize).multiply(quantity);
    }
}

NutritionCalculator

@RequiredArgsConstructor
@Component
public class NutritionCalculator {
    private final ProductRepository productRepository;

    public Nutrition calculate(Long productId, String clientChoiceServingUnit, BigDecimal quantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new BusinessException(INVALID_PRODUCT_ID));

        // 식품의 NutritionFacts을 가져와서 선택한 단위와 양에 맞게 계산한다.
        NutritionFacts nutritionFacts = product.getNutritionFacts();
        return nutritionFacts.calculate(clientChoiceServingUnit, quantity);
    }
}

V2 코드의 문제점.

  • NutritionFacts 클래스의 calculate 메서드에서 동일한 계산 로직이 if문으로 분기처리되어 있다.

    "gram으로 연산할 경우 총 중량을 나누고 섭취량을 곱해준다."
    "기본단위로 연산할 경우 식품의 서빙 제공량을 나누고 사용자의 섭취량을 곱해준다."의 계산 과정은 매우 유사하다.

  • gram이 아니면 기본단위로 계산한다는 로직이 비즈니스적으로도 어색하다.

  • 여전히 허용되지 않은 단위가 들어오더라도 기본단위로 계산되는 만큼 유효성검증에 있어서도 부족한 부분이 존재한다.

클래스 정의 V3

ServingUnit

ServingUnit Value Object 도입. ServingUnit 클래스에서 단위를 관리하며 단위에 따른 연산에 필요한 비율을 미리 계산해 놓는다. 허용되지 않은 단위가 입력되면 예외를 발생시켜준다.

@EqualsAndHashCode
@ToString
@Getter
public class ServingUnit {
    public static final String GRAM = "gram";
    private String description;
    private BigDecimal unitConversionRate;

    private ServingUnit(String description, BigDecimal unitConversionRate) {
        this.description = description;
        this.unitConversionRate = unitConversionRate;
    }

    public Boolean isSupport(String clientChoiceServingUnitDescription) {
        return this.description.equals(clientChoiceServingUnitDescription);
    }

    // 입력받은 값(clientChoiceServingUnitDescription)에 해당하는 단위를 반환하는 정적 팩토리 메서드.
    public static ServingUnit asOneServingUnit(String description) {
        return new ServingUnit(description, BigDecimal.ONE);
    }

    // 그램에 해당하는 단위를 반환하는 정적 팩토리 메서드.
    public static ServingUnit ofGram(BigDecimal productDefaultServingSize, BigDecimal productTotalWeightGram) {
        return new ServingUnit(GRAM, productDefaultServingSize.divide(productTotalWeightGram, SCALE, ROUNDING_MODE));
    }
}

NutritionFacts

다시 한번 더 리팩터링

@ToString
@Getter
@NoArgsConstructor
public class NutritionFacts {
    private Nutrition nutritionPerOneServingUnit;
    
    // 식품에 따라서 허용된 단위를 가지고 있는다. 현 상황에서는 2개의 단위를 허용한다. 기본단위 or gram
    private List<ServingUnit> allowedProductServingUnits;

    @Builder
    public NutritionFacts(Nutrition nutritionPerOneServingUnit, List<ServingUnit> allowedProductServingUnits) {
        this.nutritionPerOneServingUnit = nutritionPerOneServingUnit;
        this.allowedProductServingUnits = allowedProductServingUnits;
    }
    
    // 앞에서는 계속 clientChoiceServingUnit이라 작성했지만, ServingUnit 객체와 헷갈리지 않기 위해 뒤에 clientChoiceServingUnitDescription 으로 변경.
    public Nutrition calculate(String clientChoiceServingUnitDescription, BigDecimal quantity) {

        // 반복문을 돌며 입력받은 단위(clientChoiceServingUnitDescription)가 식품의 허용된 단위에 해당하는 지 확인한다.
        // 만약 입력받은 단위가 허용된 단위 중 하나라면 미리 정의된 비율에 따라서 영양성분을 계산한다.
        for (ServingUnit productServingUnit : allowedProductServingUnits) {
            if (productServingUnit.isSupport(clientChoiceServingUnitDescription)) {
                BigDecimal ratioFactor = productServingUnit.getUnitConversionRate();
                return nutritionPerOneServingUnit.multiply(ratioFactor).multiply(quantity);
            }
        }
        throw new BusinessException(NOT_ALLOWED_SERVING_UNIT);
    }
}

결론

리팩토링을 통해 중복된 코드를 줄일 수 있었고, 추상화와 Value Object을 정의하는 것으로 단위가 추가되거나 영양성분이 추가되더라도 범위를 최소한으로 유연하게 변경이 가능하게 하였다.

또한, 잘못된 단위가 들어오는 것도 막을 수 있게 되었다.

Clone this wiki locally