2. else 키워드를 사용하지 마라

소트웍스 앤솔로지Object Calisthenics 내용을 TypeScript로 적용해봤습니다.

대부분의 프로그래밍 언어는 if/else 구문을 지원한다. 하지만 조건문이 중첩될수록 가독성은 떨어지기 마련이다. else if를 넣어 조건문의 분기를 추가하는 것은 코드를 리팩토링하는 것보다 훨씬 쉽지만 이는 가독성이 떨어지고 유지보수하기 어려운 코드를 만들어내곤 한다.

else가 있는 다음 코드를 살펴보자.

const login = (username: string, password: string) => {
  if (userRepository.isValid(username, password)) {
    redirect('homepage');
  } else {
    addFlash('ERROR', '잘못된 인증 정보입니다.');
    redirect('login');
  }
};

이런 경우 else 구문을 쉽게 없애는 방법은 early return 기법을 적용하는 것이다.

const login = (username: string, password: string) => {
  if (userRepository.isValid(username, password)) {
    return redirect('homepage');
  }
  addFlash('ERROR', '잘못된 인증 정보입니다.');
  return redirect('login');
};

이제 예외처리를 하는 경우와 기본값으로 실행되어야 하는 경우에 대해 생각해보자.

  1. 예외를 먼저 처리하고 마지막에 기본값으로 실행시킬 것인가? (방어적 프로그래밍과 유사)

  2. 기본값을 먼저 처리하고 예외처리 로직을 코드 밑부분에 이을 것인가? (낙관적 프로그래밍)

어려운 말이 아니고 말 그대로 예외를 위에서 처리하는 것이다. 위에 제시된 코드의 경우 isValid하면 redirect("homepage")하고 그 외의 예외의 경우 addFlashredirect("login")를 실행하게 된다.

const login = (username: string, password: string) => {
  if (!userRepository.isValid(username, password)) {
    addFlash('ERROR', '잘못된 인증 정보입니다.');
    return redirect('login');
  }
  return redirect('homepage');
};

또는 return이 싫거나 함수의 반복(이 경우 redirect)을 줄이고 싶을 경우 이런 방법도 가능하다.

const login = (username: string, password: string) => {
  let redirectRoute = 'homepage'
  if (!userRepository.isValid(username, password)) {
    addFlash('ERROR', '잘못된 인증 정보입니다.');
    redirectRoute = 'login';
  }
  redirect(redirectRoute);
};

필자의 경우는 early return을 하는 방어적 프로그래밍의 방식을 선호한다. 순서만 바뀌었을뿐이지만, 개인적으로 예외상황을 먼저 처리한다면 null이나 undefined를 처리할 때 TypeError를 잡기도 용이하고 기본 로직을 확장하기 좋다고 생각한다.

객체지향 프로그래밍의 [다형성](https://ko.wikipedia.org/wiki//다형성_(컴퓨터_과학))을 활용한 상태 패턴전략 패턴을 사용하는 방법도 있다. 이런 패턴들을 사용한다면 상태(Unauthorized, Authorized)에 따라 수행되는 로직(next)을 캡슐화 할 수 있고, 코드를 수직이 아닌 수평적으로 확장할 수 있다. 사실 적당한 예시인지 모르겠지만 주요 디자인 패턴을 다룰 때 살펴보도록 하자.

interface RedirectStatus {
  next: () => void;
}

class Authorized implements RedirectStatus {
  next() {
    return redirect('homepage');
  }
}

class Unauthorized implements RedirectStatus {
  next() {
    addFlash('ERROR', '잘못된 인증 정보입니다.');
    return redirect('login');
  }
}

class Page {
  constructor(private status: RedirectStatus = new Authorized()) {}

  setStatus(status: RedirectStatus) {
    this.status = status;
  }

  handleNext() {
    return this.status.next();
  }
}

const page: Page = new Page();

const login = (username: string, password: string) => {
  if (!userRepository.isValid(username, password)) {
    page.setStatus(new Unauthorized());
  }
  page.handleNext();
};

Last updated