LALA's blog

[레벨1] 어떤 값을 상수로 관리해야 할까? 본문

개발/우아한테크코스

[레벨1] 어떤 값을 상수로 관리해야 할까?

lala.seeun 2022. 3. 4. 02:39

프리코스 때부터 많이 듣던 피드백

매직 넘버를 사용하지 말아라. 
값을 하드 코딩하지 말아라.
상수로 치환해서 사용해라.

 

이 피드백을 받고부터는 " 왠지.. 모든 매직 넘버나 출력/에러 메세지는 상수로 관리해야 할 것만 같아...! "

이런 고정관념을 가지고 있었다.

 

그래서 이번 로또 미션 1단계까지도 모든 매직 넘버나 출력/에러 메세지를 집착하듯 상수로 빼서 관리했었다.

private static final String REQUEST_MESSAGE_INPUT_PURCHASE_MONEY = "구입금액을 입력해 주세요.";
private static final String ERROR_MESSAGE_TYPE_OF_MONEY = "금액은 숫자가 아닐 수 없습니다.";
private static final String ERROR_MESSAGE_RANGE_OF_MONEY = "금액은 0이하일 수 없습니다.";
private static final String REQUEST_MESSAGE_WINNING_LOTTO_NUMBERS = "\n지난 주 당첨 번호를 입력해 주세요.";
private static final String REQUEST_MESSAGE_INPUT_BONUS_BALL = "보너스 볼을 입력해 주세요.";
private static final String REWARD_WITH_BONUS_FORMAT = "%d개 일치, 보너스 볼 일치 (%d원)- %d개\n";
private static final String REWARD_DEFAULT_FORMAT = "%d개 일치 (%d원)- %d개\n";
private static final String PROFIT_RATE_BENEFIT_FORMAT = "총 수익률은 (%.2f)입니다.";
private static final String PROFIT_RATE_LOSS_FORMAT = "총 수익률은 (%.2f)입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)";

private static final String WINNING_STATISTICS = "\n당첨 통계";
private static final String PRINT_DECISION = "---------";

private static final int REGEX_LIMIT = -1;
private static final int SECOND_DECIMAL_POINT = 100;

private static final int MINIMUM_LOTTO_NUMBER = 1;
private static final int MAXIMUM_LOTTO_NUMBER = 45;
private static final int LOTTO_NUMBER_SIZE = 6;
private static final int LOTTO_PRICE = 1000;

짧고 명확한 의미가 아닌 값들도 있었기 때문에 상수명 짓는 것이 꽤나 고통스러웠다. ^___^ (열일 해준 구글 번역기 감사해용)

 

이렇게 개발을 하다가 MINIMUM_LOTTO_NUMBER , MAXIMUM_LOTTO_NUMBER , LOTTO_NUMBER_SIZE 같은 값을 여러 클래스에서 상수로 선언하고 사용하다 보니 문득 걱정이 됐다. 만약 로또 숫자 범위가 0~20 으로 변경되면? 로또 개수가 10개로 변경되면? 해당 관련 값을 사용하는 코드들을 모두 찾아서 변경해주어야 했다.

 

그래서 상수를 하나의 클래스에서 관리할까? 생각을 했지만 그럼 나머지 상수들도 다 한 곳으로 모아야 할지 의문점이 들었다. 이때 내가 상수로 관리하는 값들을 생각 없이 무차별적으로 사용하고 있다는 것을 깨달았다. 

 

상수로 관리할 값에 대한 기준을 정해보자 !

 

1. 값의 의미가 분명하게 전달되지 않는 경우 상수값으로 관리하며 이름을 통해 의미를 명확히 한다.

private void validateLottoMoneyRange(int amount) {
    if (amount <= 0) {
        throw new IllegalArgumentException("금액은 " + MINIMUM_AMOUNT + "이하일 수 없습니다.");
    }
    if (amount % 1000 != 0) {
        throw new IllegalArgumentException("로또 구매 금액은 " + 1000 + "원 단위여야 합니다.");
    }
}

위 코드에서 금액을 검증하기 위해 1000 으로 나눈 나머지 연산을 수행한다. 그런데 다른 팀원이 보았을 때 이 1000 이 무엇을 의미하는지 한 번에 파악하기 어려울 수 있다. 이런 경우 상수로 관리하여 이름을 통해 의미를 명확하고 빠르게 전달할 수 있다.

