Optional은 반환값 처리에 사용하는 것이 권장되며, 파라미터로 사용하는 것은 안티패턴으로 지양해야 한다.
// AS IS
public User create(UserCreateRequestDTO userCreateRequestDTO,
Optional<BinaryContentCreateRequestDTO> profileCreateRequestDTO)
// TO BE
public User create(UserCreateRequest userCreateRequest,
BinaryContentCreateRequest profileCreateRequestDTO)
new 생성자로 생성하는 건 파라미터의 순서와 갯수를 고려하여 작성해야 하므로, 코드의 가독성이 떨어지는 단점이 있다.
반면 Builder 패턴을 적용하면 가독성과 유지보수성이 높아진다. 특히 파라미터가 많을 경우 유리하다.
UserStatus userStatus = UserStatus.builder()
.userId(user.getId())
.lastActiveAt(Instant.now())
.build();
메서드 작성시, 접근 제한자를 명시적으로 작성하는 것이 권장된다.
이를 작성함으로써 클래스 외부에 노출되는 범위를 명확히 하여 코드의 가독성과 유지보수성이 높아진다.
유저 생성, 파일 생성 요청 정보를 별도로 전달하기보다는
요청 DTO에 함께 포함시켜 API 요청을 단일화하는 것이 구조적으로 더 깔끔하고 직관적이다.
단, 미션의 베이스코드에서는 DTO를 2개로 나눠 파라미터로 받았기에, 해당 구조는 유지하였다.
UserStatus 등 다른 도메인의 상태를 갱신할 때는, 서비스 간 의존보다는 컨트롤러에서 서비스들을 조율하는 방식이 더 적절하다.@Controller 대신 @RestController 활용@RestController = @Controller + @ResponseBody 로, 이를 활용하는 것이 적절하다.
아래의 코드들에서 요청 1건당 쿼리가 N번 실행될 수 있는 구조로 N+1문제가 발생할 수 있다. 이를 개선하는 것이 좋다.
// 1
privateChannelCreateRequestDTO.participantIds().forEach(userService::find);
// 2
return ids.stream()
.map(id -> binaryContentRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 BinaryContent입니다.")))
// 3
createRequestDTO.participantIds().stream()
.map(userId -> ReadStatus.builder()
.userId(userId)
.channelId(ch.getId())
.lastReadAt(Instant.now())
.build())
.forEach(readStatusRepository::save);
userId,participantIds 등 동일한 의미의 필드가 여러 명칭으로 횬용되어 있을 경우, 의미 파악에 혼란을 줄 수 있다.
일관된 네이밍 (userIds 등)을 사용하는 것이 바람직하다.
equals() 호출시, null이 될 가능성이 있는 객체에서 호출하면 NPE가 발생할 수 있으므로, NPE 발생 가능성이 없는 쪽에서 호출하는 것이 적절하다.
// AS IS
if (ch.getType().equals(ChannelType.PRIVATE))
// TO BE
if (ChannelType.PRIVATE.equals(ch.getType()))