매일 스크럼에서 공유한 내용을 정리합니다.
궁금하거나 헷갈리는 내용
springboot-basic week1 미션 관련
- 애플리케이션 구동방식을 어떻게 하는지
- 처음에 실행을 담당하는 컨트롤러를 만든 후 해당 컨트롤러를 실행하는 방식으로 구현
- 콘솔 클래스를 이용해서 미션 진행하신분이 있는지
- 콘솔 클래스의 경우 콘솔에서 동작하는 것이 아니라면 무조건 null이 반환됨, 이에 관련해서 해주어야할 설정같은게 있는지 확인해야 할 것 같음
- 파일에 데이터를 저장할때 메모리에 기억해두었다가 종료시점에 한번에 저장하는 방식으로 구현하려고 하는데 적절한 방법이 무엇일지
- exit시에 저장하는 것이 가장 적절해보이지만 컨트롤러에게 너무 많은 부담을 주는 것이 아닌가라는 생각이 듬
- 잘못된 값이 들어왔을 때 (Ex : 숫자를 입력했는데 문자가 들어온경우) 어떻게 처리했는지
- 잘못된 값이 들어오면 올바른 값이 들어올때까지 반복문을 돌아 올바른 값이 들어오면 그 이후의 로직을 수행하는 방식으로 구현함
- 잘못된 값이 들어오면 exception을 발생시키고 exception이 발생했을 경우 재귀를 통해 계속 반복시키는 방식 → 재귀로 구현하는 것이 반복문을 통해 구현하는 것보다 오버헤드가 많이 발생하기 때문에 반복문을 사용하는 것이 조금 더 나아보임
Q) VoucherService 에서 바우처를 생성하는 기능을 제공한다고 할 때, 바우처 생성과정에서 발생하는 예외를 어떻게 처리 하는지 궁금합니다
저의 경우, VoucherType 에 따른 바우처 생성자를 호출 하였을 때, 생성자내의 검증 로직에 따라 예외가 발생하면 이를 서비스 계층에서 처리 해주었는데, 이렇게 하면 예외가 발생했을 때에는 바우처가 생성되지 않는 다는 문제점이 있었습니다.
null 을 리턴하는 것은 이를 사용하는 측에서 NPE 방어적인 코드를 짜야하고 이를 하지 않을 경우 문제가 발생할 수도 있기에 Optional<Voucher> 로반환하였는데 다른 분들은 어떻게 하셨나요?
- 입력을 받는 쪽에서부터 예외가 발생할 만한 입력을 받지 못하도록 하였다 ( 잘못된 입력 → 다시 루프를 돌도록 구현)
- 서비스 계층 밖으로 exceptional throw
- 그렇다고 커스텀 예외를 따로 만드는건 아직 시기상조. 자바에서 기본적으로 제공해주는 좋은 예외들도 많다!
- 내부에서 예외처리 할 경우, 결국 참조객체에 값이 할당되지 않아 그냥 null 을 반환하고 이를 받는 쪽에서 NPE 방어 코드를 작성하였음
Q) enum 으로 관리할 경우 문제 → 새로운 타입이 추가 될 때 마다 결국은 코드상의 변경이 일어나는 것 아닌가?? 구체 클래스를 추가할 때 생기는 코드의 변경들이 일어나지 않게 하기 위해 enum 을 사용한다 생각했는데 결국은 코드의 변경이 일어나니 어떻게 해야할지 모르겠다
+Q)종류가 두 개 정도 밖에 없는데 enum을 꼭 써야할까?
( 확장하기위해선 구현체가 추가되고, 이에 따라 enum 내부에 값이 추가되고 변경이 일어나야 하는게 고민이다 )
- 구현체가 추가되는데, 코드가 아예 변경되지 않을 수는 없지 않다고 생각한다. 오히려 enum 을 사용하는게 최소한으로 코드변경을 하도록 하는 것 아닐까?
Q) 멘토님께서 말씀하셨던 부분 → 생성자를 두 가지로 만들었던 것이 왜 문제가 되는건지
교육을 위해 기존에 쓰지 않는 부분을 만들었다면, 이해를 돕기 위해 그렇게 만들었거나, 교육을 위해 아예 잘못되게 만든 경우가 있을 텐데, 예제 코드는 아예 잘못만들어진거였을까??
왜 해당 코드가 잘못되었다는 걸까?
문제가 된 코드public Order(UUID orderId, UUID customerId, List<OrderItem> orderItems, Voucher voucher) { this.orderId = orderId; this.customerId = customerId; this.orderItems = orderItems; this.voucher = Optional.of(voucher); // this.orderItemRecords = orderItemRecords; } // order 만들 때 voucher 가 있을 수도 없을 수도 있으니 public Order(UUID orderId, UUID customerId, List<OrderItem> orderItems) { this.orderId = orderId; this.customerId = customerId; this.orderItems = orderItems; this.voucher = Optional.empty(); }
팀 면담 때, 멘토님이 제안하신 방법
public Order(UUID orderId, UUID customerId, List<OrderItem> orderItems, Voucher voucher) { this.orderId = orderId; this.customerId = customerId; this.orderItems = orderItems; this.voucher = Optional.of(voucher); } public Order(UUID orderId, UUID customerId, List<OrderItem> orderItems) { this(orderId, customerId, orderItems, new DefaultVoucher()); }
- 스트림이 무엇인가??
- I/O stream vs Java 8 stream
- http://www.tcpschool.com/java/java_io_stream
- https://www.programiz.com/java-programming/io-streams
- https://docs.oracle.com/javase/tutorial/essential/io/streams.html
- 전반적으로 단방향 데이터의 흐름인 것 같다
- 네이밍 컨벤션 관련 글 공유
- Object의 Clone 메소드 : 깊은 복사의 직렬화 역직렬화
- 알고리즘 할 때 써본듯?
- 배열에 많이 쓰는 것 같다! → 배열에서는 Clone이 가장 빨리 복사하는 방법이라고 함!
- Clone이 필드로 어떤 클래스를 Clone했을 때, 참조 타입 같은 경우에는 Clone이 기본적으로 얕은 복사를 하다보니까 예기치 못한 결과가 나오게 된다.
- 자바에서 직렬화를 진행하면 내부에 있는 클래스들, 참조값들도 전부 다 직렬화가 되는건가?
→ 재정의를 해야하는데, 재정의를 할 때 깊은 복사를 일일이 다 해줘야 함!
→ 그 방법 중 하나가 직렬화 : 어떤 객체를 Byte String으로 변환해서 저장함, 이걸 이용해서 새로운 객체를 생성함! 클론과 달리 깊은 복사까지 된다.
→ Clone, 직렬화 둘다 인터페이스를 무조건 구현해야한다는게 단점이다.
→ 맞다! 근데 걔들도 그러면 Serializable를 구현해놔야함
- Mock Object 관련 추가 공부가 필요하다
(연우)
제가 이해한 것이 틀리거나 추가로 해 주실 수 있는 말이 있다면 수정 및 코멘트 부탁드려요!
- 테스트를 하는데 있어 Mocking 을 너무 많이 하는 것은 설계가 잘못되었다는 것을 의미하기도 한다.
- Why?
- 해당 객체가 의존하고 있는 객체가 너무 많음을 의미하기도 하기 때문이다. 어쩔 수 없이 레거시 코드에 대한 테스트 코드를 작성하며 모킹을 많이 하게 될 수는 있지만.. 내가 짠 코드라면 컴포넌트의 분리가 필요할 것이다. 왜, 우리가 스프링에 생성자 주입 외에도 많은 의존성 주입 방법이 있음에도 생성자 주입을 선호하는 이유중 하나가 얼마나 많은 의존성을 갖고 있는지를 확인하여 냄새나는 코드를 줄일 수 있다는 이유도 있는 것과 비슷하지 않을까?
- 내 코드를 짤 때도 why? 를 생각 해 보자 . a 가 필요해서 b 를 작성했는데 어쩌다보니 c가 나왔네요 ... 이런 대답을 하게 되었다면 나의 해답을 찾아보도록 하자
- 테스트는 어디까지 작성해야할까요?
- 사용자는 사용자 마음대로 넣어보고 싶은 모든 입력은 다 넣어줄 수 가 있어요. 따라서 개발자는 최대한 다양한 경우에 대한 테스트 코드를 다 짜 보는게 중요하다고 생각해요.
- Spring Boot 1 주차의 Order 는 초기화 생성자가 두 개가 존재하는 상황이에요
- 이러한 상황에 대한 의견은 각 자 다를 수 있다고 생각해요
- 오브젝트 라는 책에서 제시한 방법을 적용해보자면, 초기화 생성자는 하나만 두고, 다른 생성자 시그니쳐를 둔다고 하더라도 내부적으로는 초기화 생성자를 호출하는 방식으로 가져갈 수 있어요.
- 이 때는, 옵셔널한 부분이 존재한다고 하더라도, 기초 생성자에서는 이런 옵셔널한 부분에 까지 기본적인 전략을 가진 전략 객체를 생성하는 방식으로 모든 필드를 초기화하는 정의를 해 둘 수 있을 것 같아요.
저는 아직 이 방식의 장점이 무엇인지 제대로 파악이 되지 않네요ㅠㅠ(연우)
- Optional 올바르게 사용하는 법