원문 캡처 · t3.at
원문 캡처 · t3.at

새로운 이야기는 아니지만 되새길 만하다. 생성자는 예외를 던지지 않아야 한다. 비동기 작업을 하거나, 사실상 어떤 부수 효과(side effect)도 일으키지 않는 편이 좋다.

“그런데 Date 생성자는 문자열을 파싱해야 해요!” 문자열을 파싱해야 한다면 파서를 만들고, 그 파서를 사용해 비공개 생성자를 호출하는 팩터리 메서드(factory method) Date.parse(string)를 작성하면 된다.

“그런데 Database 생성자는 데이터베이스에 연결해야 해요!” 그렇다면 Database 클래스를 uninitialized와 connected 상태를 갖는 상태 머신(state machine)으로 만들고, Database.connect() 메서드가 상태를 uninitialized에서 connected로 바꾸게 하면 된다. 단위 테스트도 다루기 쉬워진다.

“그런데 서비스가 API 요청을 해야 해요!” 팩터리 메서드에서 요청을 보내고, 완료된 프로미스의 값을 생성자에 전달하면 된다. 생성자에서 로그를 남기는 것은 괜찮다. 이 원칙을 무조건 고수할 필요는 없다.

왜 그렇게 원칙을 엄격하게 고수하는 걸까? C++ Core Guidelines는 인자를 받지 않는 ‘기본 생성자’가 예외를 던지지 않아야 한다고 권한다. C++에서는 RAII를 사용하므로 Foo foo;처럼 단순한 선언에서 예외가 발생하면 생성이 끝나지 않은 객체, 메모리 누수, 복잡한 정리 작업이 생길 수 있다. C++ 컨테이너 클래스도 이 때문에 noexcept 생성자를 전제로 한다. 크기가 20인 목록을 만들면 생성자를 20번 호출하므로, 정리 과정이 더 복잡해지기 때문이다.

자바스크립트에서는 이런 문제를 걱정할 필요가 없지만, 인자가 없는 생성자뿐 아니라 모든 생성자에서 예외를 피하는 것이 좋은 관행이라는 의견이다. 생성자의 역할은 객체를 유효한 상태로 만드는 것이지 기능을 수행하는 것이 아니다. 생성자는 단순하고 예측 가능해야 한다.

객체를 만드는 곳과 사용하는 곳은 서로 다를 수 있다. 객체를 만들 때마다 각 클래스의 구현 세부 사항을 알거나 처리해야 한다면 관심사 분리가 어려워진다. 객체의 생애주기가 복잡하다면 그에 맞는 API를 설계해야 한다.