우하한테크코스/precourse

[우하한테크코스] 프리코스 1주차 회고: 문자열 덧셈 계산기

kyung.Kh 2024. 10. 22. 18:00

10월 15일 우테코 7기 프리코스가 시작되었다!

 

1주차 과제는 문자열 덧셈 계산기였다.

https://github.com/sgn07124/java-calculator-7/tree/sgn07124

 

GitHub - sgn07124/java-calculator-7

Contribute to sgn07124/java-calculator-7 development by creating an account on GitHub.

github.com

 

처음 문제를 읽었을 때는 생각보다 문자가 짧았던 것 같다. 

위에서 부터 순서대로 과제 진행 요구 사항, 기능 요구 사항, 입출력 요구 사항, 프로그래밍 요구 사항을 꼼꼼히 읽은 후 방향을 잡기 시작했다.

 

과제를 시작하기 전에 먼저 한 일은 미션 저장소를 fork하고 branch를 만들어서 개발환경을 설정했다. 그리고 프로그래밍 요구 사항의 자바 코드 컨벤션을 지키기 위해 Java Style Guide를 정독하고 IntelliJ 코드 포매터를 설정하여 자동으로 적용되도록 설정하였다. 또한 커밋 메시지 작성 방법도 정해져 있어서 AngularJS Git Commit Message Conventions를 정독하였다.

 

이후에 아래의 진행 과정에 따라 과제를 진행하였다.

💻 진행 과정

기능 구현 목록 작성

과제 요구 사항을 보면 구현할 기능 목록을 먼저 작성하고 목록의 기능 단위로 커밋을 진행하라고 되어있어서 기능 목록부터 작성하였습니다. 최대한 기능에 맞춰서 구현하고 커밋을 해야하므로 하루 정도의 시간을 투자하였습니다.

기능 구현 & 테스트 코드 작성

처음에는 테스트 코드를 먼저 작성하고 기능을 구현하려고 했지만 테스트 코드 작성하는 것이 이번이 처음이라 익숙하지 못해서 이번에는 기능 단위로 기능을 먼저 구현을 하고 이를 검증하는 테스트 코드를 작성한 후 커밋하는 방식으로 진행했습니다.

한 패키지에 기능 단위로 구현을 할 지, 하나의 파일에 모든 기능을 구현할 지, MVC 구조를 적용하여 구현을 할 지에 대해서도 고민을 했었는데 가장 많이 사용하는 MVC 구조를 한 번 적용해보자라고 하여 적용하여 진행했습니다.

리펙토링

기능을 모두 구현하고 테스트도 완료한 이후에 코드의 중복을 줄이기 위해 리펙토링을 진행하였습니다. 이번 과제에서는 최대한 기능 구현을 진행하면서 객체지향적인 설계를 생각하며 기능 구현을 진행하였기에 리펙토링에 대해 많이 고려하지 못했습니다.

✏️ 구현하면서 새롭게 알게된 점

객체지향적 접근

MVC 패턴이란?

MVC(Model-View-Controller) 패턴은 디자인 패턴 중 하나로 주로 사용자 인터페이스를 가진 응용 프로그램에 사용되며, 애플리케이션의 개발과 유지 보수를 쉽게 하기 위해 프로젝트 구성 요소를 세 가지 역할로 구분한 패턴입니다.

  • Model : 비즈니스 로직 및 핵심 데이터를 나타내며 데이터 저장소와의 상호작용, 데이터 처리 같은 작업을 수행한다. 독립적으로 작동하며, 뷰와 컨트롤러와 직접적으로 통신하지 않는다.
  • View : 사용자와의 상호작용을 담당하며, 입력을 받고 결과를 출력하는 역할을 한다. 애플리케이션의 데이터 표시와 관련된 모든 작업을 처리한다.
  • Controller : 모델과 뷰 사이에서 흐름을 제어한다. 사용자의 입력을 받아 비즈니스 로직을 실행하고, 그 결과를 뷰에 전달한다.

사용자가 애플리케이션에서 작업을 수행하면, 뷰(View)가 사용자의 입력을 감지하고 이를 컨트롤러(Controller)로 전달합니다. 컨트롤러는 전달받은 입력을 처리하고, 모델(Model)에 데이터를 요청하거나 수정할 작업을 지시합니다. 모델은 비즈니스 로직을 처리하고 필요한 경우 데이터베이스와 상호작용하여 데이터를 검색하거나 수정합니다. 작업이 완료되면 모델은 결과를 컨트롤러에 반환하고, 컨트롤러는 이 결과를 뷰에 전달합니다. 뷰는 받은 데이터를 바탕으로 화면을 업데이트하여 사용자에게 처리 결과를 보여줍니다.

1주차 과제에서의 MVC 패턴 적용

  • Model : StringParser, InputValidator
    • domain/StringParser.java : 입력된 문장려을 계산에 필요한 정수 리스트로 변환하고, 이를 이용해 덧셈을 수행합니다.
    • validation/InputValidator.java : 입력값의 유효성을 검사하고, 잘못된 형식의 입력이 들어왔ㅇ르 때 예외를 발생시킵니다.
  • View : InputView, OutputView
    • view/InputView.java : 사용자가 입력한 문자열을 받아 입력을 처리하고, 이를 검증한 후 컨트롤러에 전달합니다.
    • view/OutputView.java : 계산 결과를 사용자에게 출력하는 역할을 합니다.
  • Controller : CalculatorController
    • controller/CalculatorController.java : 사용자로부터 입력을 받아 모델에서 계산을 수행하고, 그 결과를 뷰를 통해 사용자에게 전달하는 역할을 담당합니다.
  • etc
    • view/Calcualatemessage.java : 사용자에게 보여줄 메시지를 관리하는 enum 입니다.
    • enums/Delimiter.java : 전반적으로 사용하는 구분자를 관리하는 enum 입니다.
    • enums/ErrorMessage.java : validation에서 사용하는 에러 메시지를 관리하는 enum 입니다.

