티스토리 뷰

반응형

[TDD, Clean Code with Java - https://edu.nextstep.camp/c/8fWRxNWU/]

 

2022.04.20

 

  • 도메인 지식, 객체 설계 경험이 있는 경우
    • 요구사항 분석을 통해 대략적인 설계 - 객체 추출
    • UI, DB 등과 의존관계를 가지지 않는 핵심 도메인 영역을 집중 설계
    • 로직 테스트할 때 어려운 코드가 섞여 있으면 TDD하기 어려움 ex) Random
    • 1차적으로는 도메인 로직을 테스트하는 것에 집중!!
    • 도메인 객체에 대해서만 TDD를 할 때 쉬움

  • 도메인 <-> 단위 테스트
    • MVC라 치면 도메인(모델) 영역에 단위테스트를 집중하자
    • 이것만 잘해도 상당히 안정적인 애플리케이션을 개발할 수 있다
    • 도메인 영역에 객체지향체조 지키는 것에 가장 집중!! (컨트롤러 뷰는 동작하기만 해도...)
  • 대략적인 도메인 객체 설계
    • RacingCar car 분리하고
    • Random을 분리해서 TDD를 할 수 있게...

 

  • 그래도 막막하다면..
    • 단위 테스트도 없고, TDD도 아니고, 객체 설계도 하지 않고, 기능 목록을 분리하지도 않고 지금까지 익숙한 방식으로 일단 구현
    • 구현하려는 프로그래밍의 도메인 지식을 쌓는다
      • 구현할 기능목록을 작성하거나 설계를 잘하려면 도메인 지식이 많아야 기능도 작게 잘 쪼갤 수 있고 객체 설계도 잘 할 수 있음
    • 구현한 모든 코드를 버린다
      • 아무것도 없는 상태에서 새롭게 구현하는 것보다 레거시 코드가 있는 상태에서 리팩토링하는 것은 몇 배 더 어려움
      • 리팩토링이 안되면 걍 싹 다 날린 다음에 처음부터 하는게 더 좋을지도...
    • 구현할 기능 목록 작성 또는 간단한 도메인 설계
    • 기능 목록 중 가장 만만한 녀석부터 TDD로 구현 시작
      • 한번에 모든걸 다 잘하려하면 잘 안되고 재미없다.. 재밌게 꾸준히 하는 것이 더 중요하다!!
    • 복잡도가 높아져 리팩토링하기 힘든 상태가 되면 다시 버린다
    • 다시 도전
  • 질문: 4단계할 때 기능록록에서 기능별로 TDD했는데 익숙해지면 도메인 설계하고 도메인별로 TDD하는게 좋은가요?
    • 기능목록을 한 번 만들면 바꾸면 안된다고 생각하는 사람들이 많은데 그건 아니다
    • 기능목록은 만들면서 계속 업데이트해나가는것!
  • 질문: 도메인 관련 기능이 왔다갔다하는데 어떻게 하는게 좋을까요?
    • 리팩토링하답면 계속 새로운 도메인이 생길 수 있다
    • 클래스를 왔다갔다하는 상황이 발생하게 되는데 이건 당연하다
    • 클래스 분리 리팩토링을 해야하기 때문에..
    • 처음에 기능 개발할 때는 한 객체를 대상으로 단위테스트를 만들텐데 리팩토링하게되면 여러 객체가 등장할 수 있다
  • 기능목록을 작성한 후 테스트 가능한 부분을 찾아 TDD로 도전한다
    • 가능한 작은 단위로 개발 범위/기능을 쪼개는게 좋다
    • 작으면 작을수록 좋다
    • 아무리 고민해도 TDD 못하겠으면 일단 패스!
    • 가능한 부분부터 하자!!

 

 

* 단계별로 TDD하기

  • 1단계 - Util 성격의 기능이 TDD로 도전하기 좋음
    TDD 난이도 낮음. 시간 투자 대비 효과 낮음
    • 1자 이상, 5자 이하의 정상적인 이름인지 확인
    • 자동차 이동 거리에 따라 "-" 생성하기
  • 2단계 - 로직의 복잡도가 낮으면서 단위 테스트 가능한 기능을 TDD로 도전
    TDD 난이도 낮음. 시간 투자 대비 효과 낮음
    • 참여자 이름 split하고 자동차 생성
  • 3단계 - 로직의 복잡도가 높으면서 단위 테스트 가능한 기능을 TDD로 도전
    TDD 난이도 중간. 시간 투자 대비 효과 높음
    로직의 복잡도가 높은 만큼 요구사항 변경, 리팩토링의 빈도가 상대적으로 높음
    • 경주에 참여한 자동차 중에서 우승자 찾기
  • 4단계 - 단위 테스트하기 어려운 부분을 TDD로 도전
    TDD 난이도 높음. 시간 투자 대비 효과는 기능에 따라 다름
    • 자동차 이동 유무
    • 우승자 이름 출력하기
    • 데이터베이스 CRUD

 

  • 라이브 코딩