private void validateRange(int amount) {
    if (amount <= MINIMUM_AMOUNT) {
        throw new IllegalArgumentException("금액은 " + MINIMUM_AMOUNT + "이하일 수 없습니다.");
    }
    if (amount % LOTTO_PRICE != 0) {
        throw new IllegalArgumentException("로또 구매 금액은 " + LOTTO_PRICE + "원 단위여야 합니다.");
    }
}

이처럼 이름을 통해 의미를 전달하며 가독성을 높일 수 있는 점을 느낄 수 있다.

 

무작정 상수로 뺐던 출력/에러 메세지들은 오히려 모호한 이름을 가지고 있어 코드를 읽다 해당 값에 무슨 내용이 담겨있는지 체크하는 것이 더 가독성을 낮춘다는 생각이 들었다.

System.out.println(WINNING_STATISTICS);
System.out.println(PRINT_DECISION);

WINNING_STATISTICS 이 당첨 통계 정보들을 출력하는지, 단순히 "당첨 통계" 를 출력하는지 누가 한 번에 알 수 있을까..!

PRINT_DECISION 으로 무슨 값을 출력하는지 어느 누가 한 번에 알 수 있을까..! (나밖에 모름)

System.out.println("\n당첨 통계");
System.out.println("---------");

오히려 직접적인 내용을 보여줌으로써 코드를 읽으며 빠르게 파악할 수 있다!

System.out.printf(REWARD_WITH_BONUS_FORMAT, reward.getMatchCount(), reward.getPrice(), rewardCount);

이 부분은 만약 수정이 필요하게 되면 REWARD_WITH_BONUS_FORMAT 이 어떤 타입을 파라미터로 넣어줘야 하는지 체크해봐야 할 것이다.

System.out.printf("%d개 일치, 보너스 볼 일치 (%d원)- %d개\n", reward.getMatchCount(), reward.getPrice(), rewardCount);

상수로 관리하지 않고 직접 메세지를 보여주도록 수정하였다. 

 

 

2. 같은 의미의 값이 중복으로 사용될 경우 하나의 상수값으로 관리한다.

public static void showProfitRate(double profitRate) {
    if (profitRate < 1) {
        System.out.printf("총 수익률은 (%.2f)입니다.(기준이 " + 1 + "이기 때문에 결과적으로 손해라는 의미임)", profitRate);
        return;
    }
    if (profitRate >= 1) {
        System.out.printf("총 수익률은 (%.2f)입니다.", profitRate);
    }
}

위 코드에서는 수익률의 기준이 되는 1 이라는 값이 여러 번 사용되고 있다. 수익률 기준이 변경되게 되면 관련 값이 작성된 곳을 모두 찾아서 변경해주어야 할 것이다. 만약 한 곳이라도 빼먹고 다른 값을 사용하게 된다면 큰일 나겠죠?!

public static void showProfitRate(double profitRate) {
    if (profitRate < BENEFIT_STANDARD) {
        System.out.printf("총 수익률은 (%.2f)입니다.(기준이 " + BENEFIT_STANDARD + "이기 때문에 결과적으로 손해라는 의미임)",
            profitRate);
        return;
    }
    if (profitRate >= BENEFIT_STANDARD) {
        System.out.printf("총 수익률은 (%.2f)입니다.", profitRate);
    }
}

이처럼 중복적으로 사용되는 값을 상수로 빼주었고, 상수이기 때문에 다른 곳에서 수정이 일어나지 않기 때문에 일관성도 보장된다 !

 

그리고 프로그램 내 전체에서 사용되는 값들은 따로 클래스로 빼서 한 곳에서 관리하도록 했다.

public class LottoConstant {
    public static final int MINIMUM_LOTTO_NUMBER = 1;
    public static final int MAXIMUM_LOTTO_NUMBER = 45;
    public static final int LOTTO_NUMBER_SIZE = 6;
    public static final int LOTTO_PRICE = 1000;

    private LottoConstant() {
    }
}

 

이렇게 기준을 정하고 상수를 추가/삭제하다 보니 코드도 훨씬 깔끔해지고 (잔뜩 있었던 상수들 ..) 네이밍 창작의 고통도 덜어져서 너무 좋다 !!! 무조건 상수로 빼는 것이 좋은 건 아니라는 것을 확실히 느꼈다.

 

이건 나만의 기준을 정한 것이니 협업하는 개발자와 혹은 본인만의 기준을 정해서 상수를 관리하도록 하면 좋을 것 같다 ! 😉