사용자가 입력을 InputView를 통해 제공합니다. CalculatorController는 InputView로부터 받은 입력값을 유효성 검사를 위해 InputValidator를 호출하여 검증합니다. 입력이 유효한 경우, CalculatorController는 StringParser를 호출하여 문자열을 파싱하고, 각 숫자를 합산하여 결과를 계산합니다. 계산이 완료되면, CalculatorController는 계산 결과를 OutputView로 전달하여, 사용자에게 결과를 출력합니다. OutputView는 결과 메시지를 화면에 출력하여 사용자에게 계산된 값을 보여줍니다.

정규 표현식

정규 표현식은 특정한 규칙을 가진 문자열을 찾거나, 해당 규칙에 맞는 문자열을 조작하기 위한 패턴입니다. 주로 문자열 검색, 치환, 유효성 검사에 사용됩니다. 

정규 표현식의 기본 요소

  • . : 임의의 한 문자(줄바꿈 제외)
  • * : 앞의 패턴이 0번 이상 반복됨
  • + : 앞의 패턴이 1번 이상 반복됨
  • ? : 앞의 패턴이 0번 또는 1번 나타남
  • [ ] : 대괄호 안의 문자 중 하나와 일치
  • | : OR 연산자. (ex: a | b는 a 또는 b와 일치)
  • \d : 숫자와 일치 (0-9)
  • \w : 영문자나 숫자와 일치 (단어 문자)
  • ^ : 문자열의 시작을 의미
  • $ : 문자열의 끝을 의미

과제에서 사용한 정규 표현식

  • customDelimiter.replaceAll("([\\W&&[^\\s]])", "\\\\$1")
    • 특수문자를 이스케이프 처리하기 위해 사용
    • [\\W&&[^\\s]] : 특수 문자를 선택하는 패턴으로 \W는 비단어 문자(영문자, 숫자, 밑줄이 아닌 문자)를 의미하고, [^\\s]는 공백이 아닌 문자를 의미합니다. 이를 결합하여 공백이 아닌 특수 문자를 찾아 이스케이프 처리합니다.
    • \\\\$1 : 앞에서 찾은 특수 문자를 이스케이프 처리하여, 정규식에서 사용할 수 있도록 변경합니다.
  • number.matches("\\d+")
    • 하나 이상의 숫자로만 구성된 문자열인지 검사하기 위해 사용
    • \\d+ : \d는 숫자(0-9)를 의미하며, +는 1번 이상 반복됨을 의미합니다. 따라서, 입력된 문자열이 모두 숫자로 이루어져 있는지 확인합니다.
  • number.matches("0+")
    • 0이 하나 이상 포함된 문자열을 검사
    • 0+ : 0이 1번 이상 반복된 문자열을 의미합니다.

 

개선해야 할 점

구분자 파싱 로직과 계산 로직을 분리

피드백의 내용처럼 구분자나 숫자를 파싱하는 로직과 합계를 구하는 로직을 분리하는게 더 객체지향 적인 코드인 것 같습니다. 만약 덧셈 기능을 곱셈, 나눗셈 기능으로 교체를 해야 한다면, 새로운 계산 로직을 추가하고, CalculatorController에서 이를 호출하면 더 유용할 것입니다. 즉, 유연한 확장을 위해서라면 책임을 분리하는 것이 적절한 것 같습니다.

else 예약어 사용의 자제

책 소트웍스 앤솔러지에 나오는 '객체지향 생활체조 원칙'에 "else 예약어를 쓰지 않는다"라는 규칙이 있습니다. else 키워드는 '조건을 만족하지 않을 때'를 전제하고 시작하기 때문에 가독성이 떨어질 수 밖에 없습니다. 이는 if 조건을 만족하지 않는 모든 경우를 의미하기 때문에, 코드를 읽을 때 양 쪽을 함께 생각해야 하며 오류가 발생할 확률도 높다고 합니다.

 

else 예약어를 사용하지 않으려면 아래와 같이 early return문을 사용하여 해결할 수 있습니다.

public void startCalculate() {
    ...

    if (input.equals("0")) {
        result = 0;
    } 
    result = StringParser.calculateInput(input);
    
    ...
}

이렇게 리팩토링하면 가독성이 증가하고 이해하기 쉬워집니다. 또한 else문을 사용하지 않으면, optimistic(if문으로 기본 시나리오를 따르게)하고, defensive(기본 시나리오를 따르지 않으면 오류 상태를 반환)하게 접근할 수 있습니다.

주석 사용 자제(이름 축약 금지)

저는 클래스나 메서드, 변수의 이름을 지을 때, 너무 길어지면 안된다고 생각하여 최대한 축약하고 주석을 다는 것이 더 좋다고 생각했습니다. 하지만 코드 리뷰의 내용과 프리코스 1주차 피드백을 읽어보니 주석을 달기 보다 가능하다면 이름을 통해 의도를 드러내고, 의도를 드러내기 힘든 경우에 주석을 다는게 낫다고 합니다.

 

이름을 지을 때는 클래스와 메서드의 이름을 한 두 단어로 유지하려고 노력하고 문맥을 중복하는 이름은 자제하는게 좋습니다. 단, 의도를 드러낼 수 있다면 이름이 길어져도 괜찮다고 합니다. 또한 이름을 통해 변수의 역할, 함수의 역할, 클래스의 역할에 대한 의도를 드러내기 위해 노력해야 하며 이름 짓는 데 시간을 투자해야 합니다.

 

 

출처 : https://limdingdong.tistory.com/8

728x90