![[Sudoku] DDD보다는 RDD로 리팩터링](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8MnGr%2FbtsplO2wO3U%2FpSZWDVick4fQB4yqbtmdOk%2Fimg.png)
객체지향의 사실과 오해이라는 책을 읽다보면 아래와 같은 문구가 있습니다
데이터를 먼저 결정하고 객체의 책임을 결정하는 방법은 유연하지 못한 설계라는 악몽을 초래한다. 흔히 책임-주도 설계(RDD: Responsibility-Driven Design)라고 부르는 객체지향 설계 방법은 데이터를 먼저 생각하는 데이터-주도 설계(DDD: Data-Driven Design) 방법의 단점을 개선하기 위해 고안됐다
책에서 계속 언급되는 되는 부분은 객체가 외부에 제공해야 하는 행동을 먼저 생각하고 데이터를 구현해야 한다고 저자는 말하고 있었습니다. 데이터를 우선 생각하고 구조체, 클래스를 만들다 보면 데이터에 맞게끔 메서드를 구현하게 되고 더 복잡해지는 경험을 하게 되었습니다.
- 어떤 행위를 수행할 것인지
- 정해진 행위를 누가 수행할 것인지
책에서는 더 많은 내용을 담고 있지만, 본문에서는 RDD의 중요성을 좀 더 이야기하고자 합니다.
이와 비슷하게 유튜브에서 뇌과학자 박문호 박사님도 동일한 이야기를 하고 있습니다.
5 + 3 = 8 이라는 것은 더하기라는 연산이 있고 3과 5라는 숫자가 있다. 존재는 관계의 그림자이다. 사칙 연산이 있고 연산의 대상으로 숫자가 있다.
모든 학문은 집합과 관계로 이루어져 있다고 말합니다. 집합론적 사고를 하게되면 상세하게 들어가더라도 집합과 관계로 표현을 하게되면 이해하는 것도 쉽다는 이야기를 하고 있습니다.
관계를 이해하고 변화를 이해하며, 패턴을 찾아라
관계에 대해서는 상관관계와 수반관계, 인과관계 등으로 표현이 되는데, 관계를 이해를 해야한다 말합니다.
또한 변화를 이해하는 과정에서는 만들어지고 분해되는 과정을 결정하는 것은 조건으로, 물리와 현상을 구분하라고 합니다. 여기서 물리(개발에서 적용하게 되면 시간적인 흐름 혹은 사용자의 행동 등)적인 무언가로 변화가 발생하는데, 변화(메서드 혹은 함수 내부에 어떻게 변화할지)를 표현할 수 있을 거 같습니다.
그리고 단위가 연결되어 집합이 이루어 지며, 이러한 패턴을 찾으라고 합니다.


사실 영상을 보면서 개발과는 전혀 관계가 없는 내용일 수 있으나, 와닿았던 부분은 3가지가 있습니다.
- 관계(메서드)를 생각하고 변화(메서드 내부 구현 및 객체 데이터 변화 등)를 이해
- 패턴(프로토콜 및 재사용성 코드를 위한 분리 등)을 찾기
- 1번과 2번의 이해로 코드를 작성하는데, 더 매끄럽게 표현 가능성
철학적인 부분이었지만, 집합론적 사고가 개발하는데 있어서 도움이 될 것이라 생각이 들었습니다
이미 대략적인 그림만 그린 채 시작된 Sudoku 라는 프로젝트가 있습니다.
동작은 단순하다 생각했으나 구현을 하다보니 상세 동작이 제대로 되지 않은 문제점을 발견했습니다.
- 보드판의 데이터가 단순하지 않게 기획
- 동작에 대해 상세하지 않게 기획
- 데이터 기반으로 생각을 하다보니 놓치는 부분이 발생
- 비슷한 구조로 사용되는 타입들
모델 내에 메서드(행동) 기반으로 다이어그램을 그리면서 리팩터링했습니다.


