티스토리 뷰

 

지난 2장 포스팅에 이어 3장을 정리한다.

프론트엔드 관련해서 정리할 것도 밀렸는데, 객체지향 글을 쓰고 있으니 약간의 죄책감이 느껴진다.

솔직히 이게 맞을까 하는 의구심도 들지만, 스터디에서 힘들게 배웠던 내용을 잊고 싶지 않아 정리하려 한다.

그만큼 이 개념들이 내 코딩 스타일에 유의미한 변화를 줄 수 있을 것이라 믿고 있다.

아무튼 본론으로 돌아와, 이번 장의 원제는 Employment이고 번역본 상으로는 직장생활이다.

 

저자가 매 챕터 이름에 의미를 부여하는 것 같은데, 이번 챕터 이름은 어떤 의미인지 도무지 모르겠다.

추측은 일 잘하는 객체 만드는 법 정도가 아닐까 싶은데, 좀 억지 같다. 😅

 

이번 장의 목차는 다음과 같다.

1. 5개 이하의 public 메서드만 노출하세요
2. 정적 메서드를 사용하지 마세요
3. 인자의 값으로 NULL을 절대 허용하지 마세요
4. 충성스러우면서 불변이거나, 아니면 상수이거나
5. 절대 getter와 setter를 사용하지 마세요
6. 부 ctor 밖에서는 new를 사용하지 마세요
7. 인트로스펙션과 캐스팅을 피하세요

(작성 편의를 위해 개인적 동의 여부와 상관없이 단정적 어조를 사용했다.)

 

1. 5개 이하의 public 메서드만 노출하세요

갸쟝 우아하고, 가장 유지보수하기 쉽고, 가장 응집도가 높고, 가장 테스트하기 쉬운 객체는 작은 객체이다.

이전에 모든 클래스를 250줄 이하로 유지하라고 했지만, 사실 중요한 기준은 public 메서드의 수이다.

20개의 메서드를 포함하는 50줄의 클래스는 큰 객체라는 뜻이다.

 

작은 클래스는 코드량이 더 적고, 에러를 더 쉽게 찾을 수 있고, 수정하기도 더 쉽다.

또한, 객체의 진입점(메서드)의 수가 적기 때문에 문제를 더 쉽게 고립시킬 수 있다.

모든 시나리오를 테스트할 수 있기 때문에 테스트 용이성도 좋다.

 

작은 클래스는 메서드와 프로퍼티를 더 '가까운 위치'에 모아둘 수 있기 때문에 응집도도 향상된다.

간단히 말해, 각 메서드가 클래스의 모든 프로퍼티를 사용하게 된다.

 

사실 5개라는 숫자에는 큰 이유가 없다.

그냥 메서드의 수가 적어야 한다는 것을 강조하는 것이다.

4번째 메서드를 추가하고 나면 5번째 메서드를 추가하기 전에 잠시 멈춰서서 클래스의 크기를 고민해보라.

여전히 하나의 책임을 수행하는 견고하면서도 응집도 높은 클래스를 다루고 있나?

어쩌면 그 클래스를 더 작게 나눠야 할 때가 됐는지도 모른다.


2. 정적 메서드를 사용하지 마세요

정적 메서드는 절차지향적인 코드를 쓰게 하는 순수 악이다.

애초에 static 키워드가 없었다면 참 좋았을 것 같다.

하지만 애석하게도 정적 메서드는 언어에 포함되어 있으며, 우리 곁에 여전히 살아 숨쉬고 있다.

 

먼저 무슨 이유 때문에 정적 메서드가 계속 유지되고 있는지 살펴보자.

// ⚠️ 정적 메서드를 사용한 방식
class WebPage {
  public static String read(String uri) {
    // HTTP 요청을 만들고
    // UTF-8 문자열로 변환한다
  }
}

String html = WebPage.read("http://www.java.com");

 

// ✅ 객체를 사용하는 방식
class WebPage {
  private final String uri;
  
  public String content() {
    // HTTP 요청을 만들고
    // UTF-8 문자열로 변환한다
  }
}

String html = new WebPage("http://www.java.com").content();

정적 메서드와 객체를 사용하는 방식 사이에 큰 차이가 없다고 생각할지 모른다.

심지어 정적 메서드는 귀찮게 매번 새로운 객체를 생성할 필요가 없고 더 빠르기까지 하다.

게다가 많은 정적 메서드를 WebUtils와 같은 유틸리티 클래스에 모아놓을 수도 있다.

WebUtils에 포함된 메서드들은 사용하기에 쉽고 간편할 것이다.

사용성 측면에서 매우 직관적이고, 모든 사람들이 정적 메서드가 어떻게 동작하는지 쉽게 이해할 수 있다.

WebPage.read()라는 명령문을 읽는 순간 페이지가 로드될 것이라는 사실을 쉽게 눈치챌 수 있다.

 

하지만 안타깝게도 이런 방식은 완전히 틀렸다.

 

문맥과 무관하게 정적 메서드의 사용 여부는 OOP를 전혀 이해하지 못하고 있는 형편없는 프로그래머를 식별할 수 있는 완벽한 지표이다.

어떤 상황에서도 정적 메서드에 대한 변명은 있을 수 없다.

정적 메서드는 객체 패러다임의 남용이다.

성능 역시 중요한 요소가 아니다.

다시 강조하지만, 정적 메서드의 사용을 중단해야 한다.

 

한번 다양한 각도에서 정적 메서드를 살펴보자.

미리 한마디로 요약하자면, 정적 메서드는 소프트웨어를 유지보수하기 어렵게 만든다.

 

2-1. 객체 대 컴퓨터 사고

// c언어
int max(int a, int b) {
  if (a > b) {
    return a;
  }
  return b;
}

이는 개발자들이 수년간 소프트웨어를 작성해왔던 절차적인 방식이다.

이 방식의 장점은 개발자가 CPU와 가까운 위치에서 다음에 수행될 작업을 CPU에게 직접 지시할 수 있다는 점이다.

우리가 결정하고, 컴퓨터는 따른다.

흐름은 항상 순차적이며 스크립트의 위에서 아래로 흐른다.

작은 소프트웨어는 문제 없겠지만, 규모가 커질수록 순차적인 방식으로 사고하는 것이 점점 힘들어진다

 

class Max implements Number {
  private final Number a;
  private final Number b;
  
  public Max(Number left, Number right) {
    this.a = left;
    this.b = right;
  }
}

Number x = new Max(5, 9);

이 코드는 최댓값을 계산하지 않는다.

그저 x가 '5와 9 중의 최댓값이다(is a)'라고 정의했을 뿐이다.

x는 최댓값이다(is a)라고 정의했다는 것이 핵심이다.

CPU에게 계산과 관련된 어떤 지시도 내리지 않았다.

그저 객체를 인스턴스화하고 있을 뿐이다.

이 최댓값을 어떤 방식으로 계산하고 언제 계산하는지는 우리의 통제 밖에 있다.

이처럼 객체지향적으로 생각하기에서는 누가 누구인지만 정의하고, 객체들이 필요에 따라 스스로 상호작용한다.

(반면, 컴퓨터처럼 생각하기에서는 명령의 실행 흐름을 제어할 책임이 우리에게 있다.)

 

int x = Math.max(5, 9);

이런 방식은 끔직할 정도로 잘못된 방식이다.

올바른 객체지향 설계를위해서라면 정적 메서드를 사용해서는 안 된다.