Skip to content

BE 코드 컨벤션

Leetaehoon edited this page Aug 8, 2024 · 1 revision

1. 공통 요건

1.1 IntelliJ Code Style은 우테코 스타일을 적용한다.

2. 선언

2.1 클래스

2.1.1 클래스 상수, 멤버 변수, Static 메서드, Instatnce 메서드순으로 선언한다.

2.1.2 final은 기본적으로 붙이지 않는다. 필요한 경우에만 붙인다.

2.1.3 entity 클래스에는 equals, hashcode, toString을 반드시 선언한다.

public class Controller {

		private static final int NUMBER = 1;

    private final ReservationApplicationService reservationApplicationService;
    private final MemberService memberService;
    
    public static Controller of() {
    }
	  
		public static void hello() {
			System.out.println("hello");
		}
    
		public void hi() {
			System.out.println("hi");
		}
}

2.2 어노테이션

2.2.1 주요 annotation(Entity, Service…)를 맨위에 선언한다.

2.2.2 Lombok 관련 어노테이션은 하위에 선언한다.

@Service
@Transactional(readOnly=true)
@NoArgsContructor
class Service {

}

3. 개행

3.1 공통

3.1.1 첫번째 필드는 개행을 추가한뒤 선언한다.

3.1.2 어노테이션이 붙은 필드는 개행을 추가한다.

@Entity
@Getter
public class Member extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    private Name name;

    @Embedded
    private Email email;

    @Column(nullable = false)
    private String password;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;
    
    public boolean isAdmin() {
        return role == Role.ADMIN;
    }
    
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Member)) {
            return false;
        }
        Member member = (Member) o;
        return Objects.equals(getId(), member.getId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId());
    }

    @Override
    public String toString() {
        return "Member{" + "id=" + id + ", name=" + name + ", email='" + email + '\'' + '}';
    }
}

3.1.3 Interface의 메서드 선언마다 개행을 추가한다.

public interface ReservationRepository extends JpaRepository<Reservation, Long> {
    
    Optional<Reservation> findReservationByDateAndTimeAndTheme(LocalDate date, ReservationTime time, Theme theme);

    boolean existsByTimeId(long timeId);

    boolean existsByThemeId(long themeId);
}

3.1.4 클래스 내부가 비어있는 경우 개행을 추가한다.

record Dto(String name) {

}

3.2 Controller

3.2.1 ResponseEntity에서 120줄이 넘어갈 경우에만 개행을 추가한다.

    @PostMapping("/logout")
    public ResponseEntity<Void> logout(HttpServletResponse response) {
        responseHandler.expire(response);
        return ResponseEntity.ok().build();
    }

3.2.2 매개변수 시그니처가 120줄이 넘어갈 경우, 인자당 개행을 추가한다.

    @PostMapping("/signup")
    public ResponseEntity<MemberResponse> signUp(
            @RequestBody @Valid SignUpRequest signUpRequest,
            @RequestBody @Valid SignUpRequest signUp
    ) {
        SignUpCommand signUpCommand = new SignUpCommand(
                signUpRequest.name(),
                signUpRequest.email(),
                signUpRequest.password()
        );
        MemberResponse memberResponse = authService.signUp(signUpCommand);
        return ResponseEntity.created(URI.create("/login")).body(memberResponse);
    }

3.3 stream 메서드는 점(.) 마다 개행한다.

    public List<MyReservationInfo> findMyReservations(Member member) {
	      List<MemberReservation> memberReservation = memberReservationRepository.findByMemberId(member.getId());
        return memberReservation.stream()
                .map(this::add)
                .toList();
    }

3.4 Record 클래스

3.4.1 파라미터에 어노테이션이 없는 경우 필드 사이에 개행을 두지 않는다.

public record AvailableTimeResponse(
        long timeId, 
        LocalTime startAt, 
        boolean alreadyBooked
) {

}

3.4.2 파라미터에 어노테이션이 있는 경우 필드 사이에 개행 둔다.

public record MemberReservationRequest(
        @NotNull(message = "예약 시간 id는 필수 값입니다.")
        @Positive
        Long memberId,

        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
        @NotNull(message = "예약 날짜는 필수 값입니다.")
        LocalDate date,

        @NotNull(message = "예약 시간 id는 필수 값입니다.")
        @Positive
        Long timeId
) {

}

4. Test 코드

4.1 Test 코드는 given, when, then 양식으로 작성한다.

4.1.1 ’ // ‘이후에 스페이스를 한번 사용한다.

