우아한테크코스 백엔드 6기 프리코스가 끝났다.
진행중에 매주 과제를 코드리뷰 받으며 개선해나갔다.
그 때 피드백 받은 점들을 정리해보자.
클린코드 원칙을 억지로라도 지켜보자. 가독성이 높아지는것을 느낄 수 있다.
문자열 같은거 다국어 지원까지 생각하면 별도로 관리하는게 맞다. 다만 간단할 경우 그냥 View에서 관리.
원시값 포장 했는데 막장 getter 밖에 안쓰면 그냥 원시값으로 두는게 나을수도 있다.
변수 이름 작성의 모든 기준은 보는 사람이 헷갈리지 않게.
연결된 코드끼리 근처에 두기
로직 잘 생각해서 쓸모없는 변수 줄이기
객체를 객체답게
필요없는 상태 저장 X, 상태보다는 메세지 전달
getter는 View에서 사용할 데이터를 전달할 때 말고는 최대한 사용을 지양하는게 좋다. 객체끼리 메세지를 주고 받아야지 상태를 주고받으면 안된다. 참고
추상화 되어있거나 가능성 있는 클래스만 의존성 주입 하기
컬렉션은 안전성(불변성 보장)과 역할 분리를 위해 일급 컬렉션으로 사용하는게 좋다.
게터에서는 Collections.unmodifiableList()
사용
성능이 조금 떨어지더라도 메소드의 역할을 명확히 구분하는게 더 좋다.
한 메소드가 진짜 한가지 일만 하고 있는지 다시 생각해보기.
boolean 메소드와 validate 메소드는 구분하라. 여러 비교를 하는 로직을 따로 분리해서 그것을 validate 메소드의 if 문에서 호출해라.
사용자 입력에 대한 기본적인 검증과 정제는 View 에서 수행해도 된다.
ex) view/InputUtil.class
객체 생성 과정에서 유효성 검사를 하고, 변환은 dto를 이용하면 된다.
필드(=인스턴스 변수) 가 많으면 복잡도가 높아 버그 발생 가능성이 높아지므로 줄이기 위해 노력하기.
final 이면 굳이 private으로 접근 막을필요 없다.
(하지만, 기술적으로는 문제 없다고 해도 코드 일관성도 중요하다. 유지보수 가능성까지 생각하면 그냥 private 필드와 public 게터를 사용하는것도 괜찮다.)
인터페이스 추상화, 상속 등 클래스 구조 합리적으로 잘 짜기
(쓸데없이 많아도 알아보기 힘들다)
추상클래스 접근지정자 잘 알고 쓰기
static 메소드만 있으면 생성자 private로 인스턴스 생성 막기
디폴트 생성자 사용하기. 같은 패키지에서만 쓸꺼면!
돌아가는 쓰레기를 일단 구현하라고 하지만 그래도 처음부터 domain, controller, view 정도는 분리하고 시작하기.
예외 메세지 문자열을 만든다면 View에서 만들어야지 domain등 다른곳에서 만들 필요가 없다.
게임 진행에 대한 책임은 컨트롤러가 아닌 모델이다. 컨트롤러는 입력값 전달과 메세지 전송, 결과값 수신만 하면 된다.
domain 에서 view로 데이터를 보낼 때 복잡하다면 DTO (ex 자바 record) 적극 고려하기
원래 존재하는 예외를 상속받은 임의 예외 클래스를 만들어서 사용하면 관리가 편하다.
public class ChristmasEventException extends IllegalArgumentException {
private static final String PREFIX = "[ERROR]";
private static final String FRONT_MESSAGE = "유효하지 않은";
private static final String LAST_MESSAGE = "입니다. 다시 입력해 주세요.";
public ChristmasEventException(String element) {
super(String.format("%s %s %s%s", PREFIX, FRONT_MESSAGE, element, LAST_MESSAGE));
}
}
public class OrderedMenuException extends ChristmasEventException{
private static final String ORDER = "주문";
public OrderedMenuException() {
super(ORDER);
}
}
에러메세지는 enum타입을 사용하면 좋다.
public enum ErrorMessage {
INPUT_IS_EMPTY("입력값은 비어있을 수 없습니다."),
INPUT_NOT_A_NUMBER("입력값은 숫자여야 합니다."),
INPUT_NOT_POSITIVE_NUMBER("입력값은 자연수여야 합니다.");
private final String message;
ErrorMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
테스트는 개념당 하나의 메소드.
테스트 코드도 읽는 사람 생각해서 가독성 좋게 작성하기
랜덤값 같은 테스트 어려운 것은 인터페이스를 만들어서 구현체를 전달하는 방식(의존성 주입)으로 구현하면 테스트가 편하다.
Junit의 @Nested
어노테이션으로 테스트 클래스에 계층 구도를 만들면 가독성이 올라간다.
테스트 코드에서만 사용되는 코드는 main 말고 tests/ 내부에 작성
ParameterizedTest
로 CsvSorce
, ValueSource
, ArgumentsSource
등 활용 잘 하기
작은 단위부터 작성하고 빠른 피드백을 받는것이 중요하다.
테스트로 예외 확인할때, 메세지까지 확인하자. (hasMessage()
)
예외 상황 잘 파악해서 테스트 하기
테스트 분리는 가독성 상승에 좋다. 성능은 떨어져도 된다.
리팩토링 하다가 객체의 역할이 엇나가고 있지 않은지 잘 체크하기
static 메소드는 프로그램 시작부터 종료까지 메모리가 해제되지 않아서 비효율적이고, 전역에서 접근 가능하니 객체지향에도 방해된다. 꼭 필요한 경우 (ex 여러 곳에서 사용 되는 경우)에만 사용하기
문자열 포장은 전역에서 사용되는 값 아니면 진짜 필요한 클래스에서만 privte static final으로 선언해주는게 좋은것 같다. 또한 너무 enum 했다가 상수 묶었다가 하지 말고 코드베이스 일관성 유지하는것도 좋다
stream 은 컬렉션이 매우 클 때 아니면 for문보다 성능이 떨어진다. 하지만 가독성 측면에서 장점이 있기 때문에 마음껏 써도 된다.
Map의 본질은 Key로 Value를 찾는것 (Value에 아이디를 붙혀줄 때)
출력문자 포맷 (":"
등)은 "%s:%s"
이런 식으로 포매팅 하는것이 좋다. 출력문자 문자열에는 \n
, :
같은 문자 지양하자.
문자열.isEmpty
는 빈 것만 잡지만 .isBlank
는 공백도 같이 잡는다.
클래스 내부에는 상수(또는 클래스 변수), 멤버(인스턴스) 변수, 생성자, 메소드 순으로 작성
생성자로 컬렉션 받을때 얕은복사 주의. new List<>(inputList)
이렇게 다시 생성해주는게 좋다.
Map 전체를 순회하는건 나쁜 방식은 아니다. 단, 매우 큰 Map을 자주 순회할때나 순회 도중 요소를 변경하게 될 경우에는 다른 구조 고려
Java는 날짜 관련 API를 제공한다. 기본 제공 API 잘 활용하기.
형식이 쓸데 없이 복잡할 때 var 사용 고려 ex) for문 안에서 임시 변수
Map.of 로 맵 만들면 진짜 상수라서 상수 네이밍 가능하다. (List.of 등 포함)
Collector.joining 하면 출력 형식 만들 때 좋다.
정규 표현식은 유지보수가 힘들 수 있으니 웬만하면 로직 활용해서 하기
정규식을 간단하게 사용할 때 (일치하는지)는 String.matches
, 복잡하게 사용할 때(일치한 부분 모두 찾기 등)는 Pattern.matcher
를 사용하자.
README 는 소스코드 이전에 어떤 프로젝트인지 소개하는 문서이다.
README에 요구사항이나 적용할 법칙같은 것 정리하고 시작해도 좋다. 하지만 기능 목록을 너무 상세하기 적지 말고 차라리 예외상황을 잘 파악해라. (계속 업데이트 하는것이 좋다.)
단어 일관성 유지 하기. ex) increase라고 한번 썼으면 rise 같은 유의어 대신 쭉 increase 사용
놓치는 부분이 무조건 있을 수 밖에 없으니 꼼꼼히 살피기 (접근지정자. 객체역할, 필요없는 로직 or 메소드 등)