---
# System prepended metadata

title: 리팩토링 사항(고민했던 점)

---

@llingo
링고..ㅎㅎ 저희가 실수로 STEP1에서 조금 리팩토링을 해버렸네요..ㅠㅠ 그래서 STEP1 진행하면서 고민하고 리팩토링 했던 점도 이곳에 같이 씁니다. 

# 리팩토링 사항(고민했던 점)
### 1. dropLast vs removeLast
 마지막 문자열을 제거할 때, 사용할 메서드 결정에 있어 `dropLast`와 `removeLast`를 고민했습니다. 
 - `dropLast`를 사용하게 될 경우 새로운 배열을 생성해주어야 한다.
```swift
func dropLast(_ k: Int) -> Self.SubSequence
```

- `removeLast`의 경우 `mutating func`으로 자기 자신을 변경시켜줄 수 있다.
```swift
@discardableResult mutating func removeLast() -> Self.Element

```

- 고로 현재 String값의 마지막 값만 삭제하면 되기 때문에 `removeLast`를 채택하여 사용했습니다.
<br/>
 
 기존의 코드는 다음과 같습니다:
 ```swift
 if Operator(rawValue: lastString) != nil {
                guard stringToBeCalculated = String(stringToBeCalculated.dropLast()) 
                removePreviousOperands()
            }
```

변경된 코드:
```swift
 if Operator(rawValue: lastString) != nil {
                stringToBeCalculated.removeLast()
                removePreviousOperands()
            }
```

### 2. components vs split 
`extension String`에서 `split`을 구현하는데에 있어 의견차이가 있었습니다.
다음과 같은 특성으로 인하여 components를 사용하는 것이 적합하다고 판단하였습니다:
 - components: 매개변수로 String 타입 받음, [String]반환
 - split: 매개변수로 Character 타입 받음, [Substring]반환
split을 사용하게 되면 `map`과 같은 고차함수를 통해서 배열의 요소들을 String 타입으로 모두 바꿔줘야합니다. components를 사용하면 매개변수만 String 타입으로 바꿔주어도 되기 때문에 components를 사용하게 되었습니다.



### 3. 연산자로 끝날 때, 자동으로 0까지 계산해주는 현상
1+2/3+=이라는 연산을 할 때, 기존에는 1+2/3+0으로 계산이 되어 결과값이 1로 나왔습니다. 그러나, 이 부분은 계산버튼(=)을 눌러도 계산이 되지 않아야한다는 것을 깨달았습니다.
그래서 다음과 같이 코드를 수정하였습니다:
```swift
@IBAction private func tapCalculateButton(_ sender: UIButton) {
        ...
        if let result = calculate() {
            isCalculated = true
            displayPreviousOperands()
            resetCurrentNumber()
            initializeCurrentOperator()
            displayResult(result: result)
        } else { return }
        ...
    }
    
    private func calculate() -> Double? {
        let lastIndex = stringToBeCalculated.index(before: stringToBeCalculated.endIndex)
        let lastString = stringToBeCalculated[lastIndex]
        if Operator(rawValue: lastString) != nil {
            return nil
        }
        ...
    }
```
수정 후 코드에 따르면 계산하고자하는 String의 마지막 값이 `Operator`에 해당하면 nil을 반환합니다. 그리고 `result = calculate()`가 `nil`일 경우에는 `return`을 하여 메서드에서 빠져나오게 하였습니다. 이를 통해서 마지막 값이 연산자이면 계산이 진행되지 않도록 할 수 있었습니다.