public class WinnersTest{    
    @Test 
    void findWinners() {
        List<Car cars = new ArrayList<>();
        cars.add(new Car("pobi"));
        cars.add(new Car("json"));
        cars.add(new Car("cu"));

        List<Car> winners = findWinners(cars); // 테스트할 메소드의 인풋과 메소드를 먼저 정의하고
        assertThat(winners); // 잘 실행됐느지 검증하는 코드 넣기
            // 누가 우승자일까? -> 지금 다 0ㅇ이니까 셋 다 우승자!!
            // 내가 포비를 우승자를 만들고 싶다면 어떻게 해야할까?
            // -> 포비만 move해주면 되겠다!!
    }
}

//////
public class WinnersTest{
    @Test
    void findWinners() {
        List<Car cars = new ArrayList<>();
        Car pobi = new Car("pobi")
        cars.add(pobi);
        pobi.move(); // -> move가 랜덤인데 얘가 이길꺼라고 어떻게 아냐?
                        // -> 테스트하기 어려운 코드,, 언제는 성공하고 언제는 실패해서...
                        // 우리가 원하는건 포비가 가장 많이 이동한 상태를 만드는 것!!
                        // position 상태가 가장 먼 것을 만드는게 목표.. racing이 끝난 상태!!
                        // move에 int random을 parameter로 넣으면 우리가 지정함으로 인해서 언제든지 조작가능함
                        // -> pobi.move(4); json.move(0) => 이렇게 하면 테스트 데이터 준비하는게 지저분해지고 읽기도 힘듦
                        //    테스트 가능상태를 만들기 위한 것으로 단위테스트 복잡도 증가..
                        // 생각의 전환 필요! 내가 원하는 포지션을 가질 수 있게 만들면 엄청 쉬울텐데
                        // -> 거꾸로 생각해서 인위적으로 상태를 만들려니까 좀 힘든데
                        //     그냥 한 번에 생성자를 통해서 포지션을 넣어버리면? 위치값을 만들 수 있다면 간단해지고 쉬워진다.
        cars.add(new Car("json"));
        cars.add(new Car("cu"));

        List<Car> winners = findWinners(cars);
        assertThat(winners);
    }
}

///////
public class WinnersTest{
    @Test
    void findWinners() {
        List<Car cars = new ArrayList<>();
        cars.add(new Car("pobi", 4);
        cars.add(new Car("json", 0));
        cars.add(new Car("cu", 0)); // car의 구조를 바꿔서 레이싱이 끝난 차를 만들게 하자! -> 새로운 생성자 만들기

        List<Car> winners = findWinners(cars);
        assertThat(winners);
    }
}

// Car 를 수정하자!
// position을 받는 생성자를 추가!!
class Car {
    String name;
    int position;
    
    Car(String name) { // 인자가 작은 생성자에서는 초기화X this()를 호출해서 넘기기!!
        this(name, 0);
    }
    
