TDD 총정리0-요구사항 정리와 테스트 코드 작성 정리
TDD학습 내용 압축 정리
📜 제목으로 보기
TDD 작성
요구사항 정리
- 제목(##)과
- [ ]
박스를 만든다. - 키워드를 추출한다
- 객체 vs 메서드 vs UI vs 세부요구사항으로 나눈다.
- 관리 vs 객체 시작점을 고른다.
## 요구사항
- [ ] 각 자동차에 이름을 부여할 수 있다.
- [ ] 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- [ ] 자동차 이름은 쉼표(,)를 기준으로 구분한다.
- [ ] 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한명 이상일 수 있다.
## 키워드 -> 분류 -> 객체 vs 메서드 vs 세부요구사항 vs ui 나누기 -> 시작점 고르기
- 자동차
- 이름 : object
- , 기준으로 구분 : 세부요구사항 - ui 맨 나중에
- 전진 : method
- 자동차 경주 게임
- 완료한 후 누가 우승했는지를 알려준다. : 메서드
- 우승자
- 한명 이상 : 세부요구사항
테스트
테스트 작성요령
-
**시작점을 고를 때, **
-
**
기능(메서드)을 가진 데이터객체
이외에 ** -
controller에서
정제된 input이 들어오고, controller로 output응답도 해야하는 Service
부터 시작할 수 있다.-
정제된 input부터 -> 메인 흐름을 다 나열 -> 출력될 ouput객체까지 반환해주는 것이 service의 역할이다.
- 처음부터 객체도출은 어려울 수 있다.
- 서비스의 메서드부터 짠다면, input 부터 output까지 메인 흐름을 생각하고 정리해야한다
- 서비스 start~end까지 로직이 짜여졌으면
해당 로직에 맞는 메서드명
으로 변경한다
-
정제된 input부터 -> 메인 흐름을 다 나열 -> 출력될 ouput객체까지 반환해주는 것이 service의 역할이다.
-
service에 대한 input이
내부 로직에 의해 데이터객체
로 주어진다면,컴퓨터가 줄 데이터객체로부터 만들
어놓고,그것을 이용한 input으로 한 -> output을 내는 핵심로직
부터 시작한다.- 예를 들어, 블랙잭게임을 하면, 카드덱으로부터 카드2장이 주어진다.
- 이미
카드 2장이 주어졌다고 가정
하고데이터객체 카드
부터 만들고핵심 로직으로서 시작
하자.
- 이미
- service가 핵심로직이 아닐 수 있다.
서비스내 Game등이 핵심로직
일 수 있다. -
input -> output이 나오는 핵심로직부터 시작한다.
Game
.start()
로 시작하자.
- 예를 들어, 블랙잭게임을 하면, 카드덱으로부터 카드2장이 주어진다.
-
핵심로직 자체도 service가 아니라 (상태패턴이라면) 상태를 가지는 객체로 바뀔 수 있다.
-
서비스 메서드 == 스태틱 메서드 like 유틸메서드
에서 ->객체
가 되었다면- 상태값을 가지며, 상태값을 만드는 정보input들은 다 생성자로 들어와야한다.
- 메서드로 상태값 정보가 들어온다면, setter다.
-
서비스의
유틸메서드 input
->생성자로 주입
된다 -
서비스의
유틸메서드내 로직을 거친 뒤 1개의 output응답값(상태객체)
->필드
로 가진다.-
상태객체는, 정보만 가져야하므로
상태객체가 아닌 상태객체를 사용하는 객체
일뿐이다. -
상태객체로 다시 변환하려면
다른 상태객체들과 상태값(필드) 와 메서드를 동일
하게 사용하도록 수정해야한다.-
Ready도 다른 상태객체들과
동일 상태값 필드
(Cards)를 가지며생성자로는 아무것도 주입 안된 빈카드로 초기화
하는기본 생성자
로 만들어져야한다. -
Ready도 .start()로 다른 객체로 넘어가는 것이 아닌 다른 상태객체들의 메서드처럼
Trigger메서드(draw())
로부터 setter정보를 받아상태값업데이트 이후 상태값을 가지고 판단하여 다른 상태로 넘어가
도록 수정한다.- Ready는 카드를 2장을 받는다. -> 다른상태객체처럼 draw로 1장씩 받도록 정의하고
외부에서 2번을 호출
하던지다른 파라미터로서 받도록 오버로딩메서드
로 정의해준다.
- Ready는 카드를 2장을 받는다. -> 다른상태객체처럼 draw로 1장씩 받도록 정의하고
-
-
-
서비스 or 핵심로직 통합테스트가 Ready라는 1개 상태객체 테스트로 바꼈다.
- 개별 상태객체로 테스트코드들을 나누어서 옮긴다.
-
-
-
정제된 input(원시값)을 통해 service개발을 완료하고 나면
-
가장작은 단위의 input
에 대한서비스에서 예외발생
테스트를 먼저 만들고 ->필요에 의해 도메인 객체에서 예외발생 되도록 도메인을 생성한다
-
원시값input을 도메인객체로 만들면서 사전검증
이 이루어지도록 해야한다.- 검증이 필요한 원시값에 new도메인()을 때려서 시작한다
-
- 그 다음에
컬렉션을 일급컬렉션으로 만들면서 중복 검증 등
의 검증이 이루어져야한다.
-
-
가장 빠르게 실패는 thr IllegalStateException을 활용하고. 가장 빠르게 성공은 로직 없이 응답값만 return하도록 만든다.
- 실패 -> 성공 -> 리팩토링 을 반복한다.
-
생성자로 시작 이외에
나중엔 (상태필드 변경후 상태로 확인) 바뀌더라도 상수 응답 메서드
로 먼저 테스트를 작성한다.- car.move()는 setter같은 메서드인데, 처음 메서드 작성시 1칸 전진마다 상수1을 응답값으로 주도록 만든다
-
응답도 getter로서 조회의 일
이다.조회용 메서드가 아니라면, 로직 테스트 완료후 응답 대신 상태로 확인
하도록 변경해야한다.- 조회용 메서드가 맞는지 확인한다.
- 조회용 메서가 아니라면 getter같은 return응답을 제거한다.
- 기존에 메서드결과로 응답값을 조회했던 부분을
값 비교 -> 객체 비교
를 하도록eq/hC 재정의 및 테스트 assert문 수정
을 해줘야한다.
-
상태가 변하는 메서드는 setter
이므로불변객체를 유지하기 위해, 상태값 변화 대신, 상태값이 변화한 객체 응답메서드
로 바꾸자.- 다시 한번 응답메서드가 되지만, 상수 응답이 아닌
새 객체를 만들어 반환
이다. - 객체의 상태값 및 생성자 등이 바뀌므로
2메서드가 아니라 2객체를 생성해서 테스트
한 뒤 반영한다.
- 다시 한번 응답메서드가 되지만, 상수 응답이 아닌
-
테스트할 메서드는
무조건 응답하도록 먼저 작성
하며 이 때,응답값은 [넣어준 인자에 대한 case값을 응답]
을 해줘야한다- 만약, 로또번호 vs 당첨번호 인자 입력을 1등 번호로 예시case로 넣어줬다면, 그 인자 case에 맞는 1등이 응답값으로 반환해야한다(아무거나 반환X)
- 2번째부터는일반화하는 경우가 많으니,
첫 메서드 생성하는, 1번째 첫 인자를 입력할 때, case에 맞는 반환도 생각하고 입력
하자
- 2번째부터는일반화하는 경우가 많으니,
TDD메서드의 응답값은 테스트메서드의 테스트CASE에 의해 작성순서가 결정
된다.
- 만약, 로또번호 vs 당첨번호 인자 입력을 1등 번호로 예시case로 넣어줬다면, 그 인자 case에 맞는 1등이 응답값으로 반환해야한다(아무거나 반환X)
-
case별로 테스트 메서드를 만든다.
메서드명_CASE____세부내용
형태를 빌린다.- 2번째case부터는 일반화해도 좋다
- case == 메서드의 인자가 결정
-
**테스트케이스 추가에 따른
기능추가
,사전 검증과 같이 전체메서드가 걸린 기능추가
는 **- 메서드를 전체를 2메서드로 복사해놓고 테스트
-
기존 테스트들을 2메서드로 호출하도록 변경 후 전체 테스트
- 이 때
기존테스트()
의 소괄호까지를ctrl + h
로 찾아서 변경하면 쉽게 변경된다.
- 이 때
- 기존테스트들 다 통과시
회색으로 안쓰게 된 원본메서드 안전삭제(alt+del)
- 2메서드를 -> 원본메서드로 변경
-
기능 추가 -> 2case부터 일반화로직 추가시, 1case를 보존하기 위해,
2메서드 복붙후 테스트에서 1case 인자를 그대로 사용
+ 내부로직의 결과값에 if를 사용하더로 먼저 통과시키게 해야한다. -
특정로직(private메서드)에 대한 기능 추가는
private메서드만 복사해서 테스트
해놓고 끝나면 반영하고 삭제한다. -
요구사항이 복잡해질 땐, 그 부분만 아래에서 학습테스트를 진행해도 된다.
-
상태변경 메서드의 테스트가 끝나고 다른 기능 테스트를 할 때,
특정 상태를 만들기 위해 상태변경메서드 반복 호출
을 하지말고,직접 해당 상태로 만드는 생성자를 추가
해서 현재하는 테스트에 집중하게 한다- 특정상태의 객체를 불변객체로 바로 만들면, 빈컬렉션+add가 사라진다.
-
기존파라미터(원시 컬렉)를 살려두고 이어서갈 땐
새 인자(도메인 컬렉)로 메서드 작성후 오버로딩
- 기존 파라미터(도메인 컬렉)를 변환된 파라미터(일급컬렉션)로 대체하고 싶다면
내부변환후 파라미터 추출
- 기존 파라미터(도메인 컬렉)를 변환된 파라미터(일급컬렉션)로 대체하고 싶다면
-
서비스내
getter
가 보이면,도메인 내부 로직
으로서캡슐화로 감춰야하는 로직
임을 100% 생각한다.- 출력을 제외하고 getter는 없다고 보자.
- getter이후가 같은형의 비교면 ->
해당형으로 책임을 위임해 옮긴다
- getter이후가 다른형의 비교면 ->
제3형을 만들어 책임을 위임한다.
- static메서드였으면,
static을 삭제하고 -> 메서드 이동 -> 파라미터 중 위임할 객체가 2개가 같은형이면, 하나의 변수를 택1
한다
-
같은형의 일급vs일급은 -> 일급vs단일(contains) 메서드를 사용할 확률이 높다. 확인해서 처리한다.
-
정해진 갯수의 인스턴스는
정팩메를 통한 캐싱
을 도입하자 -
정팩메든 생성자든 파라미터를 추가하면 견고한 클래스가 된다.
- 자바(부생성자개념)이외 언어들은
주생성자로만 생성
하고다양한 타입의 파라미터로 생성하려면 정팩매
를 이용해야한다. - 나도 앞으로
파라미터 추가를 위해서 정팩매 도입
을 고려해봐야겠다.
- 자바(부생성자개념)이외 언어들은
-
일급컬렉션도
빈컬렉션으로 시작하여 add시 새객체반환
하는 일급컬렉션으로 만든다.- new 빈컬렉션을 인자로 받는
부생성자
를 만들어주면 된다.
- new 빈컬렉션을 인자로 받는
-
기존에 생성자 없이 사용하던 객체에,
상태값이 추가되어 생성자가 추가될 경우
,부생성자로 빈값할당
으로 초기화해주는기본생성자
를 추가해서기존코드가 망가지게 않게 한다
-
여러종류의 클래스객체 응답이 가능한 메서드
에 대해 특정 클래스의 객체를 응답하는 테스트를 작성하려면, Object actual -> isIntanceof(특정클래스.class)로 테스트코드를 작성한다. -
여러 종류의 객체return는
추상체 응답의 상태패턴
을 고려한다.- 상태패턴을 도입했으면,
응답값으로 나온 현재 상태
를 바탕으로추상체에 있는 [다음상태로 갈 수 있는 인자]를 받아 [다음상태로가는 메서드]
를 정의해줘야한다.에러호출로 종료되는 제일 쉬운 현재상태(blackjack)부터 [다음 상태로 넘어가는 메서드]를 만들자
- 상태패턴을 도입했으면,
-
추상체를 만드는 순간부터
패키지를 분리하여, 패키지폴더 대상 다이어그램으로 의존성을 확인
하자.- 이후,
구상체만의 메서드 개발후 올리기전 다이어그램 + CompareFile하자
- 이후,
-
상태변화가 없는 객체
는캐싱객체
orVO로, eq/hC를 재정의하여 값이 같으면 같은 객체가 되는 값객체
라서testutil패키지 > Fixtures클래스
안에상수객체
로 만들어 써도 된다.- test루트에서
testutil
패키지를 만들고 내부Fixtures
를 만들자 -
Fixtures에 쓸려면
어디서든 불러도 같은 객체가 생성되도록 캐싱==싱글톤이 보장
되어야한다.-
캐싱객체
는 test코드에서isSameAs
로 확인해야한다. eq/hC의 VO값객체
는 test코드에서eq/hC 재정의 후 isEqualTo
로 확인해야한다
-
- 상수객체 Fixture는
한글로 알아보기 쉽게 네이밍
해도 된다.
- test루트에서
-
상태패턴의 상태객체반환 with trigger되는
결국엔 다른 객체의 상태값으로 포장
될객체
들은view에 넘기기 위해 getter를 무조건 가진다
trigger 등 set계열 메서드들 개발이 완료되면 getter도 개발
해야한다.
-
좋은 부모를 만들기 위한 전략(템플릿메소드) 중
중복 필드 처리
-
Object)중복로직제거 Strategy to Templatemethod(Plan3)
-
추상클래스는 좋은 상속의 부모로서
변수는 private
,메서드는 final or protected abstract
,생성자없이 setter로
확인하여 달아준다.
-
추상클래스는 좋은 상속의 부모로서
구상체별 중복되는 필드
를 올릴 땐, 구상클래스생성자 주입
->setter메서드 -> private필드
로 올린다.-
부모의 private필드는 부모내에서 처리하게 하고,
자식은 부모private필드자체는 못쓰고, final 물려주는 템플릿메소드들만 쓴다.
- 자식들이 훅메서드 구현시 필요한 정보들은, 자기가 생성자 주입받아서 쓰면 된다.
- 자식이 부모의 private필드값을 써야한다면,
부모에게 구현된 getter를 물려받아 쓰면
된다.
-
Object)중복로직제거 Strategy to Templatemethod(Plan3)
-
암기getter
-
getter정의시 return 불변객체
라면,public열어두기 가능 + dto없어도 됨
- 불변의 일급컬렉션 객체 반환 -> dto없는 public getter로 제공
- 불변 객체 아니라면,
Dto로 만들어서 반환
-
getter정의시 return 컬렉션필드라면, view로 보낼 땐, 깊은 복사로 못건들게 해서 반환
-
불변의 일급컬렉션
자신 내부의 컬렉션 필드 반환
- server사이드 반환이라면, 얕은복사 반환후, 내부에서 객체들 조작
- view반환이라면,
깊은 복사 반환 or DTO로 반환
후, 내부 객체들 조작안되게
-
불변의 일급컬렉션
-
-
중복제거 등 추상화/상속 관련작업
이 끝날 때마다, diagram을 보자-
복잡하다면,
자물쇠의 show dependencis만 끄면
상속관계가 잘 보인다. -
추상화 레벨을 보고 싶다면,
바로 위의 추상클래스에 데고 usage(shift+F12)
를 통해어떤 놈들이 나를 직접 extends했는지
확인하면 된다.
-
-
랜덤이 껴있는 로직은, 테스트를 고려해서 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)로서 빼주면 된다.
테스트 코드 작성시 팁
-
split등으로 actual될 값이 2개이상이면, assertAll을 활용해서 0번인덱스부터 단계별로 확인하자
-
상수 리팩토링은 result_index , result_prefix 등으로 case_result + 명칭을 그대로 붙여서 지어도 된다.
-
메서드 안에서 또다른 일을 하는 경우, 파라미터 -> 또다른 일 -> 반환 과정을 메서드추출하고,
파라미터로 올리면, 추출메서드가 현재메서드의 인자로 올라가서 일하고 있게된다.
-
케이스 추가 -> 테스트메서드 추가 -> 메서드2 작성시
개별 메서드 호출마다 저장소가 필요하다면, 메서드주체인 객체의 상태필드를 이용
해야한다. 필드를 만들어서 쓰자. -
메소드에 존재하는 테스트 힘든 코드는 확정값을 만들 수 있는 전략객체로 위임하되 전략객체는 협력객체로서 메서드 인자가 아닌 생성자 인자로 받아야한다
- 프로덕션용 기본 전략값이 정해져있다면 생성자 오버로딩을 이용하자
-
getter에서 get을 뺀 뒤 필드처럼 지어서, 내부의 필드명이 없는 것처럼 감출 수 있다.
-
.setter처럼 상태변화 메서드가 만들어졌다면, 상태변화한 객체를 새로생성해서 반환하는 메서드로 변경한다.
- 필요시 생성자를 추가한다.
- 자신형 객체반환 메서드는 체이닝이 가능하니 테스트에서 체이닝으로 작성해도 된다.
-
특정 상태를 만들기 위해 상태변경메서드 반복 호출
을 하지말고,직접 해당 상태로 만드는 생성자를 추가
해서 현재하는 테스트에 집중하게 한다- 특정상태의 객체를 불변객체로 바로 만들면, 빈컬렉션+add가 사라진다.
-
컬렉션 인자 작성사
List.of
는 컬렉조작불가능한 얕은복사을 사용하자.-
Arrays.asList
는 조작가능한 생성자 복사(얕은 복사) 사본
-
-
인자가 3개이상이면, 엔터쳐서 줄바꿈해준다.
-
컬렉션 vs 컬렉션 -> 반복문 + 요소 vs 컬렉션 + 저장/업데이트용 지역변수가 생긴다
-
원시값 인자 포장
시, 검증대상에 new때려서 만들기 시작하며, 검증로직 작성 후,전체 원시값인자들을 도메인 인자로 변경
하여 ->도메인 파마리터 메서드
를 생성한 뒤,원시값 파라미터 메서드 내부에서 도메인 전환후 오버로딩 호출
할 수있도록 로직을 변경한다.- 테스트코드에서 도메인 인자들이 다 사라지고 원시값으로 다시 복구되었다면, 오버로딩의 도메인 파라미터 메서드는 private화 시켜서, 외부에서 객체 생성을 막는다.?!
-
도메인 컬렉션 인자 포장
시 [이미 원시값 인자 존재 + 1개의 오버로딩 존재 + 도메인컬렉션 파라미터는 필요없을 때]- 내부에서
p1 -> 일급컬렉션 변환과정
까지 작성하고 새롭게파라미터 추출
해서파라미터 자체를 변경
한다. - 원시값 포장시에는 도메인입력 < 원시값입력이므로
기존 파라미터 메서드를 살리는 방향으로 오버로딩 적용
을 했었지만,도메인 컬렉션 -> 일급컬렉션
에서는 도메인컬렉션 입력을 죽인다.
- 내부에서
-
일급컬렉션의 검증은 도메인객체들의
갯수, 중복여부
등이다. -
같은형의 비교시 -> 내부 메서드로 돌아갈 때,
하나는 other라는 파라미터
명으로 잡아서 처리해준다. -
getter를 포함한 로직을 위임할 땐,
- 메서드 추출 -> 위임받을 객체가 파라미터로 뽑혀야함
- 그외 보라색 내부context는
위임객체 포함 모두 외부context -> 파라미터가 되도록 추출
해야함 static이 붙어있으면 반드시 삭제
(빨간줄감수)하고 이동해야함- 같은형이 2개가 파라미터에 있다면 알아서 선택하는 창이 뜸
- private을 객체에 위임한다면, 옮길 때, public으로 선택할 수 있음(안하면 default로 감)
- 옮긴 후 (this.)getter()호출을 삭제하고 내부 필드context로 바꿈
-
**enum은 **
-
**
{}
:값객체가 외부에서 파라미터로 입력
되면추상클래스로서 추상메서드를 이용해 [가상인자+람다식]에의해 수행되도록 전략객체로서 행위를 구현
해놓을 수 있지만, ** -
()
:분기별 값객체 반환
시분기문 자체를 값객체에 매핑
해놓고,values()를 통해 매핑된 정보를 바탕으로 해당 값객체를 반환
해주는정적팩토리메서드
가 될 수 있다.-
지연실행될 로직은
함수형 인터페이스(or전략인페.전메())으로 지연호출부 정의 -> 가상인자 람다식에서 외부구현(or전략객체로 생성)
의 방법이 있다 -
위임의 첫단추는
내부context로 사용하는 값은 위임객체에선 외부context
가 되도록위임객체context외에 모든 context값들을 변수로 만들어서 추출
해야한다. -
특히 파라미터가 제일 많은 부분을 확인해야하며,
조건식 내에서 메서드호출된 것도 값이다!!
-
boolean문안에서
여러객체를 이용한 메서드호출() -> 1개의 응답값
을 가지는값(파라미터) 1개
로서 ->1개의 외부context로 위임
될 수 있도록추출될 로직보다 더 위쪽에 미리 1개의 지역변수로 빼놔야한다.
-
badCase:
로직 위임전 [내부 여러객체.메서드호출()]부를 지역변수로 안빼놨을 때
- 여러객체를 사용한 메서드호출은 어차피 1개의 값으로 사용되는데, 연관된 객체가 모두 변수로 뽑힌다.
-
GoodCase:
로직 위임전 [내부 여러객체.메서드호출()]부를 위임로직 더 위쪽에 지역변수 1개로 응답값을 받았을 때
-
-
-
캐싱 도입 방법
- 정팩메가 필요하다
- static컬렉션(hashMap) CACHE (기존 생성자파라미터를 key, 객체를value로 하는 Type)를 필드초기화하고
- static정팩메에서
if 없으면 생성해서 넣어주고 , 있으면 get
을 요청한다- computeIfAbsent(key, 가상인자람다식으로 value생성(객체생성))
- 미리 static블럭에서 static변수들(캐싱변수도)을 초기화해서 캐싱할 객체들을 미리 생성할 수 있음.
- 여러쓰레드에서 입력할 가능성이 있다면, ConcurrentHashMap으로 캐싱static 변수를 초기화한다.
- 캐싱대상 객체는, 상수나 다름없기 때문에, 값으로 비교하기 위해 eq/hC 오버라이딩 한다.
-
일급컬렉션도
빈컬렉션으로 시작하여 add시 새객체반환
하는 일급컬렉션으로 만든다.- new 빈컬렉션을 인자로 받는
부생성자
를 만들어주면 된다.
- new 빈컬렉션을 인자로 받는
-
enum의 생성은 Enum.상수의 빨간줄로 만든다.
-
메소드 인자나 조건식에서 연산
을 하고 있다면 -> 메서드 추출로 리팩토링한다. -
functional자리에 supplier의 객체생성자 호출이면, 가상인자를
ignored ->
로 만들고 안쓰면 된다. -
여러종류의 객체를 응답받는 메서드의 응답값
중에 특정 class가 응답되어야하는 메서드의 테스트 코드 작성시- return null으로 작성하고
- Object형으로 변수로 받고
- assert문에서 isInstanceOf( 특정클래스.class )로 확인한다.
-
enum에 값이 매핑되는 순간부터 바로 getter를 작성
해주면 된다. -
일급컬렉션으로 포장안된 상태
라면,동일형 객체 일괄처리
는 List.of()나 Stream.of()를 사용해서 묶어서 처리한다.동일형 객체끼리의 단순연산
도 List.of()로 묶은뒤 stream으로 연산이 가능하다.- 단일형은
단순연산도 일단 묶어서 처리
하자.
-
동일형을 묶어서
일괄처리 or 단순연산
한다면,묶는 부분(List.of)에서 일급컬렉션을 도입
하고,일괄처리 + 단순연산되는 로직을 위임
하자.단순연산도 -> 묶어서 일괄처리
해놓으면 ->add로 갯수가 늘거나 줄어도 메서드가 유지
된다
-
있는지 없는지 유무
만(몇개 있는지)filter + count
가 아니라anyMatch( -> 개별요소판단 )
로 판단한다. -
책임위임을 위임하려면, 일단
현재context상의 메서드의 파라미터
에 걸려있어야 하며,static메서드라면, static키워드를 삭제하고 위임
해야한다.- 파라미터로 걸면서 getter를 사용했다면, 위임후에도 내수용getter를 쓰고 있으니 필드로 바꿔줘야한다.
-
또다른 리팩토링으로서
조건식에 하나의 도메인에 대한 메서드호출이 나열되어있다면, 메서드 추출시 파라미터에 1개만 도메인만 걸리며, 이 또한 묶어서 책임을 위임
할 수 있다.-
역시 static안이라면, static을 지우고 위임한다.
-
-
여러 종류의 객체return는
추상체 응답의 상태패턴
을 고려한다.- 상태패턴을 도입했으면,
응답값으로 나온 현재 상태
를 바탕으로추상체에 있는 [다음상태로 갈 수 있는 인자]를 받아 [다음상태로가는 메서드]
를 정의해줘야한다.에러호출로 종료되는 제일 쉬운 현재상태(blackjack)부터 다음 상태로 넘어가는[trigger메서드]를 만들자
-
특정 쉬운 구현체부터 시작한다면,
테스트에서 다운캐스팅해서 구현체만의 메서드로 생성하여 테스트하고 공통이면 올린다
-
특정구현체의 메서드이며,
thr 던질 메서드
라도미래에 공통으로 사용될 예비오퍼레이터라면, 응답형을 지정
한뒤 던진다.- 게임종료는 thr로 한다.
-
다운캐스팅된 구상체만의 메서드 개발이 완료
되면,다이어그램을 확인해서, 올려도 되는지 판단
한다.-
다이어그램 + CompareFile을 펼쳐 모든 구상체가 호출해도 되는 메서드인지 확인한다.
- 다이어그램 단축키 : ctrl + alt + shift + U
- compareFile : 구상체들만 선택후 ctrl + D
- 올릴 거면, 다운캐스팅했떤 로직을 삭제하고, 다른 구상체들도 구현한다.
-
다이어그램 + CompareFile을 펼쳐 모든 구상체가 호출해도 되는 메서드인지 확인한다.
- trigger + 정보를 판단하려면,
상태객체는 생성시부터 이미 정보를 상태값으로 가지고 있도록
해야한다.
- 상태패턴을 도입했으면,
-
일급컬렉션에서 add할 때, 내부에서 새로운 일급컬렉션 상태를 만들기 위해,
업데이트된 컬렉션 필드 상태
를 만들어야하는데,기존상태값(컬렉션 필드)를 얕은복사해서 연관성을 [떼어낸 상태에서 업데이트]야한다
-
기존 컬렉션 필드는 add하기 전에 얕은복사한 뒤,
생성자복사된 새 컬렉션에 add
하고, 그것을 새로운 상태로 삼는다.
-
기존 컬렉션 필드는 add하기 전에 얕은복사한 뒤,
-
상태필드가
객체이상, 일급컬렉션
인 경우,현재 구체적인 값의 상태를 물어볼땐 getter대신 메세지
를 보낸다 -
setNext, add와 같이
같은형 객체
를 반환하는 메서드들뿐만 아니라같은카테고리인 추상체
를 반환하는 메서드들도체이닝 메서드
이다. -
작은 코드라도 수정한다면, 복사해서 수정하고 지운다.
-
인자입력시 List.of() -> 가변배열
로 입력하도록 변경한다. -
상태변화가 없는 객체
는캐싱객체
orVO로, eq/hC를 재정의하여 값이 같으면 같은 객체가 되는 값객체
라서testutil패키지 > Fixtures클래스
안에상수객체
로 만들어 써도 된다.- test루트에서
testutil
패키지를 만들고 내부Fixtures
를 만들자 -
Fixtures에 쓸려면
어디서든 불러도 같은 객체가 생성되도록 캐싱==싱글톤이 보장
되어야한다.-
캐싱객체
는 test코드에서isSameAs
로 확인해야한다. eq/hC의 VO값객체
는 test코드에서eq/hC 재정의 후 isEqualTo
로 확인해야한다
-
-
상수객체 Fixture는
한글로 알아보기 쉽게 네이밍
해도 된다. -
상수 추출후,
Fixture로 옮기기전
에 미리 다 상수로 바꿔놓으면 더 쉽다(ctrl + H
)-
Fixture로 옮긴 후 프로젝트 전역으로 확인하고 싶다
면,상수값(우항)을 Ctrl + shift + F
로 찾으면 된다.
-
- test루트에서
-
시작객체
가 있다면,같은 패키지로 몰아서 시작객체public을 제외한 나머지들은 default가시성
을 가지도록 해서, 외부에서 생성안되도록 한다면,사전검증을 안해도 된다.
-
만약, 생성자가 기본생성자라서 정의를 안해줬다면,
재정의 후 가시성변경
해줘야한다.- 대표적인 예가
유틸클래스(상태값없이 input->output static메서드만 가지는)는 생성자를 private가시성으로 변경
해주는 것이다. - 생성자가 없는 클래스 ->
public으로 열여있으며, 재료를 바탕으로 상태값을 만들지 않는 클래스
- 대표적인 예가
- client가 잘못입력할 수도 있기 때문에
중간에 생성되는 상태객체들
은외부에서 생성못하도록 가시성으로 막아주기
-
만약, 생성자가 기본생성자라서 정의를 안해줬다면,
-
특정 구상체에만 사용되는 메서드
가 등장했다.- 상속관계면, 조합관계로 바꾼다.
-
이미 인터페이스를 구현하는 조합관계의 구상체였다면
- 일단 인터페이스에 올리고, 특정구상체만 자기메서드를 로직을 구현한다.
- 마저 테스트를 진행해야하니
나머지 구상체들은 로직 없이 구현만 해준다
-
이후
해당메서드 테스트가 끝
나면,특정구상체 외 사용하지 않는 구상체들에게 내리기 전
에중간에서 막아주는 추상클래스로 만들어지는 중간추상층
을 만들어준다.억지로 구현한 코드가 중복
이라서 중간추상층을 두고 중복코드를 제거한다는 개념으로 간다.
-
추상클래스 도입시
중간에 먹어줘서 공짜로 물려주는 것들은 (템플릿메소드) final을 달아서 자식이 수정 못하게 막아야한다
- 추상클래스의 public 템플릿메서드라면
눈꽃모양으로 final
을 확인하자.
- 추상클래스의 public 템플릿메서드라면
-
좋은 부모를 만들기 위한 전략(템플릿메소드) 중
중복 필드 처리
-
Object)중복로직제거 Strategy to Templatemethod(Plan3)
-
추상클래스는 좋은 상속의 부모로서
변수는 private
,메서드는 final or protected abstract
,생성자없이 setter로
확인하여 달아준다.
-
추상클래스는 좋은 상속의 부모로서
구상체별 중복되는 필드
를 올릴 땐, 구상클래스생성자 주입
->setter메서드 -> private필드
로 올린다.-
부모의 private필드는 부모내에서 처리하게 하고,
자식은 부모private필드자체는 못쓰고, final 물려주는 템플릿메소드들만 쓴다.
- 자식들이 훅메서드 구현시 필요한 정보들은, 자기가 생성자 주입받아서 쓰면 된다.
- 자식이 부모의 private필드값을 써야한다면,
부모에게 구현된 getter를 물려받아 쓰면
된다.
-
Object)중복로직제거 Strategy to Templatemethod(Plan3)
-
암기getter
-
getter정의시 return 불변객체
라면,public열어두기 가능 + dto없어도 됨
- 불변의 일급컬렉션 객체 반환 -> dto없는 public getter로 제공
- 불변 객체 아니라면,
Dto로 만들어서 반환
-
getter정의시 return 컬렉션필드라면, view로 보낼 땐, 깊은 복사로 못건들게 해서 반환
-
불변의 일급컬렉션
자신 내부의 컬렉션 필드 반환
- server사이드 반환이라면, 얕은복사 반환후, 내부에서 객체들 조작
- view반환이라면,
깊은 복사 반환 or DTO로 반환
후, 내부 객체들 조작안되게
-
불변의 일급컬렉션
-
-
getter 추가 -> 중복필드로서 같이 움직이는 해당 상태값
이 추가된 상황에서-
최상위 인터페이스에서부터 추가
되었으며 -
구현시,
어느 구상체들도 thr로 예외없이 다 100% 다 구현
된 상태라면 -
구상체 100%구현 메서드 + 필드
로서중복제거를 위한 중간추상층
이 추가된다.
-
-
상태객체의 인터페이스
오퍼레이터는 최소 4개 + @가 필요
하다-
trigger 메서드: draw()
-
중단 메서드: stay()
-
현재상태값 반환메서드: getter cards()
-
is끝난상태[중간추상층]인지
확인 메서드: boolean isXXXX()-
상태객체 소유 객체(Player)는
상태가 isFinished될때까지 반복작업할 것
이다. -
여기서는 stay개발시 불가하여 묶여진
Finished 형용사 중간추상층
이 이미 있으니끝난 상태냐
라고 물어볼 수 있다.특정 구상체인지는 물어보지 않는다. 그놈빼고 다 false를 대답하기 때문에
->특정 구상체인지는 물어보지말고 메세지를 던진다.
-
다형성 인터페이스에 존재하는 boolean 메서드는 -> 구상체들이 구현만 해주면 알아서 개별 답변되는 메서드
다
-
-
-
추상체변수의
구상체별 기능 구현
은전략패턴처럼 구현만 하면 알아서 적용
된다.-
이미 외부생성(전략패턴) or trigger메서드(상태패턴)에 의해 State state안에는 Blackjack이나 Bust나 Stay가 담겨있는 상태다
- 개별로 구현만 해주면, 알아서 작동한다.
- 대신, 알아서 계산안되어야할 놈들은 thr나 예외처리 해놔야한다.
- 추상체 변수의
상태이용 개별구현 메서드
는 구상체를 물어보지말고 구현만 해놓으면 알아서 적용된다. - 상태값이 일반 객체였으면… 물어보고 판단했을 것인데… 추상체변수인 순간 알아서 구현됨을 생각하자.
-
이미 외부생성(전략패턴) or trigger메서드(상태패턴)에 의해 State state안에는 Blackjack이나 Bust나 Stay가 담겨있는 상태다
-
중간추상층이 좋은 부모가 되려면,
공통점이 아예 없는 개별구현로직 조차, 개별구현훅메서드를 래핑하고 있는 public final 템플릿메소드
로 가지고 있고,서로 다른 로직은 protected abstract 훅메서드만 내보내줘야한다.
- CompareFile(ctrl+D)로 구상체 메서드들의 공통점과 차이점을 살펴보고,
어느부분까지를 훅메서드로 뺄 것인지 생각
한다.- 상수만 서로 달라도, 해당부분을 훅 메서드로 뺄 수 있다.
- 구상체들 중
서로 다른 훅 부분에 가장 파라미터가 많이 사용되는 로직
을 가진 구상체 1개를 선택하고해당 메서드를 중간추상층으로 잘라내서 올린다
-
올린 public 템플릿메서드 내부에서
다른 부분만 내수용 메서드 추출(구상체private -> 추상체protected abstract)의 훅메서드로 만든다
- 올리고 나서 훅을 만든다면,
메서드 추출후 가시성을 private -> protected abstract로 직접 변경
해줘야한다. - 올린 템플릿메서드는 final로…
- 올리고 나서 훅을 만든다면,
- 나머지 구상체들은,
훅메서드를 구현
하면서, 기존 구현메서드의 다른 부분만 훅에 입력한 뒤, 기존 메서드는 삭제해준다.
- CompareFile(ctrl+D)로 구상체 메서드들의 공통점과 차이점을 살펴보고,
-
다 구현된 상속구조에서
추상화레벨을 맞추기 위한 중간추상층 도입
을 한다. -
제한된 종류의 상태값을 가진다면
not final 시작상태 필드초기화
+setter대신 -> public void toggle
로 정의해준다. -
if 상태확인 -> 상태업뎃
을 매번 확인하면서 반복하는 것이while 상태확인 -> 상태업뎃
이다. -
좋은 부모를 위해 생성자없는 [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도 같이가야한다면, 내부에 포함시키고, 파라미터 추출을 해준다.