리팩토링Tell Dont Ask(TDA) 원칙
- TDA란객체의 상태나 데이터를 외부에서 직접 요청하거나 변경하지 않고, 객체에 메소드를 호출하여 그 객체가 스스로 책임지고 행동하도록 해야 한다는 원칙이다.
class BankAccount {
private int balance;
public int getBalance() {
return balance;
}
public void setBalance(int amount) {
balance = amount;
}
}
class AccountManager {
public void processAccount(BankAccount account) {
if (account.getBalance() > 1000) {
// 높은 잔액을 처리하는 로직
} else {
// 잔액이 적을 때의 처리
}
}
}
위 코드에서는 AccountManager 가 BankAccount 의 잔액을 확인하고, 그 값을 기반으로 로직을 처리하고 있습니다. TDA의 “asking” 방식
class BankAccount {
private int balance;
public void processTransaction() {
if (balance > 1000) {
// 잔액이 많을 경우 처리
} else {
// 잔액이 적을 경우 처리
}
}
}
class AccountManager {
public void processAccount(BankAccount account) {
account.processTransaction();
}
}
- 이 코드에서는 AccountManager가 BankAccount에 대해 상태를 직접 확인하지 않고, BankAccount 객체가 자체적으로 상태를 관리하며 로직을 처리하도록 했습니다. 이는 "telling" 방식입니다.
장점
- 캡슐화: 객체가 자신을 어떻게 처리할지 책임지게 하여, 외부에서 객체의 내부 상태에 접근하는 것을 줄일 수 있다. 쉽게 말해 “나쁜 예” 에서 내부( account.getBalance )에서 로직을 바꾸면 processAccount 로직도 변경해야 할 수도 있다. 반대의 경우도 마찬가지이다. 외부에서에 account,getBalance() 에 접근해 값을 변경하는 경우 여기서는 딱 보이지만, 스케일이 커질 때, 모든 코드를 하나씩 확인해서 처리하는 건 매우 힘들고 어려울 것이다.
- [좋은 예(Tell)]
- [나쁜 예(ASK)]
- 객체 지향 설계에서 중요한 개념 중 하나.
[devices/timer.c][threads/thread.c]
- 현재 구현에서 TDA 원칙을 위반하는 부분은 timer_interrupt 함수에서 get_next_tick_to_awake() 를 호출하여 외부에서 next_tick_to_awake 값을 직접 가져오는 부분이다. 이는 timer_interrupt 가 next_tick_to_awake 를 직접 확인하는 방식으로 TDA 원칙에서 말하는 “asking”에 해당한다.
- 이 문제를 해결하려면 next_tick_to_awake 값을 외부에서 가져오는 대신, 해당 객체나 구조체(여기서는 thread 나 timer 관련 객체)에게 상태를 확인하도록 “tell” 방식으로 설계해야 한다. 즉, 객체가 스스로 언제 깨어날지 결정하고 그에 따라 행동하도록 해야 한다.
[해결방법]
- thread 나 timer 가 “스스로 다음 깨어날 시간을 확인하고, 그에 따라 행동하게” 만들어야 한다.”
- timer_interrupt에서 직접 next_tick_to_awake 값을 조회하는 대신, thread 나 timer 객체에 “다음 깨어날 시간을 결정”하도록 요청하는 방식으로 변경해야 한다.
- thread 객체에서 next_tick_to_awake 를 관리하도록 리팩토링
- thread 나 timer 가 스스로 다음 깨어날 시간을 계산하고, timer_interrupt 에서는 그 시간에 맞춰 작업을 수행하도록 할 수 있다.
/* Timer interrupt handler */
static void
timer_interrupt (struct intr_frame *args UNUSED) {
ticks++;
thread_tick ();
/* project1-Alarm Clock */
thread_check_awake(ticks); // next_tick_to_awake와 관련된 로직을 thread가 처리하도록 위임
}
/* thread.c */
void thread_check_awake(int64_t current_ticks) {
if (next_tick_to_awake <= current_ticks) {
thread_awake(current_ticks);
}
}
- thread에 get_next_tick_to_awake 를 요청하지 않고, thread 가 스스로 처리하도록 수정
/* thread.c */
void
thread_awake (int64_t wakeup_tick)
{
next_tick_to_awake = INT64_MAX;
struct list_elem *sleeping;
sleeping = list_begin(&sleep_list);
while (sleeping != list_end(&sleep_list)) {
struct thread *th = list_entry(sleeping, struct thread, elem);
if (wakeup_tick >= th->wakeup_tick)
{
sleeping = list_remove(&th->elem);
thread_unblock(th);
}
else
{
sleeping = list_next(sleeping);
update_next_tick_to_awake(th->wakeup_tick);
}
}
}
/* thread.c */
/* threads/thread.c */
void thread_check_awake(int64_t current_ticks) {
if (next_tick_to_awake <= current_ticks) {
thread_awake(current_ticks);
}
}
- thread_awke 함수가 next_tick_to_awake 값을 업데이트하거나, thread 객체가 스스로 다음 깨어날 시간을 계산하도록 설계할 수 있다.