더 구체적인 학습 내용과 회고는 일일 학습 기록에서 확인할 수 있습니다.
[ 3주차 시작 ]
3주차가 시작되었습니다. 3주차는 처음부터 설계 계획을 세워서 설계해나가고자 했습니다. 따라서 3주차에서는 제일 먼저 설계 계획을 세웠습니다.
설계 계획
1. 도메인을 단순화해서 도메인 모델을 그려본다.
2. 어떤 메시지가 필요한지 정하고, 그 메시지를 어떤 객체가 책임질지 결정한다.
3. 이렇게 얻은 객체들의 인터페이스를 정리한다.
4. 구현한다.
[ 로또 과제 ]
간단한 로또 발매기를 구현하는 과제입니다.
손님은 로또를 구입할 금액을 입력할 수 있습니다. 로또 1장의 가격은 1,000원입니다.
손님이 구매한 로또는 모두 자동(랜덤)으로 발행됩니다.
당첨 번호는 중복 없는 6개의 숫자 + 보너스 번호 1개로 구성되어 있고, 각 번호의 숫자 범위는 1~45까지입니다.
프로그램은 모의 당첨 번호 6자리와 보너스 번호 1자리를 입력받을 수 있습니다. 입력은 쉼표(,)를 기준으로 구분합니다.
사용자가 구매한 무작위 로또 번호와 모의 당첨 번호를 비교하여 당첨 내역과 수익률을 출력하는 것이 프로그램의 목표입니다.
[ 설계의 번복 ]
도메인 모델과 메시지를 정리한 결과 위와 같은 그림이 되었습니다.
물론 잘 단순화한 도메인 모델이라고 말할수는 있지만, 실제 개발 과정에서 많은 것이 달라졌습니다.
이것이 오히려 혼란을 낳았고, 잘못된 설계인 것 같다는 생각을 했습니다. 따라서 개발 계획을 변경했습니다
이 과제는 현실 세계를 그대로 구현한 시뮬레이터가 아닙니다. 따라서 '손님' 객체처럼 실제 환경에서 존재할 수 있는 요소를 구현할 필요가 없었습니다. 단지 컨트롤러가 View에서 금액과 로또 번호를 입력받고, 이를 적절한 모델에 넣어주는 기능만 필요했습니다. 제가 그린 도메인 모델은 객체들 간의 협력에만 집중하는 추상적인 설계로 그려져 있었음을 깨달았습니다. 따라서 이 설계 방법이 처음 갈피를 잡기엔 좋지만 막상 개발을 시작하면 너무 많은 변경으로 인한 혼란을 준다고 판단했습니다.
그래서 저번 주차에서 사용했던 약간은 추상적인 What/Who 사이클로 다시 설계해 봤습니다.
What (무엇이 필요한가)
1. 로또 정보를 저장해야 한다.
2. 우승 로또 정보도 저장해야 한다
3. 돈에 맞게 로또를 발급해야 한다.
4. 당첨 여부를 파악해야 한다.
5. 입력값 검증을 해야 한다.
6. 우승 로또 번호를 입력받아야 한다.
7. 우승 보너스 번호를 입력받아야 한다.
Who (누가 담당할 것인가)
1. 로또 정보는 Lotto 객체에 담는다.
2. 우승 로또 정보는 WinningLotto 객체에 담는다.
3. LottoShop이 돈에 맞게 로또를 발급을 요청한다.
4. LottoGenerator는 요청받는 대로 로또를 랜덤값에 의거해 생성한다.
5. LottoCompany는 당첨 여부를 파악하고 상금을 집계한다.
6. Validator는 입력값을 검증한다.
7. InputHandler는 입력을 받는다.
8. Money는 돈을 저장하고, 돈을 로또 티켓으로 교환한다.
[ 책 "오브젝트" ]
사실 과제의 기능 요구사항을 충족하기는 쉽지만 그것을 깔끔하고 유연한 객체지향적으로 풀어내는 것이 어려웠습니다.
따라서 지금 해야 할 것은 과제의 구현을 끝마치는 게 아니라, 객체 지향 설계에 대해 조금 더 공부를 한 뒤 시작하는 것이 좋을 것 같았습니다. 물론 과제를 끝내지 않은 상태에서 이런 공부를 하는 것은 어느 정도의 부담감이 따르지만.. 이것이 더 좋은 결과를 낳아주리라 예상했습니다.
따라서 책 "오브젝트"를 읽으면서 느낀 점을 정리하면서 객체지향에 대한 이해를 더 깊이 하였습니다.
현재 ~97p까지 읽었습니다.
느낀 점 중 일부:
- 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮추며 응집도를 올릴 수 있다.
- 객체 내부의 상태를 캡슐화하고, 외부에서 내부에 대해 알 수 없게 하라. 단지 외부는 인터페이스에 의존할 뿐이다.
- 모델이 본인의 데이터를 책임지지 않고 제어하는 역할을 외부에 맡기면, 그것은 모델이 아니라 Data가 될 뿐이다. 그리고 가운데의 프로세스에 의해서 제어당한다. 이것은 우리가 익히 알고 있는 절차 지향 방식의 프로그래밍이다. 단순히 데이터는 수동적인 존재가 되는 것. 만약 본인의 데이터를 본인이 책임지도록 만들면 비로소 자율적인 객체인 모델이 된다.
- 절차적 프로그래밍은 읽는 사람의 직관을 크게 벗어나기 때문에 코드를 읽는 사람과 원활한 소통이 힘들다.
- 객체 지향 프로그래밍의 본질은 데이터와 프로세스가 동일한 모듈에 위치하는 것이다. (응집도 UP)
- 객체 상호 간 서로의 자율성을 침해하지 않도록 한다!
- 변경 가능한 코드란 이해하기 쉬운 코드다
- 훌륭한 객체지향 설계란 의존성을 적절히 관리하는 설계다.
[ 돌아가는 쓰레기 ]
처음부터 깔끔한 코드를 작성하려고 하니 부담감이 크고, 오히려 작업이 막히는 느낌이었습니다. 따라서, 당장 명확한 구현 방안이 떠오르지 않더라도, 우선 단순하고 돌아가는 코드를 먼저 작성한 뒤 개선해 나가는 것이 더 효율적이라는 판단을 내렸습니다.
따라서 먼저 객체의 책임과 역할을 잘 나눈 설계를 한 뒤, 구현은 떠오르지 않으면 제일 naive 하게 하고 구현 이후 리팩토링 하는 것이 5시간의 제한이 있는 최종 코딩테스트에서도 유효한 방법일 것이라고 생각합니다.
객체지향 설계에 대한 인사이트도 계속 얻고 있습니다. 특히 책 "오브젝트"에서 강조하는 "구현에 의존하지 않고 인터페이스에 의존하라"는 개념이 인상 깊었습니다. 이를 통해 private 코드를 수정하더라도 외부에 영향을 미치지 않는 안정성을 확보할 수 있음을 이해하게 되었습니다. 따라서 public 코드에는 복잡한 로직을 두지 않고, 인터페이스를 통해 의존성을 관리하는 것이 좋은 전략이라 느꼈습니다.
이런 방법으로 주요 로직 중 하나인 로또 당첨 확인과 결과 출력 기능을 거의 마무리했으며, 이 과정에서 EnumMap의 유용성도 발견했습니다. EnumMap은 HashMap과 유사한 사용 방식이지만, key로 Enum을 사용할 수 있고 내부 구조가 배열 형태로 구현되어 효율적이라는 점이 흥미로웠습니다.
[ 의문점들 ]
LottoController는 컨트롤러의 역할을 넘어서지 않았는가?
> LottoController는 객체 생성과 협력을 조율하며, 전체적인 흐름을 담당합니다. 원래는 객체 생성과 흐름은 Application.main의 역할이었고, main에서 생성한 객체를 LottoController가 의존성 주입받아 사용했지만, 생성자의 파라미터가 너무 길어진다고 생각하여 의존성 주입을 포기하고 LottoController에서 new 키워드를 사용해서 객체를 생성하고 협력을 조율하고 있습니다. 그리고 현재의 LottoController는 객체를 생성, 호출할 뿐 비즈니스 로직은 하나도 포함하지 않고 있다고 생각합니다. 저는 이 코드가 Application.main에서 처리하고 의존성 주입받는 것보다 나아 보이는데, 다른 분들의 생각은 어떨지 궁금합니다.
View 객체에 싱글톤 패턴을 적용한 것은 유효한 설계일까?
> InputHandler와 OutputHandler는 필요할 때마다 LottoController에서 객체 생성을 계속하기에 싱글톤 패턴을 적용했습니다. 제가 싱글톤 패턴 적용으로 의도한 이득은 매번 new 키워드로 새로운 객체를 생성하지 않고 하나의 인스턴스를 재사용해 메모리를 절약하는 것입니다. 하지만 이때 들었던 의문점은 new 키워드로 새로운 객체를 생성해도, 사용하지 않게 되는 시점에서 GarbageCollector에 의해 바로 파기될 텐데 싱글톤 패턴 적용이 유의미한 효과를 가져올까에 대해서입니다. GarbageCollector로 인한 오버헤드를 생각해도 이것은 너무 미묘한 차이가 아닐까 하는 생각입니다.
Validator의 메서드를 정적(static)으로 선언한 것은 좋은 전략일까?
> LottoValidator는 InputHandler에서 호출되는 입력 값 검증 로직입니다. 단순한 로직이고 어디서든 실행 결과가 달라지지 않습니다. 따라서 기존에는 InputHandler가 LottoValidator을 의존성 주입받았지만 LottoValidator을 static method로 전환하여 복잡성을 줄였습니다. 과연 이것은 올바른 선택일까요? 다양한 분들의 의견이 궁금합니다.
[ 최종 소감 ]
- 배운 점 -
1. 깔끔한 단위 테스트를 위해 JUnit의 다양한 어노테이션에 대해 학습했습니다.
2. 다양한 상수 값을 체계적으로 관리할 수 있는 Enum에 대해 이해하게 되었습니다.
3. Enum을 키로 사용하는 EnumMap 자료구조를 배우며, 구조적인 값의 관리를 배웠습니다.
4. 설계 과정에서 많은 시행착오를 겪으며, 객체 지향 설계에 대한 시야를 더욱 넓히게 되었습니다.
- 느낀 점 -
1. 이번 과제에서는 구체적인 설계 순서를 정해서 진행해 봤는데, 실제 구현 과정에서 설계의 여러 부분이 변경되었습니다. 이렇게 실제 구현이 설계와 다른 방향으로 흘러가는 것이 혼란스러웠고, 오히려 이런 설계가 불필요한 방해 요소로 느껴졌습니다. 이러한 경험을 통해 처음부터 너무 구체적인 설계보다는 오히려 직관적이고 추상적인 설계를 하는 것이 유효하다는 생각이 들었습니다. 객체 지향 설계는 실세계를 모델링하여 코드를 직관적으로, 소통이 쉬운 형태로 읽히게 만드는 것이 본질이라고 생각합니다. 그리고 사실 그러한 실세계를 잘 알고 모델링할 수 있는 건 나 자신이라는 생각이 들었습니다. 따라서 처음 설계 시에 정해진 틀에 맞추는 것은 시작점을 잡기에 좋지만, 이후에는 직관적인 내 느낌에 따른 설계를 하는 것이 나을 수 있다고 느꼈습니다.
2. 지금까지 과제를 진행하며 다양한 고민을 해왔고, 많은 피드백을 들으며 설계의 시야를 넓힌 것을 느꼈습니다. 특히 완벽한 설계, 다시 말해 모두가 만족하는 장점만 있는 설계란 존재하지 않는다는 것을 실감했습니다. 그저 서로 다른 방식들이 지닌 장단점 사이에서 적절히 트레이드오프(trade-off)하는 것이 필요할 뿐이라는 것을 느꼈습니다. 예를 들어 지난 과제에서는 Application이 객체 생성과 협력을 조율하고 전체적인 흐름을 담당하여 Controller가 필요한 객체들을 의존성 주입받을 수 있었습니다. 하지만, 이 방식으로 하면 Controller의 생성자에 너무 많은 파라미터가 들어가는 게 문제였습니다. 그래서 이번 과제에서는 Controller가 필요한 객체를 new 키워드로 생성하고 직접 협력을 조율하도록 했습니다. 객체를 의존성 주입받는 것이 느슨한 의존성을 유지하는 장점이 있지만, 이번 과제에서는 new 키워드를 활용한 방식이 오히려 컨트롤러의 목적에 더 부합하고 가독성이 좋아 깔끔하다고 느꼈습니다. 하지만 다양한 레퍼런스와 코드 리뷰 등에서 이 방식에 대한 의견이 갈리는 것을 보면서, 설계에는 정답이 없고 다양한 접근이 가능하다는 점을 깨달았습니다.
- 많은 시간을 투자한 부분 -
1. 기존의 설계와 실제 구현 사이에서 차이가 발생할 때, 첫 설계의 방향을 따라야 할지 혹은 현재 구현하는 나 자신의 직관적인 설계를 따라야 할지 고민하고, 올바른 설계 방식을 찾는 데에 시간을 많이 쓰게 되었습니다.
2. 저번 주차에서 작성한 테스트 코드는 다소 깔끔하지 않게 느껴졌고, 단위 테스트의 목적에 부합하지 않는 테스트 코드도 있었다고 생각했습니다. 따라서 이번 주차에서는 정말로 Unit에 집중한 테스트를 꼼꼼히 작성하기 위해 노력했고 이를 위해 많은 시간을 투자했습니다. 이렇게 테스트 코드를 잘 작성해 두니 코드에 변경 사항이 생길 때도 간단하게 로직의 신뢰도를 테스트할 수 있는 것이 편하게 느껴졌습니다.
확실히 이번 과제는 "객체들과의 소통"에 포커싱이 되어있는 과제 같았습니다. 구현해야 할 객체도 많았고 따라서 설계가 조금 더 어려웠던 것 같습니다.
과제를 진행하면서 의문점이 들 때마다 명쾌한 해답과 깨달음이 간절한 느낌이었습니다. 코드만 작성하고 있는 것이 답답한 느낌이라고 표현해야 할까요. 그래서 이번 주차에서는 과제를 구현하는 시간을 쪼개 틈틈이 책 "오브젝트"를 읽으며 진행했습니다. 확실히 이론적이고 모범적인 사례를 소개하면서 객체 지향 설계의 철학을 소개해 주는 책을 읽으니 시야가 넓어지는 기분이었습니다. 4주차에서는 객체 지향 설계에 대한 기본기를 더욱 탄탄하게 잡아서, 아쉬움 없이 정말 좋은 설계의 코드를 작성하는 것이 목표입니다!
'우아한테크코스' 카테고리의 다른 글
[우아한테크코스 7기] 최종 합격 (0) | 2025.01.01 |
---|---|
[우아한테크코스 7기] 1차 합격 및 최종 테스트 회고 (3) | 2024.12.19 |
[우아한테크코스 7기] 프리코스 4주차 및 종료 회고록 (1) | 2024.11.16 |
[우아한테크코스 7기] 프리코스 2주차 회고록 (0) | 2024.10.29 |
[우아한테크코스 7기] 프리코스 1주차 회고록 (0) | 2024.10.21 |