✅ 나만의 개발 규칙 정리
아래의 규칙이 정답이 아닐 수는 있다. 하지만 지금까지 경험으로 이렇게 했을 때 실수를 줄일 수 있었다.
1. 주석은 메소드의 목적에만 작성한다
- 설명: 메소드가 무엇을 하는지에 대해서만 주석을 달고, 어떻게 구현했는지에 대한 주석은 가능한 한 배제합니다.
- 이유: 과도한 주석은 코드 변경 시 함께 관리해야 할 대상이 되어 오히려 유지보수의 부담이 커질 수 있습니다.
💡 팁: 코드 자체가 잘 읽히도록 이름 짓기와 구조 개선을 우선하고, 정말 필요한 부분에만 간결한 주석을 달자.
2. Request/Response 객체는 별도의 DTO(Entity)로 분리하여 작성한다
- 설명: 컨트롤러나 서비스에서 직접 엔티티(Entity)를 사용하지 않고, 반드시 요청(Request)과 응답(Response)에 알맞는 DTO를 분리하여 사용합니다.
- 이유:
- 엔티티를 직접 사용하는 방식은 단일 책임 원칙(SRP)을 위반하고, 유효성 검증/직렬화/변환 등 다양한 관심사들이 뒤섞이는 구조가 됩니다.
- DTO 분리는 역할을 명확히 나눠주며, 확장성과 유지보수성을 높입니다.
- 또한 DTO 내부에 toEntity(), from(Entity) 같은 정적 팩토리 메서드를 명시함으로써, 변환 책임을 캡슐화하고 외부 호출자(Service, Controller)의 복잡도를 줄일 수 있습니다.
💡 팁 1: 요청 DTO에는 @NotBlank, @NotNull, @Pattern 등의 검증 어노테이션을 붙여 사용자 입력 검증까지 처리하도록 설계하세요.
💡 팁 2: 응답 DTO는 from(Entity) 정적 메서드를 사용해 변환 책임을 한곳에 집중시키면, 테스트 및 재사용이 훨씬 쉬워집니다.
✅ 예시
// 요청 DTO
@Getter
@Builder
public class ShopRequestDto {
private String shopName;
...
public Shop toEntity(User user) {
return Shop.builder()
.shopName(shopName)
...
.user(user)
.build();
}
}
// 응답 DTO
@Getter
@Builder
public class ShopResponseDto {
private String storeName;
...
public static ShopResponseDto from(Shop shop) {
return ShopResponseDto.builder()
.storeName(shop.getShopName())
...
.build();
}
}
3. for문과 삼항 연산자는 가급적 자제한다
- 설명: 복잡하거나 중첩된 for문, 삼항 연산자는 가독성을 떨어뜨리고, 버그나 오해의 소지가 커집니다.
- 이유: 추후 코드 분석이나 수정 시 의도를 파악하기 어려워 유지보수가 어려워집니다.
💡 팁: Stream, if-else, Optional 등을 적극 활용하고, 로직은 별도의 메서드로 분리하는 습관을 들이자.
4. 서비스 계층의 테스트 코드는 Best Case를 먼저 작성한다
- 설명: 서비스 계층에서 정상 흐름에 대한 테스트(Best Case)는 구현 직후 빠르게 작성합니다.
- 이유: 구현 직후 테스트 코드를 작성하면, 수정 포인트가 명확해지고 불필요하거나 목적에서 벗어난 수정을 방지할 수 있습니다.
💡 팁: 테스트 함수 이름은 given-when-then 패턴을 따르도록 작성하여, 테스트 목적과 기대 결과를 명확히 하자.
5. 유효성 검증은 별도의 validateEntity() 또는 서비스의 validateXXX() 메서드로 분리한다
- 설명:
- 이유:
- 비즈니스 로직과 검증 로직이 섞이면 코드 흐름이 복잡해지고 테스트하기 어려워짐
- 검증 실패 시 빠르게 예외 처리할 수 있어, fail-fast 설계에 적합
public void validateEntity() {
if (minDeliveryPrice < 0) {
throw new IllegalArgumentException("최소 배달비는 0 이상이어야 합니다.");
}
if (openTime.isAfter(closeTime)) {
throw new IllegalArgumentException("영업 시작 시간이 종료 시간보다 늦을 수 없습니다.");
}
}
💡 팁: validateEntity()는 Service 계층에서 엔티티 생성 전 호출하는 것이 일반적입니다.
💡 팁: Spring Validation(@Valid)이 부족한 비즈니스 규칙 검증을 보완할 때 유용합니다.
6. @Setter 사용을 자제하고 불변 객체(Immutable Object)를 지향한다
- 설명: 엔티티나 DTO에 무분별하게 @Setter를 사용하면 예측하지 못한 값 변경이나 JPA dirty checking 오작동을 유발할 수 있다.
- 이유:
- @Setter는 객체 일관성 유지에 불리하고, 무분별한 쿼리 생성을 유도할 수 있음
- 특히 JPA에서는 필드 값 변경 = update 쿼리 생성 조건이 되므로, 원치 않는 UPDATE가 발생할 수 있음
- 의도를 가진 메서드(changeStatus(), updateAddress())로 명시적 변경하는 것이 안전함
// Bad Practice
@Setter
private String address;
// Good Practice
private String address;
public void updateAddress(String newAddress) {
this.address = newAddress;
}
7. if-else보다는 if만 사용하기
- 설명: 조건문에서 else를 사용하는 대신, 가능한 한 if만 사용하여 코드의 가독성을 높입니다.
- 이유: if-else 구조가 복잡해지면, 코드 흐름을 파악하기 어려워질 수 있으며, 조건문을 단순화함으로써 더 명확하게 의도를 전달할 수 있습니다. 또한, if만 사용할 경우 조건이 하나씩 독립적으로 평가되므로, 각 조건에 대한 수정이나 확장이 용이합니다.
💡 팁: 조건문이 길어지지 않도록 최대한 간결하게 유지하고, 필요 시 메서드로 분리하여 가독성을 높입니다.