중간에 Controller가 생략된 관계입니다.
물론 상세하게 들어가면 아래와 같은 관계로 끝나지는 않습니다.
- SquareButton ➔ Change Cursor ➔ Associated Coordinates ➔ Change SqaureView background
- NumberButton ➔ MemoToggle == false ➔ Change Number ➔ Change NumberLabel
- NumberButton ➔ MemoToggle == true ➔ Change Memo ➔ Change MemoLabels
- UndoButton ➔ Undo ➔ Change BoardView
- EraseButton ➔ Chage Number & Memo ➔ Change NumberLabel & MemoLabels
동작에 대해서 상세 부분까지 매개변수는 최대 2개까지로 제한을 하여 구현하였습니다.
매개변수를 줄이기 위해서는 구조체를 만들어서 사용하게 되었습니다. 예시를 하자면, 필요한 데이터는 NumberButton을 누를 때, 위치를 나타내는 row, col과 어떤 숫자의 버튼인지 Number 총 3개의 매개변수가 필요했지만, 해당 값을 Cursor라는 구조체를 만들어서 row와 col의 값을 가지도록 표현했습니다.
물론 더 많은 내용이 있지만, 자세한 내용을 깃허브에 기재를 했습니다.
1. 보드에서 사용되는 데이터를 단순화
메서드에서 사용되는 좌표가 두 타입이 존재하면서 변환하는 작업으로 복잡해지는 구조를 가졌습니다.
BoardView에 전달하기 위한 타입이 있고 Board에서 사용하는 타입이 따로 존재하였습니다.
좌표를 나타내는 타입을 하나로 통합하면서 메서드가 단순해졌습니다.
// Before
struct Coordinate {
let compass: Int
let index: Int
}
struct Cursor {
let row: Int
let col: Int
}
// After
struct Coordinate {
let indexOfBoard: Int
let compass: Int
let index: Int
init(indexOfBoard: Int) {
self.indexOfBoard = indexOfBoard
let row = indexOfBoard / 9
let col = indexOfBoard % 9
self.compass = row / 3 * 3 + col / 3
self.index = row % 3 * 3 + col % 3
}
init(compass: Int, index: Int) {
self.compass = compass
self.index = index
let row = compass / 3 * 3 + index / 3
let col = compass % 3 * 3 + index % 3
self.indexOfBoard = row * 9 + col
}
}
Board 내에서 데이터를 관리하는 메서드가 복잡하였습니다.
Coordinate에서는 copass와 index로 표현이 되었습니다.
compass는 3 * 3으로 방향을 나타내며, index는 내부에 9개의 button를 나타냅니다.
Coordinate는 BoardView를 신경써서 만든 구조로 메서드에서 처리할 때 동작이 더 늘어나게 되었습니다.
Model과 View의 관계를 신경쓰지 않도록 변경하니 메서드가 더욱 간단해지는 것을 확인하였습니다.
// Before
struct Record: Equatable {
var board: [Int?]
var memo: [Coordinate: [Bool]]
}
// After
struct Record: Equatable {
typealias BoardIndex = Int
var board: [Int?]
var memo: [BoardIndex: [Int]]
}
여기서 느끼게 됩니다.
데이터와 동작에 대해서 같이 고민을 하게되다 보니 View를 우선적으로 생각하게 되고, 보여지는 데이터에 맞게 표현하다보니 불필요한 데이터가 2개로 늘어나고 관리를 하기 위해서 메서드가 증가하다보니 비효율적인 모습을 갖추게 되었습니다.
물론 지금 리팩토링이 만족스럽지는 않지만 이전보다는 더 좋은 모습을 한다고 생각이 듭니다.
우선 Model의 경우라면 어떤 행동을 해야할 지 고민을 먼저하고 그 행동에 맞는 데이터의 타입을 정하게 되는 것입니다.
결국 데이터를 생각하면 더 불필요한 메서드가 증가하고 억지로 맞춰지는 느낌이 듭니다.
2. 동작에 대해 상세하지 않게 기획
MVC에서 Model은 View와 데이터 분리를 어떻게 잘할 수 있는지 물어보니, 같이 공부하는 @Mason의 말이 제일 와닿았습니다.
Model은 Console에서 동작할 수 있도록 구현한다고 생각하면 좋아요
Model을 구현할 때, Console에서 작동하려면 어떻게 구현하면 될 지 고민을 하게 되었습니다.
또한, 이전 행동으로 되돌리는 동작에서 문제가 발생했습니다.
데이터가 변화할때마다 Stack에 넣어줬습니다. 뒤로가기를 계속 하다보니 Stack에 제일 먼저 들어간 Default 값까지 지워지게 되면서 오류가 발생하는 문제가 발생했습니다. 해결하기 위해서는 Pop() 동작 시, 아래와 같은 조건이 필요했습니다.
- top과 default 값이 같은지 비교
- stack의 count가 1인지
위와 같은 조건으로 탈출을 하게되었습니다.
게임에서는 무한정으로 뒤로가기를 방지하기 위해 초기 20개로 제한, 넘어가면 오래된 데이터 삭제하는 Stack을 구현
3, 4번의 경우에는 위에서 1번과 2번을 해결하면서 동시에 해결되었습니다.
메서드 구현 시, 테스트 케이스를 더 다양하게 확인할 수 있도록 시야를 넓게 가져야 겠다고 생각이 들었습니다.
코드를 볼 때 리팩터링 모자를 쓰고 코드를 점검하게 되는 계기를 가진 거 같아서 많은 것을 얻었습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!