글을 작성하게 된 계기
맡은 프로젝트에서 불필요한 추상화, 최적화를 해서 복잡도를 올리다가 야근 에 철야 까지 하게 되었습니다. 이 과정에서 든 생각을 정리하기 위해 글을 작성하게 되었습니다.
1. 왜 이런 생각이 들었을까?
프로젝트에서 불필요한 추상화와 최적화를 하다보니 구조가 복잡해졌고, 안그래도 빠듯한 일정에 야근에 철야까지 하게 되었습니다. 이 과정에서 유연한 설계에 대해 다시 한 번 생각해보게 되었습니다. 유연한 설계를 하면 변화에 잘 대응할 수 있어 유지보수가 쉽고, 새로운 요구사항도 쉽게 수용할 수 있죠. 그런데 곰곰히 생각해보니 입사 후, 유연한 설계가 필요한 경우가 정말 적었고, 내가 원하는 깔끔한 구조 를 만들고 싶어서 유연한 설계를 하려고 한게 아닌가? 라는 생각이 들었습니다.
심지어 제가 맡고 있는 서비스는 한 번 정해진 규칙이나 구조가 몇 년간 변하지 않는 것도 대부분이었습니다. 😐
그리고 일정이 딜레이되는데, 유연한 설계를 말하는게 맞나? 하는 생각도 함께 들었습니다. 최근 기술도 중요한데, 비즈니스는 더 중요하다는 생각이 들었기 때문에 생각이 많았거든요. 이런 고민 과정에서 필요할 때, 쉽게 변경할 수 있는 최소한의 장치가 있는 구조가 유연한 설계 아닐까? 라는 생각이 들었습니다. 미래는 예측할 수도 없고, 변화는 생각한 대로 오지도 않으니, 최대한 현재에 집중하고 변경을 위한 최소한의 여지만 남겨두는 것 이죠.
- 일단 현재 요구사항을 충족하는 가장 심플한 구조를 만든다.
- 현재 요구사항을 충족하는데 필요한 최소한의 유연성만 고려한다.
- 예측 가능한 변화에 대해서만 유연하게 설계한다.
2. 어디까지 유연해야 할까?
그러면서 설계의 유연성에는 한계 와 제약 사항 이 필요하다는 생각이 들었는데, 현재의 문제 와 예상 가능한 변경 에 대해서만 유연하게 설계하는 것이 적당한 것 같았습니다. 유연해야 한다는 말을 그대로 따르다 보면, 끝없는 추상화 와 복잡성의 늪 에 빠질 수 있기 때문입니다. 그렇다고 유연함을 완전히 배제하라는 의미는 아닌데요, 여기에 딱 맞는 원칙이 이미 있더라고요.
구현체는 몇 년째 한 개인데, 불필요한 추상화를 하는 경우. 한 번쯤은 이런 경험 있지 않나요? 👀 이거 외에도 디자인 패턴을 과하게 사용한다던지, 너무 작은 단위로 인터페이스를 분리한다던지 제가 했던 수 많은 실수들이 떠오르더라고요.
1
2
3
4
5
interface PaymentService: PaymentProcessor {
fun processPayment(command: PaymentCommand)
......
}
1
2
3
abstract class PaymentProcessor {
......
}
여튼, 비즈니스 요구사항에서 확실하게 반복될 수 있는 변화가 예측 된다면, 그 부분은 유연하게 설계할 이유가 충분합니다. 반면, “혹시 나중에 이런 경우도 생기지 않을까?” 하는 막연한 가능성에 대비 해 미리 확장 가능한 구조를 만드는 것은 위험합니다. 위에서 말했듯, 변화는 예측하지도 못할 뿐더러 설사 오더라도 내가 예측한 범위 밖에서 오는 경우가 많았거든요.
안타깝게도 변화가 왔을 때는 자잘한 변화보다 기획이나 설계 자체가 바뀌는 큰 변화가 훨씬 많았습니다.
요구사항이 잘 바뀌지 않는 시스템이라면, 굳이 복잡한 유연성을 고려할 필요는 더욱 적습니다. 이런 경우, 변경 가능성보다 현재의 명확성 과 유지보수의 단순성 이 훨씬 더 큰 가치죠. 따라서 너무 많은 고민을 하기보다 요구사항을 빠르게 충족하며, 이 과정에서 예측 가능한 범위 까지만 대비를 하도록 합시다. 물론 그렇다고 아래처럼 하면 안되겠죠? 💩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Service
class PaymentService(
private val paymentRepository: PaymentRepository,
private val notificationService: NotificationService,
private val userService: UserService
) {
@Transactional
fun processPayment(
type: String?,
amount: BigDecimal?,
country: String?
) {
if (type != null && amount != null) {
if (type == "CARD") {
if (country == "KR") {
......
} else if (country == "US") {
......
} else {
......
}
} else if (type == "KAKAOPAY") {
if (country == "KR") {
......
} else {
......
}
} else if (type == "TOSS") {
if (amount > BigDecimal("1000000")) {
......
} else {
......
}
} else {
......
}
} else {
......
}
}
}
3. 정리
야근에 철야까지 하면서 유연한 설계에 대해 다시 한 번 생각해보게 되었습니다. 머리가 나쁘면 손발이 고생한다고, 몸으로 배우고 있네요. 조금씩 실무 경험이 생기면서 현실적 으로 변하고 있는 것 같은데, 참 생각이 많아집니다. 이상적으로 개발하는, 스스로 피곤할 정도로 고민하는 과정 이 나쁘지는 않았는데, 이젠 점점 현실과 타협하는 개발자 가 되는 것 같아서요. 처음 입사했을 때, 사수님들이 패기 넘친다고 했던 것 같은데 이제는 일단 일정 맞추고.... 가 먼저 나오네요.