    Car(String name, int position) {
        this.name = name;
        this.position = position;
    }
}
  • 테스트 코드를 위해서 프로덕션 코드를 바꿔도 되나?
    • 테스트 코드를 위하든 외부에서 편하게 생성자를 추가하는 것은 언제나 환영한다...
    • 생성자를 여러개 만드는건 얼마든지 해도 괜찮다! 거부감이 없어도 된다!
  • 그럼 하지 말아야 하는것은?
    • 테스트를 위한 메소드를 만드는 건 절대 안된다!!
    • 테스트를 위해 메소드를 추가하는 건 안티 패턴이다
  • 생성자 추가는 왜 괜찮을까?
    • 생성자를 많이 추가하면 추가할수록 이 객체를 만들어서 사용하는 다른 개발자들은 편의성이 높아진다
    • 확장성도 좋아진다
    • 완전히 상태가 다른 인스턴스를 생성하는 것이라서 포지션을 다른 값으로 생성을 해도 문제가 없다. 영향을 미칠수가 없다
    • setter 메소드나 외부에서 값을 변경할 수 있게는 웬만하면 하지 말아라
      • 생성자나 final로 해라! 그것이 안전한 코드를 만들 수 있는 좋은 습관이다
      • 의식적으로 생성자를 활용하도록 노력해라!!
public class Winners {
    public static List<Car> findWinners(List<Car> cars) {
        int maxPosition = 0;
        for (Car car : cars) {
            if (maxPosition < car.getPosition()) { // 부등호 고민되면 우린 테스트 코가 있으니까 그걸 믿고 일단 넣어서 해본다...
                maxPosition = car.getPosition()
            }
        }
            // 이런 블랭크를 나중에 리팩토링 포인트로 잡을수도 있다
        List<Cars> winners = new ArrayList<>();
        for (Car car : cars) {
            if (car.getPosition() == maxPosition) {
                winners.add(car);
            }
        }

        return winners;
    }
}

// equals, hashCode, toString() 굉장히 중요한데 우리가 잘 만드는 버릇이 없음.. 객체 단위로 비교할 때 굉장히 많이 중요함...
// equals 만 구현하면 특정 자료구조에서 문제가 발생할 수 있어서 hashCode랑 쌍으로 같이 구현하는게 좋다(왜 그런지는 찾아보기)

//////
public class Winners {
    public static List<Car> findWinners(List<Car> cars) {
        return findWinners(cars, maxPosition(cars));	
    }

    public static List<Car> findWinners(List<Car> cars, maxPosition) {
        List<Cars> winners = new ArrayList<>();
        for (Car car : cars) {
            if (car.getPosition() == maxPosition) { // 데이터를 꺼내지 말고 카에게 maxPosition을 보내면서 같냐고 물어보자!!
                                                    // 상태를 가진 도메인 객체에게 메시지를 보내자
                                                    // 이런 사고를 계속 할 수 있어야 한다!!
                winners.add(car);
            }
        }

        return winners;
    }

    private static int maxPosition(List<Car> cars) {
        int maxPosition = 0;
        for (Car car : cars) {
            if (maxPosition < car.getPosition()) {
                maxPosition = car.getPosition()
            }
        }

        return maxPosition;
    }
}


// Car 에 isMaxPosition()이라는 함수를 만들어서 데이터를 꺼내지 않고 >메세지를 넘겨서< maxPosition인지 검사하기!!
// +) 한 라인이면 끝나는거 굳이 if else 하면서 하지말아라
public boolean isMaxPosition(int maxPosition) {
    return this.position == maxPosition;
} // -> good

public boolean isMaxPosition(int maxPosition) {
    if (this.position == maxPosition) {
        return true;
    } else {
        return false;
    }
} // -> bad
// 3항 연산자는 가독성이 떨어져서 지양하신다.... ㅋㅋㅋ가독성 사망연산자...

// getter 메서도를 쓰게 됐을 때 자꾸 의식을 전환해서 메시지를 보낼 수 없을까? 생각해보기
// 그럼 getter를 줄일 수 있어서 최소화 할 수 있다
// getter를 최소화할 수 있게 계속 노력해야 된다! 테스트하기가 쉬워지게!!!
public class Winners {
    public static List<Car> findWinners(List<Car> cars) {
        return findWinners(cars, maxPosition(cars));	
    }

    public static List<Car> findWinners(List<Car> cars, maxPosition) {
        List<Cars> winners = new ArrayList<>();
        for (Car car : cars) {
            if (car.isMaxPosition(maxPosition)) { // 메시지를 보내면 테스트하기가 쉬워지고 중복로직이 줄어든다
                // car.getPosition() == maxPosition -> 이런식으로 하면 다른데서 비교할 일 있을 때 계속 적어야 되서 여러곳에 중복으로 생김
                // 이걸 처리하는 부분의 요구사항이 달라지면 이 비교하는 부분을 찾아서 다 바꿔 야됨....
                // getter 메서드를 사용하지 말라는 것에서 이 부분이 중요함!!
                // 외부 개발자느 그냥 isMaxPosition()을 갖다쓰기만 하면되어서
                // 조금조금씩 응집도가 높아진다!! 중요한 부분!!
                winners.add(car);
            } // stream을 쓰지 않는한 indent를 줄이기 힘들다! 그럼 이제 스트림 연습해서 정리해보자 filter라던가 메서드를 써서 구현할 수 있다
                // 객체 지향 연습하는데 스트림 쓰는것이 저해가 되서 객체 지향 연습이 익숙해지고 getter 쓰기보다 메세지 쓰는게 익숙해지면 스트림을 써보자
        }

        return winners;
    }

