---
# System prepended metadata

title: '은행창구 매니저 [STEP 3] Ash, Rhovin'

---

# 은행창구 매니저 [STEP 3] Ash, Rhovin

(@hyunable) 안녕하세요 hyunable =) 애쉬, 로빈 입니다~
step3 구현 완료하게 되어 PR 올립니다!
이번 리뷰도 잘 부탁드리겠습니다. 부족한 점이 있다면 말씀 부탁드려요 😊

### 📊 UML
<img width=700px, src="https://i.imgur.com/L1PY7IX.jpg">


---

### 📂 Tree

```
├── BankManagerConsoleApp
│   ├── Bank.swift
│   ├── BankManager.swift
│   ├── BankWorker.swift
│   ├── Client.swift
│   ├── BankWork.swift
│   ├── NameSpace.swift
│   └── main.swift
├── BankManagerConsoleAppTest
│   ├── LinkedListTest.swift
│   └── QueueTest.swift
└── DataStruct
    ├── LinkedList.swift
    ├── Node.swift
    └── Queue.swift
   
```

---

### 🖌 코드 개요
- 객체 지향을 생각하며 어떤 타입이 어떤 메서드를 가지고 있어야 할지 고민을 했습니다.
- main
  - generateBankWorkers(by count: Int) : `count`명의 은행원을 생성합니다.
  - generateClient(): 10~30명 사이의 고객 생성합니다.
  
- Bank: 은행 시스템
  - addClient(): BankManager를 통해 대기열에 고객을 추가합니다.
  - publishTicketNumber(): 고객 대기열 티켓번호를 발급합니다.
  - openSystem(): 은행 개점 및 종료 메뉴를 콘솔창에 출력합니다.
  
- BankManager: 대기열에 고객 추가 / BankWorkers에게 업무 배정 및 할당/ 은행 개점 및 폐점을 담당합니다.

- BankWorker: BankManager로부터 배정받은 고객의 업무를 처리합니다. 담당할 업무를 프로퍼티로 가지고 있습니다.
  - STEP3에서 여러명의 은행원이 존재하는 점을 고려하여 BankWorkable 프로토콜의 기본구현을 통해 추후 인스턴스를 유연하게 생성할 수 있도록 하였습니다.

- Client: 대기번호를 기준으로 구분되는 고객 타입입니다. 요청하는 은행업무를 프로퍼티로 가지고 있습니다.

- NameSpace: 아래의 magic number에 네임스페이스를 활용하였습니다.
  - 은행이 가지는 최소-최대 고객 명수
  - 은행 시스템 메뉴 내의 옵션 번호
  - 예금 업무를 담당하는 직원 명수
- BankWork: 은행 업무타입 열거형 입니다.
  - 업무 타입에 따라 업무이름, 업무걸리는 시간(초/마이크로세컨드)를 반환하도록 연산 프라퍼티를 만들어 주었습니다.

---

### 🙋‍♀️ 궁금한 점
**네이밍에 대한 고민**
- 이번 프로젝트에서는 '업무 종류를 배정하다', '어떤 업무를 하다' 와 같이 유사한 네이밍이 붙여질만한 메서드가 많았던 것 같습니다.
  그래서 네이밍에 대한 고민이 정말 깊었는데요🥹 혀나블이 보시기에 수정할만한 네이밍이 있을지 궁금합니다.

**`DispatchSemaphore`에 대한 고민**
- `DispatchSemaphore`는 여러 스레드가 하나의 공유자원에 접근할 수 있는 수를 제한해 thread unsafe한 상황을 예방하는 방법 중 하나로 이해하였습니다.
- 현재는 2명의 예금업무 담당 직원이 존재하기 때문에 동일한 client에 접근하는 상황을 방지하고자 `DispatchSemaphore` 타입의 인스턴스를 `doDepositWork(by:BankWorker)` 메서드의 지역변수로 선언해 `wait()`와 `signal()` 메서드를 사용하였습니다. 
  이 때, 저희가 `DispatchSemaphore` 적절하게 사용한 것인지 궁금합니다.
