현재 카카오 테크 캠퍼스 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"에 나오는 기법이다.
이는 메서드를 단순하게 유지하는 것이며, 각 메서드를 가능한 작고 하나의 작업만 수행하도록 만드는 것이라고 한다.
규칙은 아래와 같습니다.
- 메서드가 수행하는 모든 작업이 같은 추상화 수준에 있어야 한다.(Keep all of the operations in a method at the same level of abstraction)
- 메서드가 한 번에 하나의 작업만 수행해야 한다.(Divide your program into methods that perform one identifiable task)
- 한 메서드에서 다른 메서드를 호출하면, 그 메서드는 완전한 작업을 수행해야 한다.(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
'Project Trouble Shooting > [축팅] 축제 소개팅 어플리케이션 - 카카오 테크 캠퍼스 1기' 카테고리의 다른 글
Transaction을 고려한 CheckedException 예외 처리 (3) | 2023.11.12 |
---|---|
ThreadLocalRandom의 설계의도와 스레드의 관계 (1) | 2023.11.01 |
"Run all Tests"로 모든 단위테스트를 한번에 돌릴 때 실패하는 이슈 (0) | 2023.10.14 |
Instant 클래스 도입에 관한 고찰 (0) | 2023.10.02 |
이력 유형 데이터 모델링을 어떻게 해야할까?? (0) | 2023.09.14 |