# 계산기 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. ![](https://hackmd.io/_uploads/H1YsD3dw3.png) - 현재 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`일 때 숫자 버튼을 누르면 새로운 연산식을 받을 수 있도록 수정하였습니다. -