- 또한, 대출업무 담당 직원이 2명 이상이 될 수 있는 상황을 가정하여 `doLoanWork(by:BankWorker)` 메서드에도 동일하게 구현하였습니다.
  이 때, 각 지역변수가 `depositClientQueue`와 `loanClientQueue` 타입에 각각 적용되는 것이 맞을까요? 
- `DispatchSemaphore`에서 언급하는 `공유자원`의 의미를 명확히 알기 어려워서인 것 같은데, 혹시 참고할만한 글이 있을지 알려주실 수 있을지 궁금합니다. 🥲







---

# 🏦 은행창구 매니저
>프로젝트 기간: 2022-10-31 ~ 2022-11-11 (2주)

## 📑 프로젝트 소개
- Queue와 LinkedList 자료구조의 모델 타입을 구현하고, 은행창구 매니저 애플리케이션을 제작하는 프로젝트 입니다.
- 동시성 프로그래밍(GCD) 개념을 기반으로 진행되었습니다.

<img height = 21px, src = "https://img.shields.io/badge/swift-5.6-green">

## 🌱 팀 소개
 |[Rhovin](https://github.com/yuvinrho)|[애쉬](https://github.com/ash-youu)|
 |:---:|:---:|
| <img width="180px" img style="border: 2px solid lightgray; border-radius: 90px;-moz-border-radius: 90px;-khtml-border-radius: 90px;-webkit-border-radius: 90px;" src="https://i.imgur.com/soKEJcy.png"> | <img width="180px" img style="border: 2px solid lightgray; border-radius: 90px;-moz-border-radius: 90px;-khtml-border-radius: 90px;-webkit-border-radius: 90px;" src="https://avatars.githubusercontent.com/u/101683977?v=4">|

- `Rhovin`와 `애쉬` 팀의 은행창구 매니저입니다.



###
## 📝 목차
#### 1. [UML](#-uml)
#### 2. [Tree](#-tree)
#### 3. [타임라인](#-타임라인)
#### 4. [기능 설명(실행 화면)](#-기능-설명실행-화면)
#### 5. [트러블 슈팅](#-트러블-슈팅고민한-점)
#### 6. [참고 링크](#-참고-링크)

---

## 🔖 1. UML
<img width=700px, src="https://i.imgur.com/XcJnXnR.png">

---

## 📂 2. Tree

```
├── BankManagerConsoleApp
│   ├── main.swift
│   ├── Bank.swift
│   ├── BankManager.swift
│   ├── BankWorker.swift
│   ├── Client.swift
│   ├── NameSpace.swift
│   └── BankWork.swift
├── BankManagerConsoleAppTest
│   ├── LinkedListTest.swift
│   └── QueueTest.swift
└── DataStruct
    ├── Queue.swift
    ├── LinkedList.swift
    └── Node.swift
   
```

---

## ⏱ 3. 타임라인
- **2022.10.31**
  - STEP1 진행을 위한 Queue, LinkedList 개념 학습

- **2022.11.01**
  - Queue, LinkedList, Node 타입 정의
  - STEP1 PR 발송


- **2022.11.02**
  - 자료구조 타입에 적절한 인터페이스로 리팩토링
  - 구현한 자료구조의 unit test 진행

- **2022.11.03**
  - STEP2 구현을 위한 Bank, BankWorker, Client 타입 정의

- **2022.11.04**
  - Bank, BankManager, BankWorker, Client 구현부 작성
  - Unit test 메서드 네이밍 수정
  - STEP2 PR 발송
  - STEP1 README.md</b> 작성

- **2022.11.08**
  - BankWork 타입 정의
  - BankWorker가 동시성을 가지고 일하도록 수정
  - STEP3 PR 발송

- **2022.11.09**
  - BankManager 타입 `configureWorkType()` 메서드 반복문 리팩토링
  - BankManager 타입 semaphore 로직 수정
  - 객체지향에 따라 역할에 맞게 main의 메서드 수정

- **2022.11.11**
  - STEP3 README.md</b> 작성
---

## 💻 4. 기능 설명(실행 화면)
### [STEP 1]
- Queue, Linked-list, Node 타입을 구현하였습니다.
- Queue 타입 인스턴스 메서드의 동작을 검증하기 위해 Unit Test를 진행하였습니다.

### [STEP 2]
- Linked-list타입과 Queue타입을 활용해 MVC 중 Model 파트를 구현하였습니다.
- Bank: 은행 시스템
  - `generateClient()`: 고객 리스트 업데이트
  - `openSystem()`: 은행 개점 및 종료 메뉴 출력
- BankManager: 대기열에 고객 추가 / BankWorker에게 업무 지시 / 은행 개점 및 폐점 담당
- BankWorker: BankManager로부터 배정받은 고객의 업무 처리
- Client: 대기번호를 기준으로 구분되는 고객 타입
- NameSpace: magic number에 네임스페이스를 활용
  - 은행이 가지는 최소-최대 고객 명수
  - 은행 시스템 메뉴 내의 옵션 번호
- 실행화면
![](https://i.imgur.com/gSttUXU.gif)

### [STEP 3]
- GCD를 활용해 동시성 프로그래밍을 구현하였습니다.
- 이로 인해 3명의 은행원(예금업무 2명, 대출업무 1명)이 동시에 업무를 처리하도록 수정하였습니다.
- 예금업무는 0.7초, 대출업무는 1.1초가 걸립니다.
- 예금업무 고객 대기열에 접근하는 은행원이 2명이므로 semaphore를 이용해 한 명의 은행원만 대기열에 접근할 수 있도록 쓰레드를 제한하였습니다.

```swift
let depositSemaphore = DispatchSemaphore(value: 1)
```

- 실행화면(최초 실행 후, 종료)
![](https://i.imgur.com/9kfjoi9.gif)
- 실행화면(최초 실행 후, 재실행)
![](https://i.imgur.com/wKwQkV8.gif)


---

## 🚀 5. 트러블 슈팅(고민한 점)

<details>
<summary>1. 타입별 인스턴스 초기화 위치에 대한 문제</summary>
<div markdown="1">

- 기존에는 아래와 같은 순서로 타입의 인스턴스를 초기화 했습니다.
  - 1. main 소스파일에서 모든 타입의 인스턴스 초기화
  - 2. 의존하고 있는 타입의 인스턴스에 해당 인스턴스를 할당

- 하지만 객체지향 프로그래밍의 관점으로 보았을 때, 이는 main 소스파일이 책임 이상의 역할을 하고있어 단일 책임 원칙을 위반하고 있다고 생각했습니다.
(모든 타입의 인스턴스 생성)

- 그래서 각 타입의 인스턴스는 참조하고 있는 타입의 인스턴스 `init()` 내에서 초기화되도록 리팩토링하였습니다.

```swift
// BEFORE
// main 소스파일
let bankWorker: BankWorker = BankWorker()
let bankManager: BankManager = BankManager(bankWorker: bankWorker)
var bank: Bank = Bank(bankManager: bankManager)

bank.openSystem()
    
    
// AFTER
// main 소스파일
var bank: Bank = Bank()

bank.openSystem()

// Bank 타입
struct Bank {
    private var bankManager: BankManager = BankManager()
...
    
}
 
// BankManager 타입
struct BankManager {
    ...
    
    init() {
        var currentDepositWorkerCount: Int = 0
        
        for _ in 0..<(DepositWorkerNumber.max + LoanWorkerNumber.max) {
            if currentDepositWorkerCount < DepositWorkerNumber.max {
                let bankWorker: BankWorker = BankWorker(bankWork: .deposit)
                
                bankWorkers.append(bankWorker)
                currentDepositWorkerCount += 1
            } else {
                let bankWorker: BankWorker = BankWorker(bankWork: .loan)
                
                bankWorkers.append(bankWorker)
            }
        }
    }
...

}
```
    
</div>
</details>
</br>

<details>
<summary>2. 유닛 테스트 메서드 네이밍에 대한 문제</summary>
<div markdown="1">
    
- 테스트 메서드 이름만 보고 어떤 테스트인지 파악할 수 있는 네이밍을 하고 싶었습니다.
- 테스트 메서드 이름이 입력값과 결과값에 디펜던시가 없도록 `should_기대결과_When_테스트상태` 방식으로 네이밍을 하였습니다.
- `test_list에있는노드값을_search로호출했을때_해당노드값을_반환해야한다()`

</div>
</details>
</br>

<details>
<summary>3. 'DispatchSemaphore' 사용에 대한 문제</summary>
<div markdown="1">

- STEP3에서는 2명의 은행원이 동시에 예금 업무를 진행하기 때문에 race condition을 방지할 방법이 필요했습니다.

- GCD를 활용해 동시성 프로그래밍을 구현했기 때문에 공유자원에 접근하는 스레드의 수를 제한해주는 'DispatchSemaphore' 사용해 thread safe하게 작동할 수 있도록 구현하였습니다.
    
```swift
struct BankManager {
    
    private let depositSemaphore = DispatchSemaphore(value: 1)
    
    ...
    
    private func doDepositWork(by worker: BankWorker) {
        DispatchQueue.global().async(group: group) {
            while !depositClientQueue.isEmpty {
                depositSemaphore.wait()
                guard let client = depositClientQueue.dequeue() else {
                    depositSemaphore.signal()
                    print("업무를 처리할 예금 고객이 없습니다.")
                    return
                }
                
                depositSemaphore.signal()
                worker.startWork(for: client)
            }
        }
    }
}   

```

- 위 코드와 같이 BankManager 타입 내 예금 고객 대기열에 접근하는 스레드 수를 제한하는 `depositSemaphore` 프로퍼티를 정의했습니다.

- 은행원이 예금 고객 대기열에서 dequeue하기 전에, `depositSemaphore.wait()` 로 다른 은행원이 예금 고객 대기열에 동시에 접근할 수 없게 하였습니다.

</div>
</details>
</br>

<details>
<summary> 4. 모든 업무가 끝나기 전에 은행업무 종료 메시지를 실행하는 문제 </summary>
<div markdown="1">
    
- 은행 업무가 끝나기 전에 은행업무 종료 메시지 및 메뉴를 출력하는 문제가 발생했습니다.
    
![](https://i.imgur.com/2FLg3Se.png)
    
- 아래 코드와 같이 DispatchGroup()을 정의하여 모든 업무에 DispatchQueue 그룹을 설정 및 `group.wait()` 를 사용하여 해결하였습니다.
    
```swift
private let group: DispatchGroup = DispatchGroup()

private func doDepositWork(by worker: BankWorker) {
    DispatchQueue.global().async(group: group) {
        // 예금업무 수행 코드
    }
}
    
private func doLoanWork(by worker: BankWorker) {
    DispatchQueue.global().async(group: group) {
        // 대출업무 수행 코드
    }
}

struct BankManager {
    ...
    
  private func assignBankWork() {
    for worker in bankWorkers { 
       switch worker.bankWork {
         case .deposit:
            doDepositWork(by: worker)
         case .loan:
            doLoanWork(by: worker)
         default:
           return
        }
    }
        
        group.wait()
    }
}
```


</div>
</details>

### 

---

## 📎 6. 참고 링크
- [UML: 클래스 다이어그램과 소스코드 매핑](https://www.nextree.co.kr/p6753/)
- [Linked List](https://ko.wikipedia.org/wiki/%EC%97%B0%EA%B2%B0_%EB%A6%AC%EC%8A%A4%ED%8A%B8)
- [Queue](https://ko.wikipedia.org/wiki/%ED%81%90_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0))
- [The Swift Programming Language: Generic](https://docs.swift.org/swift-book/LanguageGuide/Generics.html)
- [야곰닷넷 - 동시성 프로그래밍](https://yagom.net/courses/%eb%8f%99%ec%8b%9c%ec%84%b1-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d-concurrency-programming/)
- [유닛테스트 네이밍 짓는 방법](https://dzone.com/articles/7-popular-unit-test-naming)