3주차 미션도 마무리되었다. 이번 미션은 특히 고민할 거리가 많았고 학습한 것도 많았다고 생각한다! 특히 문제 조건이 은근 까다로워서 애를 많이 먹었다 ㅎㅎ..
내가 3주차 미션을 진행하면서 느꼈던 것, 학습했던 것들을 시간 순으로 정리해보려고 한다.
객체 간 메세지를 전달해라
나는 2주차 미션에서 '객체 간 메세지'를 고려하지 않아서 아쉬움이 남았었다. (2주차 회고에서 언급했던 부분이다)
그 당시 시스템을 설계할 때, 메세지보다는 클래스 자체에 비중을 많이 뒀다. 이것이 객체지향의 핵심이라고 생각했다.
객체지향이라는 이름에 걸맞게, 객체가 가장 중요한게 아닌가? 이런 생각을 해왔었다.
정말 객체가 객체지향의 중심일까? 그렇다면 객체지향은 클래스 중심인가? 객체지향의 진짜 의미는 무엇인가?
궁금증이 들어 2주차 미션을 제출하는 날, 도서관으로 향해 조영호 님의 저서인 '객체지향의 사실과 오해' 라는 책을 빌렸다.
이 책을 읽고 내가 알게된 것은 다음과 같다.
객체지향은 객체나 클래스가 중요한 것이 아니다.
객체나 클래스는 언제든지 대체될 수 있다. 반대로 이야기하면 대체될 수 있도록 설계하는 것이 좋은 객체지향 설계이다. 중요한 건 객체나 클래스가 아니라 메세지이다. 즉, 역할, 책임, 협력을 중요시하는 시스템을 만들어야 한다는 것을 알게 되었다. 또 객체는 자율성을 보장받아야 한다는 것을 알게 되었다.
바로 3주차 미션 설계에 이를 적용했다. 나는 2주차 미션까지 시스템을 설계할 때 클래스 다이어그램을 구상했다. 이는 너무 클래스 중심이었다. 따라서 이번 미션부터는 메세지 다이어그램을 구상했다. 실제 내가 만든 메세지 다이어그램은 아래와 같다.
모양이 조금 웃길지 모르겠지만, 내가 직접 고안한 메세지 다이어그램이다. 사실 메세지 다이어그램이라는 말도 내가 만들었다.
위 다이어그램의 핵심은 객체명이 없다는 것과, 각 객체가 세부적으로 해야 할 일을 정해두지 않았다는 것.
즉, 인터페이스만 중요시한다. 객체와 객체 간 무엇을 주고받을 것인지를 정의하고 있다. 이런 접근 방식은 사실 처음이다. 이전 주차까지 클래스 다이어그램을 작성했던 것을 생각하면 새로운 시도가 아닐 수 없다. 사실 그래서 이걸 제대로 구현해낼 수 있을까에 대한 두려움도 있었던 것은 사실이다.
이처럼 메세지(인터페이스)를 먼저 정의하니, 기능 목록을 도출하는 건 쉬웠다. 그저 메세지를 주고받기 위해 필요한 기능들만 도출하면 되었다. 이렇게 도출된 기능들을 기반으로 공통 부분을 묶어, 객체를 만들어냈다.
자율성
객체가 보장받아야 할 가장 중요한 특징 중 하나는 자율성이다. (이 역시 '객체지향의 사실과 오해' 라는 책을 통해 알게 된 내용이다)
그렇다면 자율성이란 무엇일까? 내가 해석한 바로는, 인터페이스 외의 행동에는 객체가 다른 객체에게 간섭하지 않는 것이다.
2주차까지 나는 컨트롤러 중심 설계를 해왔다. 도메인 객체들은 정적이며, 컨트롤러가 입력을 받고 객체들의 순서를 조율하고... 실행시키는 그런 시스템을 만들어왔다. 즉, 컨트롤러가 시스템의 심장이었다.
이번에는 자율성을 보장하고 싶었다. 서로 협력하는 살아 있는 시스템을 만드려고 노력했다. 각 객체는 필요한 일을 본인이 수행하도록 설계했다. 실제로 내 코드를 봐도, 컨트롤러가 하는 일은 별로 없다. 심지어는 IO Layer에 도메인 객체가 필요할 때 직접 명령을 내린다. 객체가 자율적으로 행동하는 것이기 때문에 이 행위들은 자연스레 private 메소드가 된다.
이렇게 설계하면 문제가 하나 발생한다. 무엇일까?
테스트가 어렵다.
테스트가 어렵다는 것은 단순히 테스트에서 그치지 않는다. 피드백을 받기가 어렵다는 것이고, 유지보수에서 어려움을 겪을 수 있다는 것이다. 이는 객체지향의 개념에 정면으로 맞선다.
따라서 이 부분에 대해서는 추가적으로 학습하려고 한다. 이번 3주차 미션에서 객체에게 자율성을 부여하는 것은 성공했지만, 이런 부분에서 아쉬움이 남았다. 자율성을 부여하는 것의 적정선은 어디인지 알아내는게 4주차 목표이지 않을까 싶다. Input Layer를 어떻게 하면 객체의 자율성을 보장하면서도 분리할 수 있을지 찾아보려 한다.
검증
검증 절차는 어디에 위치해야 할까?
당연히 Input이 들어오는 위치가 아닐까라고 생각했지만, 아니다.
이는 요구사항에서 제공된 Lotto 클래스를 보면서 알게 된 사실이다.
해당 Lotto 클래스에서는 생성자와 validate 메소드가 이미 정의되어 있다.
잠깐, 생성자는 그렇다 쳐도 validate 메소드는 왜 정의되어 있을까?
public class Lotto {
private final List<Integer> numbers;
public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
}
private void validate(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException();
}
}
}
// 로또 클래스는 위처럼 생김!
나는 여태까지 사용자 입력이 들어오는 곳에만 검증 절차를 배치했다. 즉, 비정상적인 입력을 걸러내는 것이 검증 절차라고 생각했다.
제공된 Lotto 클래스를 보면서 상당한 고민에 잠겼다. 도메인 로직에서 검증을 수행하는 것이 좋은 것인가?
고민을 통해 내가 내린 결론은 검증 절차를 도메인 로직에서 수행해야 될 이유가 있다는 것이다.
우선, 도메인 로직에서의 검증은 사용자 실수 뿐만 아니라 개발자 실수를 방지한다.
개발자가 실수로 로또 객체를 생성하게 될 수도 있다. 코드 한줄만 추가하면 될 일이다. 콘솔에서는 아무런 에러 메세지도 출력되지 않는다.
좋은 코드란, 사용자의 실수와 개발자의 실수를 모두 방지하는 코드라는 생각이 들었다. 이것이 도메인 로직에서 검증을 수행해야 할 첫번째 이유이다.
두번째로, 시스템의 상태 변화를 검증할 수 있다.
사용자 입력을 받을 때만 데이터의 유효성을 검증한다고 해보자. 올바른 형식인지, 올바른 값인지..
그러면 시스템이 계속 작동함에 따라 변화하는 데이터는 올바르다고 보장할 수 있을까? 로또 시스템을 반복적으로 사용했을 때 로또 객체 내부의 값들이 유효하다고 보장할 수 있을까?
아니다. 도메인 로직에서 검증해야만 한다. 나는 이 부분을 간과했다. 시스템은 상태가 변화한다. 이 각각의 상태에 대한 검증을 진행해야만 한다. 3주차 미션에서 내가 겪었던 일을 예로 들어보려고 한다.
나는 보너스 번호가 당첨 로또에 포함되서는 안된다는 조건을 검사하고 싶었다. (포함되면 그게 무슨 보너스 번호인가..!)
그렇다면, 보너스 번호는 당첨 로또 번호 입력에 따라 의존적이게 된다. 즉, 상태가 제각각 다르다.
그러나 나는 검증 절차를 사용자 입력에만 구성했기 때문에 상태 변화에 따른 검증은 할 수 없었다. 이것이 문제이다. 시스템의 상태 변화를 모니터링 할 수 없다. 그래서 다음부터는 검증 절차를 되도록 도메인 로직에 종속시키려고 한다. 이 부분에 대해서도 찾아봐야겠다고 느꼈다. 어디까지는 도메인 로직에 종속시키고, 어디까지는 사용자 입력에만 종속시킬지에 대한 고려도 필요할 듯 보이니 말이다.
테스트
테스트란 녀석을 접한지 이제 2주가 되었다. 테스트가 중요하다는 건 족히 알고 있다.
2주차 공통 피드백에서 테스트를 학습에 사용해보라는 피드백이 있었다. 엥.. 테스트를 학습에 사용하라니?
테스트의 가치는 무엇이라고 생각하는가?
아마도 대부분의 나를 포함한 사람들은 피드백을 빠르게 얻을 수 있는 것이라고 이야기할 듯 하다.
즉, 테스트는 내가 만든 객체나 메소드를 검증할 뿐만 아니라, 학습에서도 활용될 수 있다. 다양한 테스트케이스를 손쉽게 만들어내고,
바로 결과값을 한번에 알아낼 수 있다.
이번 미션에서는 수익률을 출력하는 기능이 필요했다. 즉, 수익률을 계산해 00.0% 처럼 적당한 포맷으로 변환시켜야 했다.
이 과정에서 나는 DecimalFormat 이라는 라이브러리를 사용했다. (java.text.DecimalFormat)
사실 구글링으로 알아낸 터라, 몇가지 테스트를 돌렸을 때 잘 돌아가는 거 같아서 그대로 사용할까 고민했는데 아무래도 찝찝해서 테스트로 학습해보기로 했다.
이런 저런 테스트를 진행해보면서, DecimalFormat에 대해 잘 알게되고, 이전에는 올바르게 사용하지 않고 있었다는 것 또한 알게 되었다. 테스트를 통해 기존 코드의 문제점을 알아내기까지 30분이 채 걸리지 않았다. 이처럼 테스트가 주는 학습효과는 거대하다는 것을 몸소 알게 되었다. 처음 사용해보는 기술이나 라이브러리가 있다면, 먼저 테스트를 통해 학습하는 것도 좋은 방법인 듯 하다.
마치며
매 주 새로운 요구사항을 지키고, 새로운 문제들을 해결하다 보니 그 과정에서 얻어가는 게 많다. 앞서 이야기한 '검증'에 대한 것도 요구사항을 보다가 알게 된 사실이다. 그리고 지속적으로 요구사항을 지키려 노력하다 보니, 나중에는 의식하지 않아도 자연스럽게 지키게 된다. 이 역시 성장하고 있다는 증거라고 생각한다.
요구사항을 지키는 것 이외에도, 나는 객체지향을 공부하는 것에 많은 시간을 투자하고 있는데, 확실히 쉽지 않고 방대한 개념이다. 좋은 설계에는 고려해야 할 요소가 엄청 많다는 것을 깨달았다. 그래도 매주 좋은 방향으로 나아가는 것 같아 뿌듯하다.
이제 프리코스도 마지막 주차에 접어든다. 솔직히 말하면 후련함보다는 섭섭함이 크다. 많은 동료들과 함께 한 가지 목표로 열정적으로 도전하며 몰입하는 과정, 정말 귀한 경험이라고 생각한다. 단기간에 이렇게 성장했던 적도 처음이다.
4주차에도 마찬가지로 동료들과 함께 많이 성장했으면 좋겠다..!! 다음 회고록을 쓸 때에는 지금보다 더더욱 성장한 나의 모습을 기대하면서,
끝!
'우테코 5기' 카테고리의 다른 글
[우테코] 우아한 테크코스 5기 1차 합격 + 최종 합격 (1) | 2022.12.15 |
---|---|
[우테코 프리코스] 4주차 회고 (1) | 2022.11.24 |
[우테코 프리코스] 2주차 회고 (3) | 2022.11.08 |
[우테코 프리코스] 1주차 회고 (2) | 2022.11.01 |
우테코 프리코스를 앞두고 (1) | 2022.10.24 |