본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
2장 : 값·식·분기·반복

조건 경계 훈련

학점·환율·시간 범위를 경계 표로 바꾸고 바로 아래·같음·바로 위 입력으로 조건식을 검증합니다.

조건문을 한 번 실행해 원하는 라벨이 나왔다고 해서 범위 전체가 맞는 것은 아닙니다. 오류는 대개 기준값 바로 옆에 숨어 있습니다. 90점 기준이라면 89, 90, 91을 실행하고, 허용 시간이 1–600이라면 0, 1, 600, 601을 실행해야 <<=의 차이가 드러납니다.

대표 경계값 누락

lab/GradeBoundaryBug.java
public final class GradeBoundaryBug {
    public static void main(String[] args) {
        int score = 90;
        String grade;

        if (score > 90) {
            grade = "A";
        } else if (score > 80) {
            grade = "B";
        } else {
            grade = "C";
        }

        System.out.println(score + "=" + grade);
    }
}
90=B

91만 실행했다면 A가 나와 오류를 놓칩니다. 요구가 “90점 이상 A”라면 조건은 score >= 90입니다. B도 “80점 이상”이라면 score >= 80이어야 80이 포함됩니다.

src/GradeBoundary.java
public class GradeBoundary {
    public static void main(String[] args) {
        int score = 90;
        String grade;

        if (score < 0 || score > 100) {
            grade = "invalid";
        } else if (score >= 90) {
            grade = "A";
        } else if (score >= 80) {
            grade = "B";
        } else {
            grade = "C";
        }

        System.out.println(score + "=" + grade);
    }
}
90=A

먼저 정상 예제 하나를 이해한 뒤 score만 79, 80, 89, 100, 101로 바꾸어 다시 실행합니다. 아직 배우지 않은 배열과 향상된 for를 경계 검사 도구로 미리 사용하지 않습니다. 점수 유효성 0–100을 먼저 검사했으므로 A 조건은 90–100만 받습니다.

겹치지 않는 조건 구간

코드 전에 범위를 표로 만듭니다.

