본문 바로가기

책 리뷰/엘레강트 오브젝트

엘레강트 오브젝트 [4장 - (1)]

⌗ 엘레강트 오브젝트 책을 리뷰해보도록 하겠습니다.

책의 내용은 다양한 언어로 되어있지만, 저는 예시 코드로 Typescript 를 사용해 제 사족을 조금씩 붙일 예정입니다.

 

Chapter4. Retirement

 

절대 null 을 반환하지 마세요

 

이건 typescript 에선 null 혹은 undefined 를 반환하지 말라는 얘기로 가능할 것 같습니다.

 

public title(): string | null {
  if (...) {
    return null;
  }
  
  return 'Elegant Objects';
}

 

굳이 객체 지향에 초점을 맞추지 않아도 이런식으로 반환 타입이 되어 있는 메서드나 함수는 참 쓰기가 불편했습니다.

이렇게되면 title 이 반환하는 객체에 우리는 신뢰를 할 수 없습니다. null 도 반환하기에, 특별하게 대우해줘야 합니다.

 

const title = x.title();
console.info(title.length());

 

사실 이런 사용은 typescript 에선 애초에 빌드가 실패해버립니다만, 반환타입이 null 이나 undefined 가 가능한건 꽤나 귀찮아지는 길이긴 합니다.

 

const title = x.title();

if (title === null) {
  ...
}

console.info(title.length());

 

결국은 length 를 찍어야만 하는 상황이라면, 위와 같이 예외 처리를 해줘야겠죠.

객체를 신뢰한다는 것은 객체가 자신의 행동을 전적으로 책임지고 우리는 간섭하지 않는다는 의미가 담겨 있습니다.

반환값을 검사하는 방식은 어플리케이션에 대한 신뢰가 부족하다는 명백한 신호입니다.

신뢰가 필요하지만 null 또는 undefined 는 신뢰를 빼앗아갑니다. 하지만 이런 반환타입을 갖는 메서드들이 왜 있는걸까요?

 

- 빠르게 실패 vs 안전하게 실패

 

빠르게 실패하기와 안전하게 실패하기 라는 두 가지 철학이 존재합니다.

저자는 빠르게 실패하기의 열렬한 지지자이며, 안전하게 실패하기에 대해선 강하게 반대하는 입장이네요.

 

안전하게 실패하기는 버그, 입출력 문제, 메모리 오버플로우 등이 발생한 상황에서도 소프트웨어가 계속 실행될 수 있도록 최대한 노력을
기울일 것을 권장합니다. null 을 반환하는 것도 일종의 그러한 방법이며, 안전하게 실패하기에선 이 상황을 구조하기 위해 노력합니다.

Exception 을 던지는 대신, 누군가 이 상황을 처리할 수 있도록 null 을 반환합니다.

 

빠르게 실패하기는 정반대의 접근 방식을 따릅니다.

상황을 구조하지 않는 대신, 가능하면 실패를 분명하게 만듭니다.

 

사실 저도 어지간하면 빠르게 실패하는 방법을 선호하고 이게 맞다고 보지만, 실무에선 트레이드오프에 의해 안전하게 실패하는 방법을
간혹 선택할 때가 있습니다. 하나의 예를 들어보자면, 빠르게 실패하기의 방법으로 어떠한 아이템의 설정이 잘못된 경우 특정 서버에선 계속해서 500 error 를 던지는데, 무언가 설정이 잘못된걸 빠르게 캐치할순 있지만 결국 수정되기까진 회사의 매출이 계속 빠지게됩니다.

매출은 회사에선 상당히 중요한 부분이기에 이런 손해를 감수하기보단 우선 안전하게 실패하는 방식으로 구현해놓고 다른 형태로 에러를
빠르게 인지할 수 있도록 메트릭을 추가하는 등의 형태를 취하기도 했습니다.

 

따르면 좋은 원칙은 맞지만 실무에선 상황에 따라 개발자가 선택해야 할 부분인 것 같습니다.

 

- null 의 대안

 

null 을 반환하는 방식의 대안으로는 어떤 것이 있을까요?

 

public user(name: string): User | null {
  if (/* db 에서 조회 실패 */) {
    return null;
  }
  return ...;
}

 

만약 db 에서 조회에 실패해 유저 객체를 반환할 수 없을 때, null 이 아닌 에러를 던져버린다면 핸들링하기 나름이겠지만 이런 문제 때문에
전체 어플리케이션이 종료될 수도 있습니다. 이런 상황은 딱히 원하는 상황은 아닙니다.

 

해결 방법 중 하나는, null 을 반환하거나 예외를 던지는 대신 빈 컬렉션을 반환하는 것입니다.

 

public users(name: string): Array<User> {
  if (/* db 조회 실패 */) {
    return [];
  }
  return ...;
}

 

java 의 예시를 약간 변형해봤는데, 이렇게 반환해 클라이언트가 객체를 추출하기 위해 컬렉션에 접근하도록 변경합니다.

근데 이건 users 를 호출한 결과를 Iterable 한 연산을 적용하는 경우엔 상당히 효과적이겠지만, 단일 객체만 필요한 경우엔

원래 null 을 반환하던것과 뭐가 다른지는 모르겠습니다.

 

다른 방법은 null object 디자인 패턴입니다.

 

널 객체 패턴에서는 원하는 객체를 발견하지 못할 경우 겉으로 보기엔 원래의 객체처럼 보이지만 실제로는 다르게 행동하는 객체를 반환합니다. 예를 들면 위의 경우엔, User interface 를 구현하는 NullUser 클래스를 만들고 위와 같은 상황에서 NullUser 를 반환하는 것이죠.
구현할 부분은 조금 늘고 제한된 상황에서만 사용이 가능하나 객체지향적 사고에 어울리는 방법인 것 같습니다.