BudgetUseCase 리펙토링 방향 #263
Replies: 3 comments 2 replies
-
|
Discussion 활용을 위해 기존 노션에 작성했던 내용을 옮겨놓았습니다~ |
Beta Was this translation helpful? Give feedback.
-
|
넵 옮겨주셔서 감사합니다! |
Beta Was this translation helpful? Give feedback.
-
|
쓰신 글을 보면서 프로토콜을 분리해봤는데, 한 번 검토해보시고 구현에 참고할 수 있으면 참고하셔도 될 것 같아요! protocol BalanceManagementUseCase: DefaultHarubeeCalculatable {
/// 잔액을 조정합니다.
func updateBalance(
salaryBudget: SalaryBudget,
newBalance: Int
) throws -> SalaryBudget
/// 지출 및 수입을 기록합니다.
func recordTransaction(
expense: Int?,
income: Int?,
date: Date,
salaryBudget: SalaryBudget
) throws -> (DailyBudget, SalaryBudget)
} protocol FixedTransactionManagementUseCase: DefaultHarubeeCalculatable {
/// 고정 수입을 업데이트합니다.
func updateFixedIncome(
salaryBudget: SalaryBudget,
newIncome: Int
) throws -> SalaryBudget
/// 고정 지출을 업데이트합니다.
func updateFixedExpenses(
salaryBudget: SalaryBudget,
expenses: [TransactionItem]
) throws -> SalaryBudget
/// 수입일을 업데이트합니다.
func updateIncomeDay(
day: Int,
salaryBudget: SalaryBudget
) throws -> SalaryBudget
/// 수입일을 저장합니다.
func setIncomeDay(day: Int)
/// 저장된 수입일을 조회합니다.
func getIncomeDay() -> Int?
} protocol HarubeeAdjustmentUseCase: DefaultHarubeeCalculatable {
/// 특정 날짜의 하루비를 조정합니다.
func adjustHarubee(
amount: Int?,
date: Date,
salaryBudget: SalaryBudget
) throws -> (DailyBudget, SalaryBudget)
} protocol MemoManagementUseCase {
/// 메모 리스트를 업데이트합니다.
func updateMemoList(
memoList: [String],
dailyBudget: DailyBudget
) throws -> DailyBudget
/// 특정 날짜의 DailyBudget을 조회합니다.
func getDailyBudget(date: Date) throws -> DailyBudget
} protocol OnboardingUseCase {
/// 온보딩에서 최초 SalaryBudget을 생성합니다.
/// - Parameters:
/// - startDate: 시작일
/// - endDate: 종료일
/// - currentBalance: 현재 잔액
/// - fixedIncome: 고정 수입
/// - fixedExpenses: 고정 지출 내역
/// - Throws: DomainError.duplicateData - 동일 기간의 SalaryBudget이 존재하는 경우
func createInitialSalaryBudget(
startDate: Date,
endDate: Date,
currentBalance: Int,
fixedIncome: Int,
fixedExpenses: [TransactionItem]
) throws -> SalaryBudget
} protocol SalaryBudgetManagementUseCase {
/// SalaryBudget을 생성합니다.
/// 이 부분은 currentBalance를 파라미터로 받지 않는 메서드이겠죠?
/// 현재 기간의 SalaryBudget을 조회합니다.
func getCurrentSalaryBudget(date: Date?) throws -> SalaryBudget
/// 모든 SalaryBudget을 조회합니다.
func getAllSalaryBudgets() throws -> [SalaryBudget]
/// 모든 SalaryBudget을 삭제합니다.
func deleteAllSalaryBudgets() throws
}
protocol DefaultHarubeeCalculatable {
func calculateDefaultHarubee(
salaryBudget: SalaryBudget,
anchorDate: Date
) -> Double
}이런 형태의 프로토콜을 만든 다음, 확장으로 상세 구현체를 만들어서 유즈케이스가 이 프로토콜을 채택하게 하는 식으로 사용하는 걸 생각해보았습니다! 시간이 많지는 않아서 자세하게 검토해보진 못했습니다! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
기존 생각했던 리펙토링 방향
이전에 BudgetUseCase를 한번 리펙토링을 진행한 후 추가적으로 리펙토링을 진행할 계획이었습니다.
하고자 했던 방향은 BudgetUseCase에 구현된 로직들을 각 Model 별로 나눠 SalaryBudgetUseCase와 DailyBudgetUseCase로 분리하고자 했습니다.
결과적으로 아래 코드와 같이 리펙토링을 생각했습니다.
위처럼 각 모델별로 독립적으로 비즈니스 로직을 담당하고자 했지만 아래와 같은 부분들로 인해 기존 리펙토링 방향을 고민하게 되었습니다.
문제 1) DailyBudget의 변경사항이 SalaryBudget에도 영향을 끼친다.
위 문제에 대한 상황들은 다음과 같습니다.
위 두 상황 모두 DailyBudget을 업데이트(하루비 및 지출,수입 금액)한 후 그에 따라 SalaryBudget을 업데이트(기본 하루비)를 업데이트하는 순서를 가지고 있습니다.
즉, DailyBudget의 비즈니스 로직을 수행 후 SalaryBudget의 비즈니스 로직을 수행하고 있습니다.
그렇기에 위에서 처음 세웠던 방향성대로 각 모델별로 책임을 명확히 나눠서 구현하는데는 어려움이 있다고 생각했습니다.
(하지만, 이 문제는 각 UseCase를 구현한 후 ViewModel에서 비즈니스 로직 흐름에 맞게 호출해주면 되긴 할 거 같습니다.)
지금 이 문제로 인해 UseCase 리펙토링 방향성에 대해 고민을 시작했지만, 다음 두 번째 이유로 인해 더 나은 리펙토링 방향이 무엇일까를 깊이 고민하게 되었습니다.
문제 2) 과연 SRP를 준수하고 있는가?
아래 사진은 각 뷰가 호출하는 UseCase의 메서드를 연결지은 것입니다.
(UseCase 메서드에 칠해진 형광색은 둘 이상의 화면에서 사용되고 있는 메서드를 표시한 것입니다.)
BalanceAdjustView를 예시로 들면, updateBalance() 메서드 하나를 사용하기 위해 나머지 사용하지 않는 메서드를 불필요하게 소유하고 있음을 확인할 수 있습니다.
BalanceAdjustView에서는 잔액을 변경할 책임(updateBalance())만 필요하고 그 외는 필요가 없기에 위 구조는 SRP를 준수하고 있지 않다고 판단했습니다.
(다른 뷰를 봐도 불필요한 책임을 가지고 있다고 판단했습니다)
거래 내역 입력 로직을 변경해야 하거나 하루비 조정 로직, 그외 다른 로직들을 변경해야할 상황이 오던 결국에는 BudgetUseCase 클래스를 수정하게 될 것입니다.
이는 SRP의 원칙인
클래스(또는 메서드)의 변경 이유(여기서 변경의 이유는 액터 기준)는 오직 하나여야 한다.에서 벗어난다고 생각했습니다.새롭게 생각해 본 방향성은?
UseCase에 대해 다시 이해해보고자 정의에 대해 검색해본 결과 다음과 같이 설명하고 있습니다.
따라서, 기존 각 모델 중심으로 UseCase를 구현하고자 했던 방향에서 행위 중심으로 UseCase를 구현하는 방향을 생각했습니다.
추가적으로 이전에 한번 진행했던 UseCase 리펙토링 작업에서 공통 코드들을 최대한 재사용할 수 있도록 분리하는 작업을 수행했었습니다.
하지만, 최근 읽었던 ‘클린 아키텍처’ 책 내용에서 아래 문장이 눈에 들어왔었습니다.
따라서 리팩토링 작업을 새로 진행하게 되면 위 문장에 대해 다시 한번 생각하면서 기존에 재사용을 위해 분리했던 중복 코드들을 다시 살펴보는 과정을 추가적으로 진행해보려고 합니다.
아래는 제가 새롭게 구상해본 UseCase 입니다.
1) 온보딩 작업
온보딩 흐름에서 발생할 비즈니즈 로직을 포함시켰습니다.
현재는 온보딩에서 생성될 SalaryBudget, 수입일이 변경됨에 따라 새로 생성될 SalaryBudget, 이번 기간의 SalaryBudget이 없다면 생성할 SalaryBudget 모두 createSalaryBudget() 메서드를 공통적으로 호출했습니다.
하지만, 위에서 말씀드렸던
진짜 중복인지 확인하라문구를 생각했을 때, 온보딩에서 생성되는 SalaryBudget 흐름은 메인 뷰와 독립된 흐름을 가지기에 결이 다르다고 생각했습니다. (현재 생각은)왜냐하면, 온보딩 흐름 변경에 따라 로직이 수정될 가능성이 있을 것이라 생각했습니다.
그래서 온보딩 화면 흐름에서 사용될 createSalaryBudget을 따로 구현해볼 생각입니다.
2) SalaryBudget 관리
SalaryBudget을 새로 생성하고 불러오고 삭제하는 CRD 작업을 포함시켰습니다.
단순히 SalaryBudget 자체 타입을 관리하는 작업으로 분리시켜보았습니다. (하위 프로퍼티 조작 X)
3) 메모 리스트 작업
일별 화면에서 메모를 작성하고 삭제하는 등의 작업을 포함시켰습니다.
4) 잔액 조정 작업
홈 화면에서 잔액 조정 작업, 지출 및 수입 입력 작업을 포함시켰습니다.
잔액 조정, 지출 및 수입 입력 작업을 따로 분리시켜볼까를 생각했었는데, 지출 및 수입을 입력하게 되면 연쇄적으로 잔액도 같이 수정해야 하기에 하나의 유스케이스로 묶어보았습니다.
5) 하루비 조정 작업
하루비 조정 작업을 포함시켰습니다.
6) 고정 내역 관리
고정 수입, 수입일, 지출 내역을 관리하는 작업을 포함시켰습니다.
(현재 TodayViewModel에서 getIncomeDay() 메서드를 호출하고 있기 때문에 TodayView에서도 위 UseCase를 의존하고 있습니다. 하지만, TodayViewModel의 로직을 리펙토링하게 된다면 getIncomeDay() 메서드 자체가 필요없어질 수 있을 거 같습니다.)
고정 내역 관리에 대해서 다른 이야기를 하자면,
현재 고정 수입, 지출 내역을 이번 기간의 SalaryBudget에서 관리하고 있습니다.
따라서 다음 달의 SalaryBudget을 생성할 때 이전달의 SalaryBudget을 참고해야만 고정 수입, 지출 내역을 불러올 수 있습니다.
그런데 저는 이것이 논리적인 흐름이 맞지 않다고 생각했습니다. 고정 내역을 관리하는 객체를 만들어서 고정 내역을 업데이트하거나 새로운 SalaryBudget을 생성해야할 때 이 객체를 통해 데이터를 불러오는 것이 논리적 흐름에 적절하다고 생각합니다.
따라서 후에 고정 내역을 관리하는 객체를 만들어서 이를 통해 데이터를 불러오고 업데이트 하는 것이 적절해 보이는데 어떻게 생각하시는지 궁금합니다.
7) 그 외
위에 calculateDefaultHarubee()는 포함이 되지 않았습니다.
이 메서드는 updateBalance(), updateFixedIncome, updateFixedExpenses(), adjustHarubee()에서 사용되고 있는데, 이를 protocol로 구현 후 각 유스케이스에 채택하여 사용하는 방식으로 구현해보고자 합니다.
Manager 객체를 구현해서 메서드를 호출하는 방법도 생각해 보았는데, 위와 같이 protocol로 구현한 이유는 다음과 같습니다.
한번 오래 고민해보고 설계를 해보았는데, 위 방식에 대해 다들 동의해주시면 바로 작업 들어가볼 예정입니다.
물론 위 방법이 100퍼센트 정답이라고는 할 순 없겠지만, 그래도 이 전과 비교하여 책임을 좀더 명확히 분리하면서 SRP를 준수했다고 생각합니다.
그렇기에 이후 테스트나 코드를 수정하게 될 상황에서 좀 더 원할하게 작업이 이루어지지 않을까라는 기대를 하고 있습니다..!!
Beta Was this translation helpful? Give feedback.
All reactions