# 계산기 2 Grounds Rule & PR
## Grounds Rule
#### <월, 목>
| 시간 | 내용 | 특이 사항 |
| -------------- | --------------------| -------- |
| 10:00 ~ 10:30 | 스크럼 | |
| 10:00 ~ 12:00 | 짝프로그래밍 및 공부 | |
| 12:00 ~ 14:00 | 점심 식사 및 휴식 | |
| 14:00 ~ 17:00 | 활동 학습 | |
| 17:00 ~ 19:00 | 저녁 식사시간 및 휴식 | |
| 19:00 ~ 21:30 | 짝 프로그래밍 | |
#### <화, 수, 금>
| 시간 | 내용 | 특이 사항 |
| -------------- | -------------------- | -------- |
| 10:00 ~ 10:30 | 스크럼 | 수(14일) 카렌 불참 |
| 10:30 ~ 12:00 | 짝프로그래밍 및 공부 | 수(14일) 카렌 불참 |
| 12:00 ~ 14:00 | 점심 식사 및 휴식 | |
| 14:00 ~ 17:00 | 짝 프로그래밍 및 공부 | |
| 17:00 ~ 19:00 | 저녁 식사 시간 및 휴식 | |
| 19:00 ~ 21:30 | 짝 프로그래밍 및 공부 | |
#### <특이사항>
- 카렌 : 수요일 오전에 불참(10:00~12:00)
### <프로젝트 규칙>
- 현 미션 진행사항 `밈`하고 대화 후 방향 정하기
- Step01을 합치고 Step02를 Mary의 코드 리팩토링(?)후 PR
- Step03까지 진행하고 현 계산기의 Step01 PR하는지 여부 확인
- 짝 프로그래밍 전 전체적인 코드 구조 얘기 후 프로그래밍 시작
- 문서화를 잘 하자!
- README 작성
- 타임 라인 ⇒ 구체적으로 작성하고 당일 정리
- 트러블 슈팅 ⇒ 내용도 문제 상황 / 해결방법으로 구분지어서 당일 정리
- 기능 단위로 나눠서 commit하기!
- 모르는 거 있으면 솔직하게 모른다고 이야기하고, 같이 이야기 해보기!
- 둘 다 모르는 개념이면, 같이 찾아보고 학습하기
- 한 쪽만 아는 경우면, 아는 사람이 모르는 사람에게 설명해주기
- 페어 프로그래밍 전에 간단하게 스크럼 후 진행하기!
</details>
## step1 PR
## 🤔 고민한 부분
### 1. 연산자만 누르고 "=" 버튼을 눌렀을 때 오류 or 사용자의 실수
- 계산기에서 연산자만 입력이 들어왔을 때 해당 상황을 입력오류로 인식해서 사용자에게 오류문구를 출력해줄 것인지 아니면 사용자의 실수로 인식해서 기본값인 "0"으로 반환을 해줄지 고민하였습니다.
- 팝업창이 뜨는 앱이 아니므로 오류로 보지 않고 "0"을 반환해주기로 했습니다.
- '='을 했을 때 '0'을 반환해주는지 오류로 볼 지를 두고 근거를 두자면 계산기앱이 팝업창을 별도로 띄워주는 앱이 아니기에 오류로 보지 않고, 입력할 때 마다 기본 값인 "0"이 깜빡 깜빡 한다는 건 값이 들어온다는 것이니 "0"을 반환해주는 것이 맞는 구현이라고 생각했습니다.
### 2. `componentsByOperators` 메서드에서 `split` 대신 `components`를 이용한 이유
- String 타입의 확장 메서드인 split을 이용하여 `componentsByOperators`를 구현하면 다음과 같습니다.
``` swift
private static func componentsByOperators(from input: String) -> [String] {
return Operator.allCases.reduce([input]) { resultArray, operatorItem in
resultArray.map { $0.split(with:operatorItem.rawValue) }.flatMap { $0 }
}
extension String {
func split(with target: Character) -> [String] {
return self.split(separator: target).map { String($0) }
}
}
```
- 고차함수를 이용하여 줄은 짧아지기는 했지만 너무 많은 고차함수들을 사용하여 가독성이 좋지 않다고 판단하였습니다. 따라서 저희는 `components(separatedBy:)` 메서드를 사용하는 것으로 결정하였습니다.
## 계산기 II [STEP 2] Mary, Karen
안녕하세요. 밈~!
드디어 1학기 마지막 프로젝트인 계산기II Step02 PR을 보내게 된 Mary, karen입니다.
짧은 한 주간의 밈의 리뷰라 많이 아쉽지만 말씀해주시는 부분 하나 하나 새겨듣도록 하겠습니다.
잘 부탁드립니다. 밈~!🙇🏻♀️
## 🤔 고민한 부분
#### 1️⃣ View 분리
- `createUILabel`과 `createSubStackView` 메서드는 `MVC` 구조 중 `View`에 해당할 것 같은데, 메서드만 `View` 그룹으로 빼기에는 어색하여 `ViewController` 내에 `extension`으로 분리하였습니다.
#### 2️⃣ 오토레이아웃
- 길게 수식을 입력 후에 '='연산자를 입력해주면 결과값이 보이지 않는 오류가 발생했습니다. `expressionScrollView`의 크기가 정해지지 않다 보니 오류가 자꾸 발생 되어졌고 안에 `Label`을 넣어 `height`값의 조건을 충족 시켜주어서 해결했습니다.
## 🙇🏻♀️ 조언을 얻고 싶은 부분
#### 1️⃣ numberFormatter
- 최대 글자수를 20개까지 나오게 하기 위해서 최대 유효 자릿수를 길게 설정해보았는데, 18개까지밖에 나오지 않았습니다.
``` swift
extension Double {
func numberFormat() -> String? {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.roundingMode = .halfDown
numberFormatter.usesSignificantDigits = true
numberFormatter.maximumSignificantDigits = 21
return numberFormatter.string(for: self)
}
}
```
- ex.

