---
# System prepended metadata

title: "\U0001F3E6\U0001F4B0계산기 _ 훈민트"
tags: [project]

---

###### tags: `project`

# 🏦💰계산기 _ 훈민트

![](https://hackmd.io/_uploads/SJStjdW82.png)

## Ground Rules

### 규칙
- TIL, 일일 회고 작성 시간(매일 23시부터 1시간 작성 진행)
- 월, 목 12 - 2시는 활동학습 예습 시간

### 스크럼
- 오전 10시 디스코드에서 진행
- 금일 진행 사항 공유하기(오늘의 할일)

### 프로젝트 규칙
- 네이밍 준수하기(가이드 라인)
- 커밋 메시지 규칙 진행
- 코드에 대한 기록 그때그때 하기

### 팀 규칙
- 컨디션이 좋지 않을 때는 꼭 말해주기!!

## 일일 스크럼
### 🙌 05/29
- 오늘의 컨디션
    - MINT: 속이 울렁울렁
    - hoon: 행복합니다.🎁
- 특이사항
    - MINT: 훈과 함께해서 너무너무 행복한 민트😈
    - hoon: 2주간 잘 부탁드립니다.😆
- 오늘 할 일: MINT 
    - [x] Unit Test 작성하기(야곰닷넷) 공부
    - [x] TIL
    - [ ] protocol 공부
    - [ ] step 1 시도는 해보면서 모르는 것들 정리
- 오늘 할 일: hoon
    - [x] Unit Test 작성하기(야곰닷넷) 공부
    - [ ] Queue와 List 구현
    - [ ] protocol 공부
    - [x] TIL

### 🙌 05/30
- 오늘의 컨디션
    - MINT: 잠을 푹 자서 피부가 반짝반짝! 
    - hoon: 벌써 피곤해요. ㅋㅋㅋㅋㅋ
- 특이사항
    - MINT: 어제 할 일을 다 밀렸어요. 12시 쯤 점심~
    - hoon: 민트 상태 확인하기😄
- 오늘 할 일: MINT
    - [ ] TIL
    - [ ] protocol, Extentions 공부
    - [x] Queue와 List 구현
    - [x] 재미난 컴퓨터 이야기 PART 2 - 자료구조
    - [x] 가능하면 PR 보내기
- 오늘 할 일: hoon
    - [x] 재미난 컴퓨터 이야기 PART 2 - 자료구조
    - [ ] Queue와 List 구현
    - [x] protocol 공부
    - [ ] TIL

### 🙌 05/31
- 오늘의 컨디션
    - MINT: 잠이 계속 깼어요. 중간에 낮잠을 자야겠어요.
    - hoon: 피곤해요...😭, 오늘은 할 수 있겠죠?
- 특이사항
    - MINT: 저도 가능하면 저녁에 운동을 다녀오려구요!
    - hoon: 가능하다면 저녁에 운동을 다녀오겠습니다🤣
- 오늘 할 일: MINT 
    - [x] 밀린 TIL
    - [x] STEP 1 수정 후 다시 PR
    - [ ] SOLID 활동 학습 예습
    - [X] test Double 공부
- 오늘 할 일: hoon
    - [ ] 밀린 TIL
    - [x] Queue, List 구현
    - [x] STEP 1 PR 보내기
    - [x] SOLID 활동 학습 예습

### 🙌 06/01
- 오늘의 컨디션
    - MINT: 조금만 더 자고 싶어요😵
    - hoon: 어제의 영향인지 개운합니다 ㅋㅋㅋㅋ
- 특이사항
    - MINT: 오늘 밤에 서울을 가요😈 과연 오늘 구두가 올까요?? 제가 출발 전까지 오면 좋게써요🥲
    - hoon: 민트 옆에 있기
- 오늘 할 일: MINT 
    - [x] 활동학습 예습
    - [ ] 스트레칭
    - [ ] extension 공부
    - [ ] STEP 2 구상하기
    - [x] TIL
- 오늘 할 일: hoon
    - [x] 활동학습 예습
    - [ ] STEP 2 구상하기
    - [x] Generic 공부
    - [x] 밀린 TIL😭

### 🙌 06/02
- 오늘의 컨디션
    - MINT: 졸 예정이에요🫠
    - hoon: 조는 민트 구경할 예정ㅎ
- 특이사항
    - MINT: 클라이밍 소원 풀어따요~! 오프라인 모각코 with hoon!!!!
    - hoon: 오프라인 모각코😆 with MINT😈, 민트랑 함께 해서 너무 즐거웠습니다.😊
- 오늘 할 일: MINT 
    - [x] README 작성
    - [ ] STEP 2 생각해보기 -> 공부할 것 정리
    - [x] UML 공부
    - [x] 스터디 과제
- 오늘 할 일: hoon
    - [x] README 작성
    - [ ] STEP 1 generic constant 적용해보기
    - [x] 토요스터디 예습

--

### 🙌 06/05
- 오늘의 컨디션
    - MINT: 정신력으로 멀쩡(?)합니다🤩
    - hoon: 아직까지 멀쩡합니다🤣
- 특이사항
    - MINT: 저도 낮잠 잘래요~!
    - hoon: 중간에 피곤하면 자고 오겠습니다.
- 오늘 할 일: MINT 
    - [x] STEP 2 구현 1차 완료 해보기
    - [ ] TIL
    - [ ] lldb 예습
    - [ ] 고차함수 공부
- 오늘 할 일: hoon
    - [x] STEP 1 마무리
    - [ ] STEP 2 구상하기
    - [ ] TIL
    - [ ] Generic WWDC 영상 보기 -> 훈 볼 때 알짱알짱

### 🙌 06/06
- 오늘의 컨디션
    - MINT: 지금 겨울인가요🥶
    - hoon: 상쾌합니다🤭
- 특이사항
    - MINT: 이불 속에만 있을 예정
    - hoon: 밤에 산책?
- 오늘 할 일: MINT 
    - [x] 밀린 TIL
    - [x] STEP 2 PR 보내보기
    - [ ] 고차함수 공부
- 오늘 할 일: hoon
    - [ ] 밀린 TIL
    - [x] STEP 2 구현

### 🙌 06/07
- 오늘의 컨디션
    - MINT: 퉁퉁 부은 민트
    - hoon: 코가 막혀요☁️
- 특이사항
    - MINT: 훈 기절시키기!
    - hoon: 민트가 준 초콜릿을 먹어보겠습니다😈
- 오늘 할 일: MINT 
    - [ ] STEP 3 읽어보고 생각해보기~
    - [ ] 활동학습 예습
    - [X] STEP 2가 머지되는 그날까지~
- 오늘 할 일: hoon
    - [x] STEP 2 PR 보내기
    - [x] STEP 3 읽어보고 생각해보기~
    - [x] 활동학습 예습
    - [ ] 밀린 TIL.....😵

### 🙌 06/08
- 오늘의 컨디션
    - MINT: 푹자서 좋아요~!
    - hoon: 저도 많이 잤습니다.😆
- 특이사항
    - MINT: 낮잠을 잘거에요 행복한 낮잠🤸‍♀️
    - hoon: 민트 상태 확인하기🧐
- 오늘 할 일: MINT 
    - [ ] STEP 3 구현
    - [x] 활동학습 예습
    - [x] TIL
- 오늘 할 일: hoon
    - [ ] TIL
    - [x] STEP 2 마무리
    - [ ] STEP 3 구현

### 🙌 06/09
- 오늘의 컨디션
    - MINT: 햅삐햅삐 벗 스텝3 띵킹 쏘새드
    - hoon: 살짝 피곤합니다 ㅎㅎ
- 특이사항
    - MINT: 훈 스트레칭 시키기
    - hoon: 마지막 날 불태우고 죽을 예정....
- 오늘 할 일: MINT 
    - [ ] TIL
    - [ ] STEP 3 PR 보내기
    - [ ] README 작성
    - [ ] 토요스터디 예습
- 오늘 할 일: hoon
    - [x] STEP 3 PR 보내기
    - [ ] README 작성
    - [x] 토요스터디 예습
    - [ ] TIL


# hoon
# STEP 1
## UML Class Diagram
![](https://hackmd.io/_uploads/BJuRGQr82.png)

## 고민했던 점
### `linked list` 사용
- `queue`를 만들기 위해 어떠한 자료 구조를 사용할지 고민했습니다. 처음 기본적인 틀을 구성할 때는 가장 쉬운 `array` 타입을 생각하였습니다. 내부적으로 구현되어 있는 메서드를 통해 `queue`의 `list`를 구성할 수 있었습니다. 이 외에 다른 방법으로 찾은 자료 구조가 `linked list`였습니다. `linked list`가 `array`에 보다 좋은 점은 처음 입력받은 값을 `dequeue`하는 과정에서 맨 앞의 `node`에 대한 연결만 끊어주면 된다는 점이었습니다. `head`를 사용하여 `linked list`의 첫 노드에 대해 알고 있다가 `dequeue`가 일어나는 과정에서 `head`를 다음 `node`로 옮기고 이전 `node`를 해제해 줌으로써 메모리 사용에 장점이 있다고 생각하였습니다.

    ```swift
    mutating func append(_ data: T) {
        guard head != nil
        else {
            head = Node(data: data)
            tail = head
            return
        }
        
        tail?.updateNext(Node(data: data))
        tail = tail?.fetchNext()
    }
    ```

### `Generic type` 사용
- 계산기라는 프로젝트에 맞춰 처음에는 `Int` 타입의 값이 `queue`에 들어간다는 가정하에 `Unit Test`를 진행하였습니다. 다음 테스트 케이스에서는 `Int`만이 아닌 사칙연산(`String`) 또한 `queue`에 담을 수 있도록 코드를 확장하였습니다. 이 과정에서 하나의 지정된 타입이 아닌 일반화하여 다양한 타입에 대해 입력을 받기 위해 `Generic` 타입을 활용하여 문제를 해결하였습니다.

## 해결하지 못한 점
### `private Unit Test`
- `Unit Test`를 진행하며 `private` 접근 제어와 관련하여 문제가 발생하였습니다. 테스트 케이스에서 `private`으로 선언한 프로퍼티나 메서드에 접근할 수 없다는 문제였습니다. 처음에는 이 문제를 해결하기 위해 접근 제어를 사용하지 않고 `Unit Test`를 진행하였지만 후에 코드를 리팩토링하는 과정에서 더 이상 테스트 케이스가 유효하지 않다는 문제점이 발생하였습니다.

    문제를 해결하기 위해 찾은 방법 중 하나는 Test Double인 `Mock`과 같은 타입의 객체를 생성하는 것이었습니다. 여러 자료들을 찾아보며 [Testing private methods and variables in Swift](https://www.avanderlee.com/swift/testing-private-methods-variables/)라는 글을 보게 되었습니다. 여기서 설명하는 내용 또한 `protocol`을 생성하고 이를 채택한 객체를 활용하여 테스트 케이스에서 사용하는 객체를 `Mock` 객체로 변경하여 실험할 수 있다고 이해했습니다. 이 내용을 적용해 보는 과정에서 `LinkedList`의 `protocol`을 활용했습니다.
    
    ```swift
    // LinkedList.swift
    protocol Listable {
        associatedtype T
        init(head: Node<T>?)
        mutating func append(_ data: T)
        mutating func removeFirst() -> T?
    }

    struct LinkedList<T>: Listable {
        private var head: Node<T>?
        private var tail: Node<T>?

        init(head: Node<T>?) {
            self.head = head
        }

        mutating func append(_ data: T) { ... }

        mutating func removeFirst() -> T? { ... }
    }
    ```
    
    위처럼 LinkedList.swift 파일을 수정했습니다. 이렇게 수정한 내용을 바탕으로 `Unit Test`에서 사용하는 LinkedListTests.swift에 다음과 같은 방법으로 적용해 보려 했습니다.
    
    ```swift
    final class LinkedListTests: XCTestCase {
        var sut: Listable! // LinkedList<Int>!에서 protocol로 변경
        
        ...
        
    }
    ```
    
    기존 LinkedListTests.swift를 수정하는 경우 아래와 같은 컴파일 에러가 발생하였습니다.
    
    ```swift
    Use of protocol 'Listable' as a type must be written 'any Listable'
    ```
    
    - [📙 Stackoverflow](https://stackoverflow.com/questions/76136642/understanding-equatable-and-any-swift-use-of-protocol-foo-as-a-type-must-be-w)
    - [📙 Stackoverflow](https://stackoverflow.com/questions/75178234/use-of-protocol-yourprotocol-as-a-type-must-be-written-any-yourprotocol-erro)

    에러와 관련하여 찾아본 내용이지만 실질적으로 제가 수정한 코드와는 다른 답변이라고 생각하였습니다. 여기서부터 어떤 방향으로 `private`에 대한 테스트를 작성해 볼 수 있을지 궁금하여 질문드립니다. 

### `Node` 객체 `deinit`
- `LinkedList` 구조체에 `removeFirst` 메서드의 코드에서 마지막 `Node`를 가리키는 `tail` 프로퍼티를 리팩토링하기 전의 코드입니다.

    ```swift
    // LinkedList.swift
    mutating func removeFirst() -> T? {
        guard head != nil else { return nil }
        
        let data = head?.fetchData()
        head = head?.fetchNext()
        
        return data
    }
    ```
    
    위 코드에서는 `head`가 다음 `Node`로 넘어가면서 이전에 사용한 `Node`는 더 이상 사용하지 않으니 `deinit` 될 것이라는 예상으로 테스트를 진행하였습니다. 이를 위해 `Node`에도 `deinit` 메서드에 출력하는 부분을 추가하였습니다.
    
    ```swift
    // Node.swift
    final class Node<T>: NodeType {
        ...
        
        deinit {
            print("Node deinit")
        }
    
        ...
    }
    ```

    테스트 케이스를 실행하면서 `removeFirst` 메서드를 호출한 횟수만큼 `deinit`의 출력이 발생하여 예상한 내용과 같이 동작한다고 생각하였습니다. 하지만 이후 `tail`을 사용하면서 의심이 들기 시작하였습니다. `tail`은 마지막 `Node`에 대해 참조를 하고 있어 위처럼 `tail`을 `nil`로 변경하지 않는다면 마지막 `Node`에 대해 참조를 끊어주지 않아 `deinit`이 `removeFirst`를 호출한 횟수보다 한번 적게 출력해야 한다고 생각했습니다. 하지만 실행 결과는 제가 생각했던 것과 달리 `removeFirst`와 같은 횟수로 호출되었습니다. 어떤 부분에서 문제가 발생해서 예상과는 다른 동작이 일어났는지 해결하지 못했습니다.

## 조언을 얻고 싶은 점
### `Unit Test`의 테스트가 늘어날 때마다 이전 테스트의 변경
- TDD를 진행하면서 정말 다양한 경우로 이전 작성한 테스트 케이스의 변경이 요구되는 점이 많았습니다. 말씀해 주신 내용처럼 UML을 작성하고 테스트 케이스를 작성하는 방향을 통해 이런 문제가 많이 줄었지만 여전히 올바른 TDD의 활용에 대해서는 의문이 남습니다. 특히 아직 해결하지 못한 `Mock`과 같은 경우 "전체 코드가 완성되어야 사용해 볼 수 있는 방법이지 않을까?"라는 생각이 듭니다. 매번 모든 경우에 적용할 수는 없겠지만 이런 TDD 방식의 개발론에 대해 어떨 때 사용할 수 있을지 조언을 얻고 싶습니다.

### 행위 주도 설계 방식
- 객체지향적 관점에서 객체를 설계할 때 행위 주도 설계를 기반으로 할 수 있다는 내용을 공부하였습니다. 이번 STEP 1을 진행하며 `Node` 클래스에서 사용하는 `data`와 `next` 프로퍼티에 대해 `private` 접근 제어를 사용하였습니다.

    ```swift
    final class Node<T>: NodeType {
        private let data: T
        private var next: Node<T>?

        init(data: T, next: Node? = nil) {
            self.data = data
            self.next = next
        }

        deinit {
            print("Node deinit")
        }

        func fetchData() -> T {
            return data
        }

        func fetchNext() -> Node<T>? {
            return next
        }

        func updateNext(_ next: Node<T>) {
            self.next = next
        }
    }
    ```
    
    위처럼 `private`으로 선언한 프로퍼티를 외부에서 접근하기 위해 몇 가지 메서드를 통해 캡슐화를 해주었습니다.
    
    행위 주도 설계의 관점에서는 이렇게 사용해야 한다고 생각하여 위와 같은 방법을 사용하였지만 때때로 관용적으로 사용하는 프로퍼티에 대해서는 외부에 직접 노출하여 사용하는 경우들이 있었습니다. 이번에 사용하는 `Node`와 같은 경우에도 대다수의 많은 코드 예시에서 직접 접근하여 프로퍼티를 읽고 쓰는 방식들이 많았습니다. 이런 부분에서 어떻게 접근 제어를 활용하고 캡슐화를 할 수 있을지 궁금합니다.

# STEP 2
## 고민되었던 점
### 음수와 빼기 연산자 구분하기
- 수식이 "1+2/-3--6"과 같이 입력으로 주어지면 3과 6은 각각 음수인 -3과 -6을 나타냅니다. "-" 부호에 대해 피연산자(`operand`)와 연산자(`operator`)를 구분해 주어야 했습니다. 이를 해결하기 위해 아래와 같이 코드를 작성하였습니다.

    ```swift
    enum ExpressionParser {
        static func parse(from input: String) -> Formula {
            ...
            
            let operands = componentsByOperators(from: input).compactMap { Double($0) }
            let operators = input
                .replacingOccurrences(of: "+-", with: "+")
                .replacingOccurrences(of: "--", with: "-")
                .replacingOccurrences(of: "/-", with: "/")
                .replacingOccurrences(of: "*-", with: "*")
                .compactMap { Operator(rawValue: $0) }

            ...
        }

        static private func componentsByOperators(from input: String) -> [String] {
            var operands: [String] = Operator.allCases.reduce([input]) { splitInput, operatorCase in
                splitInput.map { $0.split(with: operatorCase.rawValue) }.flatMap { $0 }
            }

            for index in 0..<operands.count - 1 {
                if operands[index].isEmpty {
                    operands[index + 1] = "-\(operands[index + 1])"
                }
            }

            return operands
        }
    }
    ```

    먼저 위의 예제가 정상적으로 동작하기 위해서는 다음과 같은 가정이 필요합니다.
    1. 올바른 수식만이 입력됩니다.
    2. 부호는 음수(-)만 사용합니다.

    두 가정을 통해 수식에서 연속적으로 나타나는 연산자는 다음과 같은 경우만 있다고 정의하였습니다.
    1. "+\-" : 덧셈 연산자와 다음에 올 피연산자의 음수 부호
    2. "-\-" : 뺄셈 연산자와 다음에 올 피연산자의 음수 부호
    3. "/\-" : 나눗셈 연산자와 다음에 올 피연산자의 음수 부호
    4. "\*-" : 곱셈 연산자와 다음에 올 피연산자의 음수 부호

    이렇게 가정을 하고 나서 다시 연산자만을 모을 때는 뒤에 음수 부호를 무시하여 연산자를 선택할 때 거르도록 하였습니다. 또한, 피연산자는 수식을 연산자를 기준으로 나누면 두 개의 연산자가 연속으로 오는 경우에 빈 문자열("")이 발생합니다. 이전 가정을 통해 연속으로 연산자가 나타나는 경우는 뒤에 음수 부호만이 존재한다고 했기 때문에 빈 문자열의 다음 문자열에 음수 부호를 추가함으로써 직접적으로 음수인 피연산자를 만들었습니다.

### 수식의 처음이 음수일 때
- 위 가정을 바탕으로 수식이 음수로 시작하는 경우 음수 부호 앞에 다른 연산자가 존재하지 않기 때문에 피연산자의 음수 부호 대신 연산자로 판별하는 문제가 발생하였습니다. 이를 해결하기 위해 수식에서 연산자만을 모으는 로직을 실행하기 전에 입력받은 수식을 수정하였습니다.

    ```swift
    let operatorCandidates: String = input.hasPrefix("-") ? String(input.suffix(input.count - 1)) : input
    ```
    
    수식의 처음이 "-"인지를 확인하여 "-"가 없다면 입력받은 수식을 변형 없이 사용하고 "-"가 있다면 "-" 부호를 제외한 남은 수식만을 사용하여 연산자를 찾는 로직을 수행합니다.

## 조언을 얻고 싶은 부분
### UML dependancy 관계
- `ExpressionParser`는 `Formula`와만 의존 관계로 UML에 나타나있습니다.

    ```swift
    enum ExpressionParser {
        static func parse(from input: String) -> Formula {
            var formula: Formula = Formula(operands: CalculatorItemQueue<Double>(),
                                           operators: CalculatorItemQueue<Operator>())
    
            ...
        }
    }
    ```

    코드로 작성하면 위와 같이 `Formula`를 초기화할 때 전달인자 타입으로 `CalculatorItemQueue`를 사용하게 됩니다. 이런 경우에도 UML 상에서 `ExpressionParsar`가 `CalculatorItemQueue`에 의존한다고 봐야 할까요?
    
### `ExpressionParsar` Unit Test
- `enum` 타입인 `ExpressionParsar`를 Unit Test 하기 위해 테스트 케이스를 작성할 때 `static`으로 선언한 타입 프로퍼티가 문제였습니다. 타입 프로퍼티이기 때문에 새로운 인스턴스를 생성하지 않는다는 점에서 메서드 내부에 지역변수로 사용해도 괜찮을까요?

# STEP 3
## 고민되었던 점
### 계산기 동작의 예외 처리
- 계산기가 동작할 때 생각해야 하는 많은 예외들이 있었습니다. 0을 입력하였을 때는 0도 연산에 포함해야 하며 다른 조건들을 기억하기 위해 `isResult`과 `isInputZero` flag를 사용하여 조건을 처리하도록 하였습니다. 특히 계산기 요구 사항에 설명되지 않았던 예외의 경우에는 실제 계산기 앱을 기준으로 예외 처리를 진행하려고 하였습니다. 예를 들어 소수점 뒤에 숫자가 없는 경우에는 소수점을 버린 실수부만을 가지고 연산을 하도록 하였습니다.

## 해결이 되지 않은 점
### NumberFormatter
- `NumberFormatter`를 사용하여 계산 결과로 받아온 `Double` 타입의 값을 `String` 타입으로 변환하여 `UILabel`에 표현하였습니다. 이때 숫자는 최대 20자리까지 표현한다는 조건이 있었습니다. `NumberFormatter`에 있는 프로퍼티를 사용하여 최대 20자리의 표현을 허용해 주었습니다.

    ```swift
    let numberFormat = NumberFormatter()
    numberFormat.usesSignificantDigits = true
    numberFormat.maximumSignificantDigits = 20
    ```
    
    위의 코드에서 `usesSignificantDigits`를 활성화해야 `maximumSignificantDigits`를 사용할 수 있습니다. 이후 최대 표현 가능한 숫자를 20으로 설정하였습니다. 하지만 여기서 문제가 발생하였습니다. `Double` 타입의 최대 표현 자릿수가 문제였습니다. `Double`은 64bit로 소수점 이하 최대 16자리까지만 저장이 가능하였습니다. 이처럼 최대 20자리까지 표현을 하지 못하는 타입을 사용하였을 때 위와 같은 문제를 해결할 수 없었습니다. `Double` 타입을 대체하는 다른 타입을 사용하여 문제를 해결해야 했을까요?

## 조언을 얻고 싶은 부분
### 조건문 사용
- 여러 예외 처리를 하려고 하다 보니 정리가 되지 않은 상황에서 하나씩 예외 처리에 맞게 조건문을 추가하였습니다. 이렇게 코드를 작성하다 보니 중복이 되는 조건도 있었고 계속해서 조건문이 차지하는 부분들이 늘어만 갔습니다. 조건문이 많아지니 코드에서 어떠한 위치에 필요한 조건이 무엇인지 또는 어디에서 문제가 발생하였는지를 확인하기가 어려웠습니다. 이처럼 여러 예외 케이스가 발생하는 부분에 대해 처음부터 모든 엣지 케이스를 가정하고 코딩을 할 수는 없다고 생각합니다. 어떠한 방식으로 접근을 해야 코드를 작성할 때 조금 더 유효한 코드를 작성할 수 있었을까요?


# MINT
# STEP 1
## 고민했던 점
### Queue의 개념
선입선출! 의 개념을 처음에 단순 삭제로 잘못 이해했습니다. 때문에 `dequeue`를 하면 맨 처음에 있는 `element`가 삭제되는 것만을 생각해서 제대로 된 결과물이 나오지 않는 일이 있었습니다. `T?`를 반환값으로 주어 애초에 `enqueue`된 것이 없거나 이미 요소들이 전부 `dequeue`되어 나간 경우 `nil`을 반환할 수 있게 변경하였습니다. 이후 `Queue`의 개념에 대해 더 알아보다가 보통 `count`라는 개수를 세어주는 기능이 있길래 연산 프로퍼티를 사용해서 추가하였습니다. `subscript`를 사용해보고 싶었는데 그렇게 하면 `CalculatorItemQueue.count` 로 사용할 수 없고 `CalculatorItemQueue[1]`로 사용해야 하기에 변경하였습니다.

### generic type의 사용
계산기 프로젝트에서 이 `queue`를 사용한다면 입력받는 숫자들과 사칙연산 기호를 넣고 순서대로 맞춰서 선입선출하며 계산해주는 것에 사용할 것이라고 생각했습니다. 그런데 계산기에서는 단순한 `Int` 타입 뿐만 아니라 10.1과 같은 `Double` 타입도 사용하고, 사칙연산 기호 같은 경우는 `String` 타입을 사용할 것이기에 `Queue`의 구현에서 여러가지 타입들 중 선택할 수 있는, 범용적인 타입이 필요하다고 생각했습니다. 때문에 `Generic type`을 사용하여 `Queue`를 구현하였습니다.

## 해결하지 못한 점
### list 구현의 의미
프로젝트 스텝 안내에 나와있는 `list 자료구조 구현`의 의미를 정확하게 이해하지 못하고 있습니다. 기존 스위프트의 `array`에서 제공하는 `removeFirst()` 에서 시간복잡도를 더 낮춘 경우들을 구현해보는 것을 말하는 걸까요? ex) `linked-list`, `stack`

### test와 private 사용
`private`을 걸어주면 `test`에서도 그 함수를 사용할 수가 없었습니다. 고민하다 결국 `test`를 전부 다 할 때까지는 `private` 키워드 없이 진행하였고 마지막에 `private`이 필요하다고 생각되는 것들에만 붙여주었습니다. `count`, `enqueue`, `dequeue`는 이 `CalculatorItemQueue`를 인스턴스해서 사용할 때 불러야 하는 기능들이라고 생각해 `private` 키워드를 달지 않았습니다. 

## 조언을 얻고 싶은 점
### TDD의 활용
활동학습에서 봤던 방법을 따라했는데 제대로 된 방법이었는지는 잘 모르겠습니다.😵 
1. 먼저 선언만 한 후 테스트 함수를 작성합니다. 
    -> 이를 돌리면 `fail`이 뜹니다. 실패한 케이스.
2. 구현하고자 하는 함수 내부를 작성합니다.
    -> `refactor`
3. 다시 테스트를 돌립니다.
    -> 성공 시 다음 함수로, 실패 시 다시 리펙토링
    
### Queue 구현 방법들 중 선택사항: Double stack
queue를 구현하는 방법들은 여러가지가 있었습니다.
1. 기본적인 array 
    _ `removeFirst()``
2. linked list
    _ 본인 이전의 정보와 다음의 정보를 알고 있는 `linked list`로 맨 처음의 `linked list`를 해제하면 됩니다.
3. Double Stack
    _ `stack` 2개를 사용해 구현합니다. 두 개의 스택용 배열을 만들어서 1번 스택에 `enqueue`의 값들을 순서대로 추가합니다. 1번 스택의 요소들을 뒤에서부터 빼면서 2번 스택에 넣습니다. 2번 스택은 1번 스택을 거꾸로 뒤집은 것 같은 모양을 하게 됩니다. 2번 스택의 제일 위에 있는 요소를 빼냅니다. 다시 2번 스택을 뒤에서부터 빼면서 1번 스택에 넣으면 1번 스택은 기존에서 첫번째 요소를 제외하고 나머지 요소들이 앞당겨진 모양이 됩니다. 
    
=> 저는 이 중에서 `Double Stack`을 사용했습니다. 1번 방법은 간편하지만 맨 처음 요소를 삭제한 후 그 뒤의 것들을 하나씩 끌어와야해서 시간복잡도에 관한 문제가 생기게 됩니다. `linked list`나 `Double Stack`은 둘 다 시간 복잡도 문제를 해결해주지만 제가 먼저 생각이 난 것은 `Double Stack`이었기에 이것으로 구현하였습니다. 이렇게 시간복잡도가 같을 때는 두 가지 중에서 어떤 기준으로 선택하는 것이 좋을까요?

### reversed 기능을 가진 함수
`Double Stack`을 구현하기 위해 `reversed()`를 두번 해주어야 합니다. 이 `reversed()` 기능은 `dequeue`에서 바로 해줘도 되지만, `test`를 할 때 거꾸로 잘 바뀌는지도 확인해보고 싶어서 따로 빼주었습니다. 이렇게 빼는 것이 오히려 구현 시 복잡하게 만드는 걸까요? -> reversed()를 배열로 선언하는 순간 시간복잡도가 O(n)이라고 합니다. 

# STEP 2
## 고민했던 점
### 고차함수의 사용_ compactMap
프로젝트의 설명 중 고차함수를 최대한 사용해보라는 안내에 따라 `parse`함수 내에서 for 문 대신 `forEach`를 사용했습니다. 그러기 위해서 옵셔널 바인딩이 필요한 부분을 고민하게 되었는데 전에 옵셔널 스터디에서 했던 것이 생각나 `compactMap`으로 바인딩 해 주었습니다. 결론적으로 변수 선언하는 부분을 줄일 수 있었습니다.

### UML의 해석_ split, componentsByOperators
처음 주어진 UML을 온전하게 이해하는 것에 오랜 시간이 걸렸습니다. 특히 split과 componentsByOperators 함수를 이해하기가 쉽지 않았는데 split이 있는데 굳이 componentsByOperators가 있어야 하나는 생각을 했습니다. 온전히 저만의 생각은 아니지만 componentsByOperators를 구현함에 있어 split의 기준을 " " 공백으로 두었습니다. 이 때문에 위와 같은 생각이 더 커졌는데, 코드를 읽을때 조금 더 쉽게 읽기 위함으로 이해하고 넘어갔습니다.

## 해결하지 못한 점

## 조언을 얻고 싶은 점
### enum Test
unit Test를 하는 것에 있어서 enum 타입인 ExpressionParser나 Operator를 따로 test 할 수가 없는데 이 경우는 다른 FormulaTests에서 같이 해도 되는지 궁금합니다. sut로 부르지 않아도 사용할 수 있기에 formula의 given 형식으로만 일단 test를 작성해 놓았습니다.

### unit Test
이전 스텝에서는 TDD로 진행하였기에 unit Test를 최대한 자세하게 작성하였습니다. 이번 스텝의 경우는 TDD로 진행한 것이 아니라 단순히 unit Test의 기능을 사용하고 싶었던 건데 밈은 이 경우에도 최대한 자세하게 작성하시는지 궁금합니다. test를 어느 정도까지 진행해야 하는 것인지 아직 감이 잘 안 오는 것 같아요. 저는 이번 스텝에서는 구현을 한 후 unit Test로 생각을 검증만 하고 있기에 어느정도 간략하게 작성하였습니다. 

# STEP 3
## 고민되었던 점
### scroll view, stack view
scroll View 안에 있는 Stack View에 매번 계산기의 수식을 추가할 때마다 sub Stack View를 쌓게 되는데 기존 scroll view와 stack view는 그대로 스토리보드에 있는 것을 끌어와 활용만하면 될 것 같아 두었습니다. subStackView만 매번 수식을 추가할 때마다 만들어 넣어서 올리는 방식으로 구현하여 subStackView만 코드로 작성하였습니다.

### autolayout
scrollView의 StackView 안의 subStackView들은 직접 필요할 때 코드를 통해 구현하여 추가해주었기에 스토리보드에서는 지워주었습니다. 그랬더니 scrollView의 height가 없다는 autolayout error가 떴습니다. top으로 부터의 거리를 20보다 같거나 크다로만 설정해주어서 기본적인 값이 없기에 나타나는 문제였습니다. scrollView에 기본적인 height를 주고, 그렇게 되면 subStackView가 들어갈 때 scrollView의 가운데 부터 표시되는 것이 문제였기에 alignment를 .bottom으로 주어 해결했습니다.

### isInputZero, isResultValue flag 사용
입력받은 0인지, 기본적으로 표시되어 있는 0인지에 따라 수식에 들어가고 들어가지 않고에 대한 여부가 달랐습니다. 이를 위해 isInputZero라는 bool type 파라미터를 만들어 구별하였습니다. 이와 비슷하게 = 를 눌러 결과값이 나온 경우 그 결과값에 새로운 피연산자를 덧붙일 수 없고 +- 부호 전환을 할 수 없기에 이 역시 Bool type 파라미터로 구별하였습니다. 

### settingFormula(isEndByPoint: Bool = false, isResultComma: Bool = false) 분기처리
입력되는 부분에서 .으로 끝나거나 긴 결과값이라 ,로 구분된 경우 그대로 [String]에 쌓이면 그 후 parse 함수에서 에러가 나는 일이 있었습니다. 때문에 이러한 경우들을 따로 빼서 각각 filter를 사용해 .과 ,를 삭제해 주었습니다. 


- 

## 해결이 되지 않은 점
### numberFormatter

부동소수점 오류로 인해 의도한 값이 제대로 계산되지 않는 경우가 있었습니다. Double을 사용했기 때문인 것 같은데 numberFormatter로 Decimal로 바꾸었지만 여전히 온전히 못한 값들의 계산이 나왔습니다. 또한 20자리수로 설정하는 것에 있어서도 maximumSignificantDigits로 유효숫자가 20자리일 수 있게 설정하였지만 20자리까지 갔을때는 항상 값들이 변하는 일이 있습니다.. 😥

### layoutIfNeeded
scrollView에서 새로운 라벨이 들어오면 항상 최하단으로 자동 스크롤을 할 수 있게 로직을 짜는 과정에서 문제가 있었습니다. 항상 하나 위의 값이 보여서 찾다가 layoutIfNeeded()라는 것을 알게 되었습니다. 업데이트 주기를 당겨주어 온전하게 크기를 처리할 수 있게 해주는 것으로 이해했는데 그렇다면 이것을 사용하지 않은 경우에는 subStackView가 CGpoint를 잡으라고 되어 있는 후에야 올라가게 되는지 궁금합니다... 😥
- 

## 조언을 얻고 싶은 부분

### 수많은 조건문들

예외적인 사항들을 처리하다 보니 한 버튼 액션들에 굉장히 많은 조건문을 담게 되었습니다. 이렇게 굉장히 많은 조건문을 두게 된 것은 구조적인 문제로 인해 생겨난 것일까요? 액션이 있을 때 처리해야하는 일들이 많다보니 일어난 일 같은데 지금 제 코드의 경우에도 버튼 액션들이 하나의 역할을 하는 함수라고 볼 수 있는지 의아합니다. 함수 안의 내용들을 조금 더 분리하여 처리해야할까요? 😥
- 


---

# 리드미 작성: hoon

# 🏦계산기


## 📖 목차
1. [소개](#-소개)
2. [팀원](#-팀원)
3. [타임라인](#-타임라인)
4. [시각화된 프로젝트 구조](#-시각화된-프로젝트-구조)
5. [실행 화면](#-실행-화면)
6. [트러블 슈팅](#-트러블-슈팅)
7. [참고 링크](#-참고-링크)
8. [팀 회고](#-팀-회고)

</br>

## 🍀 소개
사용자의 입력을 받아 기본 연산을 수행하는 계산기입니다.

* 주요 개념: `Protocols`, `Extensions`, `Generic`, `Type Casting`, `Unit Test`, `Queue`, `Scroll View`

</br>

## 👨‍💻 팀원
| hoon |
| :--------: |
| <Img src = "https://hackmd.io/_uploads/HylLMDsN2.jpg"  width="200" height="200"> |
|[Github Profile](https://github.com/Hoon94) |


</br>

## ⏰ 타임라인
|날짜|내용|
|:--:|--|
|2023.05.29.| - Unit Test 실험|
|2023.05.30.| - Node, Linked List 타입 생성 및 Unit Test 진행 |
|2023.05.31.| - CalculatorItemQueue 타입 생성 및 Unit Test 진행 </br> - UML 작성 |
|2023.06.01.| - Generic 타입 수정|
|2023.06.02.| - README 작성|
|2023.06.03.| - |
|2023.06.04.| - |
|2023.06.05.| - |
|2023.06.06.| - |
|2023.06.07.| - |
|2023.06.08.| - |


</br>

## 👀 시각화된 프로젝트 구조
### Diagram
<p align="center">
<img width="800" src= "https://hackmd.io/_uploads/BJR4n6lw2.png" >
</p>

</br>

## 💻 실행 화면 - 추후 작성 예정

| 화면 |
|:--------:|
|<img src="" width="800">|

| 화면 | 화면 | 화면 |
|:--------:|:--------:|:--------:|
|<img src="" width="250">|<img src="" width="250">|<img src="" width="250">|



</br>

## 🧨 트러블 슈팅

1️⃣ `Linked List` 사용 <br>
-
🔒 **문제점** <br>
- `CalculatorItemQueue`를 만들기 위해 어떠한 자료 구조를 사용할지 고민했습니다. 처음 기본적인 틀을 구성할 때는 가장 쉬운 `Array` 타입을 생각하였습니다. 내부적으로 구현되어 있는 `Array`의 메서드를 통해 `CalculatorItemQueue`의 `list`를 구성할 수 있었습니다. 하지만 `CalculatorItemQueue`에서 `dequeue`를 실행할 때 `Array` 자료구조의 문제점이 드러났습니다. `Array`에서 마지막이 아닌 원소를 제거할 때 남은 원소들을 앞으로 옮기며 빈 메모리 공간을 채웁니다. 이런 동작을 수행하기때문에 `dequeue` 메서드를 실행할 때 시간 복잡도는 O(n)이라고 합니다.

🔑 **해결방법** <br>
- `Linked List`를 사용하여 `dequeue`의 문제점을 해결할 수 있었습니다. 처음 입력받은 `node`를 `dequeue`하는 과정에서 맨 앞의 `node`에 대한 연결을 끊어주었습니다. `head`를 사용하여 `Linked List`의 첫 노드에 대해 알고 있다가 `dequeue`가 일어나는 과정에서 `head`를 다음 `node`로 옮기고 이전 `node`를 해제했습니다. `head`는 항상 첫 `node`를 참조하고 있기 때문에 시간 복잡도가 O(1)인 만큼 더 빠른 동작이 가능했습니다.

    ```swift
    mutating func append(_ data: T) {
        guard head != nil
        else {
            head = Node(data: data)
            tail = head
            return
        }

        tail?.updateNext(Node(data: data))
        tail = tail?.fetchNext()
    }
    ```

<br>

2️⃣ **확장성** <br>
-
🔒 **문제점** <br>
- 

🔑 **해결방법** <br>
-

<br>

## 📚 참고 링크
- [🍎Apple Docs: Generics](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics)
- [🍎Apple Docs: Protocols](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols)
- [🍎Apple Docs: ]()
- [🍎Apple Docs: ]()
- [📘blog: Generic](https://babbab2.tistory.com/136)
</br>

## 👥 회고
- [회고 링크]()



# 리드미 작성: MINT

# 🏦계산기

## 📖 목차
1. [🍀 소개](#-소개)
2. [👨‍💻 팀원](#-팀원)
3. [⏰ 타임라인](#-타임라인)
4. [👀 시각화된 프로젝트 구조](#-시각화된-프로젝트-구조)
5. [💻 실행 화면](#-실행-화면)
6. [🧨 트러블 슈팅](#-트러블-슈팅)
7. [📚 참고 링크](#-참고-링크)
8. [👥 회고](#-회고)

</br>

## 🍀 소개
민트가 만든 계산기. <br>
사용자는 숫자패드와 기호를 눌러 정수와 실수의 사칙연산을 할 수 있습니다.

</br>

## 👨‍💻 팀원
| MINT |
| :--------: |
| <Img src = "https://hackmd.io/_uploads/SyW7zfDUn.jpg"  width="200" height="200"> |
|[Github Profile](https://github.com/mint3382) |


</br>

## ⏰ 타임라인
|날짜|내용|
|:--:|--|
|2023.05.29.| - `Unit Test` 공부 |
|2023.05.30.| - `enqueue`, `dequeue`, `popLastStack` 함수 구현 <br> - TDD 사용 |
|2023.05.31.| - `count`, `isEmpty`, `first`, `last` 연산 프로퍼티 구현 <br> - `enqueue`, `dequeue` 함수 리펙토링 <br> - `popLastStack` 함수 삭제 <br> - `test` 리펙토링 |
|2023.06.01.| - `enqueue`, `dequeue` 함수 리펙토링 <br> - `test` 리펙토링 <br> - `first`, `last` 연산 프로퍼티 리펙토링 |
|2023.06.02.| - `static`, `UML`공부 |
|2023.06.04.| - `UML` 이해 후 정리 <br> - 수도코드 작성 |
|2023.06.05.| - `ExpressionParser`, `Operator`, `Formula` 구현 |
|2023.06.06.| - `Formulatests`, `ExpressionParserTests`, `CalculatorErrors` 구현 |
|2023.06.07.| - 컨벤션 및 네이밍 수정 |
|2023.06.08.| - `NumberFormatter` 공부, 구현 <br> - `Scroll View` 공부, 구현 |
|2023.06.09.| - `IBAction` 구현 <br> - 자동 스크롤 기능 구현 |
|2023.06.10.| - 예외사항 수정 <br> - `NumberFormatter` 파라미터 타입 수정 |
|2023.06.11.| - 예외사항 수정 <br> - 네이밍 및 컨벤션 수정 <br> - `Operator` 계산 타입 수정 |


</br>

## 👀 시각화된 프로젝트 구조
### File Tree
````
Calculator
    ├── Extension
    │   ├── String+
    │   └── Double+
    ├── Model
    │   ├── CalculatorItemQueue
    │   ├── CalculateItem
    │   ├── Formula
    │   ├── Operator
    │   ├── ExpressionParser
    ├── View
    │   ├── LaunchScreen
    │   ├── Main
    ├── Controller
    │   ├── AppDelegate
    │   ├── SceneDelegate
    │   ├── CalculateViewController
    ├── Resource
    │   ├── Assets
    │   ├── Info
    ├── CalculatorTests
        ├── TestPlan
        ├── ExpressionParserTests
        ├── FormulaTests 
        ├── CalculatorItemQueueTests
````


### Class Diagram
<img width="800" src= "https://hackmd.io/_uploads/rJApOw7v2.jpg" >
</p>
</br>

## 💻 실행 화면 

| AC 버튼 | CE 버튼 | +/- 버튼 |
|:--------:|:--------:|:--------:|
|<img src="https://hackmd.io/_uploads/ByiQ3SmPh.gif" width="250">|<img src="https://hackmd.io/_uploads/Hy6UnBmPn.gif" width="250">|<img src="https://hackmd.io/_uploads/BJyO3Hmwh.gif" width="250">|

| 정수 연산 | 소수 연산 | 실수 연산 |
|:--------:|:--------:|:--------:|
|<img src="https://hackmd.io/_uploads/ryu8ArmD2.gif" width="250">|<img src="https://hackmd.io/_uploads/HJFH0HmP2.gif" width="250">|<img src="https://hackmd.io/_uploads/SkPV0HXPn.gif" width="250">|

| ÷ 0 | 10 ÷ 3 | 5 ÷ 3 |
|:--------:|:--------:|:--------:|
|<img src="https://hackmd.io/_uploads/Hy090Smv3.gif" width="250">|<img src="https://hackmd.io/_uploads/rJkh0BXvn.gif" width="250">|<img src="https://hackmd.io/_uploads/B1i3CHXDn.gif" width="250">|

| 0버튼 예외처리 | 20자릿수 예외처리 | 결과값 예외처리 |
|:--------:|:--------:|:--------:|
|<img src="https://hackmd.io/_uploads/B1RXkUXwh.gif" width="250">|<img src="https://hackmd.io/_uploads/SJJr1L7w2.gif" width="250">|<img src="https://hackmd.io/_uploads/ByJ8yLQwn.gif" width="250">|

| 연산이 길어질 경우 자동 스크롤 | 피연산자 라벨 숫자가 커질 경우 decimal 양식 | dot으로 끝날 경우 수식 표현 |
|:--------:|:--------:|:--------:|
|<img src="https://hackmd.io/_uploads/SkbbxLXP3.gif" width="250">|<img src="https://hackmd.io/_uploads/SJxll87wn.gif" width="250">|<img src="https://hackmd.io/_uploads/Syb1g8QP2.gif" width="250">|

</br>

## 🧨 트러블 슈팅

1️⃣ **Queue 구현 자료구조 선택: Double Stack vs Linked_List** <br>
-
🔒 **문제점** <br>
`Queue`를 구현하는 방법들은 여러가지가 있습니다.

본인 이전의 정보와 다음의 정보를 알고 있는 `linked list`라는 구조의 방법이 있습니다. `enqueue`시 마지막 `node`인 `TAIL`에 새로 추가된 `node`를 잇고, `dequeue`시 맨 처음의 `node`인 `HEAD`의 정보를 반환하면서 `HEAD`의 위치를 다음 `node`로 전환하는 방법을 사용합니다.

`stack` 2개를 사용해 구현하는 `Double Stack`이라는 구조의 방법 또한 있습니다. 두 개의 스택용 배열을 만들어서 `enqueueStack`에서는 요소의 추가(`enqueue`의 기능)만, `dequeueStack`에서는 선입선출을 맞춰 요소 반환(`dequeue`의 기능)만을 담당하게 하여 사용합니다.

이 중 계산기의 기능을 위해 `Queue`를 구현할 때, 어떤 방법을 선택할지에 대한 고민이 있었습니다.


🔑 **해결방법** <br>
계산기의 기능을 위함에는 `Double Stack`이 더 맞는 것 같아 `Double Stack`으로 구현하였습니다. 

`linked list`는 `node`라는 곳에 단순한 본인의 `data` 뿐만 아니라 다음 차례의 `node`의 주소값을 알고 있는 구조입니다. 끊임없이 연결되어 있는 구조이고 `HEAD`와 `TAIL`을 설정해주면 손쉽게 처음과 끝을 알 수 있고, 내용을 변경할 수 있습니다. 때문에 `array`에서 구현되는 `removeFirst()`와는 다르게 `HEAD`만 옮겨서 1번 자리를 가리키는 것이 바뀔 뿐 배열의 내용을 앞으로 당길 필요가 없기에 시간복잡도가 O(n)이 되는 경우가 없어서 빠릅니다. 또한 중간에 데이터A를 넣고 싶을 때 넣고 싶은 부분의 앞의 노드의 다음 노드에 대한 정보를 A에 대한 노드로 바꾸고, A 노드에서 다음 노드에 대한 정보를 기존에 넣고 싶었던 부분의 뒤의 노드로 변경해주면 됩니다. 이처럼 데이터를 중간에 삽입하는 것에 큰 장점이 있습니다.

`Double stack`은 `dequeueStack`이 비어있는 경우 `enqueueStack`을 뒤집어주어야 하는 일이 있어서 시간 복잡도는 조금 더 높을 수 있지만 배열을 이용한 구현이기에 원한다면 인덱스를 통한 접근을 빠르게 할 수 있습니다.

계산기의 경우 큐의 구현이기에 중간에 데이터를 추가해 줄 필요가 없습니다. `linked-list`를 사용한다면 `enqueue`를 할 때마다 매번 `node`를 생성하고 할당하여 `TAIL`과 연결짓는 작업을 해주어야 하는데 `double stack`은 단순히 `enqueueStack`에 `append`만 해주면 되고, `dequeue`를 할 때도 `linked-list`는 기존 HEAD에서 데이터를 저장하여 반환한 후 `HEAD`가 가리키고 있는 `node`를 다음 `node`로 변경하는 작업을 해주어야 하는데 `double stack`에서는 이미 `dequeueStack`에 있다면 단순히 배열의 마지막 요소를 꺼내기만 하는 것이기에 해야하는 작업이 더 적고 구조가 간단하다는 생각이 들어 `double stack`으로 구현한 것을 남겼습니다.


<br>

2️⃣ **Double Stack: enqueue와 dequeue시 시간 복잡도** <br>
-
🔒 **문제점** <br>
초반 `dequeue`를 할 때 항상 `enqueueStack`을 `dequeueStack`에 `reversed()` 해준 후 `popLast()`를 하고 다시 `enqueueStack`에 `reversed()` 해주는 방법을 사용했습니다. 그러자 매번 시간복잡도가 O(n)으로 `array`의 `removeFirst()`를 사용해주는 것과 다를 바 없는 결과가 나왔습니다. 
```swift
    dequeueStack = reversedStack(enqueueStack)
    let output = backwardStack.popLast()
    enqueueStack = reversedStack(dequeueStack)
```

🔑 **해결방법** <br>
한번 `enqueueStack`을 `reversed()`하여 `dequeueStack`에 넣게 되면 기존의 `enqueueStack`의 내용은 지우고 `dequeueStack`에만 남겨, `dequeueStack`이 비어서 꺼낼 요소가 없을 때까지 `reversed()`할 필요 없이 마지막 요소를 꺼내는 작업만 할 수 있도록 변경하였습니다. 매번 `reversed()` 해주는 것이 아니라 필요한 순간에만, 한번씩 해주기에 시간 복잡도를 줄일 수 있었습니다. 
```swift
mutating func dequeue() -> T? {
        guard isEmpty == false else {
            return nil
        }
        
        if dequeueStack.isEmpty {
            dequeueStack = enqueueStack.reversed()
            enqueueStack.removeAll()
        }
        
        return dequeueStack.removeLast()
    }
```


<br>

3️⃣ **TDD_private** <br>
-
🔒 **문제점** <br>
`private` 키워드를 사용하면 `Unit Test`에서도 해당 프로퍼티나 메서드를 사용할 수 없습니다. 때문에 테스트를 할 때는 `private`를 제거하고, 테스트가 끝난 후에 다시 `private`을 붙여주었습니다. 그러니 최종적인 `test문`들에 빨간 에러가 뜨는 일들이 발생하였는데 특히 `enqueue` 메서드가 제대로 동작하였는지를 알아보기 위해 `private enqueueStack`의 내용을 비교하는 과정에서 문제들이 생겼습니다.

```swift
 XCTAssertEqual(sut.enqueueStack, [10, 20])
```

🔑 **해결방법** <br>
테스트는 은닉화를 하였음에도 오류 없이 잘 돌아가야합니다. 때문에 `private` 키워드를 붙이더라도 해당 프로퍼티나 메서드를 확인할 수 있는 다른 방법을 생각해야 했습니다. `count` 프로퍼티를 활용하여 `enqueue` 메서드가 동작하였을 때 `count`의 값이 올라갔는지 확인하는 방법으로 변경하여 해결하였습니다.

```swift
//when
let result = sut.count
        
//then
XCTAssertEqual(result, 2)
```

<br>

4️⃣ **Scroll View 구현** <br>
-
🔒 **문제점** <br>
`ScrollView`는 스크롤뷰 안에 `StackView`가 있고 그 스택뷰 안에 `SubStackView`가 있는 형식입니다. 계산기의 수식이 서브 스택뷰의 라벨로 표시되면서 스택뷰로 들어가고, 스택뷰는 스크롤뷰의 안에 있기에 끊임없이 늘어나더라도 스크롤을 통해 내용들을 전부 확인할 수 있습니다. 처음 스크롤뷰를 본 순간 매번 새롭게 수식 라벨이 추가되어야 하는데 스토리보드만으로는 구현하기 힘들지 않을까, 라는 생각을 했습니다. 때문에 스크롤뷰와 스택뷰의 구현부터 코드로 해야하나 하는 고민을 했습니다. 또한 스크롤뷰에 새로운 수식이 추가될 때마다 해당 수식을 확인할 수 있게 자동스크롤이 되어야 하는데 기존 코드에서는 최신 것 직전의 수식까지만 보이도록 한칸 덜 내려가는 문제가 있었습니다. 

```swift
private func setCurrentFormulaViewOnScroll(_ `operator`: String, _ operand: String) {
        currentFormulaStackView.addArrangedSubview(makeCurrentFormulaLabelStackView(`operator`, operand))
        
        currentFormulaScrollView.setContentOffset(CGPoint(x: 0, y: currentFormulaScrollView.contentSize.height - currentFormulaScrollView.bounds.height), animated: true)
    }
```

🔑 **해결방법** <br>
서브 스택뷰만 코드로 매번 생성하여 `IBOutlet`으로 연결한 스택뷰 안에 넣어주는 방법으로 해결하였습니다. 
```swift
private func makeCurrentFormulaLabelStackView(_ `operator`: String, _ operand: String) -> UIStackView {
        let operatorLabel: UILabel = {
            let label = UILabel()
            
            label.text = `operator`
            label.font = .preferredFont(forTextStyle: .title2)
            label.textColor = .white
            
            return label
        }()
        
        let operandLabel: UILabel = {
            let label = UILabel()
            
            label.text = operand.replacingOccurrences(of: ",", with: "")
            label.font = .preferredFont(forTextStyle: .title2)
            label.textColor = .white
            
            return label
        }()
        
        let subStackView: UIStackView = {
            let stackView = UIStackView(arrangedSubviews: [operatorLabel,operandLabel])
            
            stackView.spacing = 8
            stackView.alignment = .bottom
            
            return stackView
        }()
        
        return subStackView
    }
```
자동 스크롤의 경우는 서브 스택뷰를 스택뷰에 추가한 후의 기준이 아니라 추가하기 전을 기준으로 `CGPoint`를 잡아서, `layoutIfNeeded()`를 통해 강제적으로 추가한 후의 기준을 불러와 위치를 잡게 하여 해결했습니다. 
```swift
private func setCurrentFormulaViewOnScroll(_ `operator`: String, _ operand: String) {
        currentFormulaStackView.addArrangedSubview(makeCurrentFormulaLabelStackView(`operator`, operand))
        
        currentFormulaScrollView.layoutIfNeeded()
        currentFormulaScrollView.setContentOffset(CGPoint(x: 0, y: currentFormulaScrollView.contentSize.height - currentFormulaScrollView.bounds.height), animated: true)
    }
```
<br>

5️⃣ **NumberFormatter 구현** <br>
-
🔒 **문제점** <br>
`Double`을 `String`으로 바꾸는 형식으로 작성하였더니 피연산자를 눌러서 라벨에 추가할 때마다 `decimal` 양식에 맞춰 3자리수 마다 `comma`가 찍혀야 하는데 이 경우 `String -> Double -> String`으로 매번 바꾸어야 하는 문제가 있었습니다.

🔑 **해결방법** <br>
기존에 `Double`의 `extension`으로 두었던 `NumberFormatter`를 `ViewController` 내부의 함수로 구현하여 `String -> String` 형식으로 전환하였습니다. 
> _조금 더 좋은 방법이 있을 것 같기에 추후에 다시 리펙토링 할 예정입니다._

```swift
private func formattingNumber(_ input: String) -> String {
        let formatter = NumberFormatter()
        let number = NSDecimalNumber.init(string: input)

        formatter.maximumSignificantDigits = 15
        formatter.numberStyle = .decimal
        formatter.roundingMode = .halfUp
        formatter.usesSignificantDigits = true
        
        return formatter.string(from: number) ?? "NaN"
    }
```

<br>

6️⃣ **부동소수점 오류** <br>
-
🔒 **문제점** <br>
`Double`은 최대 16자리까지만 정확한 수를 저장할 수 있습니다. 그러나 주어진 과제의 표현 가능한 자릿수는 20이기에 `maximumSignificantDigits`를 20으로 주었더니 온전한 계산을 하지 못하였습니다. 또한 `Double`과 `Double`의 계산을 하고 있었기에 소수점 자리 계산 역시 근사값끼리의 계산 때문에 정확한 결과가 나오지 않았습니다.
````
10 ÷ 3 = 3.333333333334528
0.1 + 0.2 = 0.3000000000512
````
🔑 **해결방법** <br>
문제의 해석을 다르게 해보았습니다. 결과값으로 표현될 수 있거나 입력값으로 넣을 수 있는 자릿수는 20으로 해석해여 `maximumSignificantDigits`을 15로 제한하였고 기존 `Operator`에서 `Double`끼리의 연산 부분을 `NSDecimalNumber`에서의 `Decimal`끼리의 연산으로 변경하여 해결하였습니다.

```swift
private func add(lhs: Double, rhs: Double) -> Double {
        return NSDecimalNumber(decimal: Decimal(lhs) + Decimal(rhs)).doubleValue
    }
    
    private func subtract(lhs: Double, rhs: Double) -> Double {
        return NSDecimalNumber(decimal: Decimal(lhs) - Decimal(rhs)).doubleValue
    }
    
    private func divide(lhs: Double, rhs: Double) -> Double {
        guard rhs != 0 else {
            return .nan
        }
        return NSDecimalNumber(decimal: Decimal(lhs) / Decimal(rhs)).doubleValue
    }
    
    private func multiply(lhs: Double, rhs: Double) -> Double {
        return NSDecimalNumber(decimal: Decimal(lhs) * Decimal(rhs)).doubleValue
    }
```

<br>

7️⃣ **깔끔한 분기처리** <br>
-
🔒 **문제점** <br>
여러가지 예외들을 처리하느라 한 함수에 굉장히 많은 조건문을 작성하게 되었습니다. `flag`도 4,5개 정도로 과하게 있어서 코드를 타인이 읽고 해석하기가 매우 어려웠습니다. 예외사항들에 대한 분기 처리에 대한 문제였습니다.

🔑 **해결방법** <br>
한가지 버튼에 밀어넣지 않고 예외적이게 될 수 있는 버튼 마다 따로 `IBAction` 함수를 선언하여 해결하였습니다. `0, 00, . `을 숫자와 분리하였더니 과한 조건문들과 `flag`를 줄일 수 있었습니다. 또한 `flag`를 두어 `property Observer`를 사용하던 부분을 함수로 빼서 선언만 하면 되도록 수정하였습니다. 한결 가독성을 높일 수 있었습니다.

```swift
@IBAction func tappedDotButton(_ sender: UIButton) {
        guard let operandLabelText = currentOperandLabel.text,
              operandLabelText.contains(".") == false,
              isCalculated == false else {
            return
        }
        
        currentOperandLabel.text = operandLabelText + "."
    }
```

<br>

## 📚 참고 링크
- [🍎Apple Docs: reversed](https://developer.apple.com/documentation/swift/array/reversed())
- [🍎Apple Docs: popLast](https://developer.apple.com/documentation/swift/array/poplast())
- [🍎Apple Docs: removeLast](https://developer.apple.com/documentation/swift/array/removelast())
- [🍎Apple Docs: NSDecimalNumber](https://developer.apple.com/documentation/foundation/nsdecimalnumber)
- [🍎Apple Docs: CustomStringConvertible](https://developer.apple.com/documentation/swift/customstringconvertible)
- [🍎Apple Docs: Data Formatting](https://developer.apple.com/documentation/foundation/data_formatting)
- [🍎Apple Docs: ScrollView](https://developer.apple.com/documentation/uikit/uiscrollview)
- [🍎Apple Docs: StackView](https://developer.apple.com/documentation/uikit/uistackview)
- [🍎Apple Docs: replacingOccurrences(of:with:)](https://developer.apple.com/documentation/foundation/nsstring/1412937-replacingoccurrences)
- [🍏Apple Docs_Archive: NSFormatter](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSFormatter/Description.html#//apple_ref/occ/instm/NSFormatter/stringForObjectValue:)
- [📘blog: NumberFormatting](https://www.swiftbysundell.com/articles/formatting-numbers-in-swift/)
- [📘blog: layoutIfNeeded](https://zeddios.tistory.com/359)
</br>

## 👥 회고
- [회고 링크](https://github.com/mint3382/ios-calculator-app/wiki/%F0%9F%8F%A6%EA%B3%84%EC%82%B0%EA%B8%B0%F0%9F%92%B0)




# 팀 회고

## 서로에게 하고싶은 말
- to.MINT
    오랜만에 민트랑 함께 프로젝트를 해서 너무 좋았습니다.😆 같이 공부하고 질문도 하고 항상 아이디어 넘치는 민트라서 대화하는 내내 많이 배울 수 있는 기회였습니다. 항상 열심히 하는 민트라 저도 덩달아 같이 하게 되는 것 같아요. 그래도 꼭 민트 스스로 건강은 챙기면서 하면 좋겠습니다.😭 앞으로 남은 4개월 정도의 시간 동안 다른 외부적인 스트레스 없이 공부할 수 있기를 빌며 커리어 캠프가 끝날 때에는 민트는 엄청나게 성장할 거라고 확신합니다.😈👍 필요하시다면 언제든 민트의 대화 상대가 되어드리겠습니다.☺️ 2주간 함께 공부할 수 있어서 정말 즐거운 시간이었어요. 남은 계산기 프로젝트도 화이팅입니다.🔥
    
- to.hoon
    훈! 선뜻 저와 같이 이번 개인 프로젝트의 스크럼을 함께 해주어서 고마워요.☺️ 훈과 하루종일 있으면서 의견 교류를 할 수 있다는 것은 새삼스럽지만 정말 행복한 일이더군요.😎 첫 번째 고비라고도 불리는 계산기를 너무나 잘하고 싶은 마음과 따라주지 않는 몸에 더더욱 무리를 했는데 훈이 아니었으면 제가 이 마지막 주 팀플을 참여할 수도 없었겠다는 생각이 들더라구요. 항상 너무 걱정시켜서 죄송하고 또 그만큼 감사했어요.😂 훈에 대해 알게되면 알게 될 수록 존경하게 되는 것 같아요. 제 찡찡도 들어주셔서 정말 감사합니다.🤣 훈도 언제든지 대화상대가 필요하시다면(심심해서라도) 저를 불러주세요!😈 남은 기간도 파이팅! 잘 부탁드려요~🤗