Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
66870f2
chore: 프로젝트 초기설정
Shin-Jae-Yoon Jun 7, 2023
e359d47
chore: assertJ 라이브러리 추가
Shin-Jae-Yoon Jun 14, 2023
510a36a
feat: 입출력 콘솔 기능
Shin-Jae-Yoon Jun 14, 2023
3e95e71
feat: 연산자 enum 구현
Shin-Jae-Yoon Jun 14, 2023
470ce8d
feat: 수식 토큰으로 분석 구현
Shin-Jae-Yoon Jun 14, 2023
6e66b73
feat: 후위표기식 이용 계산기능 구현
Shin-Jae-Yoon Jun 14, 2023
879648b
feat: Map 저장소 기능 구현
Shin-Jae-Yoon Jun 14, 2023
2c5d788
docs: 과제 문서 작성
Shin-Jae-Yoon Jun 14, 2023
274d53a
refactor: 콘솔이 구현체가 아닌 추상체에 의존하도록 변경 및 콘솔 단일화
Shin-Jae-Yoon Jun 14, 2023
f427173
refactor: 컨트롤러 메서드 수정 및 전체 패키지 구조 수정
Shin-Jae-Yoon Jun 15, 2023
5eb3ba2
test: OptionType 테스트 코드 구현
Shin-Jae-Yoon Jun 15, 2023
7fda6a3
refactor: Regex 방어코드 리팩토링
Shin-Jae-Yoon Jun 15, 2023
2682e74
refactor: Regex 필드 캡슐화
Shin-Jae-Yoon Jun 15, 2023
ed21f89
refactor: Regex 필드 캡슐화
Shin-Jae-Yoon Jun 15, 2023
a2d1f90
refactor: Regex 클래스를 enum으로 리팩토링
Shin-Jae-Yoon Jun 15, 2023
79d5585
refactor: 수식과 결과 타입을 Wrap 하기
Shin-Jae-Yoon Jun 15, 2023
b134c2f
refactor: Wrapper에 로직이 있는 것이 이상해서 리팩토링
Shin-Jae-Yoon Jun 15, 2023
37900f6
refactor: domain의 계산 로직 자체를 추상화 이용하도록 전체 수정
Shin-Jae-Yoon Jun 15, 2023
ac63eb3
refactor: regex를 Enum으로 수정
Shin-Jae-Yoon Jun 15, 2023
b344322
refactor: parser를 없애고 RegexEnum에 메서드 추가
Shin-Jae-Yoon Jun 15, 2023
92f5057
test: RegexEnum 테스트 구현
Shin-Jae-Yoon Jun 15, 2023
94f6a02
refactor: parser 삭제
Shin-Jae-Yoon Jun 15, 2023
91fc8fe
fix: BigDecimal 반올림 추가
Shin-Jae-Yoon Jun 15, 2023
56713bf
test: domain-component 테스트 구현
Shin-Jae-Yoon Jun 15, 2023
e8231c0
test: RegexEnumTest 리팩토링
Shin-Jae-Yoon Jun 15, 2023
916c946
test: 전체 테스트코드 작성
Shin-Jae-Yoon Jun 15, 2023
89dda38
docs: 과제 문서 작성
Shin-Jae-Yoon Jun 15, 2023
7556fde
refactor: String 내부 CharSequence 확인 후 리팩토링
Shin-Jae-Yoon Jun 18, 2023
437de05
refactor: 무분별한 static 지양 리팩토링
Shin-Jae-Yoon Jun 18, 2023
3d514fb
refactor: Option enum 스트림 리팩토링
Shin-Jae-Yoon Jun 18, 2023
f95a68b
refactor: HistoryRepository toString()을 다른 메서드로 리팩토링
Shin-Jae-Yoon Jun 18, 2023
85bec14
test: import static으로 변경 및 @ParameterizeTest 이용 리팩토링
Shin-Jae-Yoon Jun 18, 2023
8649316
refactor: Controller에서 예외처리 및 메서드 리팩토링
Shin-Jae-Yoon Jun 18, 2023
2ede059
refactor: NotationConverter로 인터페이스명 변경
Shin-Jae-Yoon Jun 18, 2023
6301578
refactor: Objects.equals() 이용하여 NPE 방지
Shin-Jae-Yoon Jun 18, 2023
f3b2eb3
refactor: CalculationResult final 변경
Shin-Jae-Yoon Jun 18, 2023
d7d5bc1
refactor: line.separator 이용 리팩토링
Shin-Jae-Yoon Jun 18, 2023
cef9a2d
test: @ParameterizedTest 이용 리팩토링
Shin-Jae-Yoon Jun 18, 2023
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
53 changes: 53 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!src/main/**
!src/test/**

### macOS ###
.DS_Store

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/
# Ignore Gradle build output directory
build

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties

# Cache of project
.gradletasknamecache

### Gradle Patch ###
# Java heap dump
*.hprof
20 changes: 20 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id 'java'
}

group = 'com.programmers.calculator'
version = '1.0-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.assertj:assertj-core:3.22.0'
}

test {
useJUnitPlatform()
}
97 changes: 97 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
## 📌 과제 설명

- [x] 콘솔로 구현
- [x] OOP 계산기 구현
- [x] 더하기
- [x] 빼기
- [x] 곱하기
- [x] 나누기
- [x] 우선순위 (사칙연산)
- [x] 테스트 코드 구현
- [x] 계산 이력을 맵으로 데이터 저장 기능
- 애플리케이션이 동작하는 동안 DB 외에 데이터를 저장할 수 있는 방법 고민
- [x] 정규식 사용

<br>

## 👩‍💻 요구 사항과 구현 내용

1. 입력과 출력이 현재는 콘솔이지만, 각기 다른 형태로 바뀔 수 있어서 인터페이스로 추상화하고 이를 구현한 InputConsole과 OutputConsole이 생겼습니다.
2. Calculator 클래스에서 입력을 받게 했다가 계산이라는 책임에서 멀어지는 것 같아서 Controller를 추가하여 연결시켰습니다.
3. Enum 클래스를 활용하여 선택 가능한 메뉴를 정의하였고 예외처리를 진행했습니다.
4. Calculator 클래스에서는 계산이라는 동작만 수행하고 Expression이라는 표현식 클래스에서 표현식과 관련된 모든 책임을 갖습니다.
- 연산자에 대한 책임은 Enum 클래스를 활용한 Operator에서 가지고 있습니다.
5. 저장소를 구현할 때, HistoryRepository로 추상화하였고 이를 구현했습니다. 추가로, CalculationHistory 객체를 이용하여 저장시키도록 구현했습니다.
6. 기존에 정규식을 다루던 곳은 Expression 이었으나, 수식 계산마다 Pattern을 생성하는 비용이 크다고 생각하여, 따로 util로 뺐습니다.
- Pattern 생성 비용에 관한 근거는, 클래스를 살펴보면 `new Pattern()` 하는 부분입니다.

<br>

## 😭 1차 자기주도적 리뷰 피드백

**영수님 피드백**
1. 값의 검증에는 예외처리를 하지 않는 것이 좋습니다. (`Expression` 클래스의 `isNumber()` 메서드)
2. `Calculator` 클래스에서 필드로 `expression`을 가지는 것에 의문이 듭니다.
3. `Expression` 클래스가 너무 비대하고 특히 `converToPostfix()` 메서드를 최대한 쪼개봅시다.
4. `if-else` 보단 `switch`를 이용하여 리팩토링 해봅시다.
5. 최대한 3 depth를 넘지 않게 메서드를 리팩토링 해봅시다.
6. 여러 객체가 참조하는 형태가 아니라면, static 사용에 관하여 고민해봅시다.
- OutOfMemory에 관해서도 고려해봅시다.
- 가능한 static을 사용하지 않고 코드를 짜봅시다.
7. Regex는 상수를 모아놓기 위한 장소같아보이는데, 어떤 고민을 해봐야할까요? 생각해봅시다.

<br>

**흑구님 피드백**
1. 현재 모두 구현인 것 같고, 확장에 너무 닫혀 있습니다.
2. 궁금한 것을 설명하고 싶은 의도는 알겠으나, 코드리뷰 요청하는 입장에서 코드를 주석처리한 것은 반드시 없애봅시다.
3. 인스턴스를 생성하지 못하게 하고 싶을 때, 방어적으로 코드 작성하는 것을 생각합시다.
4. 1개의 콘솔로 입출력 하는 상황인데, `InputConsole`과 `OutputConsole`이 마치 2개의 콘솔처럼 보입니다.
5. 객체지향적 설계가 어렵다면, 저만의 방식인데, logic → behavior → data 를 생각해봅시다.
- logic : data를 이용한 behavior의 전체적인 흐름
- behavior : data를 이용하는 어떠한 행위
- data : 말 그대로 데이터, 객체
- logic은 behavior에, behavior은 data에 의존(사용)
- 각각 interaction(상호작용)에 관해서도 생각해볼 것
6. 무조건 메서드로 표현할 필요는 없습니다. 객체간 상호작용이 부족한 지 생각해봅시다.
7. 무조건 적인 set 지양은 하지말고, 어떤 문제가 있는지, 어떤 이유 때문인지 알고 set을 지양해봅시다. 너무 무서워하지 마세요.

<br>

## 😤 피드백 기반 리팩토링

- [x] Console 단일화 및 구현체가 아닌 추상체에 의존하도록
- [x] Controller 클래스 리팩토링
- [x] logic → behavior → data 고려했는가?
- [x] 객체간 상호작용을 고려했는가?
- [x] Expression 클래스 리팩토링
- [x] 3 depth를 넘지 않는가?
- [x] 추상화를 고려하였는가?
- [x] Regex에 방어 코딩을 적용했는가?


## ✅ PR 포인트 & 궁금한 점

**PR 포인트**

- Expression 클래스의 기능이 비대한 것 같아서 나눴습니다.
- Converter, Evaluator 추상체를 만들고 그를 구현한 구현체를 활용했습니다.
- Calculator 추상체를 구현한 구현체에서 조립하여 사용했습니다.
- Parser도 추가했었는데, 맡는 책임이 Regex에서 역할과 비슷하여 없애고 이관했습니다.
- 계산 결과는 수식을 감싸기 위하여 CalculationResult와 Expression vo를 만들었습니다.
- Regex의 역할이 상수만 저장하는 것이라서 util과 맞지 않아서 enum으로 따로 뺐습니다.

<br>

**궁금한 점**

1. logic behavior data를 최대한 고려해봤는데, 사실 아직도 감이 잘 잡히지 않습니다. 최대한 추상화해보려고 했는데, 역할이 너무 작나? 과한 추상화인가? 라는 고민이 계속 생겼습니다.
2. 테스트 코드가 미흡하여, 영수님 강의를 듣고 감이 조금 잡혀서 작성해봤는데, 방식이 맞는지 모르겠습니다.
3. 홍섭님 피드백을 봤을 때, 공학용 계산기와 사칙연산만 가능한 계산기를 고려해보려 했습니다.
- 현재, Operator enum에 사칙연산이 모두 몰아두고 enum의 values()를 반복돌리는 메서드를 정의해놔서 enum에 정의되어 있다면 반환해주고 없으면 예외를 던지는 상태입니다.
- enum에 기능만 추가하면 되기 때문에 공학용 계산기가 추가되는건 문제가 없습니다.
- enum에 다른 기능을 추가하면 사칙연산만 가능한 계산기가 다른 기능도 사용할 수 있는 상황이 되어버립니다.
- 이에 대하여 생각한 해결 방법은 아래와 같습니다.
- 공학용 계산기 enum을 하나 더 만들 때, 인터페이스를 이용해 확장 가능한 Enum Type이 되도록 리팩토링 하는 것 입니다. [아이템38 - 인터페이스 활용 Enum Type 확장](https://medium.com/lucky-sonnie/item-38-%ED%99%95%EC%9E%A5%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-enum-type%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%A9%B4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC-9c9f78334a5e)를 참고하였습니다.
- enum을 없애고 FourArithmeticStrategy 라는 인터페이스를 구현하는 각각 사칙연산이 있고, 공학용 전략 인터페이스를 하나 만들어서 거기에 심화적인 기능을 추가하고, 기본 계산기에는 사칙연산 전략만 적용, 공학용 계산기에는 사칙연산전략 + 심화전략 적용 이렇게 가보려고 합니다.
- 어떠한 방법이 더 괜찮을지 궁금합니다.
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
6 changes: 6 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Thu Jun 08 01:00:05 KST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading