안녕하세요. 제 인생 최초의 코드 리뷰를 받아보았습니다.
카카오 테크 캠퍼스를 신청하게 된 계기가 내가 옳은 방향으로 나아가고 있는지 코드는 잘 짜고 있는지를 확인할 수 있는 코드 리뷰가 있다는 것이었는데 드디어 받게 되었습니다. 그 결과를 기록해보려 합니다.
아래의 pr을 통해 제가 받은 코드 리뷰를 확인할 수 있습니다.
https://github.com/kakao-tech-campus-2nd-step2/spring-gift-product/pull/50
충남대 BE_이은경 1주차 과제(1단계)(2단계)(3단계) by pkyung · Pull Request #50 · kakao-tech-campus-2nd-step2/sp
과제 질문 ʕ •ᴥ•ʔ 이번 1단계 과제에서 응답 값에 http 상태 코드가 함께 반환된 것을 보고 ResponseEntity를 사용했는데 이를 이렇게 사용하는 것이 맞는지 궁금합니다. mapping을 통한 메서드 구현
github.com
구현한 기능 📄
일단 구현한 기능은 아래와 같습니다.
처음에는 HashMap 자료구조를 통해 db를 대신하여 api를 구현하였는데 3번째 과제에서 jdbc를 통해 데이터베이스와 연동 작업을 진행했습니다. 항상 jpa를 사용해왔기에 처음 사용하는 jdbc는 쉽지만은 않았습니다.
# spring-gift-product 🎁
## 기능 목록 📄
### api 기능
- [x] 상품 조회 api 구현
- [x] 상품 추가 api 구현
- [x] 상품 수정 api 구현
- [x] 상품 삭제 api 구현
### 관리자 페이지
- [x] 모든 상품 조회된 페이지
- [x] 상품 추가 버튼 및 기능
- [x] 상품 삭제 버튼 및 기능
- [x] 상품 수정 버튼 및 기능
### 상품 api jdbc
- [x] 모든 api를 jdbc를 이용하여 데이터베이스에 저장하도록 수정
- [x] 상품 생성
- [x] 상품 조회
- [x] 상품 수정
- [x] 상품 삭제
받은 피드백 🎯
생성자의 위치
대부분 필드 작성 후에 생성자를 작성합니다.
객체를 생성할 때, 생성자를 호출하기에 상단에 작성했었는데 이런 코드 스타일을 주로 사용한다는 것을 알게되었습니다.
public class Product {
private Long id;
private String name;
private int price;
private String imageUrl;
public Product() {}
public Product(Long id, String name, int price, String imageUrl) {
this.id = id;
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}
}
부정보다는 긍정을
!= 로 if문을 사용하기 보다는 == 로 사용한다고 합니다.
if (product != null) {
return new ResponseEntity(HttpStatus.CREATED);
}
return new ResponseEntity(HttpStatus.CONFLICT);
if (product == null) {
return new ResponseEntity(HttpStatus.CONFLICT);
}
return new ResponseEntity(HttpStatus.CREATED);
select -> find 네이밍
select 라는 단어는 자주 나올 수 있는 단어여서 객체를 조회할 때는 find로 주로 사용한다고 합니다.
따라서, find와 findAll로 수정해주었습니다.
public Product select(Long id) {
String sql = "SELECT id, name, price, imageUrl from products WHERE id = ?";
try {
return jdbcTemplate.queryForObject(sql, productRowMapper(), id);
} catch (Exception e) {
return null;
}
}
ResponseEntity의 팩토리 패턴 지향
위의 생성자를 통한 ResponseEntity 객체의 생성보다는 정적 팩토리 패턴으로 작성하는 게 좋다고 합니다.
그 이유는 메서드 체이닝으로 연결된 빌더 패턴을 사용하는 것이 의미가 직관적이며 유지보수에 좋기 때문입니다.
이후, 이펙티브 자바의 아이템 2를 읽어보라고 권하셨습니다.
return new ResponseEntity<>(product, HttpStatus.OK);
return ResponseEntity.ok().body(product);
null 반환 지양 -> ExceptionHandler로 에러 처리
다른 프로젝트에서도 제가 사용한 코드 방식이었는데 특정 id로 객체를 조회했을 때 없는 경우에 Repository, Service에서 null을 반환하여 Controller에서 null 인지 확인 후에 다른 응답 값을 주곤 했습니다.
그런데 Service에서 에러를 반환하면 controller가 그 에러를 잡아서 ExceptioHandler에서 처리하면 더 좋겠다는 피드백을 받았습니다.
이 글을 읽어보라고 권하셨습니다.
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
Exception Handling in Spring MVC
NOTE: Revised April 2018 Spring MVC provides several complimentary approaches to exception handling but, when teaching Spring MVC, I often find that my students are confused or not comfortable with them. Today I'm going to show you the various options avai
spring.io
그래서, 상품이 이미 존재한다는 에러 ProductAlreadyExistsException 에러와 상품을 찾을 수 없다는ProductNotFoundException 에러를 만들었습니다.
public class ProductAlreadyExistsException extends RuntimeException {
public ProductAlreadyExistsException(String message) {
super(message);
}
}
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String message) {
super(message);
}
그리고 Service 단에서 해당 에러를 던졌고, 컨트롤러에서 ExceptionHandler를 통해 에러 처리를 해주었습니다.
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<String> handleProductNotFoundException(ProductNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
@ExceptionHandler(ProductAlreadyExistsException.class)
public ResponseEntity<String> handleProductAlreadyExistsException(ProductAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
}
@Autowired 생략 가능한 경우
생성자를 통해 의존 관계가 호출되는 경우, 호출 시점에 1회 호출되는 것이 보장됩니다. 그래서 주입받은 객체가 변하지 않는다면 @Autowired를 생략할 수 있습니다.
public ProductController {
private ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
}
public ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
}
후기🎓
코드 리뷰를 통해 코드를 더 깔끔하게 작성할 수 있게 된 것 같습니다. 이제껏 알지 못해서 사용하지 못한 것들, 제대로 코드를 짜고 있는 지 들었던 의문점들이 해결될 수 있는 시간인 것 같습니다. 앞으로의 카테캠의 기간동안 코드 리뷰의 시간을 소중히 여기고 잘 배워야겠다고 생각했습니다.
'🍫카카오 테크 캠퍼스 2기 BE' 카테고리의 다른 글
[카카오 테크 캠퍼스 / BE] 2단계 세 번째 코드 리뷰 (1) | 2024.07.14 |
---|---|
[카카오 테크 캠퍼스 / BE] 2단계 두 번째 코드 리뷰 (0) | 2024.07.14 |
[카카오 테크 캠퍼스 / BE] 두 번째 미니과제, 자동차 경주🚗 (1) | 2024.06.10 |
[카카오 테크 캠퍼스 / BE] 첫 번째 미니과제, 숫자 야구 게임⚾ (0) | 2024.05.27 |
[카카오 테크 캠퍼스 / 2기] '웰컴 키트' 라는 것을 받다 (0) | 2024.05.14 |