Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 122 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,122 @@
# java-calculator-precourse
# java-calculator-precourse
# 문자열 덧셈 계산기

입력된 문자열을 구분자를 기준으로 분리하여 합계를 계산하는 프로그램입니다.
절차지향적으로 기능을 완성한 뒤, 객체지향 원칙에 따라 리팩토링하며 구조를 개선했습니다.

---

## 미션 개요

- 우아한테크코스 프리코스 1주차 과제
- 문자열 입력을 받아 구분자 기준으로 숫자를 분리하고, 합을 계산하는 프로그램
- 기본 구분자( `,` , `:` )와 커스텀 구분자(`//;\n`) 모두 지원
- 잘못된 입력(음수, 문자 등)에 대해 예외 발생

---

## 기능 요구사항

- 입력된 문자열에서 숫자를 추출하여 덧셈 결과를 반환한다.
- 기본 구분자(쉼표 `,` , 콜론 `:`)를 사용한다.
- 커스텀 구분자를 지정할 수 있다. (예: `//;\n1;2;3`)
- 잘못된 입력(문자, 음수, 공백 등)은 `IllegalArgumentException`을 발생시킨다.

---

## 구현 단계별 정리

이 프로젝트는 **절차지향적으로 구현한 뒤**,
**리팩토링을 통해 객체지향적으로 개선한 과정**을 담고 있습니다.
핵심 로직을 세분화하고, 역할에 따라 클래스를 분리하며 점진적으로 구조를 발전시켰습니다.

---

### 1단계: 핵심 로직 구현 (절차지향 → 기능 분리)

- 문자열을 입력받아 구분자 기준으로 분리하고, 숫자를 더하는 기본 기능 구현
- 커스텀 구분자(`//;\n`) 처리 및 음수 검증 등 비즈니스 규칙을 절차적으로 구현
- 기능 단위로 메서드를 분리하여 테스트 용이성 확보

---

### 2단계: 구조화 및 객체지향 리팩토링

- **DelimiterInfo**: 구분자 정규식과 숫자 문자열을 담는 DTO
- **DelimiterParser / DefaultDelimiterParser**: 입력 문자열 분석 및 `DelimiterInfo` 반환
- **NumberParser**: 문자열을 정수로 변환하며 예외 처리
- **Validator**: 음수 값 검증 후 `IllegalArgumentException` 발생
- **StringCalculator**
- 구성 요소를 주입받아 순차적으로 호출
- 문자열 분리 → 숫자 변환 → 검증 → 합산 로직 수행
- **Calculator 인터페이스**
- `calculate(String input)` 메서드로 계산 행위의 규약 정의

---

### 3단계: 입출력 연결 및 실행 환경 구성

- **InputHandler**
- `Console.readLine()`으로 사용자 입력 처리 및 자원 정리
- **Application (main)**
- 입력 수집 → `StringCalculator` 실행 → 결과 출력
- `try-catch-finally` 구문을 통해 예외 처리 및 리소스 정리

---

## 예외 상황

- 문자가 포함된 입력값 (예: `"a,2,3"`, `"1b:4"`)
- 음수 입력값 (예: `"-1,2,3"`)
- 구분자가 연속된 경우 (예: `"1,,2"`)
- 소수 입력 (예: `"1.5,2"`)
- 정수 범위 초과 입력
- 빈 문자열 또는 공백 입력 (예: `""`, `" "`)
- `null` 또는 입력이 존재하지 않는 경우
- 숫자만 단독 입력된 경우

---

## 미션 진행 방향

이 미션은 **절차지향적으로 작동하는 코드를 먼저 작성한 뒤**,
객체지향 원칙(OOP)에 따라 **역할과 책임 중심으로 구조를 개선**하는 것을 목표로 합니다.

1. 절차지향적으로 기능을 완성한다.
2. 역할과 책임을 분리하여 구조를 개선한다.
3. 테스트 가능한 구조로 리팩토링한다.

---

## 커밋 컨벤션

AngularJS Commit Message 규칙을 참고했습니다.
커밋은 **기능 단위**로 나누어 작성합니다. (예: 기본 구분자 처리, 커스텀 구분자 추가, 음수 예외 처리 등)

**Allowed `<type>`**

- feat: 새로운 기능 추가
- fix: 버그 수정
- docs: 문서 수정
- style: 코드 포맷팅
- refactor: 코드 리팩토링
- test: 테스트 코드 추가
- chore: 빌드, 설정 등 유지보수 작업

> 콜론(`:`) 뒤에는 반드시 공백 한 칸을 둡니다.

---

## 어떤 점에 집중했는가

- 절차지향적 설계로 기본 동작 완성 능력 향상
- 예외 처리와 입력 검증을 체계적으로 구현
- 객체지향적 리팩토링을 통한 책임 분리 연습
- 클린 코드 원칙을 적용하고 유지보수성을 고려한 설계

---

## 요약

이 프로젝트는 문자열 계산기를 절차지향적으로 구현한 뒤,
객체지향 설계 원칙에 따라 구조를 개선하며 **깨끗하고 확장 가능한 코드**로 발전시키는 과정을 담고 있습니다.
78 changes: 76 additions & 2 deletions src/main/java/calculator/Application.java

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일 한 곳에 여러 기능들이 몰려있는 느낌입니다..! 클래스의 인스턴스 생성, Caclulator의 동작 메서드 등 다양하게 역할을 분배해도 좋을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다! 2주차때 적용해보도록 하겠습니다 ㅎㅎ

