📜 제목으로 보기

TDD 작성

요구사항 정리

  1. 제목(##)과 - [ ] 박스를 만든다.
  2. 키워드를 추출한다
  3. 객체 vs 메서드 vs UI vs 세부요구사항으로 나눈다.
  4. 관리 vs 객체 시작점을 고른다.
## 요구사항
- [ ] 각 자동차에 이름을 부여할 수 있다.
- [ ] 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- [ ] 자동차 이름은 쉼표(,)를 기준으로 구분한다.
- [ ] 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한명 이상일 수 있다.

## 키워드 -> 분류 -> 객체 vs 메서드 vs 세부요구사항 vs ui 나누기 -> 시작점 고르기

- 자동차
    - 이름 : object
        - , 기준으로 구분 : 세부요구사항 - ui 맨 나중에
    - 전진 : method
- 자동차 경주 게임
    - 완료한 후 누가 우승했는지를 알려준다. : 메서드 
- 우승자 
    - 한명 이상 : 세부요구사항

테스트

테스트 작성요령

  1. **시작점을 고를 때, **

    1. **기능(메서드)을 가진 데이터객체이외에 **

    2. controller에서 정제된 input이 들어오고, controller로 output응답도 해야하는 Service부터 시작할 수 있다.

      • 정제된 input부터 -> 메인 흐름을 다 나열 -> 출력될 ouput객체까지 반환해주는 것이 service의 역할이다.
        • 처음부터 객체도출은 어려울 수 있다.
      • 서비스의 메서드부터 짠다면, input 부터 output까지 메인 흐름을 생각하고 정리해야한다
      • 서비스 start~end까지 로직이 짜여졌으면 해당 로직에 맞는 메서드명으로 변경한다
    3. service에 대한 input내부 로직에 의해 데이터객체로 주어진다면, 컴퓨터가 줄 데이터객체로부터 만들어놓고, 그것을 이용한 input으로 한 -> output을 내는 핵심로직부터 시작한다.

      • 예를 들어, 블랙잭게임을 하면, 카드덱으로부터 카드2장이 주어진다.
        • 이미 카드 2장이 주어졌다고 가정하고 데이터객체 카드부터 만들고 핵심 로직으로서 시작하자.
      • service가 핵심로직이 아닐 수 있다. 서비스내 Game등이 핵심로직일 수 있다.
      • input -> output이 나오는 핵심로직부터 시작한다. Game.start()로 시작하자.

      image-20220803171440490

    4. 핵심로직 자체도 service가 아니라 (상태패턴이라면) 상태를 가지는 객체로 바뀔 수 있다.

      • 서비스 메서드 == 스태틱 메서드 like 유틸메서드에서 -> 객체가 되었다면

        • 상태값을 가지며, 상태값을 만드는 정보input들은 다 생성자로 들어와야한다.
        • 메서드로 상태값 정보가 들어온다면, setter다.
      • 서비스의 유틸메서드 input -> 생성자로 주입 된다

      • 서비스의 유틸메서드내 로직을 거친 뒤 1개의 output응답값(상태객체) -> 필드로 가진다.

        • 상태객체는, 정보만 가져야하므로 상태객체가 아닌 상태객체를 사용하는 객체일뿐이다.

        • 상태객체로 다시 변환하려면 다른 상태객체들과 상태값(필드) 와 메서드를 동일하게 사용하도록 수정해야한다.

          • Ready도 다른 상태객체들과 동일 상태값 필드(Cards)를 가지며 생성자로는 아무것도 주입 안된 빈카드로 초기화하는 기본 생성자로 만들어져야한다.

          • Ready도 .start()로 다른 객체로 넘어가는 것이 아닌 다른 상태객체들의 메서드처럼 Trigger메서드(draw())로부터 setter정보를 받아 상태값업데이트 이후 상태값을 가지고 판단하여 다른 상태로 넘어가도록 수정한다.

            • Ready는 카드를 2장을 받는다. -> 다른상태객체처럼 draw로 1장씩 받도록 정의하고 외부에서 2번을 호출하던지 다른 파라미터로서 받도록 오버로딩메서드로 정의해준다.
      • 서비스 or 핵심로직 통합테스트가 Ready라는 1개 상태객체 테스트로 바꼈다.

        • 개별 상태객체로 테스트코드들을 나누어서 옮긴다.
  2. 정제된 input(원시값)을 통해 service개발을 완료하고 나면

    1. 가장작은 단위의 input에 대한 서비스에서 예외발생테스트를 먼저 만들고 -> 필요에 의해 도메인 객체에서 예외발생 되도록 도메인을 생성한다
      1. 원시값input을 도메인객체로 만들면서 사전검증이 이루어지도록 해야한다.
        1. 검증이 필요한 원시값에 new도메인()을 때려서 시작한다
    • 그 다음에 컬렉션을 일급컬렉션으로 만들면서 중복 검증 등의 검증이 이루어져야한다.
  3. 가장 빠르게 실패는 thr IllegalStateException을 활용하고. 가장 빠르게 성공은 로직 없이 응답값만 return하도록 만든다.

    • 실패 -> 성공 -> 리팩토링 을 반복한다.
  4. 생성자로 시작 이외에 나중엔 (상태필드 변경후 상태로 확인) 바뀌더라도 상수 응답 메서드로 먼저 테스트를 작성한다.

    • car.move()는 setter같은 메서드인데, 처음 메서드 작성시 1칸 전진마다 상수1을 응답값으로 주도록 만든다
    • 응답도 getter로서 조회의 일이다. 조회용 메서드가 아니라면, 로직 테스트 완료후 응답 대신 상태로 확인하도록 변경해야한다.
      1. 조회용 메서드가 맞는지 확인한다.
      2. 조회용 메서가 아니라면 getter같은 return응답을 제거한다.
      3. 기존에 메서드결과로 응답값을 조회했던 부분을 값 비교 -> 객체 비교를 하도록 eq/hC 재정의 및 테스트 assert문 수정을 해줘야한다.
    • 상태가 변하는 메서드는 setter이므로 불변객체를 유지하기 위해, 상태값 변화 대신, 상태값이 변화한 객체 응답메서드로 바꾸자.
      • 다시 한번 응답메서드가 되지만, 상수 응답이 아닌 새 객체를 만들어 반환이다.
      • 객체의 상태값 및 생성자 등이 바뀌므로 2메서드가 아니라 2객체를 생성해서 테스트한 뒤 반영한다.
  5. 테스트할 메서드는 무조건 응답하도록 먼저 작성하며 이 때, 응답값은 [넣어준 인자에 대한 case값을 응답]을 해줘야한다

    • 만약, 로또번호 vs 당첨번호 인자 입력을 1등 번호로 예시case로 넣어줬다면, 그 인자 case에 맞는 1등이 응답값으로 반환해야한다(아무거나 반환X)
      • 2번째부터는일반화하는 경우가 많으니, 첫 메서드 생성하는, 1번째 첫 인자를 입력할 때, case에 맞는 반환도 생각하고 입력하자
    • TDD메서드의 응답값은 테스트메서드의 테스트CASE에 의해 작성순서가 결정된다.
  6. case별로 테스트 메서드를 만든다. 메서드명_CASE____세부내용형태를 빌린다.

    • 2번째case부터는 일반화해도 좋다
    • case == 메서드의 인자가 결정
  7. **테스트케이스 추가에 따른 기능추가, 사전 검증과 같이 전체메서드가 걸린 기능추가는 **

    1. 메서드를 전체를 2메서드로 복사해놓고 테스트
    2. 기존 테스트들을 2메서드로 호출하도록 변경 후 전체 테스트
      • 이 때 기존테스트()의 소괄호까지를 ctrl + h로 찾아서 변경하면 쉽게 변경된다.
    3. 기존테스트들 다 통과시 회색으로 안쓰게 된 원본메서드 안전삭제(alt+del)
    4. 2메서드를 -> 원본메서드로 변경
  8. 기능 추가 -> 2case부터 일반화로직 추가시, 1case를 보존하기 위해, 2메서드 복붙후 테스트에서 1case 인자를 그대로 사용 + 내부로직의 결과값에 if를 사용하더로 먼저 통과시키게 해야한다.

  9. 특정로직(private메서드)에 대한 기능 추가는 private메서드만 복사해서 테스트해놓고 끝나면 반영하고 삭제한다.

  10. 요구사항이 복잡해질 땐, 그 부분만 아래에서 학습테스트를 진행해도 된다.

  11. 상태변경 메서드의 테스트가 끝나고 다른 기능 테스트를 할 때, 특정 상태를 만들기 위해 상태변경메서드 반복 호출을 하지말고, 직접 해당 상태로 만드는 생성자를 추가해서 현재하는 테스트에 집중하게 한다

    • 특정상태의 객체를 불변객체로 바로 만들면, 빈컬렉션+add가 사라진다.
  12. 기존파라미터(원시 컬렉)를 살려두고 이어서갈 땐 새 인자(도메인 컬렉)로 메서드 작성후 오버로딩

    • 기존 파라미터(도메인 컬렉)를 변환된 파라미터(일급컬렉션)로 대체하고 싶다면 내부변환후 파라미터 추출

    image-20220728230052251

  13. 서비스내 getter가 보이면, 도메인 내부 로직으로서 캡슐화로 감춰야하는 로직임을 100% 생각한다.

    • 출력을 제외하고 getter는 없다고 보자.
    • getter이후가 같은형의 비교면 -> 해당형으로 책임을 위임해 옮긴다
    • getter이후가 다른형의 비교면 -> 제3형을 만들어 책임을 위임한다.
    • static메서드였으면, static을 삭제하고 -> 메서드 이동 -> 파라미터 중 위임할 객체가 2개가 같은형이면, 하나의 변수를 택1한다
  14. 같은형의 일급vs일급은 -> 일급vs단일(contains) 메서드를 사용할 확률이 높다. 확인해서 처리한다.

  15. 정해진 갯수의 인스턴스는 정팩메를 통한 캐싱을 도입하자

  16. 정팩메든 생성자든 파라미터를 추가하면 견고한 클래스가 된다.

    • 자바(부생성자개념)이외 언어들은 주생성자로만 생성하고 다양한 타입의 파라미터로 생성하려면 정팩매를 이용해야한다.
    • 나도 앞으로 파라미터 추가를 위해서 정팩매 도입을 고려해봐야겠다.

    image-20220731152352680

  17. 일급컬렉션도 빈컬렉션으로 시작하여 add시 새객체반환하는 일급컬렉션으로 만든다.

    • new 빈컬렉션을 인자로 받는 부생성자를 만들어주면 된다.
  18. 기존에 생성자 없이 사용하던 객체에, 상태값이 추가되어 생성자가 추가될 경우, 부생성자로 빈값할당으로 초기화해주는 기본생성자를 추가해서 기존코드가 망가지게 않게 한다

  19. 여러종류의 클래스객체 응답이 가능한 메서드에 대해 특정 클래스의 객체를 응답하는 테스트를 작성하려면, Object actual -> isIntanceof(특정클래스.class)로 테스트코드를 작성한다.

  20. 여러 종류의 객체return는 추상체 응답의 상태패턴을 고려한다.

    image-20220804113722642

    • 상태패턴을 도입했으면, 응답값으로 나온 현재 상태를 바탕으로 추상체에 있는 [다음상태로 갈 수 있는 인자]를 받아 [다음상태로가는 메서드]를 정의해줘야한다. 에러호출로 종료되는 제일 쉬운 현재상태(blackjack)부터 [다음 상태로 넘어가는 메서드]를 만들자
  21. 추상체를 만드는 순간부터 패키지를 분리하여, 패키지폴더 대상 다이어그램으로 의존성을 확인하자.

    • 이후, 구상체만의 메서드 개발후 올리기전 다이어그램 + CompareFile하자
  22. 상태변화가 없는 객체캐싱객체 or VO로, eq/hC를 재정의하여 값이 같으면 같은 객체가 되는 값객체라서 testutil패키지 > Fixtures클래스안에 상수객체로 만들어 써도 된다.

    • test루트에서 testutil 패키지를 만들고 내부 Fixtures 만들자
    • Fixtures에 쓸려면 어디서든 불러도 같은 객체가 생성되도록 캐싱==싱글톤이 보장되어야한다.
      • 캐싱객체는 test코드에서 isSameAs확인해야한다.
      • eq/hC의 VO값객체는 test코드에서 eq/hC 재정의 후 isEqualTo로 확인해야한다
    • 상수객체 Fixture는 한글로 알아보기 쉽게 네이밍해도 된다.
  23. 상태패턴의 상태객체반환 with trigger되는 결국엔 다른 객체의 상태값으로 포장객체들은 view에 넘기기 위해 getter를 무조건 가진다

    • trigger 등 set계열 메서드들 개발이 완료되면 getter도 개발해야한다.
  24. 좋은 부모를 만들기 위한 전략(템플릿메소드) 중 중복 필드 처리

    1. Object)중복로직제거 Strategy to Templatemethod(Plan3)
      1. 추상클래스는 좋은 상속의 부모로서 변수는 private, 메서드는 final or protected abstract, 생성자없이 setter로확인하여 달아준다.
    2. 구상체별 중복되는 필드를 올릴 땐, 구상클래스 생성자 주입 -> setter메서드 -> private필드로 올린다.
    3. 부모의 private필드는 부모내에서 처리하게 하고, 자식은 부모private필드자체는 못쓰고, final 물려주는 템플릿메소드들만 쓴다.
      • 자식들이 훅메서드 구현시 필요한 정보들은, 자기가 생성자 주입받아서 쓰면 된다.
      • 자식이 부모의 private필드값을 써야한다면, 부모에게 구현된 getter를 물려받아 쓰면된다.
  25. 암기getter

    1. getter정의시 return 불변객체라면, public열어두기 가능 + dto없어도 됨
      • 불변의 일급컬렉션 객체 반환 -> dto없는 public getter로 제공
      • 불변 객체 아니라면, Dto로 만들어서 반환
    2. getter정의시 return 컬렉션필드라면, view로 보낼 땐, 깊은 복사로 못건들게 해서 반환
      • 불변의 일급컬렉션 자신 내부의 컬렉션 필드 반환
        • server사이드 반환이라면, 얕은복사 반환후, 내부에서 객체들 조작
        • view반환이라면, 깊은 복사 반환 or DTO로 반환후, 내부 객체들 조작안되게
  26. 중복제거 등 추상화/상속 관련작업이 끝날 때마다, diagram을 보자

    • 복잡하다면, 자물쇠의 show dependencis만 끄면 상속관계가 잘 보인다.

    • 추상화 레벨을 보고 싶다면, 바로 위의 추상클래스에 데고 usage(shift+F12)를 통해 어떤 놈들이 나를 직접 extends했는지확인하면 된다.

      image-20220806231001237

  27. 랜덤이 껴있는 로직은, 테스트를 고려해서 1개 메서드(함수형인터페이스)로 추출하고, 무조건 전략패턴으로 만들어야한다

    • 추출할 메서드는 [전략객체-or]에 대해 -동사()를 쓴다 ex> Generator -> generate() / Applier -> apply()
    • 구상 전략객체에 해당 내수용메서드를 위임하기 위해선 [인자에 구상체 생성 -> class생성 -> 파라미터에 추가]해야지 F6으로 위임된다.
    • 내수용메서드들이 포함되어있다면, 모두 해당객체를 인자추가 -> 파라미터로 추가해놓자.
    • 내부 this를 사용했다면(위임전 객체의 context) -> 위임받을객체로 일단 바꿔주자.
    • 내부 context(상태값 등)이 사용되었다면 -> 추출 전 지역변수로 받아 추출하던지 or 추출후 파라미터추출해서, 파라미터에 포함되게 한다.
    • 다 만들어놨으면, 위임은 내부 && 먼저 호출되는 순으로 위임해야지, 기존context가 안잡힌다.
    • 위임과정에서는 내수용private -> public으로 된다. 위임후 수정해주자.
    • 구상 전략객체에서 @Override만 붙이면 extract interface가 가능하다.
    • 전략패턴이 되었으면 인터페이스에 @FunctionalInterface를 달아준다.
    • 테스트에선 람다식으로 만들되, 바로 못만들면, 시그니쳐 반환형에 맞춘 객체를 수동으로 만들어, 람다식에 넣어주는 식으로 한다.
      • 꼭 또다른 전략객체를 안만들어도 된다. test용 class라면 만들필요가 없다.
      • 만드는 과정에 바뀌는 변수들을 파라미터 추출하고 메서드로 정의한다.
      • 전략주입 객체까지를 하나의 Fixture로 볼 수 있다. [ 테스트용 수동전략 생성 + 전략적용객체 생성 ]까지를 1개의 fixture로 보고
        • 여러 테스트에서도 사용할 수 있다면, testutil패키지의 FixtureGenerator클래스에 유틸메서드(public static)로서 빼주면 된다.

