우아한 테크 코스 1주차 미션인 문자열 덧셈 계산기를 구현하는 과정과 이를 통해 어떤 점에서 어려움을 겪었고 어떤 점을 개선했는지 정리하겠습니다.
문자열 덧셈 계산기
기능 요구 사항
입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
- 사용자가 잘못된 값을 입력할 경우
IllegalArgumentException
을 발생시킨 후 애플리케이션은 종료되어야 한다.
위의 요구 사항을 보고 생각보다 풀만한 문제다 라고 생각했습니다.
하지만 막상 해보니 막히는 점이 많았던 것 같습니다.
단순 기능만 구현하는게 아니라 어떻게 만들어야 좋은 프로젝트가 될지 많이 고민해보았습니다.
1. 일단 구현부터 해보자.
제가 도메인 지식이 부족하다는 것을 많이 느끼게 해준 부분이였습니다.
애초에 구현이 먼저가 아닌 기능 구현 목록을 작성해야했었는데, 이 부분을 생각을 안하고 바로 기능구현 부터 해본게 오히려 구현 시간을 오래걸리게 한 게 아닌가 생각이 들었습니다.
그냥 기능만 구현했을때는 2시간 남짓에 기능구현을 완료했습니다.
구현을 해놓고 보니, 누군가가 제 코드를 보았을때, 단숨에 이해가 가능한가? 바로는 아니더라도 읽었을 때, 어느정도 이해가 가능한 선에서 구현이 되었는가? 에 대해서 많이 고민해보았고, 이를 통해 구현된 코드를 각각의 객체로 분리하여 진행하기위해 코드 리팩터링을 진행했습니다.
이 과정에서 객체지향적인 코드를 만들기 위해 많은 노력을 했던 것 같습니다.
단순 구현이 아니라, 어떻게 하면 유지보수성이 높은 코드를 만들 수 있을지 다시 처음으로 돌아가서 시작하는 마음으로 요구사항 분석부터 차근차근 해나갔습니다.
2. 요구 사항 분석
위에 있는 기능 요구 사항을 바탕으로 객체를 나누는 작업을 먼저 했습니다.
2-1. 문자열 덧셈 계산기가 가질 객체
- Calculator - 실제 계산을 처리하는 객체입니다. 숫자형 컬렉션에 담긴 모든 값을 더하는 sumAll() 함수를 구현해 내부의 모든 값을 더하는 메서드를 만들었습니다
- Delimiters - 문자열 계산기는 사용자에게 입력받은 문자열을 구분자를 바탕으로 나누게 됩니다. 이러한 구분자들을 가지고 관리하는 객체가 하나 빠져야된다고 판단했고 이를 바탕으로 Delimiters 객체를 구현해서 input에서 구분자들을 추출해서 가지도록 책임을 분리했습니다.
- InputParser - 이 객체는 input값을 구분자를 통해 분리하고 input내부의 숫자들을 반환하도록 하는 메서드를 구현해 최종적으로 숫자형 컬렉션을 반환하도록 했습니다.
- InputView, OutputView - ui를 담당하는 객체로, 사용자로 부터 입력 받은 값과, 이를 통해 최종 반환되는 값을 Console에 나타내도록 했습니다. 하지만 추후 사용자의 입력방식, 결과값을 반환하는 부분이 추후 변경될 수 있기 때문에 필요한 메서드들을 인터페이스로 추상화 하는 작업을 했습니다.
그렇다면 이 객체들을 통해 어떤 흐름으로 가져가면 좋을지 고민해보았습니다.
2-2 비즈니스 로직의 흐름
기본적인 흐름은 아래와 같았습니다.
- InputView를 통해 사용자로 부터 입력을 받습니다.
- InputParser를 통해 숫자형 리스트를 받습니다.
- InputParser가 가지고 있는 Delimiters 객체를 활용해 구분자를 찾고 가지게됩니다.
- Delimiters는 커스텀 구현자가 있는지 없는지 여부를 파악하고 이에 맞춰서 구분자들을 반환합니다.
- InputParser에서 Delimiters 객체를 통해 찾은 구분자를 통해 사용자로부터 입력받은 input을 가공합니다.
- 가공된 숫자형 리스트는 최종적으로 Calculator내부의 sumAll 메서드를 통해 모두 더한 값을 반환합니다.
- 모두 더한 값을 OutputView를 통해 출력해줍니다.
이를 바탕으로 구현을 완료했습니다.
InputParser.extractNumbersfromInput(String input) - input으로 부터 숫자형 리스트를 반환하는 함수
Delimiters.getAllDelimiters - input으로 부터 구분자를 분리하고 구분자들의 리스트를 반환하는 함수
이 두개를 구현하는 것이 가장 중요한 부분이였던 것 같습니다.
3. 구현 과정에서 겪은 고민
가장 고민했던 부분은 Delimiters 부분이였던 것 같습니다.
제일 처음에는 Delimiters는 어떤 값을 내부에 가져야 할까? 였습니다.
고민의 결과는 split메서드를 통해 input을 잘라줄건데 split메서드는 매개변수로 문자열을 받고 그 문자열을 토대로 input값을 잘라줍니다.
예를들어 구분자가 ","과 ":" 인 상황이라면 메서드는 다음과 같이 작성해야합니다.
String split = input.split(",|:");
| <- 이 문자열로 split메서드의 매개변수를 나눠줄 수 있습니다. 저렇게 했을 때, ","혹은":"가 String에 있으면 잘라주세요 라는 뜻입니다.
그래서 처음에는 단순하게 저렇게 반환해주면 되겠구나 라는 생각으로 커스텀 구분자를 뒤에 붙이기만 해서 객체 외부로 반환시켰습니다.
처음에는 단순하게 구분자와 관련된 문자열을 분리하는 기능만 제공했지만, 이 과정에서 여러 구분자나 커스텀 구분자가 추가될 때 로직이 복잡해지는 문제가 발생했습니다.
이를 해결하기 위해 일급 컬렉션을 도입하여, 구분자 컬렉션을 하나의 객체로 취급하고, 관련된 모든 로직을 해당 객체 내부에서 처리하도록 만들었습니다.
다음은 Delimiters 객체의 일부분입니다.
public class Delimiters {
...
private final List<String> delimiters = new ArrayList<>();
public Delimiters() {
addDefaultDelimiters();
}
private void addDefaultDelimiters() {
delimiters.add(DelimiterType.COMMA.getDelimiter());
delimiters.add(DelimiterType.COLON.getDelimiter());
}
...
}
처음에는 그냥 String을 가졌지만, 후에 위의 코드대로 변경했습니다.
최종적으로 ",|:" -> {",",":"} String에서 List으로 변경된 것입니다.
위처럼 일급 컬렉션으로 상태를 감싸게 되면 어떤 이점을 얻을 수 있을까요?
이는 객체지향적인 관점에서 많은 이점을 주게 됩니다 이유는 다음과 같습니다.
- 코드의 가독성과 유지보수성이 향상됨.
- 컬렉션에 비즈니스 로직을 추가할 수 있음.
- 외부에서 값 변경이 불가능 하도록 불변성을 유지할 수 있음.
- 컬렉션 관련 메서드를 통해 확장이 용이함.
결론적으로 객체지향적인 코드를 구현하고 싶다면 일급 컬렉션을 사용하는 방법이 유용하다는 생각이 들었고 이를 바탕으로 Delimiters를 일급컬렉션 객체로 구현했습니다.
가독성이 좋은 코드가 무엇일까?
이 부분에서도 많은 고민을 했었습니다.
어떻게 하면 가독성이 좋은 코드를 만들 수 있을까? 누군가가 내 코드를 봤을 때, 빠르게 이해할 수 있을까?
개발에서는 개인의 구현력, 비즈니스 로직을 이해하는 능력도 당연히 중요시 하게 생각합니다.
제가 생각했을때, 좋은 엔지니어의 역량중 하나는 어떻게 하면 같이 일하기 좋을까? 인 것 같습니다.
소프트웨어 개발은 혼자하는 것이 아니라 여러사람이 같이하는거라 생각합니다.
그런 의미에서 코드의 가독성은 엔지니어의 역량중 매우 중요한 부분이라고 생각이되었습니다.
내 코드를 봤을때, 누군가가 같이 작업을 해야된다면, 빠르게 이해를 해야 다음 스텝으로 빠르게 나갈 수 있고 이는 곧 개발 생산성을 높이게 되는 결과를 가져옵니다.
이를 바탕으로 어떤 코드가 가독성이 좋을까에 대해 많이 고민해봤는데 결론은 다음과 같았습니다.
메서드 명을 행동에 집중하기 보다 결과에 집중하자
개발하면서 어려운 부분중하나가 메서드명 변수명 정하기 일 것 같습니다. 어떤 이름을 줘야될까?
무턱대고 split했다고 split으로 변수명 정하는 것은 바람직하지 못합니다.
내 코드를 보는 상대방은 "그래서 뭘 split 했지?" 라는 생각이 들고 split이 무슨의미인지 찾으며 시간을 쓰게 될 것입니다.
이러한 코드는 바람직 하지 못한 코드라는 생각이 들었고 메서드명을 행동에 맞추지 않고 결과 중점적인 이름을 가지도록 만들었습니다.
다음 포스팅에서 메서드 명, 변수명을 어떻게 정하면 좋을까? 에 대해 정리해보겠습니다.
'BackEnd > Java' 카테고리의 다른 글
[Java] DTO - Data Transfer Object, toEntity와 toDto (1) | 2024.11.29 |
---|---|
[Java] 스트림이란? 스트림의 사용이유와 컬렉션과 비교 예제 (0) | 2024.10.20 |
[Test] TDD - Test Driven Development (0) | 2024.10.04 |
[Java] synchronized - 동기화 (0) | 2024.08.19 |
[Java] 메모리 가시성 - volatile (0) | 2024.08.19 |