Search
Duplicate

2021년 8월 3주차 회고록 - Spring Web 으로 만드는 ToDo REST API

Facts

이펙티브 자바 제네릭 파트 2회독
Spring Developer Tools 학습
코드숨 스프링 3기 2주차 미션 Spring Web으로 만드는 ToDo REST API 미션 진행
미션 피드백 정리 및 학습

Feelings

이펙티브 자바 제네릭 파트 2회독

이전에 포스팅을 이미 해뒀지만, 실습에 적용하려니 자주쓰는 기본적인 사용법을 제외하고는 잘 기억이 나지 않기에 포스팅 해 둔 글을 옆에 띄워놓고 계속 참고하다가 결국 처음부터 다시 2회독을 했다. 예전 교수님과 얘기를 할 때 나왔던 내용 중 하나가 사소한 것일지라도 키워드는 명확해야 한다는 것인데, 내가 계속 용어를 명확히 기억하지 못해서 List<String> 이런 매개변수화 타입을 제네릭 타입이라 지칭하기도 하고, String 이라는 실제 타입매개변수를 매개변수화 인수타입이라고 뭐 창작을 해서 부르기도 했는데, 하나 하나에 대한 이해가 깊지 못하고 다른 용어의 의미와 혼재되어 기억을 하고있으니 단어도 섞여서 기억되는 것 같다.
이펙티브 자바를 기준으로 다시 한 번 기억하도록 하자.
한글용어영문 용어아이템매개변수화타입parameterized typeList<String>26실제 타입매개변수actual type parameterString26제네릭 타입generic typeList<E>26, 29정규 타입 매개변수formal type parameterE26비한정적 와일드카드 타입unbounded wildcard typeList<?>26로 타입raw typeList26한정적 타입 매개변수bounded type parameter<E extends Number>29재귀적 타입 한정recursive type bound<T extends Comparable<T>>30한정적 와일드카드 타입bounded wildcard typeList<? extends Number>31제네릭 메서드generic methodstatic <E> List<E> asList(E[] a)30타입 토큰type tokenString.class33\begin{array}{|c|c|c|c|}\hline \textbf{한글용어}&\textbf{영문 용어}&\textbf{예}&\textbf{아이템}\\\hline \text{매개변수화타입}&\text{parameterized type}&\text{List<String>}&\text{26}\\\hline \text{실제 타입매개변수}&\text{actual type parameter}&\text{String}&\text{26}\\\hline \text{제네릭 타입}&\text{generic type}&\text{List<E>}&\text{26, 29}\\\hline \text{정규 타입 매개변수}&\text{formal type parameter}&\text{E}&\text{26}\\\hline \text{비한정적 와일드카드 타입}&\text{unbounded wildcard type}&\text{List<?>}&\text{26}\\\hline \text{로 타입}&\text{raw type}&\text{List}&\text{26}\\\hline \text{한정적 타입 매개변수}&\text{bounded type parameter}&\text{<E extends Number>}&\text{29}\\\hline \text{재귀적 타입 한정}&\text{recursive type bound}&\text{<T extends Comparable<T>>}&\text{30}\\\hline \text{한정적 와일드카드 타입}&\text{bounded wildcard type}&\text{List<? extends Number>}&\text{31}\\\hline \text{제네릭 메서드}&\text{generic method}&\text{static <E> List<E> asList(E[] a)}&\text{30}\\\hline \text{타입 토큰}&\text{type token}&\text{String.class}&\text{33}\\\hline \end{array}

코드숨 2주차 미션 진행

1주차의 혼란은 끝났고 이제 안정적으로 2주차 뿐 아니라 1주차의 피드백들도 다시 받아들이는 상황이다. 그동안 새로운 기술만 배우면서 뭔가 어려운 개념이나 모듈화, 새로운 라이브러리에 대해서 안다는 점을 이용해 최대한 여러가지를 구현하려 했는데, 이는 결국 나만의 지식자랑일 뿐인 것 같다.
1~ 2주차에서 실수를 줄일 수 있는 개발 습관들을 깨닫고, 협업에 필요한 주석 처리 및 무의식적으로 작성한 코드에서 생길수 있는 비용낭비를 막을 수 있는 시선을 배운것 같다.
나는 책이나 유명인의 말에 많이 휘둘리는 편이였는데, 예를들어 주석에 관련해서도 작년까지는 정말 주절거리는 식으로 엄청나게 많은 주석을 작성했다가, 클린코드에서 메서드나 동작만으로 이해할 수 있는 코드를 짜서 주석을 최소화 하라는말을 주석을 없애라! 라고 인지해서 이젠 주석을 거의 안쓰는 식으로 개발을 했는데, 이번 주차에는 필요한 곳에 필요한 주석을 작성해서 코드의 간결함과는 별개로 협업에 필요한 수준의 주석작성을 하는 법에 대해 학습한 것 같다.
생각해보면 책에서도 주석을 작성하지 말라고는 안했는데 혼자 너무 극단적으로 생각한 것 같다.
참고
3주차도 기대되는 마음으로 8월의 4주차를 맞이해야겠다.

