-
Notifications
You must be signed in to change notification settings - Fork 1
[BE] 코드 컨벤션
- 라이센스 또는 저작권 정보 (있을 경우)
- package 명세
- import 명세
- 와일드 카드(java.enum.*)로는 가져오지 않는다.
- static import, non-static import는 따로 모아서 블록을 만든다. 블록은 static, non-static 순서다. 블록 사이에는 1줄의 개행을 넣는다.
- 각 블록 내에서의 import문은 같은 패키지 네임을 갖고 있는 것끼리 모은다.
- 하나의 자바 파일 내의 최상위 클래스는 하나여야 한다.
- 클래스의 첫 줄과 마지막 줄에는 개행을 넣지 않는다.
클래스는 반드시 하나의 역할을 해야한다.- 클래스(레코드)의 필드는 애너테이션과 필드 사이에 개행한다.
public record Spot(
@Column(nullable = false)
String address,
@Column(nullable = false, columnDefinition = "DECIMAL(16, 14)")
BigDecimal latitude,
@Column(nullable = false, columnDefinition = "DECIMAL(17, 14)")
BigDecimal longitude
) {
}
-
메소드는 다음과 같이 배치한다.
-
생성자
-
static 메서드
-
메서드
-
Override된 메서드
-
getter & setter
-
-
사용되는 메소드는 사용하는 메소드와 최대한 가까운 위치에 있어야한다.
- 단, A메소드를 사용하는 메소드가 여러 개일경우, A메소드는 마지막으로 사용한 메소드 아래에 위치시킨다.
-
메소드의 파라미터는 3개를 넘기지 않도록 노력한다.
-
메서드의 파라미터가 길어질 경우 파라미터마다 개행한다.
- 파라미터에 붙는 애너테이션 사이에는 개행하지 않는다.
@GetMapping public ResponseEntity<TravelResponses> readAllTravels( @MemberId Long memberId, @RequestParam(value = "year", required = false) Integer year ) { return ResponseEntity.ok(travelService.readAllTravels(memberId, year)); }
- 괄호는 생략하지 않는다.
-
{
의 경우- 여는 중괄호 전에는
space-bar
를 한다. - 여는 중괄호 전에는 개행하지 않는다.
- 여는 중괄호 뒤에서는 개행한다.
}
의 경우 - 닫는 괄호 앞에서 개행한다.
- 닫는 괄호 뒤의 개행은, 중괄호가 끝나거나 생성자, 메소드, 클래스가 끝날 때 개행한다.
- 여는 중괄호 전에는
-
빈 블록도 개행한다.
public void foo(){ }
- 들여쓰기는 4공백으로 지정한다.
- 스위치문은 사용하지 않는다.
- else if, else문은 사용하지 않는다.
- 주석은 사용하지 않는다.
- 클래스와 멤버의 제어자는 다음과 같은 순서로 배치한다. (순서가 우선순위는 아니다)
-
public
,protected
,private
,abstract
,default
,static
,final
-
- 클래스와 멤버는 항상 최소한으로 노출시킨다.
-
private
→package-private
→protected
→public
-
- 상속하지 말아야 할 클래스는
final
로 제약을 건다. - 멤버는 가능하다면 항상
final
로 만든다. -
static
은 남용하지 않는다.
- 모든 콤마 뒤에는
space-bar
를 사용한다.
- 꼭 필요한 경우에만 Wrapper 타입을 사용하고, 최대한 원시 타입을 쓴다.
- 클래스 이름은
UpperCamelCase
로 작성한다. - 클래스 이름은 명사나 명사구로 작성한다.
- 인터페이스 이름도 명사나 명사구로 작성한다. 하지만, 형용사나 형용사구로 작성해도 된다. ex)
Comparable
- 인터페이스 이름도 명사나 명사구로 작성한다. 하지만, 형용사나 형용사구로 작성해도 된다. ex)
- 클래스, 인터페이스 이름은 클래스, 인터페이스의 역할을 표현해야 한다.
- 구현체 이름은
XXXImpl
은 쓰지 않는다.
- 메서드 이름은
lowerCamelCase
로 작성한다. - 메서드 이름은 동사로 작성한다.
- 명사가 더 어울릴 수도 있다. ex)
Math.pow()
- 명사가 더 어울릴 수도 있다. ex)
- 필드 변수는
lowerCamelCase
를 사용한다. - 필드 변수는 명사나 명사구로 표현한다.
- 상수
static final
이름은CONSTANT_CASE
로 작성한다. (enum의 필드도 상수다.)
- 예외를 잡는 이름은
e
로 한다.
try {
/* some code... */
} catch(IllegalStateException e) {
/* some code... */
}
-
when
에서 검증하고자 하는 메서드 이름을 그대로 사용한다. (단, 실패하는 테스트는 앞에fail
을 붙임)
- 이 외의 이름은 모두
lowerCamelCase
를 사용하며 명사나 명사구로 작성한다.
- 함수(또는 메서드)의 구현부가 10라인(한 줄은 ; 기준)을 넘어가지 않도록 구현한다. 단, 테스트 코드는 예외로 한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
-
인터페이스 분리
Controller의 인터페이스를 분리하여 API 명세 작업을 한다.
ex)
TravelController
→TravelControllerDocs
-
@Schema
- 클래스와 메서드 모두 붙여준다.
- 클래스에는 속성 값으로
description
만 설정해준다. - 메서드에는 속성 값으로
example
만 설정해준다. - 해당 애노테이션은 가장 상위에 붙여준다.
-
예외
- 전역적인 예외는
GlobalExceptionHandler
에서 명시해준다. - 도메인별 특정 예외는
controllerDocs
에서 명시해준다.
- 전역적인 예외는
-
Parameter
Path variable이나 Query String, ArgumentResolver에서 처리하는 인자에 대해 명시해준다.
만약, 클라이언트 쪽에서 직접 전달하는 값이 아닌 경우(ex: ArgumentResolver나 Interceptor에서 처리하는 값)
Parameter(hidden=true)
를 설정해야한다. -
전역적인 default Media Type은 yml에서 설정했다.
-
@Tag
ControllerDocs
(클래스 레벨)에@Tag
를 붙여서name
과description
속성을 설정한다.
@Schema(description = "여행 상세를 생성/수정하기 위한 요청 형식입니다.")
public record TravelRequest(
@Schema(example = "http://example.com/london.png")
String travelThumbnail,
@Schema(example = "런던 여행")
@NotNull(message = "여행 제목을 입력해주세요.")
@Size(max = 30, message = "제목의 최대 허용 글자수는 공백 포함 30자입니다.")
String travelTitle,
@Schema(example = "런던 시내 탐방")
@Size(max = 500, message = "내용의 최대 허용 글자수는 공백 포함 500자입니다.")
String description,
@Schema(example = "2024-07-27")
@NotNull(message = "여행 시작 날짜를 입력해주세요.")
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate startAt,
@Schema(example = "2024-07-29")
@NotNull(message = "여행 끝 날짜를 입력해주세요.")
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate endAt) {
public Travel toTravel() {
return Travel.builder()
.thumbnailUrl(travelThumbnail)
.title(travelTitle)
.description(description)
.startAt(startAt)
.endAt(endAt)
.build();
}
}
@Tag(name = "Travel", description = "Travel API")
public interface TravelControllerDocs {
...
@Operation(summary = "여행 상세 수정", description = "여행 상세 정보(제목, 내용, 기간)를 수정합니다.")
@ApiResponses(value = {
@ApiResponse(description = "여행 상세 수정 성공", responseCode = "200"),
@ApiResponse(description = """
<발생 가능한 케이스>
(1) 필수 값(여행 제목, 기간)이 누락되었을 때
(2) 날짜 형식(yyyy-MM-dd)이 잘못되었을 때
(3) 제목이 공백 포함 30자를 초과했을 때
(4) 내용이 공백 포함 500자를 초과했을 때
(5) 기간 설정이 잘못되었을 때
(6) 변경하려는 여행 기간이 이미 존재하는 방문 기록을 포함하지 않을 때
(7) 수정하려는 여행이 존재하지 않을 때
(8) Path Variable 형식이 잘못되었을 때
""",
responseCode = "400")
})
ResponseEntity<Void> updateTravel(
@Parameter(description = "여행 상세 ID") Long travelId,
TravelRequest travelRequest,
@Parameter(hidden = true) Long memberId);
...
}
-
테스트 작성은 given, when, then 규약을 따른다. (AAA 패턴: Arrange-Act-Assert)
- given : 테스트에서 사용할 자원 명시
- when : 테스트의 동작 수행
- then : 테스트 결과 검증
@DisplayName() @Test void test(){ // given // when /* 실행부가 2줄 이상이라면, 테스트 분리를 고려하자. */ // then }
-
Annotation은
@DisplayName
,@Test
순서로 배치한다. -
DisplayName
은 문장 형식으로 작성한다. -
static import를 사용한다.
- ex)
import static org.assertj.core.api.Assertions.*assertThat*;
assertThat(..)..
;
- ex)
-
검증부가 2줄 이상이라면 assertAll을 사용한다.
- 사용자가 다음 단계로 무엇을 해야 할지 안내하자.
- 지금 사용자가 어떤 상황에 처했는지(상황 설명)
- 그것이 왜 발생했는지(이유)
- 해결하려면 어떻게 해야하는지(해결책)
- 이중 부정 금지
- 사용자를 위한 적절한 단어 선택
- 긍정적인 어조 사용
- 의도된 경우가 아니라면,
null
을 반환하고 체크하는 코드를 작성하지 않는다. -
null
이 예상되는경우Optional
을 고려한다.
- 존댓말 사용
- 추상적인 리뷰 금지
- 피드백은 근거나 예시를 함께 들어서 (ex. 코드 예제, 블로그 링크 첨부)
- 피드백에 대한 답변
- 이모지 남기기
- 수정 요청 피드백
- 👍 : 확인
- 🚀 : 피드백 반영 완료
- 👀 : 다른 리뷰어 피드백에 대한 동의
- 모든 코드 관련 소통은 댓글로 하기
- 리뷰 작성 기한
-
18시 이전이면 다음날 18시까지, 18시 이후면 이틀 뒤 10시 이전까지 리뷰를 남겨준다.
-
코드 리뷰가 늦어지는 경우, 언제까지 리뷰가 가능한지 슬랙 알림 댓글에 미리 알려준다.
-
급한 PR의 경우, 슬랙 PR방에 리뷰어들 태그해서 알림을 보낸다. (간단한 사유와 기한 명시)
@폭포 @리니 로그인 구현 후, 바로 다음 기능 구현해야 해서 7월 27일 오후 7시까지 리뷰 부탁드립니다. ^^
-
- 🫶 WooDangTang!Tang! HuruHuru~ Pair