테스트 코드 작성시 팁

  1. split등으로 actual될 값이 2개이상이면, assertAll을 활용해서 0번인덱스부터 단계별로 확인하자

  2. 상수 리팩토링은 result_index , result_prefix 등으로 case_result + 명칭을 그대로 붙여서 지어도 된다.

  3. 메서드 안에서 또다른 일을 하는 경우, 파라미터 -> 또다른 일 -> 반환 과정을 메서드추출하고, 파라미터로 올리면, 추출메서드가 현재메서드의 인자로 올라가서 일하고 있게된다.

  4. 케이스 추가 -> 테스트메서드 추가 -> 메서드2 작성시 개별 메서드 호출마다 저장소가 필요하다면, 메서드주체인 객체의 상태필드를 이용해야한다. 필드를 만들어서 쓰자.

  5. 메소드에 존재하는 테스트 힘든 코드는 확정값을 만들 수 있는 전략객체로 위임하되 전략객체는 협력객체로서 메서드 인자가 아닌 생성자 인자로 받아야한다

    • 프로덕션용 기본 전략값이 정해져있다면 생성자 오버로딩을 이용하자
  6. getter에서 get을 뺀 뒤 필드처럼 지어서, 내부의 필드명이 없는 것처럼 감출 수 있다.

  7. .setter처럼 상태변화 메서드가 만들어졌다면, 상태변화한 객체를 새로생성해서 반환하는 메서드로 변경한다.

    • 필요시 생성자를 추가한다.
    • 자신형 객체반환 메서드는 체이닝이 가능하니 테스트에서 체이닝으로 작성해도 된다.
  8. 특정 상태를 만들기 위해 상태변경메서드 반복 호출을 하지말고, 직접 해당 상태로 만드는 생성자를 추가해서 현재하는 테스트에 집중하게 한다

    • 특정상태의 객체를 불변객체로 바로 만들면, 빈컬렉션+add가 사라진다.
  9. 컬렉션 인자 작성사 List.of컬렉조작불가능한 얕은복사을 사용하자.

    • Arrays.asList조작가능한 생성자 복사(얕은 복사) 사본
  10. 인자가 3개이상이면, 엔터쳐서 줄바꿈해준다.

  11. 컬렉션 vs 컬렉션 -> 반복문 + 요소 vs 컬렉션 + 저장/업데이트용 지역변수가 생긴다

  12. 원시값 인자 포장시, 검증대상에 new때려서 만들기 시작하며, 검증로직 작성 후, 전체 원시값인자들을 도메인 인자로 변경하여 -> 도메인 파마리터 메서드를 생성한 뒤, 원시값 파라미터 메서드 내부에서 도메인 전환후 오버로딩 호출할 수있도록 로직을 변경한다.

    • 테스트코드에서 도메인 인자들이 다 사라지고 원시값으로 다시 복구되었다면, 오버로딩의 도메인 파라미터 메서드는 private화 시켜서, 외부에서 객체 생성을 막는다.?!
  13. 도메인 컬렉션 인자 포장시 [이미 원시값 인자 존재 + 1개의 오버로딩 존재 + 도메인컬렉션 파라미터는 필요없을 때]

    • 내부에서 p1 -> 일급컬렉션 변환과정까지 작성하고 새롭게 파라미터 추출해서 파라미터 자체를 변경한다.
    • 원시값 포장시에는 도메인입력 < 원시값입력이므로 기존 파라미터 메서드를 살리는 방향으로 오버로딩 적용을 했었지만, 도메인 컬렉션 -> 일급컬렉션에서는 도메인컬렉션 입력을 죽인다.
  14. 일급컬렉션의 검증은 도메인객체들의 갯수, 중복여부등이다.

  15. 같은형의 비교시 -> 내부 메서드로 돌아갈 때, 하나는 other라는 파라미터명으로 잡아서 처리해준다.

  16. getter를 포함한 로직을 위임할 땐,

    1. 메서드 추출 -> 위임받을 객체가 파라미터로 뽑혀야함
    2. 그외 보라색 내부context는 위임객체 포함 모두 외부context -> 파라미터가 되도록 추출해야함
    3. static이 붙어있으면 반드시 삭제(빨간줄감수)하고 이동해야함
    4. 같은형이 2개가 파라미터에 있다면 알아서 선택하는 창이 뜸
    5. private을 객체에 위임한다면, 옮길 때, public으로 선택할 수 있음(안하면 default로 감)
    6. 옮긴 후 (this.)getter()호출을 삭제하고 내부 필드context로 바꿈
  17. **enum은 **

    1. **{} : 값객체가 외부에서 파라미터로 입력되면 추상클래스로서 추상메서드를 이용해 [가상인자+람다식]에의해 수행되도록 전략객체로서 행위를 구현해놓을 수 있지만, **

    2. (): 분기별 값객체 반환분기문 자체를 값객체에 매핑해놓고, values()를 통해 매핑된 정보를 바탕으로 해당 값객체를 반환해주는 정적팩토리메서드가 될 수 있다.

      • 지연실행될 로직은 함수형 인터페이스(or전략인페.전메())으로 지연호출부 정의 -> 가상인자 람다식에서 외부구현(or전략객체로 생성)의 방법이 있다

      • 위임의 첫단추는 내부context로 사용하는 값은 위임객체에선 외부context가 되도록 위임객체context외에 모든 context값들을 변수로 만들어서 추출해야한다.

      • 특히 파라미터가 제일 많은 부분을 확인해야하며, 조건식 내에서 메서드호출된 것도 값이다!!

      • boolean문안에서 여러객체를 이용한 메서드호출() -> 1개의 응답값을 가지는 값(파라미터) 1개로서 -> 1개의 외부context로 위임될 수 있도록 추출될 로직보다 더 위쪽에 미리 1개의 지역변수로 빼놔야한다.

        image-20220729143012230

      • badCase: 로직 위임전 [내부 여러객체.메서드호출()]부를 지역변수로 안빼놨을 때

        • 여러객체를 사용한 메서드호출은 어차피 1개의 값으로 사용되는데, 연관된 객체가 모두 변수로 뽑힌다.

        a5704e03-7a3a-430e-bc57-6de5e94fd817 image-20220729143737025

      • GoodCase: 로직 위임전 [내부 여러객체.메서드호출()]부를 위임로직 더 위쪽에 지역변수 1개로 응답값을 받았을 때

        710eccce-c554-4ce1-8a24-ab02c97ae2ff

        image-20220729143915942

  18. 캐싱 도입 방법

    • 정팩메가 필요하다
    • static컬렉션(hashMap) CACHE (기존 생성자파라미터를 key, 객체를value로 하는 Type)를 필드초기화하고
    • static정팩메에서 if 없으면 생성해서 넣어주고 , 있으면 get을 요청한다
      • computeIfAbsent(key, 가상인자람다식으로 value생성(객체생성))
    • 미리 static블럭에서 static변수들(캐싱변수도)을 초기화해서 캐싱할 객체들을 미리 생성할 수 있음.
    • 여러쓰레드에서 입력할 가능성이 있다면, ConcurrentHashMap으로 캐싱static 변수를 초기화한다.
    • 캐싱대상 객체는, 상수나 다름없기 때문에, 값으로 비교하기 위해 eq/hC 오버라이딩 한다.
  19. 일급컬렉션도 빈컬렉션으로 시작하여 add시 새객체반환하는 일급컬렉션으로 만든다.

    • new 빈컬렉션을 인자로 받는 부생성자를 만들어주면 된다.
  20. enum의 생성은 Enum.상수의 빨간줄로 만든다.

  21. 메소드 인자나 조건식에서 연산을 하고 있다면 -> 메서드 추출로 리팩토링한다.

  22. functional자리에 supplier의 객체생성자 호출이면, 가상인자를 ignored -> 로 만들고 안쓰면 된다.

  23. 여러종류의 객체를 응답받는 메서드의 응답값중에 특정 class가 응답되어야하는 메서드의 테스트 코드 작성시

    • return null으로 작성하고
    • Object형으로 변수로 받고
    • assert문에서 isInstanceOf( 특정클래스.class )로 확인한다.

    image-20220803172709657

  24. enum에 값이 매핑되는 순간부터 바로 getter를 작성해주면 된다.

  25. 일급컬렉션으로 포장안된 상태라면, 동일형 객체 일괄처리는 List.of()나 Stream.of()를 사용해서 묶어서 처리한다.

    • 동일형 객체끼리의 단순연산도 List.of()로 묶은뒤 stream으로 연산이 가능하다.
    • 단일형은 단순연산도 일단 묶어서 처리하자.
  26. 동일형을 묶어서 일괄처리 or 단순연산한다면, 묶는 부분(List.of)에서 일급컬렉션을 도입하고, 일괄처리 + 단순연산되는 로직을 위임하자.

    • 단순연산도 -> 묶어서 일괄처리해놓으면 -> add로 갯수가 늘거나 줄어도 메서드가 유지된다
  27. 있는지 없는지 유무(몇개 있는지)filter + count가 아니라anyMatch( -> 개별요소판단 )로 판단한다.

  28. 책임위임을 위임하려면, 일단 현재context상의 메서드의 파라미터에 걸려있어야 하며, static메서드라면, static키워드를 삭제하고 위임해야한다.

    • 파라미터로 걸면서 getter를 사용했다면, 위임후에도 내수용getter를 쓰고 있으니 필드로 바꿔줘야한다.
  29. 또다른 리팩토링으로서 조건식에 하나의 도메인에 대한 메서드호출이 나열되어있다면, 메서드 추출시 파라미터에 1개만 도메인만 걸리며, 이 또한 묶어서 책임을 위임할 수 있다.

    • 역시 static안이라면, static을 지우고 위임한다.

      image-20220803234519904

      image-20220803234556959

  30. 여러 종류의 객체return는 추상체 응답의 상태패턴을 고려한다.

    image-20220804113722642

    • 상태패턴을 도입했으면, 응답값으로 나온 현재 상태를 바탕으로 추상체에 있는 [다음상태로 갈 수 있는 인자]를 받아 [다음상태로가는 메서드]를 정의해줘야한다. 에러호출로 종료되는 제일 쉬운 현재상태(blackjack)부터 다음 상태로 넘어가는[trigger메서드]를 만들자
    • 특정 쉬운 구현체부터 시작한다면, 테스트에서 다운캐스팅해서 구현체만의 메서드로 생성하여 테스트하고 공통이면 올린다

    • 특정구현체의 메서드이며, thr 던질 메서드라도 미래에 공통으로 사용될 예비오퍼레이터라면, 응답형을 지정한뒤 던진다.
      • 게임종료는 thr로 한다.
    • 다운캐스팅된 구상체만의 메서드 개발이 완료되면, 다이어그램을 확인해서, 올려도 되는지 판단한다.
      • 다이어그램 + CompareFile을 펼쳐 모든 구상체가 호출해도 되는 메서드인지 확인한다.
        • 다이어그램 단축키 : ctrl + alt + shift + U
        • compareFile : 구상체들만 선택후 ctrl + D
      • 올릴 거면, 다운캐스팅했떤 로직을 삭제하고, 다른 구상체들도 구현한다.
    • trigger + 정보를 판단하려면, 상태객체는 생성시부터 이미 정보를 상태값으로 가지고 있도록해야한다.
  31. 일급컬렉션에서 add할 때, 내부에서 새로운 일급컬렉션 상태를 만들기 위해, 업데이트된 컬렉션 필드 상태를 만들어야하는데, 기존상태값(컬렉션 필드)를 얕은복사해서 연관성을 [떼어낸 상태에서 업데이트]야한다

    • 기존 컬렉션 필드는 add하기 전에 얕은복사한 뒤, 생성자복사된 새 컬렉션에 add하고, 그것을 새로운 상태로 삼는다.
  32. 상태필드가 객체이상, 일급컬렉션인 경우, 현재 구체적인 값의 상태를 물어볼땐 getter대신 메세지를 보낸다

  33. setNext, add와 같이 같은형 객체를 반환하는 메서드들뿐만 아니라 같은카테고리인 추상체를 반환하는 메서드들도 체이닝 메서드이다.

  34. 작은 코드라도 수정한다면, 복사해서 수정하고 지운다.

  35. 인자입력시 List.of() -> 가변배열로 입력하도록 변경한다.

  36. 상태변화가 없는 객체캐싱객체 or VO로, eq/hC를 재정의하여 값이 같으면 같은 객체가 되는 값객체라서 testutil패키지 > Fixtures클래스안에 상수객체로 만들어 써도 된다.

    • test루트에서 testutil 패키지를 만들고 내부 Fixtures 만들자
    • Fixtures에 쓸려면 어디서든 불러도 같은 객체가 생성되도록 캐싱==싱글톤이 보장되어야한다.
      • 캐싱객체는 test코드에서 isSameAs확인해야한다.
      • eq/hC의 VO값객체는 test코드에서 eq/hC 재정의 후 isEqualTo로 확인해야한다

    image-20220805123413269

    • 상수객체 Fixture는 한글로 알아보기 쉽게 네이밍해도 된다.

    • 상수 추출후, Fixture로 옮기기전에 미리 다 상수로 바꿔놓으면 더 쉽다(ctrl + H)

      • Fixture로 옮긴 후 프로젝트 전역으로 확인하고 싶다면, 상수값(우항)을 Ctrl + shift + F로 찾으면 된다.

        image-20220805130338811

  37. 시작객체가 있다면, 같은 패키지로 몰아서 시작객체public을 제외한 나머지들은 default가시성을 가지도록 해서, 외부에서 생성안되도록 한다면, 사전검증을 안해도 된다.

    • 만약, 생성자가 기본생성자라서 정의를 안해줬다면, 재정의 후 가시성변경해줘야한다.
      • 대표적인 예가 유틸클래스(상태값없이 input->output static메서드만 가지는)는 생성자를 private가시성으로 변경해주는 것이다.
      • 생성자가 없는 클래스 -> public으로 열여있으며, 재료를 바탕으로 상태값을 만들지 않는 클래스
    • client가 잘못입력할 수도 있기 때문에 중간에 생성되는 상태객체들외부에서 생성못하도록 가시성으로 막아주기
  38. 특정 구상체에만 사용되는 메서드가 등장했다.

    • 상속관계면, 조합관계로 바꾼다.
    • 이미 인터페이스를 구현하는 조합관계의 구상체였다면
      1. 일단 인터페이스에 올리고, 특정구상체만 자기메서드를 로직을 구현한다.
      2. 마저 테스트를 진행해야하니 나머지 구상체들은 로직 없이 구현만 해준다
      3. 이후 해당메서드 테스트가 끝나면, 특정구상체 외 사용하지 않는 구상체들에게 내리기 전중간에서 막아주는 추상클래스로 만들어지는 중간추상층을 만들어준다.
        • 억지로 구현한 코드가 중복이라서 중간추상층을 두고 중복코드를 제거한다는 개념으로 간다.
    • 추상클래스 도입시 중간에 먹어줘서 공짜로 물려주는 것들은 (템플릿메소드) final을 달아서 자식이 수정 못하게 막아야한다
      • 추상클래스의 public 템플릿메서드라면 눈꽃모양으로 final을 확인하자.
  39. 좋은 부모를 만들기 위한 전략(템플릿메소드) 중 중복 필드 처리

    1. Object)중복로직제거 Strategy to Templatemethod(Plan3)
      1. 추상클래스는 좋은 상속의 부모로서 변수는 private, 메서드는 final or protected abstract, 생성자없이 setter로확인하여 달아준다.
    2. 구상체별 중복되는 필드를 올릴 땐, 구상클래스 생성자 주입 -> setter메서드 -> private필드로 올린다.
    3. 부모의 private필드는 부모내에서 처리하게 하고, 자식은 부모private필드자체는 못쓰고, final 물려주는 템플릿메소드들만 쓴다.
      • 자식들이 훅메서드 구현시 필요한 정보들은, 자기가 생성자 주입받아서 쓰면 된다.
      • 자식이 부모의 private필드값을 써야한다면, 부모에게 구현된 getter를 물려받아 쓰면된다.
  40. 암기getter

    1. getter정의시 return 불변객체라면, public열어두기 가능 + dto없어도 됨
      • 불변의 일급컬렉션 객체 반환 -> dto없는 public getter로 제공
      • 불변 객체 아니라면, Dto로 만들어서 반환
    2. getter정의시 return 컬렉션필드라면, view로 보낼 땐, 깊은 복사로 못건들게 해서 반환
      • 불변의 일급컬렉션 자신 내부의 컬렉션 필드 반환
        • server사이드 반환이라면, 얕은복사 반환후, 내부에서 객체들 조작
        • view반환이라면, 깊은 복사 반환 or DTO로 반환후, 내부 객체들 조작안되게
  41. getter 추가 -> 중복필드로서 같이 움직이는 해당 상태값이 추가된 상황에서

    1. 최상위 인터페이스에서부터 추가되었으며

    2. 구현시, 어느 구상체들도 thr로 예외없이 다 100% 다 구현된 상태라면

    3. 구상체 100%구현 메서드 + 필드로서 중복제거를 위한 중간추상층이 추가된다.

      image-20220806171853571

  42. 상태객체의 인터페이스 오퍼레이터는 최소 4개 + @가 필요하다

    1. trigger 메서드: draw()

    2. 중단 메서드: stay()

    3. 현재상태값 반환메서드: getter cards()

    4. is끝난상태[중간추상층]인지 확인 메서드: boolean isXXXX()

      • 상태객체 소유 객체(Player)는 상태가 isFinished될때까지 반복작업할 것이다.

      • 여기서는 stay개발시 불가하여 묶여진 Finished 형용사 중간추상층이 이미 있으니 끝난 상태냐라고 물어볼 수 있다.

        • 특정 구상체인지는 물어보지 않는다. 그놈빼고 다 false를 대답하기 때문에 -> 특정 구상체인지는 물어보지말고 메세지를 던진다.
      • 다형성 인터페이스에 존재하는 boolean 메서드는 -> 구상체들이 구현만 해주면 알아서 개별 답변되는 메서드

        image-20220806180149358

  43. 추상체변수의 구상체별 기능 구현전략패턴처럼 구현만 하면 알아서 적용된다.

    • 이미 외부생성(전략패턴) or trigger메서드(상태패턴)에 의해 State state안에는 Blackjack이나 Bust나 Stay가 담겨있는 상태다
      • 개별로 구현만 해주면, 알아서 작동한다.
      • 대신, 알아서 계산안되어야할 놈들은 thr나 예외처리 해놔야한다.
    • 추상체 변수의 상태이용 개별구현 메서드는 구상체를 물어보지말고 구현만 해놓으면 알아서 적용된다.
    • 상태값이 일반 객체였으면… 물어보고 판단했을 것인데… 추상체변수인 순간 알아서 구현됨을 생각하자.
  44. 중간추상층이 좋은 부모가 되려면, 공통점이 아예 없는 개별구현로직 조차, 개별구현훅메서드를 래핑하고 있는 public final 템플릿메소드로 가지고 있고, 서로 다른 로직은 protected abstract 훅메서드만 내보내줘야한다.

    1. CompareFile(ctrl+D)로 구상체 메서드들의 공통점과 차이점을 살펴보고, 어느부분까지를 훅메서드로 뺄 것인지 생각한다.
      • 상수만 서로 달라도, 해당부분을 훅 메서드로 뺄 수 있다.
    2. 구상체들 중 서로 다른 훅 부분에 가장 파라미터가 많이 사용되는 로직을 가진 구상체 1개를 선택하고 해당 메서드를 중간추상층으로 잘라내서 올린다
    3. 올린 public 템플릿메서드 내부에서 다른 부분만 내수용 메서드 추출(구상체private -> 추상체protected abstract)의 훅메서드로 만든다
      • 올리고 나서 훅을 만든다면, 메서드 추출후 가시성을 private -> protected abstract로 직접 변경해줘야한다.
      • 올린 템플릿메서드는 final로…
    4. 나머지 구상체들은, 훅메서드를 구현하면서, 기존 구현메서드의 다른 부분만 훅에 입력한 뒤, 기존 메서드는 삭제해준다.
  45. 다 구현된 상속구조에서 추상화레벨을 맞추기 위한 중간추상층 도입을 한다.

  46. 제한된 종류의 상태값을 가진다면 not final 시작상태 필드초기화 + setter대신 -> public void toggle로 정의해준다.

  47. if 상태확인 -> 상태업뎃을 매번 확인하면서 반복하는 것이 while 상태확인 -> 상태업뎃이다.

  48. 좋은 부모를 위해 생성자없는 [not final + set계열 메서드]를 정의했다면, set전에 null나올 수 있으니, 빈값 필드초기화 해야한다

  • 이미 정해진 종류의 setter는 state = state변화의 toggle에 가깝다

  • 매번 상태확인후 시행하는 if condition -> action를 반복하는 것이 while condition 반복문이다.

    • 이 때, 반복되는 동안 메서드 인자인 재료는 iterator처럼 1개씩 주어질 수 있어야한다.
  • 도메인 객체를 함부러 public메서드의 파라미터 == 외부생성으로 주지말자.

  • 내부생성 or 내부에서 1개씩 제공해주는 iter느낌의 객체를 받을 수 있다.

  • deque의 재료는 미리 list로 만들어놔야한다. 제공할 때 생성자복사시 형변환을 같이해서 줄 수 있다.

    • deque로 변환해서 가지고 있으면, pop()으로 iterator객체가 될 수 있다.
    • 형변환되어 제공될 재료 컬렉션은, 미리 만들어놓아도 된다면, static필드 + static블럭으로 초기화해놓는다.
  • 가변 변수 콜렉션에 add는 stream으로 해결할 수 있다.

  • 2중 for문은 stream을 stream->map->stream->map -> 첫번째 map을 flatmap으로 만들면 사용할 수 있다.

  • 생성자내에서 [내부context인 상태값(가변변수) 재할당]을 포함한 메서드추출은 파라미터없이 그대로 재할당을 들고들어가 only내수용이 된다.(위임불가)

    • 재할당되는 line에 지역변수를 만들고, 그 지역변수를 재할당시키게 한 뒤, 내부context재할당을 제외한 로직을 추출하여, [재할당되는 값return]이 추출되게 한다.
    • 만약, 내부context도 같이가야한다면, 내부에 포함시키고, 파라미터 추출을 해준다.