Finding

다시보는 전략패턴과 제네릭 사용

최초 ID 생성 로직
private Long generateId() { sequence = sequence + 1; return sequence; }
Java
복사
내가 기존에 구현한 리포지토리는 ID 생성을 클래스내에서 그냥 sequence 라는 전역 변수에 +1을 해주는 정도로 끝냈었다. 하지만 여기에는 큰 문제 몇가지가 존재한다.
첫 째로, 스레드 안전하지 못하기에 멀티 스레드 환경에서 sequence 동시접근으로 문제가 발생할 수 있다는 점이다.
둘 째로, 아이디 생성 전략은 현재 단순히 sequence를 증가시켜주지만, 기획에따라 변경될 가능성이 다분하다. 이 두 가지 이슈를 고려한 로직은 다음과 같았다.
1차 리팩토링 - synchronized 키워드 추가
private synchronized Long generateId() { sequence = sequence + 1; return sequence; }
Java
복사
이제 synchronized 키워드를 통해 스레드 안전한 로직이 되었다. 하지만, 아직 아이디 생성전략의 유연함이 부족한 상황이다. 여기서 전략 패턴을 사용함과 동시에 외부주입(DI)를 통해 의존성을 명시적으로 드러내고 전략 패턴을 이용한 유연성을 키워주는 리팩토링을 진행 해줬다.
2차 리팩토링 - 전략패턴 적용 및 외부주입 코드 추가
@FunctionalInterface public interface IdGenerator { Long generate(Long source); } @Component public class TaskIdGenerator implements IdGenerator{ public static final Long DEFAULT_INCREASE_SEQUENCE = 1L; @Override public Long generate(Long source) { return source + DEFAULT_INCREASE_SEQUENCE; } }
Java
복사
IdGenerator 인터페이스와 구현체
@Component public class TodoRepository { //... 기타 선언 private static Long sequence = 0L; private final IdGenerator idGenerator; public TodoRepository(IdGenerator idGenerator) { this.idGenerator = idGenerator; } private synchronized Long generateId() { sequence = idGenerator.generate(sequence); return sequence; } //... 기타 로직 }
Java
복사
생성자 외부주입을 통해 의존성을 명시적으로 드러내는 로직
이제 2차 리팩토링을 통해 스레드 안전하면서 의존성을 명시적으로 드러내고 전략패턴까지 적용하여 다른 로직의 아이디 생성방식에도 대응할 수 있는 코드가 되었다.
그런데, 이 IdGeneratorgenerate 메서드는 반환타입이 Long이고 인수타입도 Long으로 제한적이기에 유연함이 부족하다. 만약 아이디가 String이거나 Integer 혹은 기타 객체일 경우에는 대처할 수 없다. 그래서 여기서 제네릭을 적용해주도록 한다.
3차 리팩토링 - 제네릭 적용
@FunctionalInterface public interface IdGenerator<T> { T generate(Object source); } @Component public class TaskIdGenerator implements IdGenerator<Long>{ public static final Long DEFAULT_INCREASE_SEQUENCE = 1L; @Override public Long generate(Object source) { if(!(source instanceof Long)){ throw new NotSupportedIdTypeException(source.getClass().getName(), Long.class.getName()); } return (Long)source + DEFAULT_INCREASE_SEQUENCE; } }
Java
복사
@Component public class TodoRepository { //... 기타 선언 private static Long sequence = 0L; private final IdGenerator<Long> idGenerator; public TodoRepository(IdGenerator<Long> idGenerator) { this.idGenerator = idGenerator; } private synchronized Long generateId() { sequence = idGenerator.generate(sequence); return sequence; } //... 기타 로직 }
Java
복사
제네릭과 정규타입 매개변수를 이용해서 이제 더 유연한 아이디 생성기를 만들어줬다.
이 때 인수타입은 Object 타입이기에 해당 타입에 대한 유효성 검증을 해줘야 하며 나 같은 경우 instanceof 를 이용했다.

@Autowired 생략 가능

스프링에서 의존성 외부 주입(DI) 방식은 여러가지가 있다.
그 중에서 생성자 주입 방식을 주로 사용을 하는데, 이는 이러한 의존관계는 최초 주입 후 변경되지 않을 것이며 불변성을 가져야하기에 final 키워드를 붙히는게 좋다.
객체 생성시점에서는 최초 1회는 생성자가 반드시 호출되는데 이러한 불변 필드에 대해서는 반드시 생성자에서 값을 설정해줘야 하기때문에 스프링에서는 DI를 해주도록 한다. 그렇기에 수정자 주입이나 필드 주입에 비교해서 안전하다.
필드 주입도 있긴하지만, DI 프레임워크에 대한 의존성 및 고립테스트에서의 제약으로 인해 생성자 주입이 더 선호된다.
그리고 이러한 생성자 주입 방식 중에서도 단일 생성자의 경우에는 스프링 4.3부터 @Autowired 애노테이션을 생략할 수 있다고 한다.
//before Spring 4.3 public class SometingController{ private final SometingService sometingService; @Autowired // 애노테이션을 작성해줘야 한다. public SometingController(SometingService sometingService){ this.somethingService = somethingService; } } //after Spring 4.3 public class SometingController{ private final SometingService sometingService; // 애노테이션을 작성해주지 않아도 된다. public SometingController(SometingService sometingService){ this.somethingService = somethingService; } }
Java
복사

다시보는 객체지향 생활 체조

작년에 진행했던 TDD 과정에서 첫 시간에 배우는게 클린코드와 소트웍스 앤솔러지의 객체지향 생활체조 9가지 원칙이였다.
규칙 1: 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
규칙 2: else 예약어를 쓰지 않는다.
규칙 3: 모든 원시값과 문자열을 포장한다.
규칙 4: 한 줄에 점을 하나만 찍는다.
규칙 5: 줄여쓰지 않는다(축약 금지).
규칙 6: 모든 엔티티를 작게 유지한다.
규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
규칙 8: 일급 콜렉션을 쓴다.
규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.
개인적으로는 매번 주관이 많이들어가서 주니어 입장에서 그래서 어떻게 하라는거야??? 하던 어려움을 가이드라인을 제공해줘서 코드를 작성할 때 많은 도움을 받았던 내용이다.
이 중 4번째 규칙인 디미터 규칙에 대해서 매번 어려움을 겪는다.
한 줄에 점 하나만 찍는다고 하지만, 실제로 정말 점하나만 찍어야한다는 것은 아니고, 자신과 직접 연관된 것들은 한 줄에 체이닝이 되어도 되지만 간접 연관된 것들에 대해서는 구분해야 한다는 의미인데, 매 번 이 결과가 직접인가 간접인가 헷갈리고는 한다.
자주 참고하는 해당 블로그의 글에서는 다음과 같이 설명한다.
디미터의 법칙("친구하고만 대화하라")이 좋은 출발점이긴 하지만, 이런 식으로 생각하자. 자기 소유의 장난감, 자기가 만든 장난감, 그리고 누군가 자기에게 준 장난감하고만 놀 수 있다. 하지만 절대 장난감의 장난감과 놀면 안 된다. 참고( https://johngrib.github.io/wiki/law-of-demeter/)

Spring Developer tools

애플리케이션 개발을 좀 더 편하게 할 수 있도록 지원하는 라이브러리

기본 속성

스프링에서 지원하는 여러 라이브러리들이 캐시를 사용해 성능을 높이는데, 이는 개발 중에는 불편함을 줄 수 있기 때문에 spring-boot-devtools는 이런 캐싱 옵션을 비활성화 해준다.
(ex: spring.tyhmeleaf.cache 속성 자동 비활성화)

제공하는 기능

1.
Automatic restart
:classpath의 파일이 변경 될 때마다 자동으로 재시작해준다. 그래서 개발자는 코드 변경에 대한 확인을 빠르게 할 수 있다.
2.
Excluding resources
: 특정 리소스는(ex: 정적 리소스) 변경되도 재시작하지 않도록 할 수 있습니다.
(spring.devtools.restart.exclude=static/**, public/**)
3.
Watching additional paths
:classpath에 없는 파일을 변경할 때 애플리케이션을 restart or reload 할 수 있습니다.
(spring.devtools.restart.additional-paths)
4.
Disabling restart
: 재시작 기능을 사용하지 않도록 설정할수도 있습니다.
(spring.devtools.restart.enabled)

사용법

Maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>2.5.4</version> </dependency>
Java
복사
gradle
implementation 'org.springframework.boot:spring-boot-devtools'
Java
복사

Affirmation

매번 생각하는 것 중 하나가 매번 모르는게 나타나면서 새롭게 공부하는데 집중을 하는것도 좋지만,
이미 배운 것을 1회독및 포스팅만 한 번 해두고 잊어버리면 결국 공부를 안한것과 크게 다를바 없는 것 같다.
이번 미션 진행하면서 제네릭과 관련된 내용을 학습할 때 너무 크게 느꼈고, 요즘 조금씩 준비하는 코딩테스트 준비를 하며 자료구조나 알고리즘 책들을 보는데, 모두 학생때 배웠고 세미나까지 했던 것인데 그 이후로 따로 공부를 안하고 잊고살다보니 예전에 학습했던 시간까지 많이 낭비 되는것 같다.
이제 슬슬 내가 정리했고 공부했던 부분들에 대해서 되새기며 다시 공부하는 시간을 가져봐야 할 것 같다.