728x90
1.1. 복잡한 요구사항 분석과 설계
미션 난이도 변화
1~3주차: 단순 요구사항
- 익숙한 도메인과 비교적 단순한 요구사항이었습니다.
- 객체지향 설계와 TDD를 적용하는 것에 집중했습니다.
- 코드 품질을 개선하는 연습을 했습니다.
4주차: 복잡한 요구사항
- A4 2페이지 분량의 복잡한 요구사항이었습니다.
- 프로모션, 멤버십, 보너스 상품 등의 복잡한 비즈니스 로직을 이해하는데 시간이 걸렸습니다 (1시간)
- 예상 출력과 테스트 코드로부터 숨겨진 요구사항을 발견할 필요가 있었습니다.
- 5시간 내에 구현하는 것을 도전해보면서 시간에 대한 압박감이 있었습니다.
- 결국 구현하는데 이틀이라는 시간이 걸렸습니다..
발생한 문제점
요구사항 분석의 어려움
- 복잡한 요구사항을 이해하는데 어려움을 겪었고, 일부 요구사항을 누락하여 파악했습니다.
- 숨겨진 요구사항을 발견하는 것이 어려웠습니다.
- 시간 제약이 있어 부담감을 느꼈습니다.
설계, 구현 한계
- 긴 메서드(200줄 이상)이 작성되었습니다.
- 객체끼리의 결합도가 높아져서 리팩토링이 어려웠습니다.
- 절차적으로 구현하여 리팩토링시 버그가 많이 발생했습니다.
시간 부족 → 충분한 도메인 이해 어려움 → 불완전한 설계/구현 → 버그 발견 → 디버깅에 시간 소요 → 더 큰 시간 부족
해결방법 1. 요구사항 분석 개선하기
단계적으로 접근하기
-
- 제공된 요구사항 파악하기
- 입력 → 처리 → 출력 흐름
- 숨겨진 요구사항 발견하기 (예시 출력, 테스트)
- 예시 입출력 분석하기
- 테스트케이스 분석하기
- 순서, 포맷, 누락된 데이터 확인하기
- 문제 속 힌트 찾기
- 주어진 예시가 무엇을 의미하는지 생각해보기
- 객체별 책임 정의하기
- 제공된 요구사항 파악하기
검증하기
- 단계별로 구현한 기능 검증하기
- 예시 출력과 꼼꼼하게 대조하기
- 엣지 케이스 정리하기
- 입력값
- 상태 변화
- 로직 문제
해결방법2. 설계 및 구현 전략
최소한의 설계는 수행하기
- 객체별로 책임(요구사항) 나누기 (큰 틀에서 생각하기)
- 처음부터 완벽한 설계를 하려고 하는 것 보다는, 단계적으로 개선하기.
- 각 객체별 책임 정의 후 상호작용 시나리오 작성하기
- 전체 흐름을 메서드로 먼저 잡기
class RacingGame {
public void play() {
initializeGame();
while (!isGameOver()) {
playOneRound();
}
showResult();
}
private void initializeGame() {
inputPlayers();
validatePlayers();
setupGame();
}
private void playOneRound() {
moveAllCars();
updatePositions();
showRoundResult();
}
private void showResult() {
determineWinner();
showGameResult();
}
}
시간 관리하기
- 도메인 분석 + 기본 설계 : 30분
- 핵심 구현 : 2.5시간
- 테스트, 버그 수정 : 2시간
해결방법 3. 버그 대응 전략
- 먼저 버그의 원인을 분석 하고 버그가 가장 발생할 것 같은 위치부터 수정하기
- 무조건 먼저 코드를 수정하고 실행 결과를 비교해보는 것이 아님
- 놓친 버그 & 해결방법은 정리해서 다음에 똑같은 실수를 하지 않도록 하기
- 로컬과 테스트 서버 환경 차이 고려하기
- 환경 설정 값을 지정할 때 테스트 서버 고려하기
개선 계획
단기 목표
- 요구사항 분석력 기르기
- 요구사항 분석 템플릿 작성하기
- 카카오 알고리즘 문제 풀면서 복잡한 요구사항 자주 접하고 해결하기
- 요구사항 빠르게 파악하고 정확하게 분석하기
- 안정적으로 구현하는 능력 기르기
- 자료구조 학습하기
- 테스트 템플릿 작성하기
- 시간 제약 내에 미션을 풀어보며 최소 품질 유지 + 버그 최소화하기
장기 목표
- 설계 역량 키우기
- 복잡도와 의존성을 처리하는 방법 공부하기
- 확장 가능한 구조 설계하기
- 설계 패턴 공부하기 (객체지향, 도메인 주도 )
느낀점
실력이란?
- 마감 시간 지키기 : 요구사항을 제한 시간내에 구현하는 능력
- 복잡도 관리하기 : 복잡한 요구사항을 단순하게 구현하는 능력
- 유지보수 : 유지보수가 편한 코드를 작성하는 능력
- 구조가 바뀌거나 기능이 수정되더라도 변경에 유연한 코드 만들기
진정한 실력은 복잡한 문제를 단순하게 해결하는 능력이다.
1.2 절차지향에서 객체지향으로 리팩토링하기
문제점
1. 과도한 책임
- 결제 시스템의 결제 기능 하나에서 큰 책임을 수행하였다.
- 재고 확인
- 프로모션 적용
- 구매 처리
- 멤버십 계산
- 영수증 기록
2. 높은 결합도
- 5개 객체에 의존한다.
- 상태 관리가 복잡하다.
- 테스트가 어렵다.
도메인 설계 고민 : 도메인 관점 vs 객체지향 관점
- 도메인 관점: 결제기가 결제 기능에 대한 전체 프로세스를 처리하는 것이 자연스럽다.
- 현실세계와도 부합한다.
- 단점
- 단일 책임 원칙에 위배되고, 많은 의존성(5개 객체)을 가지게 된다.
- 높은 결합도를 가진다.
- 객체 지향 관점 : 단일 책임 원칙 (
SRP
)- 결제기가 결제 기능을 수행하는 동안 너무 큰 의존성과 책임을 가진다.
- 결제기의 기능을 여러개로 분리하는 것이 오버엔지니어링은 아닐까?
- 리팩토링시 흐름을 이해하기 어려울 수 있다.
- 너무 많은 객체와 협력한다.
- 결제기가 결제 기능을 수행하는 동안 너무 큰 의존성과 책임을 가진다.
결제기의 기능을 여러 객체로 쪼개는 것이 좋은 방법일까?
결제기에서 수행한다면 하나의 메서드에 파라미터가 5개 존재하는 것이 좋은 코드일까?
실행 순서 의존성 문제
- 한 작업이 순차적으로 진행되므로 조건에 대한 검증이 연쇄적으로 진행되고 상태가 단계적으로 변경되었다.
- 그래서 리팩토링시에 메서드를 분리하는데 순서가 깨지고 데이터 흐름을 추적하기 어려웠다.
- 리팩토링 할 수록 상태를 보장하기 어려워 버그가 많아지고 의존성이 복잡해졌다.
실제 미션 제출본에서는 리팩토링을 제대로 못하고 제출했지만, 앞으로 계속 미션을 다시 풀어보며 아래 포인트들을 고려하여 리팩토링할 예정이다.
해결방법 1. 도메인 - 객체지향 균형점 찾기
- 전체 흐름을 관리하는
service
객체 도입하기- 도메인 모델 계층과 비즈니스 로직 계층, 전체 흐름 계층 구조를 도입하기
- 도메인 모델 :
Inventory
,PaymentSystem
,Promotion
- 비즈니스 로직 :
PaymentProcessor
,MembershipCalculator
- 클라이언트 접점 :
PaymentService
- 도메인 모델 :
- 도메인 모델 계층과 비즈니스 로직 계층, 전체 흐름 계층 구조를 도입하기
- 객체 분리 기준
- 객체 내에서 변경의 이유가 다른 경우
- ex ) 결제 로직과 포인트 계산 로직
- 재사용 가능성이 있는 경우
- ex ) 가격 계산, 할인 적용
- 객체 내에서 변경의 이유가 다른 경우
- 객체 통합 기준
- 항상 함께 변경되는 경우
- 결제 처리와 영수증 (결제 기록)
- 일관성이 필요한 경우
- 항상 함께 변경되는 경우
해결방법 2. 점진적으로 개선하기
- 우선 요구사항을 달성하는 동작하는 코드를 작성한 후, 단계적으로 리팩토링을 수행하자.
- 리팩토링 기준
- 메서드 20줄 이상
- 파라미터 4개 이상
- 하나의 클래스가 여러 역할
- 리팩토링 기준
- 동작하는 코드 작성하기
- 핵심 기능 구현하기
- 최소한으로 구조화하기(리팩토링 시 용이하기 위함)
- 단계적으로 리팩토링하기
- 책임을 분석하여 객체를 분리하기
- 테스트 기반으로 수정하기
해결방법 3. 디자인 패턴 적용하기
facade
패턴 : 인터페이스를 이용해 단순한 메서드만 노출하기command
패턴 : 요청을 객체로 캡슐화하여 매개변수화하기- 작업 요청과 실행 분리하기
- 각 단계 모듈화하기
개선 계획
단기 목표
- 객체 분리하는 패턴, 메서드 분리 연습하기
- 의존성 관리하는 방법 연습하기
- 기존 미션 코드 리팩토링하기
장기 목표
- 리팩토링 공부하기
- 도메인 주도 설계 학습하기
느낀점
- 객체지향 + 도메인 균형 잡힌 설계란?
- 도메인 : 도메인 개념이 코드에 잘 표현되며, 비즈니스 흐름이 보인다.
- 겍체지향 : 변경(유지보수)이 쉽고 테스트가 쉽다.
- 필요한 만큼만 분리하고, 복잡도를 줄이자.
1.3. TDD 유연하게 적용하기
제한 시간내의 TDD 접근 문제점
요구사항 불완전한 이해
- 복잡한 요구사항을 모두 이해하지 못한 채 테스트를 작성하였다.
- 잘못 이해한 요구사항으로 인해 테스트와 로직 수정에 많은 시간이 소요되었다.
- 클래스 크기가 커지고 테스트케이스가 많아졌다.
- 버그를 수정하고 리팩토링하는 과정에서 연쇄적으로 테스트가 깨지는 문제가 발생했다.
- 리팩토링시에 메서드 시그니처를 변경하는데 부담이 되었다.
시간 제약
- 제한된 시간 내에서 TDD를 지키며 개발하는 것이 어려웠다.
- 기존 5시간을 넘어 구현하는데에만 이틀이 걸렸다. (스파게티 코드인데도)
- 끝 쯤에서는 버그 수정과 숨겨진 요구사항을 구현하는 경우 시간 부족으로 인해 테스트 작성을 생략하게 되었다.
해결방법 1. 요구사항을 이해하는 것을 우선순위로 두기
- 요구사항을 완벽하게 이해하는 것을 최우선으로 두자.
- 시간 제약이 있는 상황에서 낯선 도메인이라면 엄격한
TDD
적용보다는 구현부터 시작한다.- TDD는 단지 좋은 코드를 위한 수단일 뿐이다. 요구사항 구현이 더 중요하다.
단순한 도메인 : TDD
복잡하거나 낯선 도메인, 시간 제약 존재 : 구현 먼저
해결방법 2. 필요한 부분만 테스트 작성하기
- 모든 메서드에 대한 테스트 작성을 목표로 두지 않고, 핵심 로직에 대해서만 테스트를 작성한다.
- 요구사항 구현과 버그 수정을 우선으로 두고, 그 이후 리팩토링과 테스트 작성을 적절히 수행한다.
2. 미션 의도 파악하기
4주차 미션은 1,2,3주차 미션에 비해 굉장히 난이도가 상승하였다.
그리고 다른 분들 코드를 참고할 수 없도록 비공개 저장소로 제출하는 방식이었다.
좋은 코드를 작성하는 것보다 우선 비공개 테스트를 통과하는 코드를 제출하는 것이 가장 첫 목표일 정도였다.
이해하기 어려운 요구사항
- 요구사항이 복잡하고 충분히 설명되어있지 않았다.
- 테스트코드와 출력 결과를 보고 추가적인 요구사항을 파악해야했다. (히든 케이스 중에 하나)
- 실제 현업에서도 요구사항이 명확하지 않은 경우가 많다고 한다.
- 요구사항끼리 상충되는 부분이 있어 요구사항을 잘 정의해야했다.
- 주어진 요구사항을 구현하기 위해 처음부터 끝까지 AI 도구를 사용해서 요구사항을 만족시킬 수 없다.
직접 구현하는 능력을 테스트하는 것 아니였을까..
메서드 10줄 제한
SRP
를 지켜 최대한 책임을 쪼개서 작성해보라는 의미인 것 같다.- 복잡함에서 단순한 로직을 작성하는 것은 앞으로의 나의 숙제이다.
- 하나의 큰 책임을 가진 메서드를 로직을 변경하지 않고 쪼갤 수 있는 방법이 필요하다.
3. 끝으로..
4주차 미션을 제출하면서 깨달은 부분도 있고, 반성할 부분도 많았습니다.
제한 시간 내에 버그를 최소화하며 구현하기 위해서는 요구사항에 대한 정확한 이해가 선행되어야한다는 것을 깨달았습니다.
또한 파일 경로 버그로 이틀을 밤새워 고생하면서, 테스트/로컬 환경설정의 중요성도 뼈저리게 느꼈습니다.
또한 강결합된 객체들을 리팩토링하는 과정에서 설계의 중요성을 실감했습니다.
앞으로 차근차근 연습하며 요구사항을 정확히 이해하고 버그가 없는 프로그램을 만드는 개발자가 되기 위해 노력할 것입니다.
또한 복잡한 요구사항을 단순하게 풀어내어 유지보수가 쉬운 프로그램을 만드는 개발자가 되고 싶습니다.
4주차 미션 구현하시느라 다들 고생 많으셨습니다.
728x90
'우테코' 카테고리의 다른 글
[우아한테크코스] 프리코스 중간 회고 (2) | 2024.11.06 |
---|---|
[우아한테크코스] 프리코스 3주차 - 로또 미션 회고 (0) | 2024.11.04 |
[우아한테크코스] 프리코스 2주차 - 자동차 경주 미션 회고 (0) | 2024.10.28 |
값 객체(VO) : 일반 클래스 vs record (2) | 2024.10.26 |