    private static int maxPosition(List<Car> cars) {
        int maxPosition = 0;
        for (Car car : cars) {
            if (maxPosition < car.getPosition()) { // getter를 두개나 쓰는 죄악을... getter를 줄이고 인덴트도 줄일 수 있으려나?
                maxPosition = car.getPosition()
            }
        }

        return maxPosition;
    }
}

// 테스트 먼저 만들고 컴파일 에러 나서 거꾸로 만드는거에 익숙해져라~~

public int maxPosition(int maxPosition) {
	if (maxPosition < this.position) {
		return this.position;
	}

	return maxPosition;
}


public class Winners {
    public static List<Car> findWinners(List<Car> cars) {
        return findWinners(cars, maxPosition(cars));	
    }

    public static List<Car> findWinners(List<Car> cars, maxPosition) {
        List<Cars> winners = new ArrayList<>();
        for (Car car : cars) {
            if (car.isMaxPosition(maxPosition)) {
                winners.add(car);
            }
        }
        return winners;
    }

    private static int maxPosition(List<Car> cars) {
        int maxPosition = 0;
        for (Car car : cars) {
            maxPosition = car.maxPosition(maxPosition) // 메시지를 보내면 로직이 도메인으로 이동하게 됨
            // 처음에는 default로 하다가 나중에 필요할 때 public으로 여는 것도 좋다
            // 접근제어자의 접근 레벨을 서서히 높이는 것이 훨씬 안전한 코드를 만들수가 있다!!!
        }

        return maxPosition;
    }
}
  • setter는 절대 쓰지 말아라!!생성자로 다 해결이 된다(도메인 객체에 한해서)
    • 유일하게 쓸 수 있는 것은 DTO!(레이어간 데이터 전달 등에서)
    • 도메인 객체에서는 setter를 허용하면 안된다
  • getter 메서드도 계속해서 제거할 수 있다. 웬만해선 쓰지 말자!
    • view 출력에서는 필요할 수도 있는데 그런 제일 필요한 곳에서만 open 하자!
    • 무의식적으로 getter, setter을 만들지 말자! 안 좋은 습관이다
  • Lombok은 DTO 같은 곳에서만 쓰고 도메인 객체에서는 쓰지 말자
    • @Getter 같은거 습관적으로 붙이지말자... 무조건 편한게 좋은 것은 아니다
    • DTO 같은 단순 반복적인 것에는 있는게 편함
  • jackson, jpa orm 같은 프레임워크를 사용할 때는 어쩔 수 없다
    • 그 속에서 원리원칙주의자라면 프레임워크에 종속되지 않고 도메인을 분리 매핑해서... 어떻게 하는 방법이 있을 수 있지만 중복코드가 엄청 많이 생기게 됨
    • 실용주의 노선으로 가서 가능한 매핑도 하고 도메인 객체 역할도 하게 하는데 도메인 객체의 복잡도에 따라서 달라진다...
  • 아무튼 필요한 것만 오픈하자!!

 

  • 앞으로 getter 메서드가 보이면 메시지를 보낼 방법이 없나 생각해보자!
    • 대화하듯이!! 메시지를 보낼 수 있나하고!!
    • 여러 방향으로 고민해보자... isMaxPosition 대신 isWinner 이렇게 도메인 관련 메서드로 간다던가..
    • 메시지를 보낼 때에는 도메인 객체랑 대화한다고 생각해서 일을 시키자!!

 

오늘의 핵심!!
- 생성자를 만들어라! 생각의 전환!
    - 테스트를 위해 만드는건 얼마든지 만들어라!
- 메시지를 보내라!
이것만 잘해도 유지보수 하기 쉽고 테스트하기 쉬워질 것이다

 

테스트하기 어려운 부분과 쉬운 부분을 설계를 개선해서 분리할 수 있어야 한다!

