소트웍스 앤솔러지(Object Calisthenics)는 소프트웨어 설계의 훈련을 위해 제프 베이(Jeff Bay)가 제안한 9가지 규칙을 담고 있다.
마치 체조(gymnastics)처럼 매일 훈련하듯 적용하면서 객체지향 감각을 기르자는 취지인데, 그 9가지 규칙에 대해서 알아보자.
9가지 규칙 요약
- 한 메서드에 오직 한 단계의 들여 쓰기
- else 예약어를 쓰지 않는다
- 모든 원시 값과 문자열을 포장한다
- 한 줄에 점을 하나만 찍는다
- 줄여 쓰지 않는다 (축약 금지)
- 모든 엔티티를 작게 유지한다
- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다
- 일급 컬렉션을 쓴다
- 게터/세터/프로퍼티를 쓰지 않는다
규칙 1. 한 메서드에 오직 한 단계의 들여 쓰기
- 메서드 길이는 5줄 내외로 제한
- 하나의 제어 구조만 포함
// Bad
public void process(List<Order> orders) {
for (Order o : orders) {
if (o.isValid()) {
ship(o);
}
}
}
// Good (스트림과 메서드 추출)
public void process(List<Order> orders) {
orders.stream()
.filter(Order::isValid)
.forEach(this::ship);
}
규칙 2. else 예약어를 쓰지 않는다
- 조건문 중첩 = 가독성 저하
- 대안 = 조기 반환(Early Return), 다형성, Null Object
// Bad
if (user != null) {
if (user.isActive()) {
return user.getName();
} else {
return "비활성 사용자";
}
} else {
return "사용자 없음";
}
// Good (Guard Clause)
if (user == null) return "사용자 없음";
if (!user.isActive()) return "비활성 사용자";
return user.getName();
규칙 3. 원시 값과 문자열을 포장한다
- 의미 있는 객체로 감싸 의도를 드러내기
// Bad
public class User {
private String email;
}
// Good
public class Email {
private final String address;
public Email(String address) {
if (!address.contains("@")) throw new IllegalArgumentException("유효하지 않은 이메일");
this.address = address;
}
public String getValue() { return address; }
}
규칙 4. 한 줄에 점을 하나만 찍는다.
- 디미터 법칙(Law of Demeter) 준수
// Bad
order.getCustomer().getAddress().getCity();
// Good (메서드 캡슐화)
order.getShippingCity();
규칙 5. 축약 금지
- 클래스와 메서드 이름은 명확하게 한다.
// Bad
class Ord {
void sh() {}
}
// Good
class Order {
void ship() {}
}
규칙 6. 모든 엔티티를 작게 유지한다.
- 50줄 이하의 클래스 → 응집도 높고 이해하기 쉬움
- DDD(Value Object, Entity)와 잘 어울림
규칙 7. 3개 이상의 인스턴스 변수를 가진 클래스 금지
- 응집도를 잃기 쉽다. DTO는 예외 가능
// Bad
class User {
private String name;
private String email;
private String phone;
private String address; // 너무 많음
}
// Good (Value Object로 분리)
class ContactInfo {
private String email;
private String phone;
}
규칙 8. 일급 컬렉션 사용
- 컬렉션 + 부가 로직을 하나의 객체로 캡슐화
// Bad
List<Order> orders;
// Good
class Orders {
private final List<Order> values;
public Orders(List<Order> orders) { this.values = orders; }
public int totalAmount() {
return values.stream().mapToInt(Order::getAmount).sum();
}
}
규칙 9. 게터/세터/프로퍼티 금지
- 객체는 데이터 보관소가 아니라 행동을 제공해야 한다.
// Bad
order.getStatus();
order.setStatus("SHIPPED");
// Good
order.ship();
정리
✔ 이 9가지 규칙은 강제적인 법칙이 아니라, 객체지향 감각을 기르기 위한 지침이다.
✔ 실제 개발에서는 예외가 존재하지만, 규칙을 의식적으로 적용하다보면 코드가 훨씬 더 읽기 쉽고 유지보수 하기 쉬워진다는 것을 체감할 수 있다.