[새배내] 로또 미션을 하며 새로 배운 내용

Junseo Kim·2021년 2월 16일
0

2021.02.16 ~ 2021.03.01


TDD

테스트 코드를 먼저 작성하고 구현코드를 작성 후 리팩토링하는 것.

아래의 과정을 작은 단위로 자주 진행한다.
1) 실패하는 테스트 구현
2) 테스트가 성공하게 프로덕션 코드 구현
3) 프로덕션 코드 & 테스트 코드 리팩토링

  • 구현에 들어가기 전 요구 사항 분석(todo list)과 일정 수준의 설계가 먼저 이루어져야한다.(구현과 설계를 분리)

  • TDD를 진행하다가 막막하면 다시 '요구사항 분석 -> 설계' 과정을 반복한다.

  • 요구사항 변경 시 빠르게 대응하자(변화는 막을 수 없다. 완벽한 설계에 집착할 필요 없다.)

  • Object Graph 그려서 마지막 노드부터 테스트 코드 작성. 마지막 노드가 테스트 하기 힘들면 그 노드를 호출하는 노드도 테스트하기 힘들다 -> 테스트 하기 힘든 부분을 위의 노드에서 주입 받는 식으로 해결할 수 있다.

Git

페어 프로그래밍 이후 코드 가져오기

1) remote로 상대방 원격 저장소 등록
2) 상대방이 collaborate 등록(이 작업을 하지 않으면 push거절됨..)
3) 작업 진행한 로컬에서 상대방 저장소로 push

자바 문자열

자바 문자열에 관해

숫자 구분

3개마다 _ 붙여주면 가독성 좋아짐

10000 -> 10_000
2000000000 -> 2_000_000_000

바이트 코드 보는법

intellij에서 command + shift + a 누른 후, show bytecode 입력 후 실행

assertion(isSameAs와 isEqualTo의 차이점)

isSameAs: 주소 비교. 메모리 상에서 같은 객체를 참조하는지 확인

isEqualTo: 값 비교. 객체가 서로 같은 값을 가지고 있는지 확인

List

배열대신 리스트를 쓰는 이유: Collection이 제공해주는 많은 기능을 쓰기 위해

List 인터페이스의 구현체로는 ArrayList와 LinkedList 2가지가 존재하며 사용 방법은 동일하나 데이터를 저장하는 방식이 다르다.

ArrayList는 순서대로 저장하여 인덱스를 가지고 있으므로 검색 시 유리하다.

LinkedList는 데이터를 저장하는 각 노드가 이전 노드와 다음 노드의 상태를 가지고 있기 때문에 삽입, 삭제 등을 할 때 유리하다.

선언과 할당

List<> a = new ArrayList<>();

List를 쓰는 이유는 효율 때문으로 한 단계 추상화 한 것이다.
인터페이스와 구현의 분리. 필요에 따라 ArrayList를 linkedlist로 바꿔줄 수 있다.

Generic

Generic

enum

enum

EnumMap

EnumMap

주 생성자, 부 생성자

  • 생성자가 여러개인 경우 가능하다면 인스턴스 변수의 초기화는 하나의 생성자에서 해주는 것이 좋다.(해당 객체의 데이터 타입에 맞는 생성자에서 초기화) -> 검증 로직의 중복과, 변수 초기화 중복을 제거할 수 있기 때문
  • 생성자에서 다른 생성자를 호출할 때는 무조건 첫 줄에서 해야한다. 초기화 도중 생성자를 호출하게 되면 이전의 작업이 덮어쓰여지는 등의 영향을 미칠 수 있기 때문
  • 인스턴스 변수를 한 곳에서 초기화 할 수 없는 경우 대부분 정적팩토리 메소드로 해결가능하다.

정적 팩토리 메소드 사용시 생성자 private

정적 팩토리 메소드 사용시 생성자는 관례적으로 private으로 막아둔다. 그 이유는 일관성 때문이다. 생성자로도 인스턴스를 만들 수 있고, 정적 팩토리 메소드로도 인스턴스를 만들 수 있으면 헷갈리고 일관성이 깨질 수 있다.

Controller의 역할

Controller는 View와 도메인의 연결만을 담당

collectingAndThen

stream의 최종연산 .collect에서 사용할 수 있다. collectingAndThen은 collect를 진행하고 그 결과로 메소드를 하나 호출할 수 있게 해준다.

패키지 구조 나누기

  • 한 패키지에 10개 이상의 클래스가 속하면 가독성에 좋지 않다.
  • 다른 컨텍스트의 코드는 분리
  • package 접근제어자를 통해 캡슐화가 필요하다.(default 접근제어자 사용하여 같은 패키지 내부에서만 사용할 클래스를 만들 때 같은 패키지로 구분)