  • 출력, db crud등은 테스트하기 어렵다
  • 외부 시스템은 계속 변할 수 있어서 반복할 수 있는 테스트를 만들기가 힘들다
  • 우리가 상태를 건들일 수 없는 것들은 테스트하기가 어렵다

단위테스트하기 어려운 코드를 테스트하려면?

  • 아예 테스트하지 않기
    • 포비의 경우 데이터베이스 crud 같은건 테스트하지 않음(테스트까지의 비용이 너무 크다)
    • ui, 컨트롤러도 단위 테스트하지 않는다
      • 그럼 이 테스트하지 않은 부분을 어떻게 커버하느냐 -> end to end 테스트를 한다
      • 도메인에 대한 단위테스트를 tdd로 하고 인수 테스트(end to end)를 한다(인수 테스트 주도 개발?)
      • 브라우저 역할하는 테스트 코드를 만들고 데이터 흘러가는 것을 테스트한다
  • 서비스 레이어는 테스트하지 않는다
    • 서비스 레이어에 로직 많은데 안 만들어도 되나요? -> 된다
    • 정말 많은 사람들이 비즈니스 로직을 서비스 레이어에 넣는데 그렇게 하면 안 된다
    • 서비스 레이어에 다 때려넣는 것이 학습 비용이 적고 빨리 개발할 수 있어서 그렇게 하는 사람이 많다ㅠ(si의 악습..?)
  • 자동차 이동 유무는 테스트하기 어려운데 필요한 부분이다
    • 테스트하기 어려운 부분과 테스트 가능한 부분을 분리할 수 있어야 한다

 

테스트하기 어려운 부분을 찾아 테스트 가능한 구조로 개선하기

  • Object Graph에서 다른 Object와의 의존관계를 가지지 않는 마지막 노드(node)를 먼저 찾는다
  • 예를 들어 RacingMain -> RacingGame -> Car와 같이 의존 관계를 가진다면 Car가 테스트 가능한지 확인한다
  • 테스트 가능한지 확인하는 것이 중요하다

 

  • Car에 우리가 만들지 않은 Random과 의존 관계를 갖는 것을 발견함
    • Random은 우리가 어떻게 할 수 있는 것이 아니다 -> 테스트하기가 힘들다
    • Car.move()도 테스트하기 힘들고, RacingGame.race()도 테스트하기 힘들고, RacingMain.main()도 테스트하기 힘들다
    • 테스트 가능한 부분까지는 해봐야 한다

 

  • 질문: 우리에게는 mockito라던가 있지 않나요?
    • mockito 같은 프레임워크는 TDD, 단위테스트를 제대로 하지 못하는 사람들이 쓴다
    • TDD나 단위테스트를 잘하는 사람은 저런 프레임워크를 잘 사용하지 않는다.
    • 이런 외부 프레임워크를 하나씩 추가하면 테스트하기 싫고 도배하게 되고 구현비용도 들고 유지보수하기 어려워진다

 

Object graph에서 테스트하기 힘든 것을 가장 상위로 옮긴다!

  • RacingMain -> RacingGame -> Car에서 RacingGame이 Random과 의존관계를 갖게하면 Car는 테스트가 가능하다
  • Random을 RacingMain에 옮기면 RacingGame, Car 둘 다 테스트가 가능해진다
  • 마지막에 테스트하기 어려운 부분은 안 한다....
  • 하지만 우리의 정말 중요한 비즈니스 로직인 도메인 객체에 대한 테스트는 할 수가 있다
// RacingGame
private int getRandom() {
	Random random = new Random();
	return random.nextInt(MAX_BOUND);
}

private void move() {

}

// Car
// 메소드 시그니처를 바꾸면 의존 관계 있는 곳이 다 컴파일 에러가 난다
// 불안감을 없애면서 안정적으로 단계적으로 리팩토링하는게 좋다... 스텝바이스텝으로 천천히...
// 점진적인 리팩토링과 안정적인 리팩토링...

// 경계값으로 테스트하는게 좋다!!
// equal 기호 들어가느냐 마느냐로 많이 고민하는데 그러니까 중요하다...
// 과도기 단계에서는 중복코드 있어도 괜찮다...
// 컴파일 에러가 적게 나면서 불안감도 덜 생기고 안정감 있게 가능...
// 아무리 복잡하고 두렵더라도 서서히 점진적으로 개발할 수 있다
// 리팩터링이 한번에 끝나지 않더라도 과거에 코드와 미래의 코드가 공존한
// 언제든지 배포 가능한 상태로 만드는게 지속적으로 리팩토링할 수 있는 상태가 되는거다!!
// 리팩토링 주기를 짧게... 할 수 있는 만큼만 하는게 끊임없이 리팩토링할수있는 그걸 만드는..
// 특히 레거시 코드 리팩토링은 일상생활 속에 걍 계속해야되는것.. 물흐르듯이.. 거부감 없이...

 

