값과 메서드
110분 안팎
학습 목표
변수·조건·반복·메서드로 대여 가능 여부를 판단합니다.
개념
왜 대여 규칙을 메서드로 분리하나요
동아리는 도서를 1일부터 14일까지 빌려 줍니다. 접수 화면과 나중에 만들 API가 각자 기간을 검사하면 한쪽에서는 14일을 허용하고 다른 쪽에서는 거부하는 문제가 생길 수 있습니다. 허용 범위를 한 메서드에 모으면 같은 입력에 같은 판단을 돌려주며 테스트도 한 계약을 검증할 수 있습니다. 이번 레슨은 날짜 계산이나 회원 권한을 다루지 않습니다. 정수로 받은 대여 기간이 허용 범위인지 판단하고 여러 신청 중 허용되는 신청 수를 세는 데 집중합니다.
변수는 값에 이름을 붙입니다
int days = 14는 정수 14를 days라는 지역 변수에 보관합니다. boolean allowed는 true 또는 false만 보관합니다. String title은 문자열 참조를 보관합니다. 타입을 정하면 컴파일러가 맞지 않는 대입과 호출을 찾아 줍니다. 대여 일수는 이번 계약에서 정수이므로 "14"라는 문자열과 14라는 정수를 구분합니다. int에는 null을 넣을 수 없습니다. 외부 문자열을 정수로 바꾸는 일은 뒤에서 입력을 다룰 때 별도로 맡기며, 판단 메서드는 이미 정수로 변환된 값만 받습니다.
변수 선언의 =는 값을 넣는 대입입니다. 두 값이 같은지 비교하는 ==와 다릅니다. <와 >는 엄격한 비교이고 <=와 >=는 경계를 포함합니다. 1~14일이라는 문장을 코드로 옮길 때 양끝을 포함하는지부터 표로 정합니다. 0일 거부, 1일 허용, 14일 허용, 15일 거부를 먼저 적습니다. 이 표가 있으면 코드의 연산자를 고를 근거가 생깁니다. 정상 입력 7일 하나만 실행해서는 0일이나 14일에서 틀린 구현을 발견하기 어렵습니다.
입력과 반환값의 계약을 만듭니다
public static boolean isAllowed(int days) {
return days >= 1 && days <= 14;
}int days는 호출할 때 받은 값을 담는 매개변수입니다. 반환 타입 boolean은 호출자가 받을 결과의 타입입니다. return은 값을 돌려주고 메서드를 끝냅니다. 호출자는 boolean ok = LoanPolicy.isAllowed(14)처럼 결과를 받아 이후 처리에 사용합니다. System.out.println은 콘솔에 보일 뿐 업무 판단값을 호출자에게 돌려주는 방법이 아닙니다. 허용 여부를 출력만 하는 void 메서드는 다른 코드가 그 결과를 조건식으로 쓰기 어렵습니다. 결과를 반환하는 판단과 사람에게 보여 주는 출력을 분리합니다.
&&는 두 조건이 모두 참이어야 참입니다. ||는 둘 중 하나만 참이어도 참입니다. days >= 1 || days <= 14라고 쓰면 0은 뒤 조건을 만족하고 15는 앞 조건을 만족하므로 둘 다 허용됩니다. 한쪽 조건이 맞는지만 볼 것이 아니라 전체 식의 의미를 읽습니다. &&와 ||는 앞의 값으로 결과가 정해지면 뒤 식을 평가하지 않는 단락 평가를 사용합니다. 다음 레슨에서 null 검사를 먼저 놓을 때도 이 성질을 사용합니다.
이번 isAllowed는 입력값을 바꾸거나 파일에 쓰지 않습니다. 같은 days에는 같은 결과를 반환하는 작은 함수입니다. 기본형 매개변수에는 호출자가 가진 값의 복사본이 들어옵니다. 메서드 안에서 days = 1을 실행해도 호출자의 변수는 바뀌지 않습니다. 잘못된 0일을 몰래 1일로 바꾸어 허용하는 방식도 이번 계약과 맞지 않습니다. 판단은 false로 돌려주고, 실제 대여 요청을 거부하는 방법은 나중에 LoanService에서 연결합니다.
반복으로 여러 신청을 판단합니다
public static int countAllowed(int[] periods) {
int count = 0;
for (int days : periods) {
if (isAllowed(days)) count++;
}
return count;
}int[]는 정수 여러 개를 담는 배열입니다. new int[]{0, 1, 14, 15}는 네 개의 기간을 담습니다. for (int days : periods)는 배열의 원소를 차례대로 days에 넣어 본문을 실행합니다. if는 조건이 참일 때만 다음 동작을 실행합니다. count++는 count를 1 늘립니다. count는 메서드 호출마다 0으로 시작해야 합니다. 공유 필드에 두면 이전 호출의 합계가 남을 수 있습니다. return count는 반복이 끝난 뒤에 놓습니다. 반복 안에 놓으면 첫 원소만 보고 메서드를 끝냅니다.
빈 배열은 정상 입력입니다. 반복할 원소가 없으므로 count가 그대로 0이며 결과도 0입니다. 모두 허용되는 {1, 7, 14}의 결과는 3입니다. 이 두 사례가 서로 다른 실수를 잡습니다. 빈 배열은 최초 값과 종료 위치를, 모두 허용되는 배열은 원소를 끝까지 처리하는지 확인합니다. null 배열은 이번 메서드의 입력 계약에서 제외합니다. 빈 배열과 null은 같지 않으며 요구가 달라지면 명시적으로 새 계약과 테스트를 추가합니다. 조용히 한쪽으로 바꾸는 처리는 하지 않습니다.
starter를 고치는 순서
먼저 LoanPolicyTest에서 0일과 14일 테스트를 읽고 LoanPolicy.isAllowed의 비교식을 확인합니다. starter는 아래 경계를 포함하고 위 경계를 제외하는 오류가 있습니다. 두 비교 연산자를 고친 뒤 네 경계값을 다시 실행합니다. 다음으로 countAllowed가 거부할 기간도 세는지 확인합니다. 반복 구조를 없애거나 입력별 결과를 하드코딩하지 않습니다. isAllowed를 호출해 업무 판단을 재사용하면 허용 범위가 바뀔 때 수정할 곳이 분산되지 않습니다.
테스트에서 expected: <false> but was: <true>가 나오면 테스트가 요구한 거부 대신 허용을 반환했다는 뜻입니다. 이 정보만으로 모든 코드를 뜯어고치지 않고 실패한 메서드 이름과 입력을 먼저 봅니다. 컴파일 오류 incompatible types가 나오면 문자열을 정수 자리에 넣었는지 확인합니다. missing return statement는 boolean이나 int 반환 메서드의 어떤 경로가 결과 없이 끝났다는 단서입니다. 정상과 실패 분기 모두 반환하는지 살피며, 반환 타입을 void로 바꿔 오류만 지우지 않습니다.
판단을 설명하는 연습
실습을 마치면 허용 범위를 자연어, 네 줄의 입력 표, 한 개의 조건식으로 각각 설명해 봅니다. 세 표현의 의미가 같아야 합니다. 기간 판단 테스트 여섯 개와 집계 테스트 세 개를 확인하고 이전 레슨의 출력 테스트 네 개도 유지합니다. 테스트는 코드와 다른 관점에서 기대값을 적는 장치입니다. 기대값을 isAllowed의 반환값으로 다시 계산하면 같은 실수를 복제합니다. 직접 확인한 경계 표를 근거로 true, false, 2, 0, 3을 지정합니다.
실무에서는 구현을 짧게 만드는 것보다 계약이 드러나는 이름을 고르는 것이 도움이 됩니다. isAllowed는 허용 여부를 반환하고 countAllowed는 개수를 반환합니다. 두 메서드 모두 출력에 의존하지 않으므로 나중에 콘솔, 파일 목록, HTTP 요청 처리에서 다시 쓸 수 있습니다. 오버로딩과 가변인자, 값 전달을 더 깊게 살피고 싶다면 연결한 서재 장을 읽습니다. 다음 레슨에서는 여러 값을 한 도서로 묶고, 그 도서의 변경 통로를 관리합니다.
따라하기
기간 판단의 입력 표 작성
LoanPolicyTest의 입력을 읽고 0일·1일·14일·15일의 기대값을 적습니다. isAllowed는 허용 여부, countAllowed는 허용 건수를 반환합니다. 두 메서드의 반환 타입과 기대값을 구분합니다.
시작 코드의 TODO 수정
src/main/java/lab/LoanPolicy.java에서 경계 비교와 countAllowed의 조건부 증가를 고칩니다. 아래 코드를 참고해 구현합니다. 코드 조각을 해당 선언과 메서드 안에 적용한 뒤 ./mvnw test를 실행해 컴파일하고 규칙을 확인합니다.
return days >= 1 && days <= 14;
// countAllowed의 반복문 안
if (isAllowed(days)) count++;정상과 경계를 직접 실행
앞 단계의 빌드가 끝나 target/classes가 만들어진 뒤 아래 명령을 실행합니다. Demo.java는 실습 루트에 만드는 관찰용 파일입니다. 제공 테스트와는 별도이며 다른 레슨으로 이동해 실행하지 않습니다.
cat > Demo.java <<'JAVA'
public class Demo {
public static void main(String[] args) {
int[] periods = {0, 1, 14, 15};
for (int days : periods) System.out.println(days + "일: " + lab.LoanPolicy.isAllowed(days));
System.out.println("허용 건수: " + lab.LoanPolicy.countAllowed(periods));
System.out.println("빈 배열: " + lab.LoanPolicy.countAllowed(new int[]{}));
}
}
JAVA
javac --release 17 -encoding UTF-8 -cp target/classes -d target/classes Demo.java
java -cp target/classes Demo실행 결과
0일: false 1일: true 14일: true 15일: false 허용 건수: 2 빈 배열: 0
테스트와 종료 상태 확인
아래 명령으로 전체 테스트를 다시 실행하고 바로 이어 종료 상태를 읽습니다. -q는 정상 로그를 줄입니다. 아래 0은 echo가 보여 준 테스트 명령의 종료 상태입니다. target/surefire-reports의 XML 또는 txt에서 제공 테스트 13개가 실행되었고 실패·오류·건너뜀이 0인지 함께 확인합니다.
./mvnw -q test
echo $?실행 결과
0
확인 문제
실습
LoanPolicy.isAllowed는 1~14일만 true입니다. countAllowed는 null이 아닌 입력 배열에서 허용된 기간만 세고 빈 배열이면 0을 반환합니다. 경계·음수·큰 정수·혼합·빈 배열을 확인합니다. 이전 출력 테스트를 유지합니다.
실행 명령
./mvnw test
기대 결과
제공 테스트 13개 실행, Failures 0, Errors 0, Skipped 0, 종료 코드 0입니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 대여 기간의 경계값을 검사할 때 AND와 OR 중 무엇을 사용하며 왜 그런가요?
- Java에서 기본형 매개변수를 재대입하면 호출자의 변수도 바뀌나요?