구간조건식대표 경계
유효하지 않음`score < 0
Ascore >= 9090
B앞 조건 거짓 + score >= 8080, 89
C앞 조건 모두 거짓0, 79

각 정수 입력이 정확히 한 행에 속하고 빈 구간이 없는지 확인합니다. score >= 90 && score <= 100처럼 상한을 반복해도 되지만 앞의 유효성 검사와 분기 순서가 이미 상한을 보장합니다. 이 전제를 읽기 어렵다면 조건을 명시적으로 써도 됩니다.

0의 업무 의미

달러를 원화로 환산하는 요구를 생각해 봅시다. 환율 1,350원, 달러 양수만 환산, 0달러는 “교환할 금액 없음”, 음수는 오류라고 정합니다.

src/ExchangeBoundary.java
public final class ExchangeBoundary {
    public static void main(String[] args) {
        int dollars = Integer.parseInt(args[0]);
        int wonPerDollar = 1_350;

        if (dollars < 0) {
            System.out.println("invalid dollars");
        } else if (dollars == 0) {
            System.out.println("nothing to exchange");
        } else {
            long won = (long) dollars * wonPerDollar;
            System.out.println("won=" + won);
        }
    }
}
세 실행
> java -cp out ExchangeBoundary -1
invalid dollars

> java -cp out ExchangeBoundary 0
nothing to exchange

> java -cp out ExchangeBoundary 100
won=135000

0을 양수와 같은 결과로 처리할지 별도 상태로 처리할지는 문법이 정하지 않습니다. 요구사항을 먼저 정해야 > 0>= 0 중 하나를 선택할 수 있습니다. 곱셈은 큰 달러 입력에서 int 범위를 넘을 수 있어 한 피연산자를 long으로 바꿨습니다.

시간 범위의 경계값

Study Log는 1–600분을 허용합니다. 다음 프로그램은 명령으로 받은 값 하나를 분류합니다. 아직 배열과 반복문을 배우기 전이므로 경계값마다 프로그램을 다시 실행합니다.

src/MinuteBoundaryTable.java
public final class MinuteBoundaryTable {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("사용법: MinuteBoundaryTable <minutes>");
            return;
        }

        int minutes = Integer.parseInt(args[0]);
        boolean valid = minutes >= 1 && minutes <= 600;
        String label;
        if (!valid) {
            label = "invalid";
        } else if (minutes < 30) {
            label = "short";
        } else if (minutes < 60) {
            label = "focused";
        } else {
            label = "long";
        }
        System.out.println(minutes + ":" + valid + ":" + label);
    }
}
> java -cp out MinuteBoundaryTable 0
0:false:invalid
> java -cp out MinuteBoundaryTable 1
1:true:short
> java -cp out MinuteBoundaryTable 600
600:true:long
> java -cp out MinuteBoundaryTable 601
601:false:invalid

최솟값과 최댓값만 보지 않고 바로 바깥 값까지 넣습니다. 내부 분류 경계 30과 60에도 29/30/31, 59/60/61을 적용해야 합니다. 경계가 여러 개라면 각 경계마다 세 값을 만들어 전체 조건표를 확인합니다.

조건 문제 해석법

조건 연습에서 바로 코드를 쓰기 전에 다음 네 질문에 답합니다.

  1. 입력 값의 전체 유효 범위는 어디까지인가?
  2. 결과가 바뀌는 정확한 기준값은 무엇인가?
  3. 기준값 자체는 왼쪽 결과와 오른쪽 결과 중 어디에 포함되는가?
  4. 어느 결과에도 속하지 않거나 두 결과에 동시에 속하는 값이 있는가?

예를 들어 “거리에 따라 운송비를 계산하되 1km 이하는 1,000원, 10km 이하는 2,000원, 그 이상은 3,000원”이라면 경계는 1과 10입니다. distance <= 1, 그 조건이 거짓인 상태에서 distance <= 10, 마지막 else로 배치합니다. 0이나 음수를 허용하지 않는다면 이 분류보다 먼저 유효성 검사가 필요합니다.

기록 경계 실행표

RecordBoundaryCheck는 명령으로 받은 값 하나를 분류합니다. 배열과 반복문은 아직 배우지 않았으므로 같은 프로그램을 경계값마다 다시 실행해 결과표를 완성합니다.

src/RecordBoundaryCheck.java
public final class RecordBoundaryCheck {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("사용법: RecordBoundaryCheck <minutes>");
            return;
        }

        int minutes = Integer.parseInt(args[0]);
        String result;
        if (minutes < 1 || minutes > 600) {
            result = "invalid";
        } else if (minutes < 30) {
            result = "short";
        } else if (minutes < 60) {
            result = "focused";
        } else {
            result = "long";
        }
        System.out.println(minutes + "=" + result);
    }
}
> java -cp out RecordBoundaryCheck 0
0=invalid
> java -cp out RecordBoundaryCheck 1
1=short
> java -cp out RecordBoundaryCheck 29
29=short
> java -cp out RecordBoundaryCheck 30
30=focused
> java -cp out RecordBoundaryCheck 59
59=focused
> java -cp out RecordBoundaryCheck 60
60=long
> java -cp out RecordBoundaryCheck 600
600=long
> java -cp out RecordBoundaryCheck 601
601=invalid

이 출력은 이후 StudyLog record를 바꿀 때 유지해야 할 조건 계약입니다. 메서드 내부 구현을 switch나 다른 표현으로 바꾸더라도 같은 입력표의 결과가 같아야 합니다.

연습 문제

정수 진행률을 다음처럼 분류하는 ProgressBoundary를 작성하세요.

  • 0 미만 또는 100 초과: invalid
  • 0: not-started
  • 1–79: in-progress
  • 80–99: almost
  • 100: complete

-1, 0, 1, 79, 80, 99, 100, 101을 모두 실행해 출력합니다.

해설 보기
src/ProgressBoundary.java
public final class ProgressBoundary {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("사용법: ProgressBoundary <progress>");
            return;
        }

        int progress = Integer.parseInt(args[0]);
        String status;
        if (progress < 0 || progress > 100) {
            status = "invalid";
        } else if (progress == 0) {
            status = "not-started";
        } else if (progress < 80) {
            status = "in-progress";
        } else if (progress < 100) {
            status = "almost";
        } else {
            status = "complete";
        }
        System.out.println(progress + "=" + status);
    }
}

java -cp out ProgressBoundary -1부터 문제에 제시된 여덟 값을 한 번씩 실행합니다. 0과 100은 각각 의미가 있는 단일 경계라 별도 분기로 뒀습니다. 80 이상 조건을 먼저 두는 방식도 가능하지만 각 앞 조건이 제거한 범위를 설명할 수 있어야 합니다.