콘솔 기반의 커머스 시스템을 만드는 작업을 하고 있습니다.
그러던중 조건을 만족하는 상품 목록만 출력하도록하는 기능을 구현해야했습니다.
처음에 작성한 코드는 다음과 같습니다.
int getProductsSize(Predicate<Product> condition) {
return (int) products.stream().filter(condition).count();
}
getProductsSize(p -> p.getPrice() > 1_000_000);
getProductsSize(p -> p.getPrice() <= 1_000_000); getProductsSize(p -> true); // 전체
이러한 방식으로 코드를 작성해도 괜찮을지 궁금해졌습니다.
이를 개선한다면 다음과 같이 코드를 작성해야겠다고 생각했습니다.
// 전부 출력
int getExpensiveProductsSize() { ... }
// 100만원 초과
int getCheapProductsSize() { ... }
// 100만원 이하
int getAllProductsSize() { ... }
현 상황의 작은 프로젝트에서는 사실 두 방식 모두 큰 차이가 없을 수 있습니다.
하지만 각 방식이 어떤 차이가 있고 어떤 상황에서 쓰이면 좋을지 이유를 알고 고르는 것이 좋겠다는 생각에 비교해보고자 합니다.
1. 람다를 파라미터로 받는 경우
✅ 장점
- 유연성: 원하는 조건을 즉석에서 정의할 수 있어 메서드 하나로 무한히 다양한 경우를 처리 가능.
- 중복 제거: 메서드 로직(스트림 처리 등)은 한 곳에만 존재 → 유지보수 용이.
- 확장성: 새로운 조건이 생겨도 메서드를 새로 만들 필요 없음. 그냥 람다만 전달.
❌ 단점
- 가독성↓: 조건이 복잡해질수록 호출부가 지저분해질 수 있음.
(특히 조건이 길면 getProductsSize(p -> { ... })가 한눈에 잘 안 읽힘) - IDE 자동완성 활용성↓: "가격 100만원 이상 개수" 같은 도메인 용어가 바로 보이지 않음 → 의미 전달력이 약해짐.
- 재사용성↓: 같은 조건을 여러 군데서 쓴다면 매번 같은 람다를 작성해야 함.
📌 적합한 상황
- 조건이 다양하게 바뀌는 경우
→ 예: 가격 10만원 이상, 100만원 이상, 재고 10개 이하, 특정 브랜드 상품 등. - 일회성 조건을 처리할 때
→ 굳이 메서드로 이름을 만들 필요가 없고, 호출부에서 바로 정의 가능. - 범용 유틸리티성 코드를 만들 때
→ 라이브러리, 툴킷 같은 곳에서는 보통 조건을 전달받는 방식이 선호됨. - 학습/연습 단계
→ 스트림, 람다, 함수형 인터페이스 개념을 익히기에 최적.
2. 메서드를 조건별로 분리하는 경우
✅ 장점
- 가독성↑: 호출부에서 의미가 명확 → getExpensiveProductsSize()만 봐도 의도 이해 가능.
- 도메인 표현력↑: "비싼 상품 개수"라는 개념 자체가 메서드 이름으로 문서화됨.
- 중복 람다 입력 방지: 호출하는 사람은 조건을 몰라도 됨.
❌ 단점
- 유연성↓: 조건이 바뀌면 메서드를 계속 추가해야 함.
- 중복 코드 가능성↑: 필터 로직이 여러 메서드에 분산 → 유지보수 시 리스크.
- 확장성↓: 조건이 많아질수록 메서드가 폭발적으로 늘어남.
📌 적합한 상황
- 조건이 도메인 개념으로 자리 잡을 때
→ “고가 상품 개수”, “재고 부족 상품 개수”, “VIP 고객 수” 같은 건 시스템에서 자주 쓰이는 의미 있는 비즈니스 용어. - 팀 단위 협업
→ getExpensiveProductsSize()처럼 이름만 봐도 바로 이해 가능 → 가독성, 커뮤니케이션 효율 ↑. - 조건이 바뀔 가능성이 적은 경우
→ 예: “회원 등급별 분류”처럼 도메인 규칙이 명확히 정해져 있는 경우. - 테스트/문서화가 중요한 경우
→ 메서드 이름 자체가 문서 역할을 하므로 유지보수 편리.
3. 어느 쪽이 더 좋은가? (판단 기준)
- 조건이 자주 바뀌거나 다양하다 → 람다 파라미터 방식
(범용적이고 재사용 가능한 로직을 만드는 것이 목표일 때) - 조건이 도메인적으로 중요한 의미를 가진다 → 메서드 분리 방식
(예: “VIP 고객 수”, “재고 부족 상품 수” 같은 개념은 메서드 이름으로 표현하는 게 더 직관적임)
정리하자면
-
- 조건이 비즈니스 의미(도메인 용어)를 가지면 → 메서드 분리
- 조건이 단순 필터링 로직이고 다양하게 변할 수 있으면 → 람다 전달
혹은 다음과 같이 절충하여 사용할 수도 있다.
- getProductsSize(Predicate<Product> condition) 같은 범용 메서드 하나는 기본 제공.
- 자주 쓰이거나 도메인적으로 중요한 조건은 별도 메서드로 감싸서 제공.
int getExpensiveProductsSize() {
return getProductsSize(p -> p.getPrice() > 1_000_000);
}