그동안 회사에서 혼자 스스로 개발한 부분에 TDD나 단위테스트를 도입해보고 싶은데, 어렵게 느끼고 결국 실패했던 이유를 드디어 알았다. 테스트 대상인 비즈니스로직을 서비스 layer에 특히나 책임분리가 제대로 되지 않은 상태로 모든 로직을 때려박아 넣었기 때문이었음. OOP가 제대로 적용된 구조에서 TDD도 실현될 수 있다는 것을 깨달았다.

생성자 원칙 from 엘레강트 오브젝트

  • 클래스를 잘 설계한다면, 클래스는 많은 수의 생성자와 적은 수의 메서드를 포함할것이다. 2,3개의 퍼블릭 메서드와 5~10개의 생성자를 포함하는 것이 적당하다.
  • 이런 기준을 두는 핵심은 응집도가 높고, 견고한 클래스에는 적은수의 메서드와 많은 수의 생성자가 존재한다.
  • 메서드가 많아지면 클래스의 초점이 흐려지고, 단일책임원칙을 위반하기 때문이다. 이에비해 생성자가 많아지면 유연성이 향상된다.
  • 주 생성자는 프로퍼티를 초기화하는 일, 이런 초기화를 주 생성자만 담당하도록한다.
  • 하나의 주 생성자와 다수의 부 생성자 원칙
  • 부 생성자는 주 생성자를 호출한다. 코드 중복을 방지하고 설계를 더 간결하게 만들어 유지보수성이 향상된다.
  • 생성자 구현 관계를 항상 주 생성자를 모든 부 생성자 뒤에 위치한다. 주된 이유는 유지보수하기 편하도록 하기 위함이다.
  • 정적팩터리메서드를 쓸 때도 마찬가지

Immutable vs Mutable

  • 한 번 초기화된 인스턴스 값이 바뀌느냐 바뀌지 않느냐에 따라 구분
  • final 키워드로만 판단할 수는 없음.
  • 불변 객체로 만드세요 in 엘레강트 오브젝트
  • 모든 클래스를 상태 변경이 불가능한 불변 클래스로 구현하면 유지보수성을 크게 향상시킬 수 있다.
  • 불변객체를 기반으로 사고하면 더 깔끔하고, 더 작고, 더 쉽게 이해할 수 있는 코드를 만들 수 있다.
  • 멀티스레드 환경에서 가변객체는 thread-safe 하지 않을 수 있음.
  • 특히나 이런 이슈는 트래픽이 몰릴때 간헐적으로 생김(데이터 싱크가 안 맞음)
  • 레이스 컨디션에서 동시성 이슈가 생길 수 있음.
  • 불변객체로 개발했을 때 인스턴스가 너무 많이 생성되어 성능 저하가 있는게 아닌가? 캐싱!
  • 왜 primitive type인 int는 문제가 없을까? Constant pool에서 캐싱이 됨. 무제한은 아니지만 일정 범위를 캐싱하고 있음.
  • 미리 만들어두고 정적 팩터리 메서드로 생성하여 싱글톤 유지함으로써 캐싱을 적용
  • object graph상에서 leaf node가 일단 불변이어야 의존관계가 있는 상위 객체도 불변이 될 수 있음.
  • 불변객체가 되면 그게 바로 VO임.

점진적인 리팩터링 전략

  • as-is 에 to-be 코드를 추가한다.
  • as-is에 의존하는 모든 코드를 to-be코드를 사용하도록 점진적으로 리팩터링한다.
  • 의존하는 모든 코드가 to-be 코드를 사용하면 as-is를 삭제한다.
  • 일정기간동안 as-is와 to-be코드가 공존하는 방식으로 리팩터링함.

인터페이스 분리 설계에 대한 고찰

  • 처음부터 알 수는 없음. 유지보수 중에 깨달을 수 있음.
  • 요구사항이 변화해감에 따라 판단할 수 있음.
  • 변화가 지속적으로 발생하는 부분, 변화가 발생하지 않는 부분을 분리하는 감각이 필요하다.
  • 변화가 지속적으로 발생하는 부분은 인터페이스 + 구현 클래스
  • 변화가 발생하지 않는 부분 only 구현 클래스
  • if/elseif가 남발하는 상황에서 적용하기 최고임.
  • 예를들어 사칙연산에서 Operator 조건절("+".equals(Operator)), apply 구현절(return x+y)

핵심 비즈니스 로직

  • 레거시 layerd architecture에서 service layer에 핵심 비즈니스로직이 다 들어가 있음. 왜? SI 프로젝트하면서 교육없이 빠르게 개발할 수 있는 절차지향 방식을 따르게 됨.
  • 서비스 계층은 로직을 구현하는 곳이 아님. 단위테스트하기 어려움.
  • 핵심비즈니스로직은 도메인 걕체가 담당, 서비스 계층의 역할은 repository 계층의 api를 통해 도메인 객체를 초기화하는 역할(controller?).
  • 즉 상태값을 가지는 도메인 객체에 메시지를 보내는 역할임. 그 메시지의 결과를 다시 repository 계층에 전달하는 역할을 담당.
  • 따라서 서비스, repositroy 계층은 변화가 많이 발생하지 않고, 리팩터링을 거의 하지 않게 됨.
  • 대부분의 변경은 도메인객체에서 발생함.

레거시코드 리팩터링

  • service_layer에 단위테스트를 추가하고 비즈니스 로직을 도메인 객체로 이동
  • 수용가능한 테스트 추가하여 리팩토링
profile
Junior Web Developer 😊

0개의 댓글