[TDD, 클린 코드] (3) 클래스 분리, 그리고 개선 과정에서의 고민들

June Lee·2023년 5월 15일
0

좋은 코드

목록 보기
5/8
new 인터페이스 == 이름을 갖지 않는 익명 클래스 == 람다
  • 자바는 가장 작은 동작 단위가 클래스 -> 람다처럼 함수로 구현해도 결국 내부 구현은 클래스이다
  • 인터페이스 추출해서 넣어주는거 자체가 DI(Dependancy Injection) -> 소프트웨어의 유연성을 위해 스프링 이전부터 많이 쓰던 기법.

리팩토링 전

public class Car {
	...
    
	public Car() {
    	...
    }
    
	public void moveCar() {
      	RandomMovingStrategy strategy = new RandomMovingStrategy() // Car 클래스와 RandomMovingStrategy가 강결합됨
      	move(strategy);
  	}
}

리팩토링 후

public class Car {
	...
	private MovingStrategy strategy;
    
	public Car(MovingStragegy movingStrategy) { // DI를 통한 루즈 커플링
    	...
        this.strategy = movingStrategy;
    }
    
	public void moveCar() {
      	move(strategy);
  	}
}

-> 테스트하기 좋은 코드를 염두하고 짜다보면 자연스럽게 이런 식으로 유연한 설계를 하게될 가능성이 높아짐


  • Service는 객체의 상태값을 유지하지는 않음. 상태값을 유지하는 객체는 Domain 객체.

  • 객체는 작은 객체부터 바깥쪽 방향으로 만드는게 좋음

  • 처음에는 리팩토링 단계에서 랩핑을 하는데, 경험이 쌓이다보면 바로바로 랩핑할 부분을 발견할 수 있게 됨 (처음에는 기준을 만들기보다는 무작정 랩핑해보기)

  • equals, hashCode, toString을 override를 잘 안하는 이유는 객체지향이 잘 안지켜지기 때문 -> 객체지향을 잘 지켜서 하다보면 랩핑한 객체끼리 비교하거나 하는 상황이 많아져서 이런 애들이 많이 쓰임.

  • 자바 객체와 데이터베이스 테이블은 1:1 관계이면 안됨.
    테이블 하나에 자바 객체는 여러개가 되는게 맞음.
    안그러면 god object가 돼서 유지보수가 너무 힘듦.
  • 랩핑했으면 꺼내지말고 객체에 메시지를 보내기 (실제로는 행동할 수 없는 대상이지만(ex. WinNumbers) 의인화해서 생각하기)

  • a -> b -> c -> d -> e
    이런식으로 의존 뎁스가 깊어지는건 좋은 현상 (위임만 하는 메서드들이 많아짐)
    다만 이때 점 두개 이상 쓰지 말기(상태값을 가지는 객체를 노출하고 거기서 다시 호출하는 형태) -> 캡슐화 위반. 버그 유발. 디미터 법칙 위반 (오브젝트 그래프 상 옆집 친구하고만 놀아라)

  • 반환값도 원시값이 아닌 랩핑된 객체로 반환하기
    ex) int가 아닌 Rank를 반환하면 6개의 값만 반환되도록 제한된 것.

  • 리팩토링 과정에서 고쳐야할 메서드가 많다면 둘 다 유지하는 것도 방법(무분별하게 접근하지 안도록 접근 제한자는 수정..)

  • 메서드의 수를 줄이고, 생성자 수를 늘리자

  • 주 생성자는 받은 값을 인스턴스 변수 초기화하는 역할
    부 생성자는 다 주생성자를 호출하는 방식!

  • 생성자에 로직이 생기면 생성자를 private으로 바꾸고 정적 팩토리 메소드를 만들기


스프링 프레임워크의 DI와 일반 코드에서 DI의 차이점

스프링에서 DI 방법
1. 생성자를 통한 인젝션 -> 이게 가장 많이 쓰임
2. (setter) 메서드를 통한 인젝션 -> 너무 무분별하게 쓰게 됨. 외부에서 언제든 바꿔치기 가능(테스트 코드 등에서)
3. 필드 인젝션 -> 테스트 용이성이 떨어짐

그런데 우리가 지금 구현할 때 메서드 인젝션스프링의 setter 인젝션과는 다름.
스프링은 일반 메서드까지는 관여 못하기 때문에 메서드 인젝션을 지양했지만, 우리가 구현할 때에는 둘 다 괜찮음. 상황에 따라 어느게 더 좋을지 판단하기

car.move(() -> 3); // 메서드 인젝션
new Car(() -> 3, “june”); // 생성자 인젝션

클래스 분리

  • 메서드의 인자 수를 2개 이하로 줄이기 -> 관련성 있는 객체들을 묶어보기

  • 클래스의 인스턴스 변수가 많을 때에도 마찬가지. 인스턴스 변수 수를 2개 이하로 줄여보기

  • 1) 같은 역할을 하고 2) 라이프 사이클이 같을 때 같은 클래스로 묶으면 좋음

  • private 메서드는 원래 public 메서드를 통해 테스트하는게 맞음.
    그런데도 private을 테스트하고 싶은 유혹이 든다면?
    방법 1) default로 접근제한자를 바꿔서 패키지 단위에서 테스트
    방법 2) 라이브러리(보통 리플렉션을 활용) 이용해서 테스트
    방법 3) 다른 클래스가 해당 메서드를 가져야하는게 아닌지 고민해보기

클래스 분리 정량적 원칙
1. 원시값 포장
2. 일급 컬렉션
3. 메서드 인자 수 많을때
4. 인스턴스 변수 수 많을 때
5. private을 테스트하고 싶어질때


immutable vs mutable

mutable

public void add() {
 number ++;
}

immutable

public Positive add() {
 return new Positive(number + 1);
}

immutable 객체의 메모리 문제?
원시값들과 Integer, Double.. 이런 객체들은 캐싱이 됨
IntegerCache처럼 캐시로 가짐. (ex. -128 ~ +128 까지 캐싱..)
객체가 새로 매번 생기는게 문제라면 캐싱하면 됨

profile
📝 dev wiki

0개의 댓글