2022-02-21-TIL
Today I Learned
레거시에서 리팩토링 및 Hotfix 수정하기
아주 복잡하게 짜여진 레거시에서 hotfix를 수정한다거나 리팩토링을 한다는 것은 거의 폭탄제거 혹은 수술 집도와 같은 일이다. 테스트가 없는 코드는 어디를 건드렸을 때 무엇이 깨지는지 알 수 없기 때문이다. 그래서 hotfix는 변경 범위를 최소로 잡고, 리팩토링은 동작을 고정하는 테스트를 먼저 붙인 뒤 작은 단계로 나눠서 진행하는 것이 안전하다.
DataGrip Version Control
DataGrip은 IntelliJ 플랫폼 기반이라 Git 같은 VCS 연동 기능을 그대로 쓸 수 있다. SQL 스크립트나 DDL 파일을 저장소에 두고 IDE 안에서 diff, 커밋, 히스토리 조회를 할 수 있어서, 쿼리 변경 이력을 코드처럼 관리할 수 있다.
- https://www.jetbrains.com/help/datagrip/version-control-integration.html
Logger vs System.out.println
System.out.println은 표준 출력에 문자열을 쓸 뿐이라 끄거나 거를 방법이 없고, 출력 위치도 바꿀 수 없다. 로거를 쓰면 DEBUG, INFO, WARN, ERROR 같은 레벨로 출력량을 환경별로 조절하고, 콘솔과 파일 등 출력 대상(appender)과 포맷(시간, 스레드, 클래스명)을 설정으로 바꿀 수 있다. 운영 환경에서 로그를 수집하고 검색하려면 처음부터 로거를 쓰는 편이 맞다.
- https://stackoverflow.com/questions/31869391/what-is-the-difference-between-java-logger-and-system-out-println
- https://jworks.io/system-out-println-vs-loggers/
logback-spring.xml vs application.yml
간단한 로그 레벨과 파일 경로는 application.yml의 logging.level.*, logging.file.name 같은 속성으로 충분하다. appender를 여러 개 두거나 롤링 정책을 세밀하게 잡아야 하면 Logback 설정 파일을 쓴다. 이때 Spring Boot 문서는 logback.xml 대신 logback-spring.xml을 권하는데, 그래야 Spring이 로깅 초기화를 제어하고 <springProfile>, <springProperty> 같은 확장을 쓸 수 있기 때문이다.
- https://oingdaddy.tistory.com/317
log4j, logback, log4j2
세 가지 모두 자바 로깅 구현체이고, 애플리케이션 코드는 보통 SLF4J 같은 로깅 퍼사드에만 의존한다. Logback은 log4j 1.x의 후속으로 만들어졌고 Spring Boot의 기본 구현체다. Log4j 2는 log4j를 새로 설계한 것으로, 비동기 로거를 지원한다. Log4j 2는 2021년 12월 원격 코드 실행 취약점 CVE-2021-44228(Log4Shell)로 크게 문제가 되었으므로, 쓴다면 Apache Log4j 보안 페이지에서 안내하는 패치 버전 이상인지 확인해야 한다.
- https://wildeveloperetrain.tistory.com/36
Best Practice for Exception Handling
Spring MVC의 예외 처리 방식은 Spring 블로그 글이 정리하듯 크게 세 가지다. 예외 클래스에 @ResponseStatus를 붙여 상태 코드를 지정하는 방법, 컨트롤러 안의 @ExceptionHandler 메서드로 처리하는 방법, @ControllerAdvice로 여러 컨트롤러의 예외를 한곳에서 처리하는 방법이다. REST API에서는 @RestControllerAdvice에서 예외를 잡아 일관된 에러 응답 바디(에러 코드, 메시지, 필드 오류 목록)로 바꾸는 방식이 링크된 글들의 공통된 권장 사항이다.
에러 응답을 설계할 때 신경 쓸 점은 다음과 같다.
- 상태 코드는 의미에 맞게 쓴다. 클라이언트 잘못은 4xx, 서버 잘못은 5xx다.
- 클라이언트가 분기할 수 있는 안정적인 에러 코드를 바디에 둔다.
스택 트레이스나 내부 클래스명 같은 구현 세부 사항은 응답에 노출하지 않고 로그로만 남긴다.
- https://dzone.com/articles/best-practice-for-exception-handling-in-spring-boo
- https://www.youtube.com/watch?v=xBci5w9ava0
- https://stackoverflow.com/questions/66762006/spring-boot-exception-handling-best-practice
- https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
- https://medium.com/globant/best-practice-for-exception-handling-in-springboot-540484db8a1a
- https://reflectoring.io/spring-boot-exception-handling/
- https://www.baeldung.com/exception-handling-for-rest-with-spring
- https://www.baeldung.com/rest-api-error-handling-best-practices
- https://www.toptal.com/java/spring-boot-rest-api-error-handling
- https://mangkyu.tistory.com/204
- https://www.youtube.com/watch?v=nyN4o9eXqm0
- https://www.youtube.com/watch?v=PzK4ZXa2Tbc
Java Switch Expression
JEP 361로 JDK 14에서 정식 기능이 된 switch 표현식은 switch를 값을 돌려주는 식으로 쓸 수 있게 한다. JDK 12, 13에서는 프리뷰였다. case ... -> 형태는 fall through가 없어서 break 누락 버그가 사라지고, 블록 안에서 값을 돌려줄 때는 yield를 쓴다.
1
2
3
4
5
int days = switch (month) {
case FEBRUARY -> 28;
case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
default -> 31;
};
- https://www.wearedevelopers.com/magazine/modern-java-switch-expressions
- https://docs.oracle.com/en/java/javase/13/language/switch-expressions.html
- https://stackoverflow.com/questions/65657169/what-are-switch-expressions-and-how-are-they-different-from-switch-statements
- https://www.baeldung.com/java-switch
package-info.java
package-info.java는 패키지 자체에 대한 정보를 담는 파일이다. 패키지 단위 Javadoc 주석을 쓰거나, 패키지에 애너테이션을 붙일 때 사용한다. 예를 들어 @NonNullApi를 패키지에 붙이면 그 패키지의 모든 파라미터와 반환값을 기본적으로 non-null로 간주하게 할 수 있다.
- https://www.baeldung.com/java-package-info
Refactoring: Substitute Algorithm
알고리즘 대체(Substitute Algorithm)는 기존 알고리즘을 더 명확하거나 단순한 알고리즘으로 통째로 바꾸는 리팩토링이다. 메서드 본문을 새 구현으로 교체하되 입력과 출력은 그대로 유지하므로, 바꾸기 전에 기존 동작을 검증하는 테스트가 있어야 한다. 복잡한 메서드라면 먼저 메서드 추출로 작게 쪼갠 뒤 적용하는 것이 안전하다.
- https://refactoring.guru/substitute-algorithm
Unit Testing
단위 테스트는 클래스나 메서드 같은 작은 단위를 외부 의존성과 분리해서 빠르게 검증하는 테스트다. 빠르고 독립적이어야 자주 돌릴 수 있고, 리팩토링할 때 동작이 바뀌지 않았다는 안전망이 된다.
- https://devroach.tistory.com/35?category=1031971
- https://devroach.tistory.com/52?category=1031971
- http://www.yes24.com/Product/Goods/11361087
Spring 으로부터 도메인 모듈을 보호하는 방법
링크된 글의 주제는 도메인 로직을 담은 모듈이 Spring 같은 프레임워크에 의존하지 않도록 분리하는 방법이다. 도메인 모듈은 순수 자바 객체로 두고, 프레임워크 의존 코드(빈 설정, 영속성, 웹)는 바깥 모듈에 두면 도메인 규칙을 프레임워크 없이 테스트할 수 있고 프레임워크 교체의 영향도 줄어든다.
- https://msolo021015.medium.com/spring%EC%9C%BC%EB%A1%9C%EB%B6%80%ED%84%B0-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%AA%A8%EB%93%88%EC%9D%84-%EB%B3%B4%ED%98%B8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-91ffa157ee77
Git Flow vs GitHub Flow
Git Flow는 master, develop, feature, release, hotfix 브랜치를 역할별로 나누어 정기 릴리즈를 관리하는 모델이다. GitHub Flow는 기본 브랜치 하나에서 기능 브랜치를 따고 Pull Request로 리뷰한 뒤 병합하는 단순한 모델이고, hotfix도 기능 브랜치처럼 다룬다. 릴리즈 주기가 정해져 있고 여러 버전을 유지해야 하면 Git Flow가, 지속적으로 배포하는 서비스라면 GitHub Flow가 잘 맞는다.
- https://www.freshconsulting.com/insights/blog/git-development-workflows-git-flow-vs-github-flow/#:~:text=GitHub%20proposes%20an%20alternate%20workflow,hotfixes%20just%20like%20feature%20branches.
- https://techblog.woowahan.com/2553/
Completable Future
CompletableFuture는 Java 8에 추가된 비동기 작업 결과 타입으로, Future와 CompletionStage를 구현한다. supplyAsync로 작업을 시작하고 thenApply, thenCompose, thenCombine으로 후속 작업을 이어 붙이며, exceptionally나 handle로 예외를 처리한다. Javadoc에 따르면 실행기를 지정하지 않은 async 메서드는 ForkJoinPool.commonPool()을 사용하므로, 블로킹 I/O 작업에는 별도 Executor를 넘기는 편이 안전하다.
- https://12bme.tistory.com/545
댓글
아직 댓글이 없습니다