Original file line number Diff line number Diff line change
@@ -1,7 +1,81 @@
package calculator;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

패키지 구조를 더 나눠서 클래스들을 모아놓는다면 더 직관적으로 구조가 잘 보일 것 같습니다! 어떻게 생각하시나요??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 2주차때 적용해보도록 하겠습니다!


import java.util.regex.Pattern;
import camp.nextstep.edu.missionutils.Console;

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
System.out.println("===덧셈할 문자열을 입력해주세요===");
String input = Console.readLine(); // Console.readLine() 사용

try {
if (input == null || input.trim().isEmpty()) {
System.out.println("결과: 0");
return;
}

String basicDelimiterRegex = "[,:]";
String finalDelimiterRegex = basicDelimiterRegex;
String numbersToSplit = input;

// 2. 커스텀 구분자
if (input.startsWith("//")) {
int delimiterEndIndex = input.indexOf("\n");

if (delimiterEndIndex == -1) {
throw new IllegalArgumentException("커스텀 구분자 선언 후 반드시 줄 바꿈(\\n)을 해야합니다.");
}
String customDelimiter = input.substring(2, delimiterEndIndex);

finalDelimiterRegex += "|" + Pattern.quote(customDelimiter);

numbersToSplit = input.substring(delimiterEndIndex + 1);
}


String[] parts = numbersToSplit.split(finalDelimiterRegex);

int sum = 0;
StringBuilder negativeNumbers = new StringBuilder();

for (String part : parts) {
String trimmedS = part.trim();

if (trimmedS.isEmpty()) {
continue;
}

if (!trimmedS.matches("^-?\\d+$")) {
throw new IllegalArgumentException("잘못된 입력 형식입니다. 숫자만 입력이 가능합니다: " + trimmedS);
}

int num = Integer.parseInt(trimmedS);

if (num < 0) {
if(negativeNumbers.length() > 0) {
negativeNumbers.append(", ");
}
negativeNumbers.append(num);
continue;
}

sum += num;
}

if (negativeNumbers.length() > 0) {
throw new IllegalArgumentException("음수는 입력이 불가능합니다: " + negativeNumbers.toString());
}

System.out.println("결과: " + sum);

} catch (IllegalArgumentException e) {
System.out.println("오류: " + e.getMessage());
} catch (Exception e) {
System.out.println("예상치 못한 오류가 발생했습니다.");
// e.printStackTrace();
} finally {
Console.close();
}
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/calculator/DefaultDelimiterParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package calculator;

import java.util.regex.Pattern;

public class DefaultDelimiterParser implements DelimiterParser {

private static final String BASIC_DELIMITER_REGEX = "[,:]";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정규표현식 입력을 실수할 수도 있으니, 기본 구분자는 리스트로 만들어두고, 이를 정규표현식 패턴으로 만들어주는 함수를 구현하는 것도 고려해볼 수 있을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리스트로 만들면 확실히 구분자가 추가 되거나 변경되어도 리스트만 수정하면 좋겠네요 감사합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변수명에 대해 몇 가지 궁금한 점이 있습니다!

  1. finalDelimiterRegex에서 final을 붙이신 이유가 궁금해요.
    변수명에 final을 명시하는 것보다는 실제로 final 키워드를 사용하거나,
    delimiters 또는 delimiterPattern 같은 이름이 더 명확할 것 같습니다.

  2. numbersToSplit은 "분리할 숫자들"이라는 동사구 형태인데,
    아직 분리되지 않은 문자열이라 의미가 혼란스러워 보여요.
    inputText, numberString 같은 명사형이 더 자연스럽지 않을까요?


@Override
public DelimiterInfo parse(String input) {
String finalDelimiterRegex = BASIC_DELIMITER_REGEX;
String numbersToSplit = input;

if(input.startsWith("//")) {
int delimiterEndIndex = input.indexOf("\n");

if (delimiterEndIndex == -1) {
throw new IllegalArgumentException("커스텀 구분자 선언 후 반드시 줄 바꿈(\\n)을 해야합니다.");
}

String customDelimiter = input.substring(2, delimiterEndIndex);
finalDelimiterRegex += "|" + Pattern.quote(customDelimiter);

numbersToSplit = input.substring(delimiterEndIndex + 1);
}

return new DelimiterInfo(finalDelimiterRegex, numbersToSplit);
}

}
18 changes: 18 additions & 0 deletions src/main/java/calculator/DelimiterInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package calculator;


public class DelimiterInfo {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구분자에 대한 내용을 패키지로 한번 더 묶으면 가독성이 좋아질 것 같아요.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info같은 경우 전부 객체를 생성할 필요없이 클래스 래벨에서 처리할 수 있을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 피드백 감사합니다!
구분자 관련 부분은 말씀처럼 패키지로 한번 더 묶으면 가독성이 훨씬 좋아질 것 같네요!
공통으로 사용하는 기능이나 상수만 있다면 굳이 New로 매번 객체를 생성할 필요가 없겠네요
오히려 코드의 가독성을 떨어트리고 성능도 좋지 않겠네요
다음 2주차 미션때 개선 해보도록 하겠습니다!


final private String delimiterRegex;
final private String numbersString;

public DelimiterInfo(String delimiterRegex, String numbersString) {
this.delimiterRegex = delimiterRegex;
this.numbersString = numbersString;
}

public String getDelimiterRegex() {
return delimiterRegex;
}

}
8 changes: 8 additions & 0 deletions src/main/java/calculator/DelimiterParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package calculator;

public interface DelimiterParser {

DelimiterInfo parse(String input);

}