A Philosophy of Software Design — John Ousterhout 2
APoSD Ch.4 — 모듈은 깊어야 한다 (Modules Should Be Deep)
핵심 요약
모듈의 가치는 "비용(인터페이스) 대비 이익(기능)"이다. 좋은 모듈은 단순한 인터페이스 뒤에 많은 기능을 숨긴다(deep). AI는 "작은 클래스가 좋다"는 원칙을 맹신해 개별적으로는 단순하지만 전체적으로는 복잡도가 폭발하는 shallow 모듈을 양산한다. 바이브 코딩 시대에는 AI가 만든 클래스가 실제로 복잡도를 줄이는지, 아니면 단순히 복잡도를 다른 곳으로 이동시켰는지 판단하는 능력이 핵심이다.
바이브 코딩 판단 포인트
AI가 자주 만드는 Shallow Module 패턴
기계적 레이어 분리: Controller → Service → Repository에서 Service가 단순 위임만 하는 경우
과도한 추상화: 인터페이스 하나당 구현체 하나 (확장 가능성 없음)
Getter/Setter 클래스: 로직 없이 데이터만 전달하는 DTO 체인
1:1 매핑 유틸: 메서드 하나짜리 Helper/Util 클래스 남발
이런 코드는 개별 클래스는 단순해 보이지만, 전체 시스템을 이해하려면 5~10개 파일을 넘나들어야 한다. 복잡도를 해결한 게 아니라 숨긴 것이다.
Deep Module의 특징
인터페이스가 작고 명확함 (알아야 할 것이 적음)
구현은 복잡할 수 있지만 사용자는 신경 쓸 필요 없음
인터페이스 변경 없이 구현 수정 가능
다른 모듈에 미치는 영향 최소화
판단 기준
AI 코드를 볼 때: "이 클래스가 없어지면 코드가 더 단순해질까?"를 물어라.
YES → Shallow module, 제거 고려
NO → Deep module, 유지
판단 질문
"이 클래스의 인터페이스(public method)를 설명하는 데 몇 문장이 필요한가?"
3문장 이상 필요 → 인터페이스가 복잡함 (비용 高)
실제 구현이 단순함 → Shallow module
"이 클래스를 사용하려면 내부 구현을 얼마나 알아야 하는가?"
구현 세부사항을 알아야 사용 가능 → 추상화 실패
"무엇을 하는가"만 알면 됨 → 좋은 추상화
"이 클래스가 다른 클래스에 단순 위임만 하는가?"
YES → 중간 레이어 제거 고려
NO → 실제 로직/복잡도를 캡슐화하고 있음
Bad vs Good 예시
❌ Bad: Shallow Modules (AI가 자주 만드는 패턴)
// Controller: 단순 위임
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
return ResponseEntity.ok(orderService.createOrder(request));
}
}
// Service: 단순 위임 (로직 없음)
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final OrderMapper orderMapper;
public OrderResponse createOrder(OrderRequest request) {
Order order = orderMapper.toEntity(request);
Order saved = orderRepository.save(order);
return orderMapper.toResponse(saved);
}
}
// Mapper: 1:1 변환만
@Component
public class OrderMapper {
public Order toEntity(OrderRequest request) {
return Order.builder()
.productId(request.getProductId())
.quantity(request.getQuantity())
.build();
}
public OrderResponse toResponse(Order order) {
return new OrderResponse(order.getId(), order.getProductId(), order.getQuantity());
}
}
문제점:
OrderService는 아무 일도 안 함 (그냥 통과역)
주문 생성을 이해하려면 4개 파일을 읽어야 함 (Controller, Service, Mapper, Repository)
각 클래스는 단순하지만 인터페이스 누적 비용이 큼
"작은 클래스" 원칙만 따르고 실제 복잡도는 해결 안 됨
✅ Good: Deep Module
// Controller: 여전히 단순
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
return ResponseEntity.ok(orderService.createOrder(request));
}
}
// Service: 실제로 복잡한 로직을 캡슐화
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentService paymentService;
@Transactional
public OrderResponse createOrder(OrderRequest request) {
// 1. 재고 확인 및 예약
inventoryService.reserveStock(request.getProductId(), request.getQuantity());
try {
// 2. 결제 처리
PaymentResult payment = paymentService.processPayment(
request.getPaymentMethod(),
calculateAmount(request)
);
// 3. 주문 생성
Order order = Order.builder()
.productId(request.getProductId())
.quantity(request.getQuantity())
.paymentId(payment.getId())
.status(OrderStatus.CONFIRMED)
.build();
Order saved = orderRepository.save(order);
return toResponse(saved);
} catch (PaymentException e) {
// 결제 실패 시 재고 복원
inventoryService.releaseStock(request.getProductId(), request.getQuantity());
throw new OrderCreationException("Payment failed", e);
}
}
// private helper methods
private BigDecimal calculateAmount(OrderRequest request) {
// 가격 계산 로직
}
private OrderResponse toResponse(Order order) {
// 간단한 변환 로직 (별도 클래스 불필요)
}
}
장점:
인터페이스는 단순함:
createOrder(request)한 줄로 설명 가능구현은 복잡함: 재고, 결제, 트랜잭션, 예외 처리를 모두 캡슐화
사용자(Controller)는 내부 세부사항을 몰라도 됨
파일 2개(Controller, Service)만 보면 전체 흐름 이해 가능
높은 기능 대 인터페이스 비율 = Deep module
🔍 Real-world 예시: Spring의 JdbcTemplate
// Shallow하게 만들었다면 (AI가 할 법한):
DatabaseConnection conn = connectionPool.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE id = ?");
User user = resultSetMapper.mapToUser(rs);
rs.close();
stmt.close();
conn.close();
// Deep module (실제 JdbcTemplate):
User user = jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new BeanPropertyRowMapper<>(User.class),
userId
);
JdbcTemplate은 connection 관리, statement 생성, 예외 변환, 리소스 정리라는 복잡한 구현을 단순한 인터페이스 뒤에 숨긴 대표적인 deep module이다.
한 줄 판단 기준
"이 클래스를 제거하면 코드가 더 복잡해지는가? 아니면 단순해지는가?" — 전자는 deep module, 후자는 shallow module이다.
댓글
댓글이 없습니다.
