2021.02.16 ~ 2021.03.01
테스트 코드를 먼저 작성하고 구현코드를 작성 후 리팩토링하는 것.
아래의 과정을 작은 단위로 자주 진행한다.
1) 실패하는 테스트 구현
2) 테스트가 성공하게 프로덕션 코드 구현
3) 프로덕션 코드 & 테스트 코드 리팩토링
구현에 들어가기 전 요구 사항 분석(todo list)과 일정 수준의 설계가 먼저 이루어져야한다.(구현과 설계를 분리)
TDD를 진행하다가 막막하면 다시 '요구사항 분석 -> 설계' 과정을 반복한다.
요구사항 변경 시 빠르게 대응하자(변화는 막을 수 없다. 완벽한 설계에 집착할 필요 없다.)
Object Graph 그려서 마지막 노드부터 테스트 코드 작성. 마지막 노드가 테스트 하기 힘들면 그 노드를 호출하는 노드도 테스트하기 힘들다 -> 테스트 하기 힘든 부분을 위의 노드에서 주입 받는 식으로 해결할 수 있다.
1) remote로 상대방 원격 저장소 등록
2) 상대방이 collaborate 등록(이 작업을 하지 않으면 push거절됨..)
3) 작업 진행한 로컬에서 상대방 저장소로 push
3개마다 _ 붙여주면 가독성 좋아짐
10000 -> 10_000
2000000000 -> 2_000_000_000
intellij에서 command + shift + a
누른 후, show bytecode
입력 후 실행
isSameAs: 주소 비교. 메모리 상에서 같은 객체를 참조하는지 확인
isEqualTo: 값 비교. 객체가 서로 같은 값을 가지고 있는지 확인
List 인터페이스의 구현체로는 ArrayList와 LinkedList 2가지가 존재하며 사용 방법은 동일하나 데이터를 저장하는 방식이 다르다.
ArrayList는 순서대로 저장하여 인덱스를 가지고 있으므로 검색 시 유리하다.
LinkedList는 데이터를 저장하는 각 노드가 이전 노드와 다음 노드의 상태를 가지고 있기 때문에 삽입, 삭제 등을 할 때 유리하다.
List<> a = new ArrayList<>();
List를 쓰는 이유는 효율 때문으로 한 단계 추상화 한 것이다.
인터페이스와 구현의 분리. 필요에 따라 ArrayList를 linkedlist로 바꿔줄 수 있다.
정적 팩토리 메소드 사용시 생성자는 관례적으로 private으로 막아둔다. 그 이유는 일관성 때문이다. 생성자로도 인스턴스를 만들 수 있고, 정적 팩토리 메소드로도 인스턴스를 만들 수 있으면 헷갈리고 일관성이 깨질 수 있다.
Controller는 View와 도메인의 연결만을 담당
stream의 최종연산 .collect에서 사용할 수 있다. collectingAndThen은 collect를 진행하고 그 결과로 메소드를 하나 호출할 수 있게 해준다.
has로 시작하는 메소드 명은 반환값이 boolean
도메인이 자기 자신의 검증을 담당하는 것은 도메인 별로 꼭 필요한 검증 작업들만 확인할 수 있다는 장점. 응집도가 오르는 효과.
Validation으로 분리할 경우는 동일한 로직의 검증을 여러 클래스에서 사용해야 할 때 이점을 가짐. 검증할 것들이 많아지면 클래스 길이가 너무 길어져 가독성이 떨어질 수도 있음.
코드의 스타일보다는 일관성이 중요. 일관되지 않은 코드는 퀄리티를 떨어뜨릴 수 있다.
중복되는 검증을 모으고, 나머지는 각 도메인에서 처리하도록 하는 것보다 하나의 방법으로 통일하는 것이 일관성을 유지시킨다.
로또 미션에서 로또는 1 ~ 45의 수로만 이루어져 있다. 매번 로또 티켓을 생성할 때 마다 로또 넘버를 새로 만드는 것은 좋지 못하다.
이때 미리 만들어놓고 미리 만들어 놓고 꺼내쓰면 된다.
(merge 후 깃헙링크 추가하기)
자바에서 메서드 호출 시 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다.
// Call by Value
void call(int a) {
// 매개변수로 넘어온 a는 새로운 값. (해당 값은 Stack 영역 내에서만 존재)
}
// Call by Reference
void call(Object o) {
// 매개변수로 넘어온 o는 원래의 값을 가리키고 있다. (해당 값은 Heap 영역 내에 존재)
}
주의할 점은 매개변수는 일종의 변수라는 것이다.
Object o1 = new Object();
Object o2 = o1;
이 경우 o1과 o2는 변수이므로 실제 값과는 연관이 없다.
new Object() <- 값
o1 <- 변수
o2 <- 변수
매개변수로 객체를 넘겨줄때, 넘어오는 것은 주소값이라는 값이고, 변수는 참조만 하는 것이다. 따라서 메서드를 호출한 곳의 변수가 가리키는 것은 자체는 바뀌지 않는다.(로또 미션 때 이 개념이 헷갈려서 고생했다...)
리팩토링시 메서드의 파라미터가 변하거나 할 때 테스트 코드도 수정되야한다. 이 경우 기존의 메서드를 복사해서 새로 생성한 후, 이름을 바꾸고 리팩토링 해본다. 리팩토링한 메서드로 테스트코드 같이 바꿔보고 문제없으면 메서드를 교체한다
private 메서드를 테스트 해야될 때 해당 메서드가 현재 속해있는 클래스에 속하는 것이 맞는지를 생각해보기
메서드의 리턴 값은 적절한 타입을 사용하는 것이 좋다. (ex. 상금을 구하는 경우 String 보다는 int가 적절.)
설계하고 기능을 구현할 땐 View에 대한 부분은 생각하지 않고 구현 후 View를 붙이는 습관 -> 자연스럽게 역할과 협력의 기준으로 설계
DTO: 데이터 전달용 객체. View에 필요한 데이터를 최종적으로 담아 View에 넘겨주는 객체. 필요한 데이터들만 모아서 View에 전달. 데이터 전달에 목적이 있으므로 getter/setter 외의 비즈니스 로직은 가지지 않는다.
VO: 값 자체를 표현하는 객체. 값 그 자체를 나타내므로 불변. 객체 내부의 데이터 값들이 같으면 동일한 객체라고 봐야하므로 equals & hashcode를 override 해줘야함. 모든 필드 값의 동등에 따른 객체의 동등 여부가 중요하며 setter를 제외한 비즈니스 로직을 가질 수 있다.(값 자체를 나타내므로 setter로 변경 x)
일반적으로 애플리케이션이 올라갈 때 기본 클래스들이 셋팅된다. 클래스는 컴파일 시점이 아니라 클래스가 처음 사용될 때 JVM 내부로 클래스 로딩을 한다. 따라서 main에서 먼저 인터페이스 구현체를 생성하여 사용해주면 애플리케이션 시작 시점에 클래스 로딩이 되기 때문에 만약 구현체가 바뀌더라도 Controller와 Service에서는 코드 변경 없이 확장이 가능하게 되는 것이지만 Controller나 Service에서 인터페이스 구현체를 생성한다면 구현체가 바뀌면 Controller와 Service의 코드도 바뀌기 때문에 OCP를 위반한다.(기존 코드가 변경됨)
참고: 클래스 로더 시스템