4.1.2 ‘&’ 와 단어를 붙인다.

    @DisplayName("결제에 성공하면, 응답을 반환한다.")
    @Test
    void successfulPayment() {
        // given
        String paymentKey = "tgen_20240528172021mxEG4";
        String paymentType = "카드";
        BigDecimal totalAmount = BigDecimal.valueOf(1000L);
        PaymentRequest paymentRequest = new PaymentRequest(totalAmount, "MC45NTg4ODYxMzA5MTAz", paymentKey);
        PaymentResponse okResponse =
                new PaymentResponse(paymentKey, "DONE", "MC4wOTA5NzEwMjg3MjQ2", totalAmount, paymentType);
        doReturn(okResponse).when(paymentClient).confirm(any());

        // when
        paymentService.pay(paymentRequest, memberReservation);

        // then
        Optional<Payment> optionalPayment = paymentRepository.findByPaymentKey(paymentKey);

        assertAll(
                () -> assertThat(optionalPayment).isNotNull(),
                () -> assertThat(optionalPayment.get().getPaymentType()).isEqualTo(PaymentType.from(paymentType)),
                () -> assertThat(optionalPayment.get().getAmount()).isEqualTo(new Price(totalAmount))
        );
    }

4.2 Test 코드 메서드 바로 위에 @Test를 선언한다.

4.2.1 @Test 위에는 @DisplayName으로 테스트 설명을 적는다.

@DisplayName("가격 도메인 테스트")
@Test
class PriceTest {

    @DisplayName("형식에 맞지 않은 가격 생성 시, 예외가 발생한다.")
    @ParameterizedTest
    @ValueSource(doubles = {-1000.0, -1.1, 210000000000000000L})
    void invalidPrice(Double invalidPrice) {
        // given&when&then
        BigDecimal bigDecimal = BigDecimal.valueOf(0);

        assertAll(
                () -> assertThat(new Price(bigDecimal).getPrice()).isEqualTo(bigDecimal),
                () -> assertThatThrownBy(() -> new Price(BigDecimal.valueOf(invalidPrice)))
                        .isInstanceOf(IllegalArgumentException.class)
        );
    }
}

4.3 Test 코드의 메서드명은 {메서드명}_{사유}로 작성한다.

@DisplayName("가격 도메인 테스트")
@Test
class create_invalidMoney() {
	}

5. 주석

5.1 주석은 필요시에만 작성한다.

5.2 함수 주석은 사유-param-return 순으로 작성한다.

    /**
     * need some help
     * @param reservationTimeCreate
     * @return ReservationTimeResponse
     */
    @Transactional
    public ReservationTimeResponse create(ReservationTimeCreate reservationTimeCreate) {
        LocalTime time = reservationTimeCreate.startAt();
        if (reservationTimeRepository.existsByStartAt(time)) {
            throw new BadRequestException(ErrorType.DUPLICATED_RESERVATION_TIME_ERROR);
        }

        ReservationTime reservationTime = new ReservationTime(time);
        return ReservationTimeResponse.from(reservationTimeRepository.save(reservationTime));
    }

5.3 한 줄 주석은 주석이 필요한 코드 위에 작성한다.

   	/**
     * need some help
     * @param reservationTimeCreate
     * @return
     */
    @Transactional
    public ReservationTimeResponse create(ReservationTimeCreate reservationTimeCreate) {
        LocalTime time = reservationTimeCreate.startAt();
        if (reservationTimeRepository.existsByStartAt(time)) {
            throw new BadRequestException(ErrorType.DUPLICATED_RESERVATION_TIME_ERROR);
        }
        // 도와줘 냥인 제발...
        ReservationTime reservationTime = new ReservationTime(time); 
        return ReservationTimeResponse.from(reservationTimeRepository.save(reservationTime));
    }

6. DB 컨벤션

6.1 테이블

  • 집단명사 사용하는 것을 권장합니다. 이상적인 것은 아니지만 복수형 쓸 수 있습니다. → 웬만하면 단수형으로 작성합니다.
    • 예) staff, employees
  • tbl과 같은 접두사를 사용하지 않습니다.
  • 테이블명을 컬럼명으로 쓰지 않습니다.
  • 가능하면 두 테이블명을 연결해서 관계 테이블 이름을 만드는 걸 지양합니다.
    • 예) 다대다 테이블 → 일대다, 다대일로 해결하는 경우

6.2 칼럼

  • 항상 단수를 사용합니다.
  • 테이블 기본키(PK)로 id를 사용하지 않습니다.
  • 항상 소문자를 사용합니다.
    • 고유명사처럼 의미 전달이 안되는 경우, 예외입니다.