🤸♂️ 객체지향 생활체조, 왜 지금인가
처음부터 정리하고 싶었으나...
우아한테크코스를 시작할 때부터 객체지향 생활체조를 지키라는 가이드를 받았습니다.
이는 유지 보수하기 좋은 코드를 작성하기 위한 의식적인 연습을 할 수 있는
추상적이지 않은, 아주 구체적인 눈에 보이는 지표로서 아주 큰 역할을 해주었습니다.
처음부터 이 내용에 대해 정리해보고 싶었지만,
왜 이러한 규칙을 지켜야 하는지, 지켰을 때 어떤 장점이 있는지,
객체지향 생활체조를 지켜본 결과 경험한 바가 어떠한지에 이야기할 바가 없었습니다.
레벨 2 마지막 미션이 머지된 오늘, 드디어 오늘 ㅎ
지금까지 8개의 미션을 지나오며 객체지향 생활체조를 경험한 내용을 토대로 정리해보려 합니다.
📚 용어 정리
의사소통의 장애물을 먼저 없애고 시작합시다!
객체지향 생활체조란 Object Calisthenics를 번역한 표현입니다.
The ThoughtWorks Anthology라는 책의 여섯 번째 장의 제목입니다.
ThoughtWorks는 마틴 파울러가 재직해 있는 회사 이름이며,
이 회사에 대해서는 여기에서 더 자세히 알아보실 수 있습니다.
Anthology는 여러 작가들의 문학 작품을 모은 책 정도로 번역할 수 있는데요,
참고로 이 책의 6장은 Jeff Bay(Technology Principal)라는 분이 저술하셨습니다.
ThoughtWorks의 주요 사업 중 하나가 컨설팅인 만큼,
본인들의 역량과 노하우들을 어필하고 공유하고자 하는 목적을 가진 책이라고 할 수도 있을 것 같습니다.
체조는 본 운동에 들어가기에 앞서 충분한 역량을 발휘할 수 있게
사전에 몸을 풀어두는 역할을 합니다.
다시 말해, 본격적인 비즈니스 로직 작성에 들어가기에 앞서서
이러한 규칙들을 몸에 익히고 난다면 더 유지 보수하기 좋은 코드를 작성할 수 있게 될 거라는 의미가 아닐까 싶네요.
추상적이지 않고 구체적인 기준이 제시된다면, 스스로 점검 및 피드백이 가능해지고,
피드백을 주는 사람도 받는 사람도 더 원활한 의사소통이 가능할 것 같습니다.
그런 의미에서 객체지향 생활체조를 처음부터 강조해준 우테코의 교육 방식에 정말 감사할 따름입니다.
✍️ 9가지 규칙
유지보수하기 좋은 코드를 작성하기 위한 더 나은 디자인
절차 지향적이고 이해하기 어렵고, 테스트하기 어려우며, 유지 보수하기 어려운 코드들을 만난 경험, 다들 있으실 텐데요,
객체지향 체조의 목적은 테스트하기 좋은 코드, 유지 보수하기 좋은 코드, 이해하기 좋은 코드입니다.
아래의 9가지 원칙들이 있으니 한 번 살펴보죠.
The Rules Here are the rules for the exercise:
1. Use only one level of indentation per method.
2. Don’t use the else keyword.
3. Wrap all primitives and strings.
4. Use only one dot per line.
5. Don’t abbreviate.
6. Keep all entities small.
7. Don’t use any classes with more than two instance variables.
8. Use first-class collections.
9. Don’t use any getters/setters/properties.
1. 들여 쓰기는 1단만
2. else 사용하지 않기
3. 모든 원시 값은 포장하기
4. 한 줄에 점 하나만
5. 축약하지 않기
6. 엔티티들을 작게 유지하기
7. 하나의 클래스에 인스턴스 변수 2개 이하로 유지하기
8. 일급 컬렉션 사용하기
9. getter/setter 사용하지 않기
그럼 이하 내용부터는 위의 규칙을 준수하기 위해 노력해본 결과
어떤 경험을 할 수 있었는지, 저는 어떤 결론을 내렸는지 소개해보겠습니다.
👉 들여 쓰기 1단만, else 사용하지 않기
아도겐 금지
이미지 출처 : https://levelup.gitconnected.com/escape-the-pyramid-of-doom-c58edd326225
들여쓰기 깊이가 깊어질수록 사람이 이해하기 어려운 코드가 됩니다.
이 부분은 우테코 이전에 약간 경험을 해봤습니다.
while, if, else 등이 난사되어있는 코드들을 헤쳐가며 이해하는 데 많은 시간을 투자해야 했고,
어떤 분기 처리를 수정하기 위해서는 어느 부분을 수정해야 할지 반복해서 검증해야 했습니다.
(물론 이 당시 환경이 테스트 코드가 없었다는 점도 큰 몫을 했습니다;;)
else 키워드도 같은 맥락에서 사람이 이해하기 어려운 코드가 됩니다.
if 문에 선언된 조건문이 읽기 좋게 쓰이지 않았다면, else 부분에는 어떤 조건에 해당하는 코드인지 이해하기 어렵게 됩니다.
else에 대해서는 추가적인 생각이 있는데요,
바로 휴먼 에러의 위험성입니다.
A혹은 B 두 가지의 경우만 발생할 줄 알고 if/else 로만 처리를 했는데,
뜻밖의 C, D 가 나타나면서 else 문에서 의도하지 않은 상황이 발생할 수 있는 것입니다.
첫 번째 두 번째 원칙은 많은 분들이 쉽게 공감할 수 있는 내용이기도 하고,
박재성 님의 워낙 훌륭한 발표가 이미 있기 때문에 링크 공유 정도로도 사실 충분하다고 생각합니다.
정리하자면, 들여쓰기 1단만 그리고 else 금지는
이해하기 쉬운 코드를 작성하게 되는 넛지로 동작하며,
조금만 연습하면 쉽게 적응할 수 있으며 큰 효과를 거두게 해주는 원칙이었다
라는 경험담으로 마무리하고 싶습니다.
🎁 모든 원시 값 포장하기
LottoNumber.... 이건 아직 의식적인 연습이 부족합니다....
레벨 1의 두 번째 미션인 로또에서 하나의 로또 숫자를 의미하는 int값을 LottoNumber 값 객체로 포장한 적 있습니다.
그리고 이들을 List<LottoNumber> 로 묶어서 Lotto라는 클래스가 하나의 일급 컬렉션으로 역할을 했습니다.
그러나 이후 미션을 진행하면서는 값 객체를 거의 사용하지 않았습니다.
감사하게도 레벨 2 지하철 경로 조회 미션을 진행하며 이프라는 크루를 통해 큰 교훈을 얻었습니다.
이프는 정말 철저하게 값 객체 규칙을 준수하고 있었고, 값 객체 사용의 장단점과 실제적인 경험 등을 들려주었습니다.
다른 규칙들에 비해 값 객체는 저에겐 저항감이 정말 컸습니다.
값 객체가 정말 필요한가, 개발자의 사용성을 지나치게 저해하는 것은 아닌가,
지속적으로 비용이 지출되는 구조인데 괜찮은가 등등 고민이 많았는데요,
결국은 두 손을 들었습니다.
이유를 정리하자면 다음과 같습니다.
1. 타입 안정성
2. 검증 로직의 캡슐화 및 관심사 분리
// 값 객체 적용 전
public class Customer {
private final Long id;
private final String email;
private final String name;
private final String password;
public Customer(final Long id, final String email, final String name, final String password) {
this.id = id;
this.email = email;
this.name = name;
this.password = password;
}
}
// 값 객체 적용 후
public class Customer {
private final CustomerId id;
private final Email email;
private final CustomerName name;
private final Password password;
public Customer(final CustomerId id, final Email email, final CustomerName name, final Password password) {
this.id = id;
this.email = email;
this.name = name;
this.password = password;
}
}
사용성과 값 객체의 장점을 반반씩 섞는 형태도 고민해봤습니다.
생성자 매개변수에서는 원시값을 받되, 생성자 내부에서는 값객체를 만들어 필드에 할당하는 식으로요.
그러나 이 경우 타입 안정성이라는 강력한 장점을 포기할 수밖에 없기 때문에 결국
값객체에 수긍할 수밖에 없었습니다.
마지막으로 값 객체 적용 시 저만의 원칙들을 정리해보겠습니다.
1. 필요한 시점에 값 객체로 포장한다. (의식적인 연습할 땐 다 해도 좋음)
2. getter는 값 객체를 반환한다. 값 객체 내부의 값을 직접 반환하지 않는다.
3. 값 객체 내부의 값은 최대한 꺼내지 않는다.
4. 값 객체 간의 비교는 원시 값이 아닌 메시지를 이용한다.
👨👨👦 일급 컬렉션의 사용
일급 컬렉션도 로또에서 제대로 경험한 것 같군요!
사이즈가 6인 List<LottoNumber>를 가진 Lotto 클래스로 일급 컬렉션을 첫 경험했었습니다.
가장 최근에는 지하철 경로 조회에서 두 역 간의 공간을 의미하는 Section들을 묶음으로 갖는 Sections 가 있었습니다.
앞으로도 하나의 도메인 객체가 Collection 레벨로 관리가 되고, 동시에 여러 책임들로 이루어진 역할을 부여받게 된다면,
일급 컬렉션의 도입을 앞으로도 어렵지 않게 시도해볼 수 있을 것 같습니다.
일급 컬렉션의 사용은 진입 장벽이 높게 느껴지진 않습니다.
그에 비해 효과는 굉장합니다. 객체지향적인 코드를 작성하는데 드라마틱한 효과를 가져다 줍니다.
그런데 왜 그렇게 해야하는지에 대해서는 글로만 봐서는 이해가 잘 되지 않았습니다.
일급 컬렉션을 일단 적용하고, 그것에 역할을 부여하고, 활용하다보니 이것이 정말 효과적이라는 걸 뒤늦게 깨달을 수 있었습니다.
일급 컬렉션을 사용하라! 라고 간단하게만 규칙에 언급되어 있다보니,
일급 컬렉션의 정의가 무엇이냐 에 대한 이야기들도 따라올 수밖에 없었는데요,
원문을 읽어보니 일급 컬렉션이 무엇이고 이 규칙을 따라야만 한다! 라는 느낌은 아니라는 걸 알 수 있었습니다.
오히려 컬렉션 레벨로 도메인 객체들이 관리된다면, 이를 하나의 클래스로 선언해서,
해당 도메인 객체를 관리하는 나만의 자료형을 만들어서 사용하라는 의미로 이해되었습니다.
이 때의 장점은 일급 컬렉션들의 결합에서 빛을 발하게 됩니다.
그렇기에 더더욱 하나의 컬렉션 이외의 추가적인 인스턴스 변수를 가지면 안됩니다.
이제와서 원문을 읽고 놀란 건데, 로또 미션 당시에 실제로 이러한 코드를 사용한 적이 있습니다.
자동에 이어서 수동을 추가로 구현해야하는 시점에, 자동 로또들의 묶음인 Lottos와
수동 로또들의 묶음인 Lottos 두 가지가 발생했고,
이미 구성해두었던 당첨 결과를 산출하는 로직에는 Lottos 한 개만 전달해야 했습니다.
그래서 Lottos에 또다른 Lottos 를 받아 병합한 결과를 반환하는 메서드를 구성했었는데요,
얼떨결에 일급 컬렉션의 장점을 제대로 경험하고 있었네요.
public Lottos merge(Lottos anotherLottos) {
List<Lotto> mergedLottos = Stream.of(this.lottos, anotherLottos.lottos)
.flatMap(Collection::stream)
.collect(toList());
return new Lottos(mergedLottos);
}
// ...
private Lottos setupLottos(Wallet wallet) {
Lottos manualLottos = setupManualLottos(wallet);
Lottos autoLottos = setupAutoLottos(wallet);
return manualLottos.merge(autoLottos);
}
✨ 정리
그야말로 저항과 순응의 역사였습니다 ;ㅅ;
레벨 1, 2를 지나오며 의식적인 연습의 기준이 되어줬던 객체지향 생활체조를 돌아봤습니다.
(당연하다고 느껴지거나, 누구나 쉽게 공감할만한 규칙들에 대해선 추가로 언급하지 않았습니다)
돌아보니 유지보수 하기 좋은 코드를 작성하기 위한 좋은 코딩 습관을 만들어가는 데 큰 밑거름이 된 것 같습니다.
들여쓰기와 else 키워드 조건은 처음엔 꽤나 어려웠지만 금방 적응이 되었고 큰 효과를 얻었습니다.
값 객체는 가장 적응하기 어려웠으나, 결국엔 그 가치를 인정할 수밖에 없었습니다.
일급 컬렉션은 적응하기엔 어렵지 않았으나, 효용을 경험하기엔 시간이 필요했습니다.
그 외에도 인스턴스 변수 2개 이하로 유지하기, 메서드 라인 10 줄 미만으로 유지하기도 정말 큰 도움이 됐습니다.
레벨 1, 2 때처럼 리뷰어와 1대1로 미션을 진행하는 과정은 이제 끝난 것 같아서 아쉬움도 큽니다.
그러나 분명 지난 4개월을 지나오며 저의 코드는 분명히 달라져 있는 것 같습니다.
개구리가 올챙이 적 생각 못한다는 속담이 있는데요, 저는 그게 당연하고 자연스럽다고 생각합니다.
그런데 교육에 있어서는 개구리가 올챙이의 시각으로 설계를 해야 한다고 느끼는데요,
우테코를 지나오며 매순간 대체 이러한 설계는 어떻게 한 것인가 여러 차례 감탄해왔습니다.
그래서 남은 과정들도 기대가 됩니다.
혹시, 여러분의 객체지향 생활 체조 경험은 어떠셨나요?
'Java & Spring' 카테고리의 다른 글
🌱 Spring에 Handler가 등록되는 과정 (0) | 2022.06.21 |
---|---|
🖋 Servlet부터 DispatcherServlet까지 (Front Controller 패턴) (0) | 2022.06.20 |
오라클 클라우드 Autonomous Database DB접속 로컬 + 서버 (0) | 2021.10.18 |
모의면접 복기 (2) - 가비지 컬렉션 Garbage Collection (0) | 2021.09.27 |
문자열 유효성 검증 유틸 메소드 StringUtils.hasText(String) (2) | 2021.09.04 |