Enum과 함수형인터페이스(Predicate, Function)를 활용한 리팩토링 + 다국어 관리 - 1

Y_Sevin·2023년 10월 23일
0
post-thumbnail
post-custom-banner

😅 필자가 개인적으로 공부하고 남기는 것이기 때문에 정보가 미흡할 수 있습니다. 잘못된 내용이나 추천해주실 방법이 있을경우 댓글 남겨주신다면 감사히 받겠습니다.

왜 굳이 리팩토링을 하는가

헤어비 프로젝트를 진행하며 빠르게 서비스를 제공하는 것을 목표로 하다보니 설계보다는 일단 비즈니스로직을 작성해야했었다.
특히 얼굴형 보고서를 작성하는 로직이 매우 복잡했는데 설계없이 코드를 작성하다보니 굉장히 코드가 길어지는 문제가 있었다.
특히 다국어 기능을 추가하던 중 하드코딩된 부분으로 인해 코드를 분리하는 것에 어려움이 발생했고 해당 메서드를 리팩토링하는 것이 좋겠다는 판단했다.

리팩토링하는 메서드

아래는 우리 서비스의 얼굴형 보고서 중 어떤 이유로 해당 스타일을 추천하는지에 대한 내용을 작성하기위해 나에게 영향을 주었던 요소를 찾는 로직이다.

해당 메서드는 if문으로만 약 100줄가량 차지했고 비슷한 이유로 같은 형태의 if문이 존재하는 메서드가 더 존재했기에 이 코드만 잘 정리한다면 200줄을 한번에 정리할 수 있었다.


리펙토링 시작하기

위의 코드는 리펙토링하는 메서드의 if문 중 하나이다.
코드를 해석하자면 해당 헤어를 추천하는 이유에 해당하는 특징에 둥근형 얼굴이 포함되어야하는가? 추천하는 이유에 둥근형 얼굴이 존재하는가? 를 판단하는 if문이라고 생각하면 될 것 같다.
우리 서비스는 사용자에게 알맞는 헤어를 추천하기 위해 정말 많은 특징들을 분석한다.
때문에 특징의 개수만큼 위와 같은 if문 코드가 작성된다는 말이다..😭

초기에 이런 if문을 사용했던 이유는 단순하다...
FaceShapeType.ROUND 값과 effectValues의 roundFaceShape라는 필드를 매핑해야하는데 빠르게 서비스를 제공하는 것이 최우선 목표이다보니 필드명을 설계한다는 생각조차 못했기때문이다.

또한 서비스 출시이후 코드를 확장시키며 이리 꼬이고 저리 꼬이며 코드를 변경시키기 어려워졌다는 문제로 인해 계속 외면해왔지만 다국어를 제공하며 더 이상 외면할 수 없었다.

해외에도 서비스를 제공하기 위해 다국어를 제공해야했는데 둥근형 얼굴 과 같이 하드코딩 되어있는 부분은 서비스를 확장하는데에 있어 걸림돌이 됐다.😭


✨리팩토링 목표✨

  1. 향후 얼굴 분석의 요소가 늘어나더라도 비즈니스 로직을 건들이고 싶지 않다.
  2. 비즈니스 로직의 역할과 과정이 무엇인지 명확하게 보이는 코드가 필요하다.
  3. 다국어를 추가하기 용이한 구조로 변경하고 싶다.
  4. 나머지 서비스에 영향을 주지 않도록 최대한 현재 로직의 구조만 변경할 것

리팩토링을 어떻게 할 것인가..?

필자가 리팩토링을 하기위해 이용한 것은 아래와 같다.

  • Enum
  • 함수형인터페이스(Predicate, Function)

일단 FaceShapeType.ROUND 값과 effectValues.getRoundFaceShape 를 하나의 묶음으로 관리하기 위해 Enum을 사용했다.
그리고 Enum 내부에 함수형 인터페이스를 사용해 관련 로직을 처리할 수 있도록 구성했다.

1차 리팩토링 ✨

우선 매개변수를 전달받아 조건 수행 후 Boolean 타입의 값을 반환하는 Predicate 인터페이스를 활용해 FaceShapeType의 값이 현재 Round인지 비교하는 연산을 처리했다.

// FaceShapeType fst = processedFaceValue.getFaceShapeType();
// if fst.equals(FaceShapeType.ROUND)
public enum ValidFaceFeature {
    ROUND_FACE(value -> value.getFaceShapeType() == FaceShapeType.ROUND)
    .
    .
    private final Predicate<ProcessedFaceValue> type;
}

2차 리팩토링 ✨
추천하는 요소에 둥근형 얼굴이 존재하는가?

Function을 활용해 외부에서 EffectOfStyle를 주입받은 후 해당하는 요소를 반환한다.
그리고 isValidFeature 메서드를 통해 최종적으로 해당 요소가 유효한지 판단한다.

// effectValues.getRoundFaceShape != 1.0
public enum ValidFaceFeature {
    ROUND_FACE(value -> value.getFaceShapeType() == FaceShapeType.ROUND, EffectOfStyle::getRoundFaceShape)
    
    private final Predicate<ProcessedFaceValue> type;
    private final Function<EffectOfStyle, Float> effectValueGetter;
    
    // 최종적으로 유효한 값인가?
    public boolean isValidFeature(ProcessedFaceValue processedFaceValue, EffectOfStyle effectValues) {
        return type.test(processedFaceValue) && effectValueGetter.apply(effectValues) != 1.0;
    }
    
}

사실 Predicate와 Function을 분리할 필요없이 모두 Predicate로 코드를 구성할 수 있다.
하지만 Function을 사용한 이유는 모두 같은 형태의 != 1.0 조건을 가지고 있었기 때문에 이를 명시해주고 싶었다.
Predicate로 코드를 구성했다면 설마 요소마다 다른 특징을 가졌나..??하는 오해를 부를 수 있기 때문에 Function로 구성했다.
(물론.. 이게 옳은 방법인지는 모르겠다... 😥)

3차 리팩토링 과 최종코드 ✨

Enum과 함수형인터페이스(Predicate, Function)를 활용한 리팩토링 + 다국어 관리 - 2

profile
매일은 아니더라도 꾸준히 올리자는 마음으로 시작하는 개발블로그😎
post-custom-banner

0개의 댓글