ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ComposeMethod을 적용해 리팩터링 해보자
    Project Trouble Shooting/[축팅] 축제 소개팅 어플리케이션 - 카카오 테크 캠퍼스 1기 2023. 10. 13. 11:47
    728x90



    현재 카카오 테크 캠퍼스 3단계에서 축팅이라는 서비스의 인기 피드 부분를 담당해 개발하고 있는 중입니다.

    개발 하는 중 멘토님께서 피드백해주신 내용을 블로그로 포스팅해보려고 합니다~

     

     

    현재 코드

     

      @Transactional
      public void execute(){
        List<GetIncompletePopularPostDTO> top300Posts = postRepository.findTop300ByOrderByPopularityDesc(PageRequest.of(0, POPULARITY_SIZE));
        redisTemplate.delete(RedisKey.POPULAR_POST_KEY.getKey());
        top300Posts.forEach(getIncompletePopularPostDTO -> {
          redisTemplate.opsForZSet().add(RedisKey.POPULAR_POST_KEY.getKey(), getIncompletePopularPostDTO, getIncompletePopularPostDTO.getPopularity().doubleValue());
        });
      }



    위는 MySQL에서 인기도를 기반으로 상위 300개의 게시물을 가져와 이를 Redis의 SortedSet 자료구조에 저장하고 있는 코드이다.

    위 코드에서 아래와 같은 피드백을 받았다.


     

    이 메서드는 좀 장황해서 읽기 어렵습니다. 무슨 일을 하는지 다 읽어야 알 수 있습니다.

    코드는 신문이나 책처럼 읽혀야 좋습니다.

    비유하면 퍼블릭 메서드의 이름은 신문 기사의 제목입니다.
    퍼블릭 메서드 안에서 호출하는 메서드들은 부제목이고요.
    나머지 메서드들은 더 디테일하고 추상화 수준이 낮습니다.

    추상화 레벨이 고수준 -> 중간수준 -> 저수준으로 이어져야 코드를 읽기 쉽습니다.

    어려운 말로하면 ComposeMethod을 도입해서 SingleLayerOfAbstraction 을 이룰 수 있습니다. :)




    멘토님께서 리팩터링 해주신 코드는 아래와 같다.

     

      @Transactional
      public void execute(){
        List<GetIncompletePopularPostDTO> top300Posts = getTop300Posts();
        deletePopularPostsCache();
        setPopularPostsCache(top300Posts);
      }
    
      private List<GetIncompletePopularPostDTO> getTop300Posts(){
        return postRepository.findTop300ByOrderByPopularityDesc(PageRequest.of(0, POPULARITY_SIZE));
      }
    
      private void deletePopularPostsCache(){
        redisTemplate.delete(RedisKey.POPULAR_POST_KEY.getKey());
      }
    
      private void setPopularPostsCache(List<GetIncompletePopularPostDTO> top300Posts){
        top300Posts.forEach(this::setPopularPostsCache);
      }
    
      private void setPopularPostsCache(GetIncompletePopularPostDTO dto) {
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        double score = dto.getPopularity().doubleValue();
        zSetOperations.add(RedisKey.POPULAR_POST_KEY.getKey(), dto, score);
      }

     


    왜 이렇게 리팩터링을 했는 지에대해서 이해하려면, Composed Method를 이해해야한다.



    Composed Method



    Composed Method는 Ket Bech의 "Smalltalk Best Practice Patterns"에 나오는 기법이다.

     

    이는 메서드를 단순하게 유지하는 것이며, 각 메서드를 가능한 작고 하나의 작업만 수행하도록 만드는 것이라고 한다.



    규칙은 아래와 같습니다.



    1. 메서드가 수행하는 모든 작업이 같은 추상화 수준에 있어야 한다.(Keep all of the operations in a method at the same level of abstraction)
    2. 메서드가 한 번에 하나의 작업만 수행해야 한다.(Divide your program into methods that perform one identifiable task)
    3. 한 메서드에서 다른 메서드를 호출하면, 그 메서드는 완전한 작업을 수행해야 한다.(This will naturally result in programs with many small methods, each a few lines long)



    1번의 규칙을 바탕으로 리팩터링한 코드를 이해해보면, execute() 메서드를 추상화 레벨이 고수준이라 한다면, execute() 메서드에서 호출하는 메서드들

    즉, getTop300Posts(), deletePopularPostsCache(), setPopularPostsCache(List<GetIncompletePopularPostDTO> top300Posts) 들을 추상화 레벨은 중수준이라고 할 수 있겠다.

    그리고, setPopularPostsCacheI(GetIncompletePopularPostDTO dto) 메서드는 setPopularPostsCache(List top300Posts)에서 호출하므로 이 메서드의 추상화 레벨은 저수준이다.



     private void setPopularPostsCache(List<GetIncompletePopularPostDTO> top300Posts){
        top300Posts.forEach(this::setPopularPostsCache);
      }
    
     private void setPopularPostsCache(GetIncompletePopularPostDTO dto) {
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        double score = dto.getPopularity().doubleValue();
        zSetOperations.add(RedisKey.POPULAR_POST_KEY.getKey(), dto, score);
      }

     

    멘토님께서 리팩터링 해주신 코드에서 Composed Method에 대해 이해하기 전까지 이 부분이 완벽히 이해가 되지 않았다.

     

    이해가 잘 되지 않는 부분은 아래와 같다.

     



    왜 아래와 같이 하지 않고 2개의 메서드로 나눴을까?

     

     private void setPopularPostsCache(List<GetIncompletePopularPostDTO> top300Posts){
        top300Posts.forEach(getIncompletePopularPostDTO -> {
          redisTemplate.opsForZSet().add(RedisKey.POPULAR_POST_KEY.getKey(), getIncompletePopularPostDTO, getIncompletePopularPostDTO.getPopularity().doubleValue());
        });
     }

     

    이는 Composed Method 규칙 2와 3과 관련이 있는 데, forEach 즉 top300Posts들을 하나하나 나누는 작업과 Redis에 저장하는 작업을 나눈 것이다. 

     

     

    멘토님의 피드백을 통해, 코드의 모듈화와 메서드 분리를 통해  코드의 가독성을 높이고 유지보수를 용이하게 해주는 ComposeMethod에 대해 알고 이를 고민해보는 시간을 가질 수 있었다.

     

    멘토님 감사합니다.

     

     



    아래는 해당 PR 링크 입니다.

    https://github.com/Step3-kakao-tech-campus/Team14_BE/pull/86

     

    인기 피드 리팩토링 및 테스트 보충 by hwangdaesun · Pull Request #86 · Step3-kakao-tech-campus/Team14_BE

    인기 피드 상세 조회 및 전체조회 관련 코드들을 리팩토링하였습니다. Commit 요약 refactor : RedisKey 관련 명명 수정 refactor : 주석 추가 test : 인기있는 상위 300개 게시물 조회하는 SQL 테스트 및 테스

    github.com

     

     

     

    728x90
Designed by Tistory.