우테코를 1주차 과제를 진행하면서 고민했던 내용을 정리한다. 제출전까지 다른 과제 참고 없이 진행한 내용이므로 다소 주관적인 내용이 포함되어 있다.
과제
1주차 과제는 문자열 덧셈 계산기를 구현하는 것이다. 기능 요구 사항은 다음과 같다.
입력 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
기능 목록
과제를 받고 먼저 구현할 기능 목록을 README.md를 작성하였다. 기능 목록은 크게 중심이 되는 기능들을 먼저 정의하고 기능에 대한 세부 내용을 적는 식으로 진행하였다.
### 기능 목록
1. 숫자에 대해서 계산(덧셈)을 할 수 있다.
2. 특정 구분자를 통해 문자열을 숫자들로 나눌 수 있다.
// 중략
#### 1. 숫자에 대해서 계산(덧셈)을 할 수 있다.
- 계산기는 더하기 기능을 지원한다.
- 이때 더하기는 여러 숫자를 한번에 계산할 수 있다.
- 계산기는 계산 결과를 상태로 가진다.
#### 2.특정 구분자를 통해 문자열을 숫자들로 나눌 수 있다.
- 구분자는 쉼표(,), 콜론(:) 를 가진다.
- "//구분자\n" 형식으로 새로운 구분자를 등록할 수 있다.
- 문자열을 숫자들로 반환한다.
- 구분자는 Converter 인스턴스의 상태이다.
// 중략
개발할때, 세부구현하기 구체화하고 구현하기 보다는 클래스와 핵심 메소드만 구체화하고 나머지는 직접 구현하면서 방향을 찾아갔었기 때문에 기능 목록을 보고 클래스와 핵심 메소드만 생각할 수 있을 정도로 작성하였다. 기능 요구 사항이 다소 추상적으로 적혀있었기 때문에 일부분 어느정도 직접 결정하였고, 나머지는 최대한 유연하게 대처할 수 있는 형태로 생각했다. 이 부분은 우테코에서 직접 생각해 볼 것을 의도한 것 같다.
과제 후 다른 사람들의 기능 요구 사항을 확인해보니 대부분 구체적으로 작성한 사람들이 많았다. 설계 단계에서 거이 모든 구현 내용을 결정한다는 것인데, 상당히 유토피아적인(?) 방식이라 실제로 제한된 시간에서는 효과적인 방법일까 라는 의문이 있다.
객체 정의
객체를 어떻게 정의할 수 있을까? 어떤 값을 객체로 생각해야 할까?
객체는 무엇일까? 객체는 "모든 실재하는 대상"이라고 한다. 모든 것들은 객체가 될 수 있다. 그러면 모두 객체로 정의해야할까? 이 질문에 대해서 대답하기 위해서는 OOP를 사용하는 이유에 대해 생각해봐야한다.
OOP를 왜 해야할까? OOP의 장점이 뭐지?
좋은 프로그래밍은 1. 다른 사람이 쉽게 이해할 수 있고 2. 변경에 대해서 쉽게 대처 할 수 있는 프로그래밍이라고 생각한다. 그러면 OOP를 사용해야한다면 위 두가지를 만족할 수 있어야한다.
OOP는 프로그래밍 관점을 실세계에 최대한 투영해보자는 것이다. 우리는 실세계에 있는 것과 상호작용해왔다. 이것은 다른 사람에게도 동일하기 때문에 프로그래밍에서 최대한 실제를 생각해보면 개발하기 더 쉽다는 것이다.
다시 돌아와서 어떤 것들을 객체로 정의할까에 대한 경계에 대해서 나는 역활과 상태가 있으면 최대한 객체로 보기로 하였다. 실제로 과제에 문자열 파싱에 대해서 파서가 경계에 있다. 파싱에 대해서 유틸클래스로 처리를 해야하나 하는 고민이 있었다. 그러나 파서에 대해서 파싱이라는 역활이 있다는 점에서 객체로 생각하기로 결정하였다.
계산기의 상태 (과도한 실제화)
계산기는 어떤 상태를 가져야 할까?
계산기는 객체여야 한다는 것은 무리 없이 생각해볼 수 있다. 그러면 계산기는 어떤 상태를 가져야할까? 요구사항으로만 확인했을때는 계산결과를 반환하는 역활외에는 계산기는 상태를 가질 필요가 없어보인다. 그러나 나는 이전 계산 결과를 상태로 추가하였는데, 실제 계산기를 생각 해본다면 계산기는 항상 이전 계산 결과(현재 계산 상황)를 가지고 있다. 덧셈 프로그램을 계발한다면 그럴 필요없지만 우리가 만들고 있는 것은 "계산기"라는 점에서 추가하게 되었다. 객체에 대해서 생각해볼 것이 과도한 실제화는 지양해야 한다. 분명 객체는 실제하는 대상은 맞지만 프로그래밍 세계와 현실에는 간극이 있다.
이 문제는 다른 부분에서도 나타나는데, 계산기가 문자열 파서를 상태 값으로 가지는 것으로 구현했다. 이는 실제로 계산기는 다양한 입력에 대해서 옳바른 연산을 하도록 변환하는 기능도 수행하기 때문에 계산기 객체도 그렇게 되야한다고 생각하였지만, OOP 원칙인 단일 책임 원칙은 위배된 방식이다. 객체는 실제와 다른 점이 분명히 있다는 것이다.
매직넘버
문자열 파싱에 대한 예외에 대한 질문이 있었다. 파싱 범위를 예외가 발생하지 않도록 처리하였음에도 리뷰어가 이를 인지할 수 없었다는 것이고 해당 범위 처리에 대해서 상수화 처리를 하였다면 리뷰어가 어느정도 인지할 수 있었을거 같다. 비슷한 내용으로 모든 매직넘버를 상수화하는 것이 좋은 방법일까라는 의문이 있다. 예를들어 0같은 경우에 특징 컨텍스트에서는 상수화할 필요없지 의미가 명확한 경우가 있다.상수화는 분명 좋은 방법이지만 의미가 명확하고 재사용되지 않는다면 상수화를 사용하지 않아도 된다고 생각한다.