개발 프로세스 적용해보기

  1. 요구 사항을 보고 필요한 기능(행위) 분리
  2. 기능(행위)에 따른 TODO 작성
  3. 기능(행위)를 담당할 객체가 뭔지 생각
  4. TODO 리스트를 기반으로 테스트 코드를 작성하여 구현

메소드 명명 컨벤션

has로 시작하는 메소드 명은 반환값이 boolean

도메인이 자기 자신의 검증을 담당하는 것과 Validation로 분리하는 것

도메인이 자기 자신의 검증을 담당하는 것은 도메인 별로 꼭 필요한 검증 작업들만 확인할 수 있다는 장점. 응집도가 오르는 효과.

Validation으로 분리할 경우는 동일한 로직의 검증을 여러 클래스에서 사용해야 할 때 이점을 가짐. 검증할 것들이 많아지면 클래스 길이가 너무 길어져 가독성이 떨어질 수도 있음.

검증 일관성

코드의 스타일보다는 일관성이 중요. 일관되지 않은 코드는 퀄리티를 떨어뜨릴 수 있다.
중복되는 검증을 모으고, 나머지는 각 도메인에서 처리하도록 하는 것보다 하나의 방법으로 통일하는 것이 일관성을 유지시킨다.

Exception

Exception

캐싱

로또 미션에서 로또는 1 ~ 45의 수로만 이루어져 있다. 매번 로또 티켓을 생성할 때 마다 로또 넘버를 새로 만드는 것은 좋지 못하다.

이때 미리 만들어놓고 미리 만들어 놓고 꺼내쓰면 된다.
(merge 후 깃헙링크 추가하기)

Call by Value & Call by Reference

자바에서 메서드 호출 시 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다.

  • 매개변수 타입이 기본형이면 값이 복사된다.
  • 매개변수 타입이 참조형이면 인스턴스 주소가 복사된다. 주소가 복사되므로 해당 주소의 값을 읽어 올 수도 있고 변경 할 수도 있다.
// 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 메서드 테스트

private 메서드를 테스트 해야될 때 해당 메서드가 현재 속해있는 클래스에 속하는 것이 맞는지를 생각해보기

참고링크

적절한 타입 사용

메서드의 리턴 값은 적절한 타입을 사용하는 것이 좋다. (ex. 상금을 구하는 경우 String 보다는 int가 적절.)

설계하고 기능을 구현할 땐 View에 대한 부분은 생각하지 않고 구현 후 View를 붙이는 습관 -> 자연스럽게 역할과 협력의 기준으로 설계

DTO와 VO

DTO: 데이터 전달용 객체. View에 필요한 데이터를 최종적으로 담아 View에 넘겨주는 객체. 필요한 데이터들만 모아서 View에 전달. 데이터 전달에 목적이 있으므로 getter/setter 외의 비즈니스 로직은 가지지 않는다.

VO: 값 자체를 표현하는 객체. 값 그 자체를 나타내므로 불변. 객체 내부의 데이터 값들이 같으면 동일한 객체라고 봐야하므로 equals & hashcode를 override 해줘야함. 모든 필드 값의 동등에 따른 객체의 동등 여부가 중요하며 setter를 제외한 비즈니스 로직을 가질 수 있다.(값 자체를 나타내므로 setter로 변경 x)

부트스트래핑(클래스 로더)

일반적으로 애플리케이션이 올라갈 때 기본 클래스들이 셋팅된다. 클래스는 컴파일 시점이 아니라 클래스가 처음 사용될 때 JVM 내부로 클래스 로딩을 한다. 따라서 main에서 먼저 인터페이스 구현체를 생성하여 사용해주면 애플리케이션 시작 시점에 클래스 로딩이 되기 때문에 만약 구현체가 바뀌더라도 Controller와 Service에서는 코드 변경 없이 확장이 가능하게 되는 것이지만 Controller나 Service에서 인터페이스 구현체를 생성한다면 구현체가 바뀌면 Controller와 Service의 코드도 바뀌기 때문에 OCP를 위반한다.(기존 코드가 변경됨)

참고: 클래스 로더 시스템

OCP(개방 폐쇄 원칙)

  • 기능을 확장 하면서도 기능을 사용하는 기존 코드는 변경되지 않는 것.(확장에는 열려있고 변경에는 닫혀 있어야 한다.)
  • 유연함과 관련된 원칙. 기존 기능을 확장하기 위해 기존 코드를 수정해 주어야 한다면, 새로운 기능을 추가하는 것이 힘들어진다.
  • 변화가 예상되는 부분을 추상화함으로서 사용자 입장에서 변화를 고정시킨다.
  • 변화되는 부분을 추상화하지 못하면 OCP를 지킬 수 없게 된다.

읽은 도서

0개의 댓글