|
| 1 | +--- |
| 2 | +title: "객체 지향 9가지 규칙" |
| 3 | +date: "2020-02-10" |
| 4 | +tags: ["Clean Code", "Study"] |
| 5 | +updated: "2025-08-18" |
| 6 | +summary: "The nine rules of Object Calisthenics explained with Java examples to improve code readability and maintainability." |
| 7 | +description: "소트웍스 앤솔러지(Object Calisthenics)에서 제안한 객체 지향 생활 체조 9가지 규칙과 Java 예시를 통해 코드의 가독성과 유지보수성을 높이는 방법을 살펴보자." |
| 8 | +--- |
| 9 | + |
| 10 | + |
| 11 | +:::info |
| 12 | +소트웍스 앤솔러지(Object Calisthenics)는 소프트웨어 설계의 훈련을 위해 제프 베이(Jeff Bay)가 제안한 9가지 규칙을 담고 있다. |
| 13 | +마치 체조(gymnastics)처럼 매일 훈련하듯 적용하면서 객체지향 감각을 기르자는 취지인데, 그 9가지 규칙에 대해서 알아보자. |
| 14 | +::: |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## 9가지 규칙 요약 |
| 19 | +1. 한 메서드에 오직 한 단계의 들여 쓰기 |
| 20 | +2. else 예약어를 쓰지 않는다 |
| 21 | +3. 모든 원시 값과 문자열을 포장한다 |
| 22 | +4. 한 줄에 점을 하나만 찍는다 |
| 23 | +5. 줄여 쓰지 않는다 (축약 금지) |
| 24 | +6. 모든 엔티티를 작게 유지한다 |
| 25 | +7. 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다 |
| 26 | +8. 일급 컬렉션을 쓴다 |
| 27 | +9. 게터/세터/프로퍼티를 쓰지 않는다 |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## 규칙 1. 한 메서드에 오직 한 단계의 들여 쓰기 |
| 32 | +- 메서드 길이는 5줄 내외로 제한 |
| 33 | +- 하나의 제어 구조만 포함 |
| 34 | + |
| 35 | +```java |
| 36 | +// Bad |
| 37 | +public void process(List<Order> orders) { |
| 38 | + for (Order o : orders) { |
| 39 | + if (o.isValid()) { |
| 40 | + ship(o); |
| 41 | + } |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +// Good (스트림과 메서드 추출) |
| 46 | +public void process(List<Order> orders) { |
| 47 | + orders.stream() |
| 48 | + .filter(Order::isValid) |
| 49 | + .forEach(this::ship); |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +## 규칙 2. else 예약어를 쓰지 않는다 |
| 54 | +* 조건문 중첩 = 가독성 저하 |
| 55 | +* 대안 = 조기 반환(Early Return), 다형성, Null Object |
| 56 | + |
| 57 | +```java |
| 58 | +// Bad |
| 59 | +if (user != null) { |
| 60 | + if (user.isActive()) { |
| 61 | + return user.getName(); |
| 62 | + } else { |
| 63 | + return "비활성 사용자"; |
| 64 | + } |
| 65 | +} else { |
| 66 | + return "사용자 없음"; |
| 67 | +} |
| 68 | + |
| 69 | +// Good (Guard Clause) |
| 70 | +if (user == null) return "사용자 없음"; |
| 71 | +if (!user.isActive()) return "비활성 사용자"; |
| 72 | +return user.getName(); |
| 73 | +``` |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## 규칙 3. 원시 값과 문자열을 포장한다 |
| 78 | +* 의미 있는 객체로 감싸 의도를 드러내기 |
| 79 | + |
| 80 | +```java |
| 81 | +// Bad |
| 82 | +public class User { |
| 83 | + private String email; |
| 84 | +} |
| 85 | + |
| 86 | +// Good |
| 87 | +public class Email { |
| 88 | + private final String address; |
| 89 | + |
| 90 | + public Email(String address) { |
| 91 | + if (!address.contains("@")) throw new IllegalArgumentException("유효하지 않은 이메일"); |
| 92 | + this.address = address; |
| 93 | + } |
| 94 | + public String getValue() { return address; } |
| 95 | +} |
| 96 | + |
| 97 | +``` |
| 98 | + |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## 규칙 4. 한 줄에 점을 하나만 찍는다. |
| 103 | +* 디미터 법칙(Law of Demeter) 준수 |
| 104 | + |
| 105 | +```java |
| 106 | +// Bad |
| 107 | +order.getCustomer().getAddress().getCity(); |
| 108 | + |
| 109 | +// Good (메서드 캡슐화) |
| 110 | +order.getShippingCity(); |
| 111 | +``` |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +## 규칙 5. 축약 금지 |
| 116 | +* 클래스와 메서드 이름은 명확하게 한다. |
| 117 | + |
| 118 | +```java |
| 119 | +// Bad |
| 120 | +class Ord { |
| 121 | + void sh() {} |
| 122 | +} |
| 123 | + |
| 124 | +// Good |
| 125 | +class Order { |
| 126 | + void ship() {} |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## 규칙 6. 모든 엔티티를 작게 유지한다. |
| 134 | +* 50줄 이하의 클래스 → 응집도 높고 이해하기 쉬움 |
| 135 | +* DDD(Value Object, Entity)와 잘 어울림 |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## 규칙 7. 3개 이상의 인스턴스 변수를 가진 클래스 금지 |
| 140 | +* 응집도를 잃기 쉽다. DTO는 예외 가능 |
| 141 | + |
| 142 | +```java |
| 143 | +// Bad |
| 144 | +class User { |
| 145 | + private String name; |
| 146 | + private String email; |
| 147 | + private String phone; |
| 148 | + private String address; // 너무 많음 |
| 149 | +} |
| 150 | + |
| 151 | +// Good (Value Object로 분리) |
| 152 | +class ContactInfo { |
| 153 | + private String email; |
| 154 | + private String phone; |
| 155 | +} |
| 156 | + |
| 157 | +``` |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +## 규칙 8. 일급 컬렉션 사용 |
| 162 | +* 컬렉션 + 부가 로직을 하나의 객체로 캡슐화 |
| 163 | + |
| 164 | +```java |
| 165 | +// Bad |
| 166 | +List<Order> orders; |
| 167 | + |
| 168 | +// Good |
| 169 | +class Orders { |
| 170 | + private final List<Order> values; |
| 171 | + public Orders(List<Order> orders) { this.values = orders; } |
| 172 | + public int totalAmount() { |
| 173 | + return values.stream().mapToInt(Order::getAmount).sum(); |
| 174 | + } |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +--- |
| 179 | + |
| 180 | +## 규칙 9. 게터/세터/프로퍼티 금지 |
| 181 | +* 객체는 데이터 보관소가 아니라 행동을 제공해야 한다. |
| 182 | + |
| 183 | +```java |
| 184 | +// Bad |
| 185 | +order.getStatus(); |
| 186 | +order.setStatus("SHIPPED"); |
| 187 | + |
| 188 | +// Good |
| 189 | +order.ship(); |
| 190 | +``` |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +:::success |
| 195 | +<b>정리</b> |
| 196 | +✔ 이 9가지 규칙은 강제적인 법칙이 아니라, 객체지향 감각을 기르기 위한 지침이다. |
| 197 | +✔ 실제 개발에서는 예외가 존재하지만, 규칙을 의식적으로 적용하다보면 코드가 훨씬 더 읽기 쉽고 유지보수 하기 쉬워진다는 것을 체감할 수 있다. |
| 198 | +::: |
| 199 | + |
| 200 | + |
| 201 | +--- |
| 202 | + |
| 203 | +## 📚 Reference |
| 204 | + |
| 205 | +- [Jeff Bay, *Object Calisthenics*](https://williamdurand.fr/2013/06/03/object-calisthenics/) |
| 206 | +- [소트웍스 앤솔러지 한국어판 리뷰](https://book.naver.com/bookdb/book_detail.nhn?bid=3246861) |
| 207 | +- [Martin Fowler - Null Object Pattern](https://martinfowler.com/eaaCatalog/specialCase.html) |
| 208 | +- [일급 컬렉션 정리 블로그 (jojoldu)](https://jojoldu.tistory.com/412) |
| 209 | + |
0 commit comments