이름 짓기 - 의도·단위·범위
이 장에서 배우는 것
앞 장에서 읽히는 코드를 판단할 기준을 세웠다. 이번에는 수강료 계산식의 동작을 유지하면서 이름을 고친다. 계산식을 새로 설계하거나 메서드를 나누는 일은 하지 않는다. 이름만 읽어도 어떤 값을 받으며 어떤 결과를 돌려주는지 짐작할 수 있도록 만드는 데 집중한다.
이름은 코드를 읽는 사람이 가장 먼저 만나는 설명이다. 하지만 설명이 길다고 항상 도움이 되지는 않는다. 필요한 것은 글자 수가 아니라 구별에 필요한 정보다. 수강료인지 납부액인지, 원인지 다른 단위인지, 할인 대상인지 이미 할인이 끝났는지를 이름에서 구분할 수 있어야 한다.
- 자료형만으로 알 수 없는 값의 의도와 역할을 이름에 담는다.
- 금액의 단위와 문자열의 형식을 필요한 위치에 드러낸다.
- 불필요한 축약을 풀고, 이름이 사용되는 범위에 맞춰 구체성을 조절한다.
- 불리언 값을 읽을 때 참과 거짓의 의미가 자연스럽게 이어지도록 이름을 붙인다.
- 같은 입력으로 이름 변경 전후의 수강료를 계산하고 결과를 실행으로 비교한다.
문제 상황
학원 수강 관리 프로그램에 수강료를 계산하는 코드가 있다. 수업 한 회의 금액에 신청 횟수를 곱하고, 할인 대상이면 총액에서 10분의 1을 뺀다. 그다음 이미 납부한 금액을 차감한다. 계산 결과가 음수이면 추가로 받을 금액을 0원으로 표시한다. 이 장의 금액과 할인 규칙은 예제를 위해 정한 고정값이다.
담당자는 수강생의 추가 납부액이 맞는지 확인하려다가 다음 메서드를 만난다. Java 문법은 어렵지 않다. 그런데 매개변수 네 개의 뜻을 알아내기 위해 호출한 곳까지 거슬러 올라가야 한다.
static int calc(int u, int n, boolean d, int p) {
int t = u * n;
if (d) {
t = t - t / 10;
}
int r = t - p;
if (r < 0) {
r = 0;
}
return r;
}
u는 한 회의 수강료일 수도 있고 전체 수강료일 수도 있다. n이 신청 횟수인지 출석 횟수인지도 알기 어렵다. d가 할인 대상이라는 뜻인지, 이미 할인했다는 뜻인지에 따라 조건문의 해석이 달라진다. p를 비율로 오해하면 마지막 뺄셈까지 잘못 이해하게 된다.
이 상태에서 계산 결과가 이상하다는 문의를 받으면, 개발자는 업무 규칙을 확인하는 일과 변수의 뜻을 복원하는 일을 함께 해야 한다. 이름을 고치는 목적은 두 번째 부담을 줄이는 데 있다. 이름이 업무 규칙의 검증을 대신하지는 않지만, 검증할 계산식을 더 빨리 찾게 해 준다.
먼저 기존 호출부와 화면의 항목을 대조해 뜻을 확정한다. 이번 예제에서는 u가 회당 수강료, n이 신청 횟수, d가 할인 대상 여부, p가 기납부액이라는 사실을 확인했다고 가정한다. 뜻을 확인하지 않은 채 그럴듯한 이름을 붙이면, 모호한 코드가 잘못된 설명을 가진 코드로 바뀔 수 있다.
의도는 값의 역할에서 시작한다
의도를 드러내는 이름은 변수가 무엇으로 저장되는지보다 무엇에 쓰이는지를 먼저 말한다. intValue는 정수라는 사실만 반복한다. 반면 paidAmountWon은 이미 납부한 금액이며 단위가 원이라는 정보를 준다. 같은 정수라도 신청 횟수와 기납부액은 서로 바꿔 넣을 수 없는 역할을 가진다.
이름을 고를 때는 “이 값은 무엇인가”에 이어 “주변 값과 무엇이 다른가”를 묻는다. 수강료 계산에는 회당 수강료, 계산 중인 총액, 기납부액, 추가 납부액이 함께 등장한다. 이들을 모두 amount라고 부르면 금액이라는 공통점만 남고, 계산을 이해하는 데 필요한 차이는 사라진다.
| 기존 이름 | 확인한 의미 | 바꾼 이름 |
|---|---|---|
u | 수업 한 회의 수강료 | feePerSessionWon |
n | 신청한 수업 횟수 | sessionCount |
d | 할인 대상 여부 | discountEligible |
p | 이미 납부한 금액 | paidAmountWon |
t | 계산 중인 전체 수강료 | totalFeeWon |
r | 추가로 받을 금액 | amountDueWon |
특히 반환값의 이름에 주의한다. 기존 메서드는 전체 수강료를 반환하지 않는다. 할인과 기납부액을 반영한 뒤 추가로 받을 금액을 반환한다. 따라서 메서드 이름도 단순한 calculateFee보다 calculateAmountDueWon이 결과를 잘 설명한다. 호출하는 사람에게 무엇을 돌려주는지 알려 주는 이름이다.
계산 도중 값이 바뀌는 변수에는 바뀐 뒤에도 성립하는 이름을 붙인다. totalFeeBeforeDiscountWon이라는 변수에서 할인액을 빼고 같은 변수에 저장하면, 그 이름은 이후 코드에서 사실과 어긋난다. 이번에는 대입 구조를 유지하므로 할인 전후를 모두 포괄하는 totalFeeWon을 사용한다. 이름 변경만으로 모든 시점의 상태를 세밀하게 표현하려고 하지 않는다.
한 역할에는 한 표현을 쓰는 편이 좋다. 같은 프로그램에서 기납부액을 어떤 곳에서는 paidAmountWon, 다른 곳에서는 receivedMoneyWon이라고 부르면 독자는 둘이 다른 개념인지 확인해야 한다. 반대로 실제로 다른 값이라면 억지로 같은 이름에 맞추지 않는다. 입금된 금액과 특정 수강 신청에 배정된 납부액이 구분되는 업무라면 이름도 그 차이를 따라야 한다.
축약을 피한다는 말은 모든 이름을 길게 늘이라는 뜻이 아니다. 독자가 풀어 읽어야 하는 약어를 줄이라는 뜻이다. amt, cnt, disc를 각각 amount, count, discount로 쓰면 별도의 해독 과정이 줄어든다. 팀에서 익숙하다는 이유만으로 새로 온 사람도 같은 뜻을 떠올릴 것이라고 가정하지 않는다.
단위와 형식은 해석이 갈리는 곳에 적는다
int라는 자료형은 숫자의 단위를 알려 주지 않는다. feePerSessionWon의 Won은 저장된 숫자를 원 단위로 해석한다는 약속이다. 이름이 단위를 변환하지는 않는다. 다른 단위의 값을 넣어도 Java 컴파일러가 이 이름만 보고 막아 주지는 않는다. 그래서 이름과 실제로 전달하는 값이 일치하는지도 확인해야 한다.
단위는 비교하거나 계산하는 값 사이에서 통일한다. 회당 수강료에는 원 단위를 쓰면서 기납부액에는 천 원 단위를 쓰면, 두 값이 모두 정수라는 이유로 뺄셈은 실행된다. 숫자의 크기만 보고 단위를 추측하게 하지 말고 paidAmountWon처럼 이름에서 계약을 드러낸다. 기존 값이 원 단위였다는 확인 없이 접미사만 붙여서는 안 된다.
횟수는 sessionCount로 표현한다. session만 쓰면 수업 자체를 뜻하는지 수업 횟수를 뜻하는지 흐려진다. 비율도 같은 문제가 있다. discountPercent는 10을 10퍼센트로 읽는다는 뜻을 담을 수 있다. 0.1을 넣는 값이라면 다른 표현이 필요하다. 중요한 것은 특정 접미사를 외우는 일이 아니라 저장된 수의 해석을 하나로 정하는 일이다.
문자열은 단위 대신 형식이 문제를 일으킬 수 있다. startDate가 날짜 객체인지, 2027-03-02 같은 문자열인지 호출부에서 바로 드러나지 않을 수 있다. 외부 입력을 그대로 다루는 위치에서는 startDateText가 문자열임을 알리고, 형식까지 구별해야 한다면 startDateIsoText처럼 더 구체적으로 정할 수 있다. 여기서 ISO는 국제표준화기구의 표준을 가리키지만, 이름 하나가 구체적인 형식 전체를 보장하지는 않는다.
이번 예제에서 문자열 형식을 설명한다면 연도 네 자리, 월 두 자리, 일 두 자리를 하이픈으로 잇는 형태라고 명확히 정해야 한다. 다만 이 장의 수강료 프로그램에는 날짜가 필요하지 않으므로 완성 코드에 날짜 처리까지 추가하지 않는다. 이름을 설명하려고 불필요한 데이터를 프로그램에 넣을 이유는 없다.
자료형을 기계적으로 되풀이하는 접두사는 정보의 우선순위를 흐릴 수 있다. intPaidAmount보다 paidAmountWon이 이 계산에서는 더 유용하다. 반면 문자열 원문과 해석된 값을 함께 다루는 곳에서 Text를 붙이는 것은 두 역할을 구별하는 데 도움이 된다. 단위와 형식 표기는 실제 혼동을 줄이는 위치에 사용한다.
범위와 불리언에 맞춰 이름을 다듬는다
이름의 적절한 길이는 그 이름이 보이는 범위와 관련이 있다. 두세 줄짜리 반복문에서 배열의 위치를 가리키는 index는 주변 문맥으로 뜻을 알 수 있다. 여러 메서드에서 사용하는 필드의 이름이 value라면 같은 도움을 기대하기 어렵다. 선언부에서 멀어질수록 독자가 기억해야 할 문맥이 늘어나기 때문이다.
따라서 “범위가 넓을수록 긴 이름”은 글자 수를 강제로 늘리는 규칙이 아니다. 넓은 범위에서도 역할을 식별할 만큼 구체적으로 쓰라는 기준이다. 메서드 매개변수는 메서드 본문뿐 아니라 호출 관계를 이해하는 데도 쓰이므로 p보다 paidAmountWon이 낫다. 여러 강좌의 납부액이 함께 등장하는 범위라면 강좌나 신청 건을 구별하는 말이 더 필요할 수 있다.
작은 범위라고 해서 의미를 지워도 되는 것은 아니다. 짧은 계산식 안에서도 a - b와 totalFeeWon - paidAmountWon의 차이는 크다. 특히 같은 자료형의 값이 나란히 놓이면 구체적인 이름이 인수의 순서를 확인하는 단서가 된다. 다만 Java 메서드 호출에 매개변수 이름이 표시되는 것은 아니므로, 이름만으로 잘못된 인수 순서를 모두 막을 수는 없다.
불리언은 참일 때의 의미가 문장처럼 읽혀야 한다. discountEligible이 참이면 할인 대상이다. discount만 쓰면 할인 금액인지, 할인 기능을 켜는 설정인지, 처리 완료 상태인지 구별하기 어렵다. 이번 코드에서는 대상 여부를 검사하므로 if (discountEligible)이 업무의 판단과 직접 연결된다.
불리언 이름의 앞부분은 역할에 따라 고른다. 조회 메서드라면 isDiscountEligible(), 보유 여부라면 hasUnpaidFee(), 수행 가능 여부라면 canEnroll()처럼 읽을 수 있다. 모든 불리언 변수에 같은 접두사를 붙이기보다, 값이 말하는 사실을 먼저 확정한다. 특히 canEnroll과 enrolled는 신청 가능성과 신청 완료라는 서로 다른 사실이다.
부정형은 조건식의 부정과 겹치면 읽기 어려워진다. !notDiscountEligible은 머릿속에서 부정을 두 번 처리하게 한다. 같은 의미를 자연스럽게 표현할 수 있다면 discountEligible을 쓴다. 다만 기존 부정형 이름을 긍정형으로 바꾸려면 값과 조건의 부정도 함께 바뀔 수 있다. 그것은 단순한 식별자 교체보다 확인할 부분이 많다. 이번 완성 코드에서는 원래 참이 뜻하던 할인 대상이라는 의미를 유지한다.
완성 코드
다음 프로그램은 변경 전 메서드와 이름을 바꾼 메서드를 함께 둔다. calc와 calculateAmountDueWon의 계산 연산, 조건식, 대입 순서, 반환 위치는 같다. 달라지는 것은 메서드 이름, 매개변수 이름, 지역 변수 이름이다. 비교를 위한 입력 배열과 check는 실행 확인용 코드다.
입력은 일반 수강, 할인 대상, 기납부액이 수강료보다 많은 경우, 할인액 계산에서 나머지가 생기는 경우로 정한다. 회당 수강료와 횟수와 기납부액은 0 이상이고, 곱셈 결과가 int 범위 안에 있는 입력을 사용한다. 입력 검증이나 숫자 자료형 변경은 이번 이름 변경에 포함하지 않는다.
Main.java
public class Main {
public static void main(String[] args) {
String[] caseNames = {
"일반 수강", "할인 대상", "초과 납부", "할인 나머지"
};
int[] feesPerSessionWon = {30000, 30000, 20000, 10001};
int[] sessionCounts = {4, 4, 2, 5};
boolean[] discountEligibility = {false, true, false, true};
int[] paidAmountsWon = {30000, 0, 50000, 4};
int[] expectedAmountsDueWon = {90000, 108000, 0, 45001};
for (int index = 0; index < caseNames.length; index++) {
int beforeAmountDueWon = calc(
feesPerSessionWon[index],
sessionCounts[index],
discountEligibility[index],
paidAmountsWon[index]
);
int afterAmountDueWon = calculateAmountDueWon(
feesPerSessionWon[index],
sessionCounts[index],
discountEligibility[index],
paidAmountsWon[index]
);
System.out.println(
caseNames[index] + ": 변경 전=" + beforeAmountDueWon
+ "원, 변경 후=" + afterAmountDueWon + "원"
);
check(
beforeAmountDueWon == afterAmountDueWon,
caseNames[index] + " 전후 일치"
);
check(
afterAmountDueWon == expectedAmountsDueWon[index],
caseNames[index] + " 기대값 일치"
);
}
}
static int calc(int u, int n, boolean d, int p) {
int t = u * n;
if (d) {
t = t - t / 10;
}
int r = t - p;
if (r < 0) {
r = 0;
}
return r;
}
static int calculateAmountDueWon(
int feePerSessionWon,
int sessionCount,
boolean discountEligible,
int paidAmountWon
) {
int totalFeeWon = feePerSessionWon * sessionCount;
if (discountEligible) {
totalFeeWon = totalFeeWon - totalFeeWon / 10;
}
int amountDueWon = totalFeeWon - paidAmountWon;
if (amountDueWon < 0) {
amountDueWon = 0;
}
return amountDueWon;
}
static void check(boolean condition, String name) {
if (condition) {
System.out.println("PASS: " + name);
} else {
System.out.println("FAIL: " + name);
}
}
}
두 계산 메서드를 함께 남겨 두는 것은 이 장의 비교를 위한 구성이다. 실제 프로그램에 동일한 계산을 계속 두 벌 유지하자는 뜻은 아니다. 여기서는 두 본문을 나란히 읽고 같은 입력으로 실행할 수 있다는 점이 중요하다. 배열의 각 위치는 하나의 사례를 구성하며, 모든 배열은 같은 순서와 길이를 가진다.
줄별 해설
public class Main은 실행할 클래스를 선언한다. 파일 이름도 Main.java로 맞춘다. 패키지 선언이 없으므로 파일이 있는 디렉터리에서 바로 컴파일할 수 있다. main은 입력을 준비하고 두 계산 결과를 비교하는 실행 시작점이다.
caseNames는 계산 사례의 이름이다. 수강생의 개인정보 대신 계산 특징을 출력한다. 이어지는 feesPerSessionWon과 sessionCounts는 각각 사례별 회당 수강료와 신청 횟수다. 단일 값을 받는 매개변수에는 단수 이름을, 여러 사례를 담는 배열에는 복수 이름을 사용했다.
discountEligibility는 사례별 할인 대상 여부를 담는다. 각 원소가 참이면 할인을 적용한다. paidAmountsWon은 이미 납부한 금액이고, expectedAmountsDueWon은 별도로 정한 기대 결과다. 계산 결과끼리만 비교하면 두 구현이 같은 실수를 공유해도 통과할 수 있으므로 기대값도 확인한다.
for문의 index는 네 사례를 차례로 선택한다. 이 반복문 안에서는 모든 배열에서 같은 위치를 읽으므로 무엇의 위치인지 바로 보인다. 이처럼 문맥이 가까운 범위에서는 더 긴 이름이 추가 정보를 주지 않을 수 있다. 하지만 반복문에 다른 인덱스가 함께 들어온다면 구별할 이름이 필요하다.
beforeAmountDueWon과 afterAmountDueWon에는 같은 위치의 입력을 각각 전달한 결과를 저장한다. 두 호출의 인수 순서를 같게 유지한 점도 확인해야 한다. 매개변수 이름이 좋아졌더라도 수강료와 기납부액을 뒤바꾸어 전달하면 비교 자체가 다른 입력으로 이루어진다.
첫 번째 System.out.println은 사례 이름과 두 결과를 한 줄에 출력한다. 이어지는 첫 번째 check는 변경 전후가 같은지를 검사한다. 두 번째 check는 변경 후 결과가 준비한 기대값과 같은지를 검사한다. 두 조건이 모두 참이면 변경 전 결과 역시 그 기대값과 같다.
calc는 비교 기준이 되는 기존 구현이다. 짧은 이름을 그대로 두었으므로 새 구현과 한 줄씩 대응시킬 수 있다. 곱셈, 할인, 기납부액 차감, 음수 보정이라는 네 계산 단계가 이름 변경 후에도 같은 위치에 남아 있다.
calculateAmountDueWon의 선언부는 반환하는 값의 역할과 단위를 먼저 알린다. 첫 대입문은 회당 수강료와 신청 횟수를 곱한다. if (discountEligible)에서는 대상자에게만 할인을 적용한다. 이 조건의 참과 거짓은 기존 d의 참과 거짓에 그대로 대응한다.
totalFeeWon / 10은 정수 나눗셈이다. 예제의 양수 금액 50,005원을 10으로 나누면 5,000이 된다. 따라서 할인 뒤 금액은 45,005원이고, 이미 낸 4원을 빼면 45,001원이다. 이름을 바꾸면서 계산을 totalFeeWon * 9 / 10으로 고치면 이 사례에서 45,000원이 나온다. 두 식이 비슷해 보여도 정수 연산의 결과는 다르다.
amountDueWon은 기납부액을 차감한 결과를 받는다. 음수일 때 0을 대입하는 기존 동작도 유지한다. 마지막 return은 이 금액을 반환한다. check는 전달받은 조건이 참이면 PASS, 거짓이면 FAIL을 출력한다. 실패가 발생해도 남은 사례를 계속 보여 주므로 출력 전체에서 실패 여부를 확인해야 한다.
실행 결과
Main.java를 저장한 디렉터리에서 다음 명령을 실행한다. JDK 17의 표준 라이브러리만 사용한다. 프로그램은 파일을 읽거나 쓰지 않으며, 시각이나 난수를 사용하지 않는다. 입력과 출력 순서가 고정되어 실행할 때마다 같은 결과가 나온다.
javac -d out *.java
java -cp out Main
예상 출력은 다음과 같다.
일반 수강: 변경 전=90000원, 변경 후=90000원
PASS: 일반 수강 전후 일치
PASS: 일반 수강 기대값 일치
할인 대상: 변경 전=108000원, 변경 후=108000원
PASS: 할인 대상 전후 일치
PASS: 할인 대상 기대값 일치
초과 납부: 변경 전=0원, 변경 후=0원
PASS: 초과 납부 전후 일치
PASS: 초과 납부 기대값 일치
할인 나머지: 변경 전=45001원, 변경 후=45001원
PASS: 할인 나머지 전후 일치
PASS: 할인 나머지 기대값 일치
여덟 줄의 PASS는 준비한 네 입력에서 전후 결과와 기대값이 일치함을 보여 준다. 이것만으로 가능한 모든 입력의 동작을 확인했다고 말할 수는 없다. 이번 변경에서는 실행 결과와 함께 두 메서드의 연산자, 상수, 조건, 대입 순서가 같다는 점도 확인한다. 실행은 대표적인 동작을 확인하고, 본문 비교는 이름 이외의 변경이 섞이지 않았는지 확인하는 근거가 된다.
실무에서 자주 틀리는 것
뜻을 확인하지 않고 축약만 풀어 쓴다
p를 보고 백분율이라고 추측해 이름을 붙이면 이름은 길어져도 의미는 틀린다. 이 예제에서는 원 단위 기납부액이다. 호출하는 곳에서 값이 어디서 왔는지 확인한 뒤 바꿔야 한다.
틀린 코드다.
int percentage = p;
int amountDueWon = totalFeeWon - percentage;
역할을 확인한 뒤 고친 코드다.
int paidAmountWon = p;
int amountDueWon = totalFeeWon - paidAmountWon;
여기서 첫 줄은 기존 값의 뜻을 설명하기 위한 연결이다. 실제 이름 변경에서는 별칭 변수를 추가할 필요 없이 기존 선언과 사용 위치를 함께 바꾼다.
이름에 적은 단위와 실제 값이 다르다
원 단위 기납부액이 30,000원인데 화면에 보이는 30이라는 숫자를 그대로 전달하면 계산은 실행되어도 결과가 달라진다. Won이라는 이름은 값의 단위를 검사하거나 변환하지 않는다.
틀린 코드다.
int paidAmountWon = 30;
실제 기납부액이 30,000원인 상황에 맞게 고친 코드다.
int paidAmountWon = 30000;
반대로 기존 데이터가 천 원 단위로 저장되어 있다면 접미사를 붙이는 일만으로는 해결되지 않는다. 기존 값의 단위를 먼저 확정하고, 단위 변환이 필요한 변경은 이름만 바꾸는 작업과 구별해 검토한다.
불리언 이름과 참의 의미가 어긋난다
할인 대상인 수강생에게 참을 저장하면서 discountCompleted라고 부르면, 아직 계산하지 않았는데 처리가 끝난 상태처럼 읽힌다. 대상 여부와 처리 완료 여부는 다르다.
틀린 코드다.
boolean discountCompleted = true;
if (discountCompleted) {
totalFeeWon = totalFeeWon - totalFeeWon / 10;
}
참이 뜻하는 사실에 맞게 고친 코드다.
boolean discountEligible = true;
if (discountEligible) {
totalFeeWon = totalFeeWon - totalFeeWon / 10;
}
이 변경은 저장한 값과 조건식의 의미를 유지한다. 이름을 긍정형으로 만들겠다는 이유로 참과 거짓까지 뒤집는 변경을 섞지 않는다.
이름을 바꾸는 김에 계산식도 정리한다
식별자 교체와 계산식 교체가 한꺼번에 들어가면 결과가 달라졌을 때 원인을 찾는 범위가 넓어진다. 특히 정수 나눗셈이 포함된 식은 연산 순서에 따라 나머지를 버리는 위치가 달라진다.
이번 이름 변경에 섞으면 안 되는 코드다.
totalFeeWon = totalFeeWon * 9 / 10;
기존 계산을 유지한 코드다.
totalFeeWon = totalFeeWon - totalFeeWon / 10;
이름을 고치는 동안에는 기존 식을 유지한다. 계산 규칙 자체가 바뀌어야 한다는 판단은 별도의 근거가 필요하다. 이번 비교에서는 할인 나머지 사례가 두 식의 차이를 드러낸다.
한눈에 보기
| 기준 | 확인할 질문 | 적용 예 |
|---|---|---|
| 의도 | 이 값은 계산에서 어떤 역할인가 | amountDueWon |
| 단위 | 같은 숫자를 어떤 크기로 읽는가 | paidAmountWon |
| 형식 | 문자열을 어떤 형태로 해석하는가 | startDateIsoText |
| 축약 | 처음 보는 사람이 약어를 풀어야 하는가 | sessionCount |
| 범위 | 주변 문맥 없이도 역할이 구별되는가 | feePerSessionWon |
| 불리언 | 참일 때 어떤 사실이 성립하는가 | discountEligible |
| 동작 유지 | 같은 입력의 전후 결과가 같은가 | 전후 비교와 기대값 확인 |
이름을 검토할 때는 길고 짧음보다 무엇을 구별해 주는지 살핀다. 같은 개념에는 같은 표현을 쓰고, 서로 다른 역할에는 구별되는 표현을 쓴다. 이름만으로 전달하기 어려운 정보가 남을 수 있지만, 적어도 이름이 실제 값과 어긋나는 상태는 피해야 한다.
연습 문제
- 회당 수강료, 신청 횟수, 기납부액을 각각
a,b,c로 받는 메서드가 있다. 세 값의 이름과 추가 납부액을 반환하는 메서드 이름을 제안하라. 세 금액 관련 값의 단위는 원이다. boolean discount = true;가 “할인 대상이다”를 뜻한다고 확인했다. 적절한 변수 이름을 제안하라. “할인을 이미 적용했다”는 뜻의 이름과 왜 구별해야 하는지도 설명하라.- 완성 코드에 회당 25,000원, 신청 횟수 2회, 할인 대상, 기납부액 10,000원인 사례를 추가하라. 기대값과 추가로 출력될 세 줄을 적어라.
- 이름을 바꾸면서 할인식을
totalFeeWon * 9 / 10으로 바꾸었다. 완성 코드의 어떤 사례에서 어떤 결과가 달라지는지 계산하라. 이 변경이 이름 변경에 포함되면 안 되는 이유를 설명하라.
정답과 해설
a는feePerSessionWon,b는sessionCount,c는paidAmountWon으로 바꿀 수 있다. 메서드 이름은calculateAmountDueWon으로 정할 수 있다. 수강료 전체가 아니라 기납부액을 반영한 추가 납부액을 반환한다는 점을 이름에 담는다. 다른 이름을 택하더라도 역할과 단위가 분명하면 된다.discountEligible로 바꿀 수 있다. 참이면 할인 대상이라는 뜻이다.discountApplied는 이미 할인을 적용했다는 사실을 나타내므로 같은 이름으로 취급할 수 없다. 대상 여부를 완료 여부처럼 읽으면 할인을 언제 적용해야 하는지 잘못 판단할 수 있다. 이번에는 값의 뜻이 대상 여부로 확인되었으므로 조건식의 참과 거짓을 바꾸지 않고 이름만 교체한다.총액은 50,000원이며 할인액은 5,000원이다. 할인 뒤 45,000원에서 기납부액 10,000원을 빼면 기대값은 35,000원이다. 각 배열의 마지막에 같은 순서로 값을 추가한다. 사례 이름을
할인 후 일부 납부로 정했다면 추가 출력은 다음과 같다.할인 후 일부 납부: 변경 전=35000원, 변경 후=35000원 PASS: 할인 후 일부 납부 전후 일치 PASS: 할인 후 일부 납부 기대값 일치금액 배열만 늘리고 사례 이름이나 할인 여부 배열을 늘리지 않으면 사례 구성이 맞지 않는다. 하나의 입력에 해당하는 원소들을 함께 추가해야 한다.
할인 나머지사례가 달라진다. 기존 식에서는 50,005원에서 5,000원을 빼고 기납부액 4원을 차감하므로 45,001원이다. 바꾼 식에서는 50,005에 9를 곱한 450,045를 10으로 나누어 45,004원이 되고, 기납부액을 차감하면 45,000원이다. 변경 전후 비교와 기대값 비교가 모두 실패한다. 이름 변경은 값을 해석하는 표현을 바꾸는 작업이므로, 결과를 바꾸는 연산 변경을 함께 넣지 않는다.
이번에는 계산 구조를 유지한 채 이름으로 값의 역할을 드러냈다. 다음 장에서는 이렇게 뜻이 드러난 계산을 바탕으로 함수의 경계를 살펴본다. 이름을 읽어 계산의 대상을 파악할 수 있어야, 어떤 일을 하나로 묶을지도 판단하기 쉬워진다.