우리는 Java 코드를 다르게 작성해야 합니다: 언어는 줄이고, 비즈니스에 집중하자
Source: Dev.to
우리는 Java 코드를 다르게 작성해야 합니다 – 언어는 줄이고 비즈니스는 늘리자
최근 몇 년간 Java는 꾸준히 진화해 왔고, 새로운 언어 기능과 라이브러리가 계속해서 추가되고 있습니다. 하지만 이런 변화가 항상 비즈니스 가치를 높이는 방향으로 이어지는 것은 아닙니다.
코드를 읽는 사람은 대부분 비즈니스 담당자이거나 도메인 전문가이며, 그들은 “이 코드가 실제 비즈니스 로직을 어떻게 구현하고 있나요?” 라는 질문에 답을 원합니다.
따라서 코드 자체에 너무 많은 언어적 복잡성을 담기보다는, 비즈니스 의미를 명확히 드러내는 것이 더 중요합니다. 아래에서는 이를 실천하기 위한 몇 가지 원칙과 예시를 소개합니다.
1. 도메인 언어를 코드에 그대로 옮기라
❌ 기존 방식
public class OrderService {
public void process(Order order) {
if (order.getStatus() == OrderStatus.PENDING) {
// …
}
}
}
✅ 비즈니스 중심 방식
public class OrderService {
public void process(Order order) {
if (order.isPending()) {
// …
}
}
}
핵심:
OrderStatus.PENDING같은 열거형을 직접 사용하기보다, 도메인 전문가가 이해하기 쉬운 메서드(isPending())를 제공해 비즈니스 의미를 바로 드러냅니다.
2. “기술적인” 이름보다 “비즈니스적인” 이름을 사용하라
| 기술적인 이름 | 비즈니스 중심 이름 |
|---|---|
UserRepository | CustomerRepository |
save() | registerCustomer() |
update() | changeCustomerAddress() |
설명:
User라는 일반적인 용어보다 실제 비즈니스 컨텍스트인Customer를 사용하면, 코드가 어떤 문제 영역을 다루는지 즉시 파악할 수 있습니다.
3. 값 객체(Value Object)를 활용해 의미를 명확히 하라
❌ 문자열로 직접 다루기
public class Payment {
private String creditCardNumber;
// …
}
✅ 값 객체 사용
public class Payment {
private CreditCardNumber creditCardNumber;
// …
}
이점:
CreditCardNumber라는 타입 자체가 “이 값은 신용카드 번호이며, 포맷 검증이 필요하다”는 비즈니스 규칙을 내포합니다. 따라서 다른 개발자가 해당 필드를 어떻게 다루어야 할지 바로 알 수 있습니다.
4. 복잡한 로직은 도메인 서비스에 캡슐화하라
❌ 서비스 메서드에 모든 로직을 넣기
public class OrderService {
public void placeOrder(OrderDto dto) {
// 검증, 재고 확인, 결제, 알림 등 모든 로직을 여기서 수행
}
}
✅ 도메인 서비스와 애그리게이트로 분리
public class OrderPlacementService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentGateway paymentGateway;
private final NotificationService notificationService;
public void place(Order order) {
order.validate();
inventoryService.reserve(order.getItems());
paymentGateway.charge(order.getPaymentInfo());
orderRepository.save(order);
notificationService.notifyCustomer(order);
}
}
핵심: 각 책임을 명확히 분리함으로써 “주문을 어떻게 처리하는가”라는 비즈니스 흐름이 코드 구조에 그대로 드러납니다.
5. 불필요한 Java 언어 기능은 과감히 배제하라
Optional은 반환값이null일 가능성을 명시적으로 보여주지만, 비즈니스 로직에서는null대신 의미 있는 객체(예:EmptyResult) 를 반환하는 것이 더 직관적일 수 있습니다.Stream은 가독성을 높이는 경우도 있지만, 복잡한 비즈니스 규칙을 구현할 때는 명시적인 루프 가 오히려 이해하기 쉽습니다.
결론: “최신 언어 기능을 무조건 사용한다”는 사고방식보다 “비즈니스 의미를 가장 명확히 전달할 수 있는 방법이 무엇인가”에 초점을 맞추세요.
6. 테스트는 비즈니스 시나리오 중심으로 작성하라
@Test
void shouldRejectOrderWhenCustomerIsBlacklisted() {
// given
Customer blacklisted = Customer.blacklisted("John Doe");
Order order = Order.of(blacklisted, List.of(item));
// when / then
assertThrows(BlacklistedCustomerException.class,
() -> orderService.place(order));
}
테스트 메서드 이름 자체가 비즈니스 규칙을 설명하고 있습니다. 이렇게 하면 테스트가 문서 역할을 하며, 비즈니스 담당자와 개발자 모두가 같은 페이지에 있을 수 있습니다.
7. 요약
| 원칙 | 기대 효과 |
|---|---|
| 도메인 언어 그대로 사용 | 비즈니스 이해도 향상 |
| 의미 있는 타입(Value Object) 도입 | 데이터 무결성 보장 |
| 책임 분리(도메인 서비스, 애그리게이트) | 유지보수성 증가 |
| 최신 언어 기능 남용 지양 | 가독성 및 직관성 확보 |
| 비즈니스 시나리오 기반 테스트 | 문서화 겸 검증 |
코드가 “무엇을 하는가” 보다 “왜 하는가” 를 더 잘 전달한다면, 팀 전체가 비즈니스 목표에 집중할 수 있습니다.
Java는 강력한 언어이지만, 그 힘을 비즈니스 의미를 명확히 하는 도구 로 활용하는 것이 진정한 가치를 창출하는 길입니다.
이 글은 도메인‑주도 설계(DDD) 와 클린 아키텍처 원칙을 바탕으로 작성되었습니다. 여러분의 프로젝트에 적용해 보시고, 비즈니스 중심의 코딩 문화가 자리 잡기를 바랍니다.
실제로 비즈니스와 관련된 코드 비율은 얼마나 될까요?
Java 서비스 메서드를 하나 열어보세요. 줄 수를 세어보세요.
- 비즈니스가 무엇을 하는지를 설명하는 줄은 몇 개입니까?
- null 체크, 예외 처리, try‑catch 블록, 타입 변환, 로깅 보일러플레이트, 프레임워크 어노테이션은 몇 개입니까?
대부분의 코드베이스에서 답은 불편합니다: 기술적인 절차가 지배하고, 비즈니스 로직은 골격 사이에 숨어 있기 때문입니다. 새로운 개발자는 코드를 읽고 어떻게 동작하는지는 이해할 수 있지만, 무엇을 하는지 혹은 왜 하는지는 파악하기 어렵습니다.
실제 문제: 기술이 아니라 언어
기술적인 문제가 아니라 언어 문제입니다. Java는 강력한 도구들을 제공하지만, 비즈니스 의미를 보존하면서 그 도구들을 어떻게 사용해야 하는지에 대한 안내는 하지 않습니다.
일반적인 서비스 메서드
// Process an order
public OrderResult process(OrderRequest request) {
// 1️⃣ Validate the input
// (null checks, field validation, exception wrapping)
// 2️⃣ Check inventory
// (try‑catch around HTTP call, retry logic, timeout handling,
// response parsing)
// 3️⃣ Calculate pricing
// (more HTTP, more try‑catch, more parsing)
// 4️⃣ Create the order
// (database call, transaction management, exception handling)
// 5️⃣ Return the result
// (response mapping, error conversion)
}
다섯 개의 비즈니스 단계가 있지만, 각 단계의 코드는 대략 80 % 기술 처리와 20 % 비즈니스 의도로 구성됩니다. 이와 같은 골격이 신호를 가려버립니다.
Source: …
기술적인 표면이 사라진다면?
고수준 문서화로서의 타입
Result는 구현을 보지 않아도 메서드가 실패할 수 있음을 알려줍니다.Option은 값이 없을 수도 있음을 알려줍니다.Promise는 비동기임을 알려줍니다.
반환 타입 자체가 문서가 되므로 “이 메서드는 예외를 발생시킬 수 있다”는 설명을 위한 Javadoc이 필요하지 않습니다.
비즈니스 진술로서의 합성 연산자
| 연산자 | 비즈니스 의미 |
|---|---|
flatMap | “이전 단계가 성공했을 경우, 다음을 수행한다.” |
all() | “이 연산들은 서로 독립적이며 순서 의존성이 없고 병렬로 실행될 수 있다.” |
all(checkInventory, calculatePricing)을 보면 두 단계가 서로 의존하지 않는다는 것을 즉시 알 수 있습니다. 이는 구조에 인코딩된 도메인 지식입니다.
포괄적인 오류 타입
모든 실패 모드를 나열한 sealed 인터페이스는 비즈니스 분석가가 오류 계층 구조를 읽고 무엇이 잘못될 수 있는지를 구현 코드를 파고들지 않아도 이해할 수 있게 합니다. 오류는 비즈니스 실패 영역을 열거하는 일급 타입이 됩니다.
구조적 패턴, 창의적 제어 흐름이 아닌
- Sequencer – “이것들을 순서대로 수행한다.”
- Fork‑Join – “이것들을 병렬로 수행한 뒤 결과를 결합한다.”
- Leaf – “하위 단계가 없는 단일 연산.”
개발자는 제어 흐름을 새로 만들지 않고, 비즈니스 프로세스에 직접 매핑되는 소수의 패턴 중에서 선택합니다.
리팩터링된 비즈니스‑중심 메서드
static OrderService orderService(InventoryService inventory,
PricingService pricing) {
return request -> inventory.check(request.items())
.flatMap(pricing::calculate)
.map(OrderResult::placed);
}
세 줄. 각 줄이 비즈니스 단계입니다.
기술적인 의식—HTTP 호출, 직렬화, 재시도, 오류 처리—은 여전히 존재하지만, 런타임과 타입 시스템이 처리하고 개발자는 이 메서드 안에서 직접 다루지 않습니다.
소리 내어 읽어 보세요: “재고를 확인한다. 그런 다음 가격을 계산한다. 그런 다음 주문을 만든다.”
이것은 코드에 대한 설명이 아니라 코드 자체입니다.
왜 이것이 중요한가
지식 전수
-
기술적 의식이 지배할 때:
새로운 팀원들은 프레임워크(annotations, configuration, exception handlers)를 배우게 됩니다. 그 지식은 framework‑specific이며 잘 전이되지 않습니다. -
비즈니스 로직이 지배할 때:
새로운 팀원들은 domain(operation dependencies, failure modes, valid states)을 배우게 됩니다. 그 지식은 프레임워크 마이그레이션, 팀 변화, 기술 전환에도 살아남습니다.
원래 개발자의 의도—코드 뒤에 있는 비즈니스 논리는 구조 자체에 보존되며, 흐려진 주석이나 오래된 문서에 의존하지 않습니다.
Source: …
대부분의 언어 기능은 비즈니스 가치에 불필요합니다
Java는 방대한 언어입니다. 상속 계층, 체크 예외, 가변 상태, 리플렉션, 애노테이션 프로세싱, 타입 소거 우회, synchronized 블록, volatile 필드와 같은 기능은 강력하지만, 비즈니스 로직을 명확하게 표현하는 데는 거의 도움이 되지 않습니다.
실제로 도움이 되는 작은 부분
| 기능 | 일반적인 사용 |
|---|---|
| Records | 불변 데이터 캐리어 |
| Sealed interfaces | 타입‑안전 대안 / 완전한 오류 처리 |
| Lambdas & method references | 단계 구성 |
| Pattern matching | 도메인 타입에 기반한 디스패치 |
그 외의 기능—컨퍼런스 발표와 블로그 포스트를 생성하는 도구들—은 주로 기술적 형식에 기여할 뿐, 비즈니스 로직에는 크게 기여하지 않습니다.
최소화된 비즈니스‑중심 서브셋의 장점
- 학습 곡선 압축 – 새로운 개발자는 비즈니스 의미를 담은 서브셋만 마스터하면 됩니다.
- 예측 가능한 코드 – 개념을 표현하는 정규화된 방법이 하나뿐이므로 스타일 논쟁이 사라지고 도메인에 집중할 수 있습니다.
- “어떤 기능을 사용해야 할까?” 결정 제거 – 답은 언제나 같은 작은 구성 집합입니다.
Bottom Line
기술적인 표면이 거의 사라질 정도로 축소되면, 비즈니스 로직이 코드에서 지배적인 신호가 됩니다. 이렇게 되면 코드는:
- 읽고 이해하기가 더 쉬워집니다.
- 변경에 더 탄력적입니다.
- 시스템의 진정한 의도를 전달하는 데 더 좋습니다.
비즈니스 개념을 직접 표현하는 언어 기능에 집중하고, 런타임과 타입 시스템이 형식적인 부분을 처리하도록 하세요.
Source: …
Java 코드를 다르게 작성해야 합니다 – Part 6
그 함축은 하나의 언어에 국한되지 않는다. 비즈니스에 중요한 Java 서브셋이 이렇게 작다면, 언어 기능을 더 추가해도 비즈니스 표현력이 늘어나는 것이 아니라, 비즈니스 로직과 경쟁하는 기술적 표면이 확대될 뿐이다. 표현력은 도메인 모델링에서 나오며, 언어 문법에서 나오지 않는다.
코드의 기술적 부분
- 작음 – 몇 개 안 되는 타입과 패턴, 프레임워크 어휘가 아니다
- 표준화 – 모든 곳에서 동일한 패턴, 개발자마다 다른 스타일이 없다
- 의미론적으로 유의미함 – 각 구성 요소가 비즈니스 개념에 매핑된다
코드의 비즈니스 부분
- 우위 – 기술적 골격보다 더 눈에 띈다
- 가독성 – 콜백의 얽힘이 아니라 이름이 붙은 연산들의 순서
- 포괄성 – 모든 실패 모드가 드러나고, 모든 의존성이 선언된다
이 두 조건이 충족될 때, 코드는 처음부터 그래야 했던 비즈니스가 수행하는 일을 실행 가능한 사양이 된다.
이 글은 “We Should Write Java Code Differently” 시리즈의 여섯 번째 기사이다.
이전 기사: The DI Confusion.