- 현재 operands의 타입 및 계산 결과는 `Double` 타입이므로, `Double`타입에 대해 조사하였습니다.
- `Double`은 64bit로, 소수점 16자리까지만 유효합니다.
- 이후 소수점은 부동소수점 오차가 발생합니다.
➡️ 따라서 현재 `Double` 타입을 변경하지 않는 이상 해당 문제를 해결할 수 없을 것 같다는 판단이 들었습니다. 혹시 저희가 생각하지 못한 방법이 있을까요?
#### 2️⃣ 액션 함수와 함수를 분리하는 것이 좋을까요?
- 현재는 메서드가 불려지는 순서대로 변경을 해주었는데, 그렇게 하다보니 액션 함수와 함수가 섞였습니다. 가독성 관점에서는 어떤 것이 좋을까요?
#### 3️⃣ 리팩토링을 할 때 중점적으로 생각해두어야 될 사항이 무엇이 있을까요?
- 저희는 리팩토링을 할 때 되도록 가독성과 확장성 부분에서 수정할 수 있는 부분에 중점을 두고 작업을 했는데 맞는 방향성일까요? 아니면 다른 부분에 조금 더 중점을 두었어야 했을까요?
=============
### 1️⃣ 입력값에는 ","구분자가 적용되지 않는 문제
**⛔️ 문제점**
- 숫자 입력을 할 경우에도 ","로 구분 되어져야하는데 구분되어지지 않고 숫자와 소수점만 표기되던 현상이 발생했습니다.
**✅ 해결 방법**
- 결과값에서 출력할 때와 동일하게 `NumberFormatter` 활용하여 구분자 표기되게 해주었습니다.
<details>
<summary>코드 상세</summary>
#### 수정 전
``` swift
@IBAction private func hitNumberButton(_ sender: UIButton) {
if isResult {
isResult = false
initializeAll()
}
guard let number = sender.currentTitle else { return }
if operandsValue.contains(Strings.point) && number == Strings.point { return }
switch (operandsValue, number) {
case (Strings.zero, Strings.zero),
(Strings.zero, Strings.doubleZero),
(Strings.empty, Strings.zero) where expression.isEmpty,
(Strings.empty, Strings.doubleZero):
return
case (Strings.empty, Strings.point):
updateOperands(to: Strings.zero + number)
case (Strings.zero, _),
(Strings.empty, _):
updateOperands(to: number)
default:
updateOperands(to: operandsValue + number)
}
}
```
#### 수정 후
``` swift
@IBAction private func hitNumberButton(_ sender: UIButton) {
if isResult {
isResult = false
initializeAll()
}
guard let number = sender.currentTitle else { return }
if operandsValue.contains(Strings.point) && number == Strings.point { return }
switch (operandsValue, number) {
case (Strings.zero, Strings.zero),
(Strings.zero, Strings.doubleZero),
(Strings.empty, Strings.zero) where expression.isEmpty,
(Strings.empty, Strings.doubleZero):
return
case (Strings.empty, Strings.point):
updateOperands(to: Strings.zero + number)
case (Strings.zero, _),
(Strings.empty, _):
updateOperands(to: number)
default:
updateOperands(to: operandsValue + number)
}
if let formattedNumber = Double(operandsValue)?.changeNumberFormat() {
operandsLabel.text = formattedNumber
}
}
```
</details>
### 2️⃣ 수식 계산 완료 후 초기화 되는 문제
**⛔️ 문제점**
- "="으로 해당 연산의 결과값을 출력하고, 연산자를 입력하면 새로운 연산으로 이어져야 하는데 그동안의 계산이 모두 삭제되고 새롭게 시작되는 문제점이 발생했습니다.
**✅ 해결 방법**
- 초기화 되지 않고 연산자가 추가로 입력이 들어오면 결과값도 연산기록에 추가되어지고 이어서 새로운 연산이 진행되어지게 수정했습니다.
결과값으로
- `isResult`라는 Bool값을 주어 결과가 완료되었는지 여부를 확인해서
- 먼저 `isResult`라는 `Bool`타입을 생성하여 이전에 "=" 버튼을 눌렀는지 확인할 수 있도록 해주었습니다.
- `isResult`가 `true`일 때 연산자 버튼을 누르면 이어서 계산을 할 수 있도록 해주었고, `isResult`가 `false`일 때 숫자 버튼을 누르면 새로운 연산식을 받을 수 있도록 수정하였습니다.
-