### 4. 20자리수 일 때, (,)로 분리 안되는 현상 
기존의 코드는 다음과 같았습니다:
```swift
var stringWithComma: String {
        let numberFormatter = NumberFormatter()
        numberFormatter.numberStyle = .decimal
        
        let splittedNumber = self.components(separatedBy: ".")
        if splittedNumber.count == 1 {
            guard let intValue = Int(splittedNumber[0]) else {
                return self
            }
            return numberFormatter.string(from: Decimal(intValue) as NSNumber) ?? self
        } else if splittedNumber.count == 2 {
            guard let intNumberBeforeDecimalPoint = Int(splittedNumber[0]) else {
                return self
            }
            let numberBeforeDecimalPoint = numberFormatter.string(from: Decimal(intNumberBeforeDecimalPoint) as NSNumber) ?? self
            let wholeNumber = numberBeforeDecimalPoint + "." + splittedNumber[1]
            return wholeNumber
        }
        return self
    }
```
이 때, 1을 20개 입력하게 되면 11,111,111,111,111,111,111이 아닌 11111111111111111가 나오게 되었습니다. 
splittedNumber[0]이 "11111111111111111"일 경우, Int로 변환할 수 없어 self인 "11111111111111111"가 그대로 반환되기 때문에 생기는 문제였습니다.
이를 해결하기 위하여 다음과 같이 코드를 수정하였습니다:
```swift
guard let decimalValue = Decimal(string: splittedNumber[0]) else {
                return self
            }
            return numberFormatter.string(from: decimalValue as NSNumber) ?? self
```
수정 후 코드에 따르면, 기존의 string을 그대로 Decimal로 변환해줍니다. 그리하여 Int의 범위를 초과하는 수를 Int로 변환할 때 nil이 나오는 오류를 막을 수 있었습니다.

### 5. 0일 때, 음수처리가 되는 현상
입력된 창이 0인 상태에서 `-/+` 버튼을 누를 때, `-0`이 되는 현상이 있었습니다. 그러나, 이 부분은 `-/+` 버튼을 눌러도 여전히 `0`이어야한다는 것을 깨달았습니다.
그래서 다음과 같이 코드를 수정하였습니다:
```swift
private func convertSign() {
        guard currentNumber != NameSpace.emptyString else {
            return
        }
    ...
    }
```
수정 후 코드에 따르면, `currentNumber`가 빈 String일 때, 메서드를 종료해줍니다. 그리하여 숫자가 입력되지 않은(입력창에 0이 보이는) 상태에서는 음수기호(-)가 붙지 않게 됩니다.

### 6. roundingNumber 메서드가 Formatter에 있어 불필요한 싱글톤을 호출해야하는 현상
- 기존 코드에서는 `CalculatorFormatter`를 싱글톤 객체로 만들어 사용했습니다. 
- `extension Double`에 함수를 선언하면, 반환값이 `String`이 아닌 `Double` 나와야 되는 것으로 헷갈려서 `CalculatorFormatter`라는 새로운 클래스를 생성했습니다.
- 토의 끝에 `extension Double`에 함수를 선언해도 문제가 없음을 인지하고 `roundingNumber`메서드를 선언했습니다.


변경 전 
```swift
class CalculatorFormatter {
    static let shared = CalculatorFormatter()
    private init() {}
    
    func roundingNumber(from number: Double) -> String {
        let numberFormatter = NumberFormatter() 
        numberFormatter.maximumFractionDigits = 20
        numberFormatter.roundingMode = .ceiling
        let result = numberFormatter.string(for: number) ?? String(number)
        return result
    }
}
```

변경 후
```swift
extension Double: CalculateItem {
    func roundingNumber() -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.maximumFractionDigits = 20
        numberFormatter.roundingMode = .ceiling
        let result = numberFormatter.string(for: self) ?? String(self)
        return result
    }
}
```

### 7. 첫번째 피연산자만 있을 때 (=)버튼 작동 
>첫번째 피연산자가 들어가 있고 연산자는 들어가 있지 않을 때 (=)버튼을 누를 경우 첫번째 피연산자가 결과값으로 들어가는 오류가 있었습니다. 이를 방지하기 위해 첫번째 피연산자만 들어가 있을 경우 (=)버튼이 작동되지 않게 분기처리했습니다.
- 변경 전
```swift
private func calculate() -> Double? {
        let lastIndex = stringToBeCalculated.index(before: stringToBeCalculated.endIndex)
        let lastString = stringToBeCalculated[lastIndex]
        if Operator(rawValue: lastString) != nil {
            return nil
        }
    
        var calculateFormula = ExpressionParser.parse(from: stringToBeCalculated)
        stringToBeCalculated = NameSpace.emptyString
        return calculateFormula.result()
    }
```

