기능 구현이 끝나면 다 만들었다는 착각이 들 수도 있지만, 그건 정말 완성이 아니다. 버튼을 이거 눌렀다가 저거 눌렀다가 이거 누른 뒤 저거 눌렀다가 저거 누른 뒤 이거 누르다보면 내가 예상치 못했던 온갖 이상한 장면이 내 눈 앞에 펼쳐지며 나를 열받게 한다. 하지만 처리를 통해 그 현상을 없애고 나면 그 열이 식는다.
여러가지 경우에 따라 동작을 나누다보니,
buttonTapped
함수의 몸집이 많이 불었다. 함수를 나눠 그 부담을 좀 덜어주었다. 원래switch
문 안에서도ButtonRole case
내부에 온갖 경우에 따른 동작 분기를if ~ else if
문으로 처리했었는데, 각role
별로 호출할 함수를 분리하여 옮겼다.buttonTapped
함수의 모습이 한결 깔끔해졌다.
func buttonTapped(of buttonInfo: ButtonInfo) {
switch buttonInfo.role {
case .number:
if textStack == "Error" {} else {
numberButtonTapped(of: buttonInfo)
}
case .operation:
if textStack == "Error" {} else {
operationButtonTapped(of: buttonInfo)
}
case .completer:
completerButtonTapped(of: buttonInfo)
}
}
직전에 누른 버튼 뿐만 아니라, 그 전에 누른 버튼까지 함께 고려하여 처리를 해야하는 예외 상황들이 발견됐다. 그에 따라
ButtonInfo
를 하나만 저장했던 변수lastTappedButton
의 타입을(ButtonInfo?, ButtonInfo?)
로 변경하여 버튼 탭 히스토리를2개
저장하도록 하고, 히스토리 업데이트 함수를 정의하였다.
함수 이름을updateButtonTapHistory
라고 짓고 나니 이 함수가 다루는 변수가 무엇인지 연관성을 보여주는 게 좋을 것 같아lastTappedButton
의 이름도buttonTapHistory
로 변경했다.
private var buttonTapHistory: (ButtonInfo?, ButtonInfo?)
private func updateButtonTapHistory(_ buttonInfo: ButtonInfo) {
buttonTapHistory.0 = buttonTapHistory.1
buttonTapHistory.1 = buttonInfo
}
inputLabel
에*
,/
로 보였던 기호를×
,÷
로 보이게 수정했다.
// ButtonName enum 내부 프로퍼티
var title: String {
switch self {
// ... //
case .multiply: return "×"
case .divide: return "÷"
// ... //
}
}
// CalculationService
func calculate(_ text: String) -> Result<Int, CustomError> {
guard !text.contains("÷0") else {
return .failure(CustomError.dividedByZero)
}
// 특수문자를 수식 기호로 변경 후 NSExpression에 전달
let replacedText = text.replacingOccurrences(of: "×", with: "*").replacingOccurrences(of: "÷", with: "/")
let expression = NSExpression(format: replacedText)
// ... //
}
[before] 못생김. 불-편.
[after] 편-안.
입력을 처음 시작할 때는 0
에 대해 replaceText
함수로 대응하여 자연스럽게 0
이 두 번 이상 눌리지 않았다. 그런데, 연산자를 입력한 뒤에는 아무런 제어가 없어 0+00000
같은 입력이 가능한 것이다. 그렇다고 무작정 연속 입력을 막으면 300
같은 숫자도 입력을 못하게 되기 때문에, 전전 입력이 연산자, 직전 입력이 0인 경우의 예외 처리를 해주었다.
extension ButtonTapService {
private func numberButtonTapped(of buttonInfo: ButtonInfo) {
// ... //
else if buttonTapHistory.0?.role == .operation && buttonTapHistory.1?.name == .zero && buttonInfo.name == .zero {
print("No double zero after operator.")
}
// ... //
}
}
바로 위의 상황을 처리하고 나자, 0+00
입력은 막았지만 0+03
같은 입력이 가능해지는 것을 발견했다. 우선 0
의 연속 tap을 막기 위해서는 전전 버튼이 연산자, 직전 버튼이 0
이라는 히스토리가 보존되어야 하기 때문에, 0
버튼을 입력했을 때 히스토리 업데이트 없이 무동작인 것은 지켜야 했다. 그 상태에서 다른 숫자는 0
을 대체하도록 처리가 필요했다.
extension ButtonTapService {
private func numberButtonTapped(of buttonInfo: ButtonInfo) {
// ... //
else if buttonTapHistory.0?.role == .operation && buttonTapHistory.1?.name == .zero {
if buttonInfo.name == .zero {
print("No double zero after operator.")
} else {
replaceLastest(buttonInfo.name.title)
updateButtonTapHistory(buttonInfo)
}
}
// ... //
}
}
그래서 바로 위에서 &&
조건으로 걸었던 0
버튼의 세번째 tap 상황을 중첩 조건문으로 빼고, 세번째 tap이 다른 숫자일 경우와 동작을 나누었다. depth
가 깊어지면 안좋은 것을 알기에 이렇게 해도 될까? 싶지만 일단 현재의 내가 할 수 있는 최선이다.
*
또는 /
입력 후 -
를 입력했다가 다른 연산자를 눌렀을 때 모든 연산자를 대체하도록 하기말이 참 길다. 근데 간단하게 표현이 안되고 정말 저 말 그대로다. 예외처리 Chapter.1에서 연산자의 연속 tap을 막는 대신 대체
하도록 처리했는데, -
의 경우 직전 연산자가 +
라면 대체하지만 *
또는 /
라면 두번째 숫자를 음수로 입력하는 것으로 인식하여 대체가 아닌 누적
입력 처리를 했었다.
이 상태(*-
또는 /-
)에서, 숫자가 아니라 다른 연산자를 누르게 되면 위에서 처리한 막는 대신 대체
가 발동하여 2개의 연산자가 쌓이게 된다.(ex. **
) 그리고 여기서 -
를 누르면 대체가 아닌 누적
이 발동하여 3개의 연산자가 쌓인다.(ex. **-
) 즉, 연산자의 무한 누적이 가능해진 것이다.
이걸 어떻게 대처하면 좋을까? 단순한 방법은 머릿속에 바로 떠오른다.
private func operationButtonTapped(of buttonInfo: ButtonInfo) {
if (buttonTapHistory.0?.name == .multiply || buttonTapHistory.0?.name == .divide) && buttonTapHistory.1?.name == .subtract {
replaceLastTwo(buttonInfo.name.title)
} // ... //
}
private func replaceLastTwo(_ text: String) {
textStack.removeLast(2)
textStack.append(text)
}
정말 단순하다. 마치 Korean을 Swift로 번역기 돌린 것처럼, 말 그대로 구현했다. 전전에 누른 버튼이 곱하기 또는 나누기 버튼이고, 직전에 누른 버튼이 -
라면 그 2개를 지금 누르는 버튼의 텍스트로 대체하는 거다. (ex. *-
에서 +
눌렀을 때 대체)
너무 단순하면서도 조건문이 길어져서 그런가, 고쳤지만 마음이 좀 불편했다. 그래서 다른 방법이 없을까 한참 고민했지만 버튼으로부터 데이터를 전달 받고 쌓는 방법부터 뜯어고쳐야 할 것 같아 일단 두었다.
-끝-
이라고 부르고 싶지 않다. 아직 내가 발견하지 못한 예외 상황들이 남아 있을 거 같다. 근데 버튼을 요리 누르고 저리 눌러봐도 당장은 더이상의 예외 상황이 발생하지 않는다. 내가 아직 덜 요리조리 눌렀을 수도 있다. 하여튼 나중에 발견하게 되면 그때 고쳐야겠다. 일단락
이라고 부르겠다.
이제는 프로젝트의 구조적인 부분을 좀더 고민할 것이다. 그리고 제출 기한 내 완료가 가능하다면, 연산도 다른 방법으로 구현해보고 싶다. 어쨌든 1차적으로는 완성이다.
우와... 저는 생각하지도 못한 부분에 대해 처리를 고민하시고 해결하신게 멋져요!!
특히
0+00...
의 형태를 막는다는건 정말 생각하지 못했던 부분이네요!!저는 끝이라고 생각하고 UI에 대한 업데이트 위주로 구현 중이었는데, 아직 갈 길이 머네요!!