이번 미션에서는 크루 출석 관리 애플리케이션을 구현해야 했다.
출석 미션은 이번 우테코 최종 코테 문제로 출제됐던 문제다!ㅠㅠ
특히 위와 같은 추가 요구사항이 눈에 띄었다. 바로 TDD를 적용해서 진행하라는 것!
프리코스때도 TDD를 적용했었는데, 지금 다시 생각해보면 TDD가 아닌데 TDD를 적용한줄 착각했던 것 같다..ㅎ..
프리코스때는 '그냥 테스트 코드랑 같이 작성하면 TDD 아니야?' 라고만 생각했는데, 이번 출석 미션에서 TDD를 맛보고 나니 TDD를 잘못 이해하고 있었음을 깨달았다.
TDD가 뭔가요
출석 미션을 시작하기에 앞서 네오 코치님이 TDD에 대해서 설명해주셨다.
강의에서 새롭게 얻은 인사이트는 다음과 같다!
TDD는 처음부터 완벽한 설계를 하는 것이 아니라, 점진적으로 설계를 해나가는 것
오브젝트 책을 절반정도 읽어본 입장에서, 나는 당연히 '객체지향이라면 처음부터 도메인 잘 설계하고 시작해야 되는거 아니야?' 라는 생각을 가지고 있었다.
그래서 저 설명을 들었을 때는 머리가 매우매우 혼란스러웠다! 아니 도대체 설계도 없이 어떻게 하라는거지 싶고 ... 더 막막해졌다.
TDD의 핵심은 리팩터링
그리고 TDD의 핵심은 리팩터링이라는 것을 강조하셨다!
단순히 테스트를 먼저 작성한다고 해서 무조건 TDD를 했다는 것은 아니다.
리팩터링이 있어야지 TDD다.
⭐️ TDD에서 중요한 것은 리팩터링
아, 내가 그동안 TDD라고 생각하고 썼던 것은 TDD가 아니었다! 🫢
red - green - refactor 사이클 반복
red 단계는 실패하는 테스트를 작성하는 단계
실패하는 테스트를 작성하는 과정에서 내가 원하는 것(= 요구사항)을 최대한 빠르게 생각할 수 있다. 즉, red 단계에서는 요구사항에만 집중할 수 있다.
- 작은 목표들을 하나씩 설정하고, 그거에만 집중해 나간다면 완성도 높은 결과를 이끌 수 있음
green 단계는 테스트를 통과하는 코드를 빠르게 작성하는 단계
완벽한 코드를 짜는 것이 아니라, 단순히 테스트를 통과시킬 수 있는 코드를 짜는 것이 목적이다. 즉, 이 단계에서는 어떤 편법도 사용할 수 있다. (메서드 분리 고려 x, if문 남발 등등...)
이건 뒤에서도 더 언급하겠지만, 실제 미션에 TDD를 적용해보니 green 단계를 최대한 빠르게 보내야 한다는 의식을 가지다보면 불필요한 코드 작성을 방지할 수 있다는 것을 느꼈다.
refactor 단계는 코드를 리팩터링하는 단계
이 단계에서는 구현 코드 뿐만 아니라 테스트 코드도 리팩터링 대상이 될 수 있다.
TDD에 매우 능숙하신 리뷰어로 배정받은 덕분에, 전달해주신 팁을 기반으로 2단계에서는 refactor 단계에 더 집중해볼 수 있었다.
예를들어 '각 책임이 적절한 객체에게 할당되었는가', '메시지가 적절한가', '메시지가 어색하지 않은가'와 같은 고민들을 refactor 단계에서만 집중적으로 하다보니 설계가 점점 완성되어 가는 느낌을 받을 수 있었다!
결국 TDD는 개발자가 선택과 집중을 할 수 있도록 도와주는 개발 방법론이라는 생각이 들었다.
따라서 TDD를 사용하면,
요구사항 분석의 규모가 달라진다.
- 일반적으로 큰 규모의 요구사항을 분석함 (어떤 어플리케이션이고, 어떤 기능이 있고 …)
- TDD를 사용하면 필요한 부분에만 집중할 수 있음
TDD로 하게되면 각 지점마다 고민할 수 있는 포인트가 생기게 된다.
- 최소한의 작은 단위로 테스트를 통과할 수 있는 코드를 작성할 수 있음
오케이.. TDD가 좋다는건 알겠습니다..
근데 강의를 들어도 도대체 어떻게 하는건지 감이 안 잡혔다 ㅜ
결국 정신없이 지나가버린 1단계
https://github.com/woowacourse/java-attendance/pull/42
로또 미션과 마찬가지로 1단계는 페어 프로그래밍으로 진행했다.
TDD가 뭔지 감도 안 잡히는데, 일단 이틀 내로 구현을 완료해야하므로 나와 페어는 정신없이 코드를 짰다 ㅜㅜ
그리고 1단계를 마치고 회고하면서 생각한건데, 1단계에서 TDD가 어려웠던 가장 큰 원인은 레이어드 아키텍처를 미리 정해둔 채로 service 계층에서 TDD를 적용했기 때문이라는 생각이 들었다.
- 즉, red 단계에서는 서비스 계층이 수신하는 메시지를 결정하고, green 단계에서는 해당 테스트가 통과하는 코드를 작성하고, refactor 단계에서는 서비스 계층을 리팩터링했다.
위의 설명만 봐도 TDD 단위가 매우 크게 느껴지지 않은가 .. 특히 서비스 하나에는 여러 도메인이 포함되어있기 때문에, 한 사이클에 시간이 매우 많이 소요됐다.
빠른 피드백을 받을 수 있다는 것도 TDD의 장점 중 하나라고 생각하는데, 이렇게 단위를 너무 크게 잡아버리니 이러한 TDD의 장점을 느낄 수 없었던 것 같다 ㅜㅜ
1단계 리뷰 정리
도메인 로직이 섞여있는 레파지토리 ...?
앞에서도 살짝 언급했지만, 결국 이번 1단계에서 TDD에 감도 안 잡혔던 이유는 레이어드 아키텍처를 미리 가정한 상태에서 TDD를 쓰려고 했기 때문이었다...
사실 그동안 레이어드 아키텍처에 대해서 막연하게 각 클래스의 큰 역할?을 분류하는 정도로만 이해하고 있었다.
그리고 아래 사진은 리뷰어의 '왜 레이어드 아키텍처를 적용했는가?' 라는 질문에 대한 나의 답변 코멘트이다.
하지만, 실제로 작성한 코드에서는 repository 계층 코드 내부에 도메인 로직이 섞여있는 것을 볼 수 있었다 ..
그리고 리뷰어도 이를 지적했다.
- repository는 그냥 일급 컬렉션 아닌가요?
...... 그러네요? 😳
⭐️ 레이어드 아키텍처 개념 정립
이렇게 리뷰어의 날카로운 지적과 친절한 설명으로 레이어드 아키텍처에 대한 개념을 바로 잡을 수 있었다. (감사합니다.. ㅜ)
이 코멘트를 보고 머리 위에 전구가 번쩍💡 했다 ..ㅎ
이번 미션에서는 레이어드 아키텍처가 불필요하다는 것을 바로 이해할 수 있었다.
내용을 정리하자면 트랜잭션을 관리하고, 엔티티의 영속성을 관리하는 등의 어플리케이션 로직 또한 비즈니스 로직에 포함된다. 하지만 이러한 작업들을 도메인 로직 내부에서 모두 처리하기에는 도메인의 복잡도가 크게 증가할 것이다.
따라서 어플리케이션 로직과 도메인 로직의 관심사를 분리하기 위해 레이어드 아키텍처가 등장한 것이다.
따라서, 현재 미션들(로또, 출석)은 모두 도메인 로직을 구현하는 요구사항만 존재할 뿐 애플리케이션 로직 요구사항은 포함되어있지 않기 때문에, 레이어드 아키텍처가 불필요한 것이다.
리뷰어의 코멘트 덕분에, 레이어드 아키텍처의 등장 배경과 함께 도메인 로직이 무엇인지, 애플리케이션 로직이 무엇인지 구별할 수 있게 되었다!
1단계 리뷰를 마치고 나니, 2단계에서는 도메인 수준에서 TDD 사이클을 적용해봐야겠다는 생각이 들었다.
구현보다는 TDD에 집중한 2단계
https://github.com/woowacourse/java-attendance/pull/148
TDD를 완벽하게 적용하진 못하더라도, 이번 미션에서 TDD는 도대체 왜 쓰는지 납득은 하고 넘어가야겠다는 의지가 생겼다!
구현이야 뭐 어떻게든 할 수 있는거니까.. 그거보다는 이번 미션의 주제인 TDD에 더 집중하기로 했다.
의식적인 연습
그리고 2단계에서는 '내가 현재 어느 단계를 진행하고 있는지' 를 의식하는 것에 집중했다.
- red 단계 - 현재 목표하는 요구사항을 충족하기 위해, 어떤 인터페이스(메시지+파라미터+리턴 값)가 필요한지만 생각한다.
- green 단계 - 앞서 red 단계에 작성한 테스트가 통과할 수 있는 코드를 5분 이내로 구현한다.
- refactor 단계 - 책임이 적절한지, 메시지가 어색하지 않은지, 현재 설계가 최선인지, 중복되는 코드를 없앨 수는 없는지 ... 등의 고민은 모두 여기서 하고, 시간 제한은 없다.
리뷰어가 green 단계에서 5분의 시간 제한을 두는 것을 제안했는데, 사이클을 작은 스텝으로 가져가게 하기 위한 목적도 있는 것 같다.
ex) 5분이 지났다? 👉🏻 테스트 범위가 너무 크게 잡혔나? 👉🏻 범위 줄이기 👉🏻 ...
2단계 리뷰 정리
구현과 리팩터링의 분리
이러한 의식적인 연습을 하면서 가장 크게 와닿았던 부분은 구현 과정과 리팩터링 과정을 의식적으로 분리할 수 있다는 점이었다.
실제로 코드를 구현하다보면, 구현 과정에서 리팩터링해야할 부분들이 불현듯 떠오르면서 두 과정이 섞이는 때가 많았다.
그 과정에서 불필요한 코드가 생기기도 하고, 그만큼 클린 코드를 달성하기 더 어려워지는 느낌도 들었던 것 같다.
하지만, TDD의 사이클을 지키면서 의식적으로 구현 과정과 리팩터링 과정을 완전히 분리하다보니, 구현 과정에서는 불필요한 코드를 작성하지 않게 되고 리팩터링 과정에서는 설계적인 고민에만 집중할 수 있었다.
즉, 복잡한 두 과정이 서로 섞이지 않으면서 관심사가 완전히 분리되는 것을 체감할 수 있었다.
저절로(?) 완성되는 설계
이렇게 TDD 사이클을 반복하다보니, 어느새 '어느 객체에는 어떤 책임을 할당할 것인지' 와 같은 기준이 하나씩 생겨났다.
그리고 이걸 리팩터링 과정마다 반복해서 고민하다보니 저절로 설계가 완성되는 ..?
내가 느꼈던 부분들에 대해서 리뷰어가 위와 같이 코멘트를 남겨주셨다.
✍🏻 키워드 정리
1. TDD는 의식적으로 관심사를 분리할 수 있다
복잡한 구현 과정과 리팩터링 과정을 의식적으로 분리함으로써 각 단계에 필요한 작업에만 집중할 수 있다.
특히, 주기적으로 반복되는 리팩터링 과정을 통해서 점진적으로 설계를 완성해나갈 수 있다.
2. 레이어드 아키텍처는 도메인 로직과 어플리케이션 로직을 분리하기 위해 등장한 것
트랜잭션 관리, 영속성 관리와 같은 어플리케이션 로직을 도메인 로직과 명확하게 구분하기 위해서 등장한 것이 레이어드 아키텍처이다.
이번 미션에서는 어플리케이션 로직이 존재하지 않기 때문에 레이어드 아키텍처를 적용할 필요가 없다.
3. TDD 스텝의 규모는 정해진 것이 아니다
TDD를 꾸준히 연습해보면서 자신에게 맞는 범위를 설정하는 것이 중요하다.
이번 미션의 2단계에서는 TDD 연습을 위해 최대한 작은 단위로 쪼개서 진행하는 것을 추천 받은 것이지, 꼭 작은 단위로 해야한다는 것은 아니다! 한 사이클의 규모를 어느정도로 잡을 것인지는 본인에게 알맞는 기준으로 적용하면 된다.
💦 아쉬웠던 점
코드 리팩터링 과정에서는 TDD를 적용하지 못한 부분이 아쉽다.
리팩터링을 하다보면 책임을 분리하는 경우가 생기는데, 이렇게 분리된 책임에 대해서 테스트 코드를 작성하지 않고 바로 코드를 변경하는 것이 너무 익숙해서 ..ㅜ 리팩터링 과정에서 TDD를 제대로 적용하지 못했다.
그래도 TDD를 나름 찍먹까지는... 해보면서 TDD를 왜 사용하는지 느껴볼 수 있어서, 목표했던 'TDD를 왜 사용하는가에 대한 의문점 해소'는 달성한 것 같다!
'우아한테크코스 7기 > 회고' 카테고리의 다른 글
[레벨1] 장기 미션 회고록 2 - 1단계 (추상 클래스, 합성) (2) | 2025.04.20 |
---|---|
[레벨1] 레벨 인터뷰 정리 (4) | 2025.04.14 |
[레벨1] 장기 미션 회고록 1 - 우테코가 바라는 것 (2) | 2025.04.12 |
[레벨1] 블랙잭 미션 회고록 (4) | 2025.04.08 |
[레벨1] 로또 미션 회고록 + 학습 내용 정리 (2) | 2025.03.03 |