- 변경 후
```swift
private func calculate() -> Double? {
        let lastIndex = stringToBeCalculated.index(before: stringToBeCalculated.endIndex)
        let lastString = stringToBeCalculated[lastIndex]
        if Operator(rawValue: lastString) != nil {
            return nil
        }
        // 분기처리를 위한 조건문
        var isNumberOnly: Bool = true
        for `operator` in Operator.allCases {
            if stringToBeCalculated.contains(`operator`.rawValue) {
                isNumberOnly = false
            }
        }
        if isNumberOnly {
            return nil
        }

        var calculateFormula = ExpressionParser.parse(from: stringToBeCalculated)
        stringToBeCalculated = NameSpace.emptyString
        return calculateFormula.result()
    }
```

### 8. 20자리 넘어갈 때 e로 표현하기
숫자를 20자리만 표현하라는 요구사항이 있었습니다. 20자리에 20자리를 곱하면 20자리를 넘는 결과가 나타나게 됩니다. 기존의 `roundingNumber()`를 통해서 소수부를 20자리까지 만들 수 있지만, 그것을 20자리 안으로 표현하기 위해서 'e'의 개념을 사용하게 되었습니다. 
생각했던 방향은 두 가지가 있었습니다:
1. 주어진 숫자를 10^n만큼 나누고 rounding을 한 후 다시 10^n을 곱해주는 방법(n = 정수부의 자릿수 - 1)
2. String으로 앞에서부터 17자리 잘라주는 방법

처음에는 첫 번째 방법처럼 하려고 했습니다. 그러기 위해서는 주어진 result를 숫자의 형태인 Double이나 Decimal로 바꿔야했는데, Double이든 Decimal이든 그 가용범위가 40자리까지 미치지 못한다는 점이 마음에 걸렸습니다. 그래서 두 번째 방법을 채택했습니다:
```swift
func convertToExponent() -> String {
    guard self.count > 20 else {
        return self
    }
    let splittedNumber = self.components(separatedBy: NameSpace.dot)
    let eCount = splittedNumber[0].count - 1
    let stringToBeRounded: String
    if splittedNumber.count == 1 {
        stringToBeRounded = splittedNumber[0]
    } else {
        stringToBeRounded = splittedNumber[0] + splittedNumber[1]
    }
    var newString = NameSpace.emptyString
    for number in stringToBeRounded {
        newString += String(number)
        if newString.count == 17 {
            break
        }
    }
    newString.insert(Character(NameSpace.dot), at: newString.index(newString.startIndex, offsetBy: 1))
    newString += "e" + String(eCount)
    return newString
}
```
이 결과, 12345678901234567890123이라는 숫자가 있다면, 1.2345678901234567e22로 표현되게 됩니다.

### 9. 계산 후에 결과값으로 다시 계산하기
>리팩토링하기 전에는 계산 후에 연산자를 누르고 계산을 하게 되면 앞에 0이 있는 것으로 상정하고 새로운 계산이 진행됩니다. 가령 1+2+3=6을 한 후에 *3을 하면 18이 아닌 0이 나타나게 됩니다. 계산한 결과값을 이어받아 다시 계산할 수 있도록 하기 위해서 다음과 같은 리팩토링을 하였습니다:

- 변경 전
```swift
@IBAction private func tapOperatorButton(_ sender: UIButton) {
    
    displayPreviousOperands()
    insertOperatorSign(titleName: sender.titleLabel?.text)
    displayCurrentOperator(titleName: sender.titleLabel?.text)
    fixateScrollViewBottom()
    resetCurrentNumber()
}
```

- 변경 후
```swift
@IBAction private func tapOperatorButton(_ sender: UIButton) {
    if isCalculated {
        allClearViews()
        stringToBeCalculated += currentNumber
        isCalculated = false
    }
    displayPreviousOperands()
    insertOperatorSign(titleName: sender.titleLabel?.text)
    displayCurrentOperator(titleName: sender.titleLabel?.text)
    fixateScrollViewBottom()
    resetCurrentNumber()
}
```

