개발놀이터

코딩 스탠다드 3 : StreamAPI로 처리할 수 있는 반복문은 StreamAPI로 처리한다. 본문

리팩토링/코딩 스탠다드

코딩 스탠다드 3 : StreamAPI로 처리할 수 있는 반복문은 StreamAPI로 처리한다.

마늘냄새폴폴 2024. 4. 29. 22:44

이번엔 for 문과 관련된 코딩 스탠다드입니다. 

 

 

StreamAPI에 대한 내용이기 때문에 관련된 개념을 숙지하고 계시면 좋습니다!

 

https://coding-review.tistory.com/490

 

Java8 StreamAPI

StreamAPI는 제가 자주 사용하는 문법 중 하나인데, 정작 뭐가 어떻게 동작하는지는 잘 모르고 썼던 것 같습니다. 그래서 정리하면서 사용법까지 훑어보도록 하겠습니다.  StreamAPI란?자바8부터 지

coding-review.tistory.com

 

왜 "StreamAPI로 처리할 수 있는 것만 StreamAPI로 처리해야" 하냐에 대해서 먼저 말씀드리겠습니다. 

 

StreamAPI를 쓰면 확실히 코드가 간드러지긴합니다. 들여쓰기도 없고 한줄에 집약해서 숏코딩할 수 있는게 정말 보기좋습니다. 

 

하!지!만!

StreamAPI를 도입할 것인지 두가지 측면에 대해 고민해야합니다. 

 

  1. 성능
  2. 재사용성

 

우선 StreamAPI를 사용하게 되면 하나의 연산당 O(n)이 추가되어 최종적으로 O(an) 이 됩니다. 원래였으면 O(n)으로 끝났을 연산이 O(an)이 되면 성능이 절실히 필요한 곳에선 사용하기 까다롭습니다. 

 

또한, StreamAPI를 사용하면 재사용이 불가능해집니다. StreamAPI를 한번 써보면 재사용은 개나줘버린 코드란걸 바로 알아차립니다. 

 

이런 두가지 측면을 모두 고려했음에도 도입을 해야된다면 아마 성능이 딱히 중요하지 않고, 재사용할 필요도 없는 경우겠죠? 혹은 들여쓰기가 너무 많아져서 성능을 조금 포기하더라도 가독성을 챙기는 경우겠지요. 

 

이제 본격적으로 시작해보겠습니다. 시작은 늘 그렇듯 상황을 가정합니다. 

 

 

상황) 장바구니에 있는 상품의 상세 정보를 확인하고 재고를 확인해 결제가 가능한지 여부를 확인

 

우리는 위의 상황을 흐름도로 정리해보고 코드로 들어가보겠습니다. 

 

  1. ItemDetail (상품 세부정보) 를 데이터베이스에서 확인합니다. 여기에 현재 남아있는 재고등이 들어있죠. 
  2. TemporaryOrder (장바구니 품목) 를 데이터베이스에서 확인합니다. 여기에 장바구니에 들어있는 정보가 있습니다.
  3. 이제 반복문을 돌면서 장바구니에 있는 사이즈와 세부정보에 있는 사이즈를 비교합니다. 

    이 과정을 하는 이유는 예를 들어 상품 세부 정보에 S사이즈 10개 M사이즈 10개 L사이즈 2개 이렇게 있는 경우가 있고 장바구니엔 L사이즈를 선택했다면 세부정보를 돌면서 장바구니에 고른 세부정보와 상품의 세부정보를 비교해야 하기 때문입니다. 
  4. 장바구니에 고른 상품의 재고와 현재 장바구니에 담은 수량을 비교해서 0보다 작으면 (2개 있는데 3개 주문하면) 재고가 남아있지 않다는 메시지를 출력해줄겁니다. 
  5. 만약 그렇지 않다면 정상적으로 로직이 흘러가는 것이죠. 

 

이를 코드로 구현하면 다음과 같습니다. 

 

        Map<String, Object> map = new HashMap<>();
        List<ItemDetail> findItemDetail = itemDetailRepository.findByItemId(1L);
        TemporaryOrder findTemporaryOrder = temporaryOrderRepository.findByBucketId(1L);	// 편의상 Optional을 사용하지 않았습니다.

        for (ItemDetail itemDetail : findItemDetail) {
            if (findTemporaryOrder.getSize().equals(itemDetail.getSize()) {
                if (itemDetail.getStock() - findTemporaryOrder.getCount() < 0) {
                    map.put("checkStock", false);
                    map.put("message", "재고가 남아있지 않습니다. 상품이름 : " + itemDetail.getItem().getViewName() + "/ 남은 재고 : " + itemDetail.getStock());
                }
            }
        }

        map.put("checkStock", true);
        
        return map;

 

코드가 좀 읽기 힘들죠..? 일부러 복잡하게 만들었습니다. 극적으로 줄어드는 것을 보여드리기 위함이죠. 

 

우선 들여쓰기가 세번이나 되어있는게 굉장히 보기 싫습니다. 만약 들여쓰기를 두번으로 줄이고싶다면 조건식 두개를 합치고 메서드로 빼는 방법이 있습니다. 하지만 우린 그정도를 원한게 아니죠. 

 

바로 StreamAPI로 리팩토링 해보겠습니다. 

 

        Map<String, Object> map = new HashMap<>();
        List<ItemDetail> findItemDetail = itemDetailRepository.findByItemId(1L);
        TemporaryOrder findTemporaryOrder = temporaryOrderRepository.findByBucketId(1L);	// 편의상 Optional을 사용하지 않았습니다.

        findItemDetail.stream()
                .filter((itemDetail) -> findTemporaryOrder.getSize().equals(itemDetail.getSize()))
                .filter((itemDetail) -> itemDetail.getStock() - findTemporaryOrder.getCount() < 0)
                .findAny().ifPresent((itemDetail) -> {
                    map.put("checkStock", false);
                    map.put("message", "재고가 남아있지 않습니다. 상품이름 : " + itemDetail.getItem().getViewName() + "/ 남은 재고 : " + itemDetail.getStock());
                });

        map.put("checkStock", true);

 

 

어떤가요? 굉장히 깔끔해졌습니다. 

 

마치며

서두에서도 언급했지만 StreamAPI는 코드가 가독성이 올라가는만큼 사이드이펙트도 있습니다. 성능과 재사용성, 이 두개를 잘 확인해보시고 도입을 고려해보면 좋을 것 같습니다.