  • 테스트를 추가해야 하는 시점?
    • 켄트백은 100프로 TDD를 하는 것이 아니라 테스트 코드 없이 배포되면 불안한 부분에 테스트를 추가해야 한다는 이야기를 한다

 

// 원시 값이 뭐냐?
// - Car의 position 같은 값. 이런걸 포장하면 자연스럽게 클래스가 분리가 된다

// PositionTest
@Test
void create() {
	Position position = new Position(1);
	assertThat(position.getPosition()).isEqualTo(1);
}

// Position
class Position {
	private final int position = 1;
	Position(int n) {
		this.position = n;
	}

	int getPosition() {
		return this.position;
	}
}

// -> getter 쓰면 안되는데~~ isSamePosition 같은걸로?
// 여기서 한 단계 더? equals 오버라이딩?
// 꺼내서 비교하지 말고 객체 단위로 비교할 수 있도록 자꾸 해보자!!

// PositionTest
@Test
void create() {
	Position position = new Position(1);
	assertThat(position).isEqualTo(new Position(1));
}

// equals랑 hashCode를 구현하자!
// Position
class Position {
	private final int position = 1;
	Position(int n) {
		this.position = n;
	}

	@Override
	public boolean equals(Object o) /// 구현

	@Override
	hashcode // 구현...

}

// 생성자를 다양하게 만드는게 이 position을 만드는 개발자 입장에서 좋다
// 만약에 문자를 쓸 수 있게도 제공한다?
// 생성자는 다양하게 하는게 좋다...
// 관례상 마지막에 존재하는게 인스턴스 초기화하는 생성자이다

// Position
Postion(String position) { // 이게 없다? 그러면 계속해서 new Position(Integer.parseInt(position)) 해야되서 불편하다
	this(Integer.parseInt(position));
}

Position(int position) {
	this(position);
}
  • Position은 작은 클래스
    • int의 범위는 상당히 큰 범위인데 레이싱카에서 Position은 항상 0보다 커야함
    • 객체가 생성될 때 객체가 유효한 것인지에 대한 책임은 객체가 온전히 져야 함
    • -> Position이 생성된다면 Position이 가지고 있는 값은 0이상이라는 것을 보장해야 하는데 그 보장은 Position  객체가 져야한다
// positontest
@Test
void invlalid() {
	assertThat(() -> {
		new Positoin(-1);
	}).isInstanceOf(IllegalArgumentException.class);
}

// Position
Position(int position) {
	this(position);
	if (position < 0) {
		throw IllegalArgumentException("이동거리는 음수가 될 수 없습니다.");
	}
} 
// -> Positon 객체가 생성됐다는 것은 0이상이라는게 보장이 된 것! 안전하게 쓸 수 있다!!

// Car에 public Car(String name, Position position)도 추가 가능
// 이제 Car에서 position을 primitive 타입이 아니라 객체로 가져간다

// 이렇게 바꾸다보면 positon을 변경하고 증가시키는 부분에 문제가 발생!!
// 저 비즈니스 로직들이 다 Positoin 쪽으로 이동할 수 있다는 의미...
// 객체를 매핑하고 그러다보면 아무일도 안 하는데 위임하는 메소드가 많아지는데 당연하다...
// car.isMaxPosition 대신에 car.getPosition하면 디미터 법칙 위반?
// 로직 이동하면 테스트 코드도 이동한다
// 단일 책임 원칙!! 응집도!!!
// 이것이 원시값 포장이다

// 문자열 포장도 할 수 있다
// Car의 name 포장하면 이름 관련된 로직이 다 포장...

 

일급 컬렉션?

  • cars와 같은 리스트 콜렉션을 포장하는 것이 일급 콜렉션
  • class Cars에는 List<Car> cars 변수만 있어야 한다
    • 상태와 관련된 로직들은 따라간다..
  • 객체 크기가 작아지고 TDD하는 것이 엄청 수월해진다
  • TDD를 하기 어려운 것은 클래스가 크고 복잡도가 높아져서이다
반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함