기본문법을 안다고 바로 프로그램을 설계할 수 있는 것은 아니다. 실제 요구사항은 "어떤 입력을 받아 무엇을 반환해야 하는가"부터 결정해야 한다. 이번에는 주문 데이터를 요약하고 페이지 단위로 조회하는 작은 기능을 작성하면서 문제를 코드로 바꾸는 과정을 연습해 보자.
간단한 주문 관리 기능의 조건을 다음처럼 정했다.
PAID 상태의 주문만 매출 합계에 포함한다.규칙을 먼저 적는 이유는 메서드의 책임과 테스트할 경계 조건을 쉽게 찾기 위해서다.
Java 16 이상에서는 record로 간단한 데이터 전달 객체를 만들 수 있다. 다음 코드는 Java 17 이상에서 실행된다.
// OrderReportExample.java
import java.util.List;
public class OrderReportExample {
enum Status {
CREATED, PAID, CANCELLED
}
record Order(String id, String userId, int amount, Status status) {
}
static long calculatePaidAmount(List<Order> orders) {
return orders.stream()
.filter(order -> order.status() == Status.PAID)
.mapToLong(Order::amount)
.sum();
}
static int totalPages(int itemCount, int pageSize) {
if (itemCount < 0 || pageSize <= 0) {
throw new IllegalArgumentException("항목 수와 페이지 크기를 확인하세요.");
}
return itemCount / pageSize + (itemCount % pageSize == 0 ? 0 : 1);
}
static <T> List<T> getPage(List<T> items, int page, int pageSize) {
if (page < 1 || pageSize <= 0) {
throw new IllegalArgumentException("페이지 번호는 1 이상, 크기는 양수여야 합니다.");
}
long start = (long) (page - 1) * pageSize;
if (start >= items.size()) {
return List.of();
}
int from = (int) start;
int to = (int) Math.min(start + pageSize, items.size());
return List.copyOf(items.subList(from, to));
}
public static void main(String[] args) {
List<Order> orders = List.of(
new Order("O-201", "U-1", 18_000, Status.PAID),
new Order("O-202", "U-2", 9_000, Status.CREATED),
new Order("O-203", "U-1", 32_000, Status.PAID),
new Order("O-204", "U-3", 7_000, Status.CANCELLED),
new Order("O-205", "U-4", 15_000, Status.PAID)
);
System.out.println("매출 합계: " + calculatePaidAmount(orders));
System.out.println("전체 페이지: " + totalPages(orders.size(), 2));
System.out.println("두 번째 페이지: " + getPage(orders, 2, 2));
}
}
실행 결과의 앞 두 줄은 다음과 같다.
매출 합계: 65000
전체 페이지: 3
마지막 줄은 두 번째 페이지에 해당하는 O-203, O-204 두 주문의 record 표현을 출력한다.
calculatePaidAmount는 상태에 따라 필터링하고 합계만 구한다. 주문 금액 타입을 실무에서 정의할 때는 통화, 최소 단위, 범위, 환불 금액 등을 고려해야 한다. 예제에서는 이해를 위해 int를 사용하고 합계에서는 long으로 변환했다.
totalPages는 전체 항목 수와 한 페이지 크기를 받아 페이지 수를 계산한다. 예를 들어 항목 5개를 한 페이지에 2개씩 표시하면 3페이지가 필요하다. 항목이 0개일 때는 0을 반환하는 정책이다. 실제 UI에서는 빈 목록을 1페이지로 표현하는 등 제품 요구에 맞는 정책을 선택하면 된다.
getPage는 1부터 시작하는 페이지 번호를 0 기반 리스트 인덱스로 바꾼다. 범위를 벗어난 페이지는 빈 리스트를 반환한다. 오버플로를 방지하기 위해 시작 위치 계산은 long으로 수행했다.
검색 입력이나 사용자 로그를 다룰 때는 단순한 문자열 처리도 자주 필요하다.
// TextCleanupExample.java
public class TextCleanupExample {
static String normalizeKeyword(String raw) {
if (raw == null) {
return "";
}
return raw.strip().replaceAll("\\s+", " ").toLowerCase(java.util.Locale.ROOT);
}
public static void main(String[] args) {
System.out.println(normalizeKeyword(" Java API ")); // java api
}
}
strip()은 앞뒤 공백을 제거한다. replaceAll("\\s+", " ")은 연속된 정규표현식 공백 문자를 하나로 바꾼다. 단, 자바 정규표현식의 \s는 설정에 따라 모든 유니코드 공백을 의미하지 않으므로 국제화된 텍스트에서는 요구사항을 더 정확하게 정의해야 한다.
다음처럼 표를 만들어 예상 결과를 먼저 정리하면 코드 검증이 쉬워진다.
| 입력 조건 | 예상 동작 |
|---|---|
| 주문 0개, 크기 5 | 총 페이지 수 0 |
| 주문 5개, 크기 2 | 총 페이지 수 3 |
| 페이지 4, 주문 5개, 크기 2 | 빈 리스트 |
| 페이지 0 | IllegalArgumentException |
| 페이지 크기 0 | IllegalArgumentException |
| 미결제 주문 포함 | 결제 완료 주문만 합산 |
실무에서는 JUnit 같은 테스트 프레임워크로 이런 사례를 자동화한다. 빠른 학습 단계에서도 main에서 메서드별 예상 출력을 확인할 수 있다.
AI 도구로 예제나 테스트 초안을 생성하는 것도 좋지만, 나온 코드가 해당 Java 버전에서 컴파일되는지와 요구사항의 경계 조건을 만족하는지는 직접 검증해야 한다.
record는 간단한 불변 데이터 전달 모델을 표현할 때 편리하다.enum으로 허용 가능한 상태값을 타입으로 제한할 수 있다.