[모델링]코드리뷰짝 매칭결과 알림

들어가기

팀원 6명에 대해 코드리뷰 짝을 지어주면 재밌을 것 같아 작년말에 Node.js를 이용해서

간단하게 프로그램을 만들었다.프로그램에 대한 자세한 소개는 여기를 참고하면 된다.

이번에 얘기할 주제는 프로그램 구현완료 후에 모델링을 다시 해본 내용이다. =============================================================================

1. 최초버전

나름, 알림을 보내는 부분과 결과를 파싱하는 부분이 변화되는 부분이라 생각되어

AlarmSender와 Parser인터페이스를 정의했다.

알림내용에 대한 부분도 공통적인 부분은 Alarm(SmsAlarm도 생각해서)으로 정의하고

그것을 상속받아 EmailAlarm을 만들어 모델링했다.

하지만, 모델링과정에서 “역할“에 대한 정의가 잘못되어 Main 클래스에 해당하는

CodeReviewMathcingPairResultBatch클래스에서 알림내용을 만들고 있고,

통지내용을 모델링하기 위해 Alarm클래스를 만들었지만, 알람은 그냥 알람일 뿐인데

코드리뷰매칭결과“를 Alarm이란 이름으로 정의한 것도 결국 “역할“에 대한 잘못된

정의에서 나온 결과물이다.

class_diagram_mod   2. 중간버전

기존의 Alarm이 담고 있던 “코드리뷰매칭결과“를 CodeReviewPairResult클래스로

모델링했지만, 클래스다이어그램만 봤을때는 이것이 무엇을 담고 있는지 명확하지

않은 문제가 있다.

그리고 “코드리뷰매칭결과“를 가져오기 위한 부분도 저수준모듈인 HtmlParser클래스에

의존하고 있다. 저렇게 되면 “코드리뷰매칭결과“를 웹페이지가 아닌 DB 혹은 파일에서

가져왔을 때 CodeReviewService클래스를 수정해야된다.

결국, 수정에는 열려있고 확장에 닫혀있는 구조가 된 것이다.

그래도, 통지기(Notifier)인터페이스의 notify메소드 파라미터를 “코드리뷰매칭결과”와

“팀원”으로 나누어 정의한 것은 통지기의 “역할“에 대한 고민의 흔적이다 class_diagram_v5_mod 3. 최종버전

총 8번에 걸쳐 최범균님과 모델링결과물에 대해 논의하고 리팩토링한 결과 아래와 같은

클래스다이어그램이 나오게 되었다.

제일 큰 변화는 “코드리뷰매칭결과“를 가져오는 부분이다.

이전에는 Parser로 추상화해서 정의했다면, 최종버전을 만들면서 가장 고민했던 부분이

 “코드리뷰매칭결과를 가져오다“라는 부분에 집중했다는 것이다.

이 말은 즉, Parser로 추상화해서 정의한 것이 웹페이지에서 결과를 가져오는

구체적인 “구현“에 의존했다면,  MatchingResultSource인터페이스를 정의함으로서,

 “코드리뷰매칭결과를 가져오다“라는 부분을 올바르게 “추상화“할 수 있었다.

MatchingResultSource인터페이스를 정의함에 따라,  “코드리뷰매칭결과” 모델링도 각각

MatchingResult와 Group으로 정의했다.

CodeReviewPairResult클래스에 제목과 내용만 있던 전과 비교해 “코드리뷰매칭결과“에

어떤 내용이 담겨있는지가 보다 더 명확해졌다.

그리고, 전체적으로 인터페이스와 클래스, 메서드의 “이름“에 신경썼는데

결국, 겉으로 드러나는 “이름“을 통해 말하고자 하는 의도가 더욱 분명히 드러나기 때문에

더욱 고민을 많이 했던 부분이다.

class_diagram_v8_mod=============================================================================

마치며..

이번 모델링과 리팩토링을 통해  “코드리뷰짝 매칭결과를 가져와 팀원에게 알려준다

라는 고수준 로직을 적절하게 “추상화“하고 그에 따른 “다형성” 구현을 통해 각각의

역할“과 “책임“을 정의함으로서, 향후 유지보수에 유연한 구조를 만들 수 있게 되었다.

이것이 가능한 이유는 객체지향 설계 중, 추상화/다형성을 통한 “의존역전원칙(DIP)“과

리스코프치환원칙(LSP)“을 지킴으로서, 결국엔 수정에는 닫혀있고 확장에는 열려있는

개방폐쇄원칙(OCP)“을 준수했기 때문이다.

그리고, 추가적으로 OCP를 준수함으로써 CodeReviewService의 고수준모듈 로직,

코드리뷰짝 매칭결과를 가져와 팀원에게 알려준다“를 재사용할 수 있게 되었다.

8번에 걸친 수정을 하면서 익숙하지 않은 “모델링“에 대한 “역할“과 “책임“정의가

쉽지 않았다.

책으로만 봤던, 수정이 용이한 유연한 설계에 대한 이론에 고개를 끄덕이면서 머리로만

이해하려고 했던 내용을 실전에서 부딪치니 생각처럼 “모델“이란 녀석이 쉽게 도출되는

게 아니구나라는 걸 깨닫게 되었다.

적절하게 “모델”을 추출하기 위해서는 많은 지식과 경험이 필요하고, 또한 깊이있는

고민과 사색이 수반되어야 함을 느꼈다.

마지막으로 그간의 과정이 결코 쉽지 않았지만, 힘들었던 것 만큼 재미있는 시간이었다

Advertisements

[리팩토링]빌더패턴을 활용해 객체 생성하기

스프링MVC로 만들어진 기존 서비스단 코드에 아래와 같이 매개변수 별로

동일객체를 만드는 코드가 있었다.

아래는 알림객체인 Notification을 만들어 전송하는 코드이다.

public void createNotificationByCase1(int a, int b, int c, String d, String f) {
    Notiifcaiton notification = new Notificaiton(a, b, c, d, f);
    sendAlarm(notification);
}
public Notification createNotificationByCase2(int a, int b, String d, String e) {
    Notiifcaiton notification = new Notificaiton(a, b, d, e);
    sendAlarm(notification);
}
public Notification createNotificationByCase3(int a, int b, int c, String d) {
    Notiifcaiton notification = new Notificaiton(a, b, c, d);
    sendAlarm(notification);
}

메서드Case1 ~ Case3별로 각기 다른 매개변수를 가지고 Notification 객체를 만든다.

그리고 Notification 클래스 내부는 아래와 같다.

public class Notification {
    private int a;
    private int b;
    private int c;
    private String d;
    private String e;
    private String f;

    Notification(int a, int b, int c, String d, String e, String f) {
       this.a = a;
       this.b = b;
       this.c = c;
       this.d = d;
       this.e = e;
       this.f = f;
     }
     Notification(int a, int b, int c, String d, String f) {
         this(a, b, c, d, null, f);
     }
     Notification(int a, int b, String d, String e) {
         this(a, b, 0, d, e, null);
     }
     Notification(int a, int b, int c, String d) {
         this(a, b, c, d, null, null);
     }
}

Notification 클래스 필드 6개 중, Notification 객체를 만드는 데 필요한 필수 멤버변수는

a, b, d고, 나머지 c, e, f는 선택 멤버변수이다.

위 Notification 클래스의 생성자를 보면 값이 없는 건 기본값(0 혹은 null)을 넣어서

다른 생성자를 호출하는 것을 알 수 있다.

위의 예제는 샘플코드를 만드느라 멤버변수를 6개만 넣었지만, 실제 리팩토링 한 코드의

클래스 멤버변수는 총 12개였고, 필수와 선택 멤버변수가 각각 6개씩이었다.

이런 상황하에 각 서비스에서 알림객체를 만들어 전송하려고 notificationService에 각기

다른 매개변수를 가진 메서드(Case1 ~ Case3 메서드)를  호출한 것이다.

notificationService를 이용하는 서비스단의 코드는 아래와 같다.

notificationService.createNotificationByCase1(a, b, c, d, f);

위와 같이 알림객체를 생성할 경우, 새로운 서비스에서 알림서비스를 사용할  때

또 다른 매개변수를 가진 메서드를 알림서비스에 만들어야 할 것이고, 그것을 이용하는

각 서비스들의 코드는 이해할 수 없는 매개변수의 나열로 가독성이 매우 나빠질 것이다.

타인은 물론 본인도 추후에 해당코드를 유지보수 할 경우, 어떤 데이터가 어디에 들어가

있는지 정확히 기억하기도 힘들고, 생성자 매개변수가 많으면 많을수록 그 복잡도는

매개변수 갯수에 비례해 증가하게 된다.

결국, 유지보수 시간이 기하급수적으로 늘게돼 나중에는 쳐다보기도 싫은

코드로 전락해버릴지도 모른다.

그래서, 이 복잡한 객체생성코드를 빌더패턴을 사용해서 리팩토링 하기로 했다.

빌더패턴을 사용해 객체생성을 하는 코드는 아래와 같다.

public class Notification {
    private int a;
    private int b;
    private int c;
    private String d;
    private String e;
    private String f;

    private Notification(Builder builder) {
        this.a = builder.a;
        this.b = builder.b;
        this.c = builder.c;
        this.d = builder.d;
        this.e = builder.e;
        this.f = builder.f;
     }

     public static class Builder {
        // Require Fields
        private int a;
        private int b;
        private String d;

        // Option Fields
        private int c;
        private String e;
        private String f;

        public Builder(int a, int b, String d) {
            this.a = a;
            this.b = b;
            this.d = d;
        }

        public Builder c(int c) {
            this.c = c;
            return this;
        }

        public Builder e(int e) {
            this.e = e;
            return this;
        }

        public Builder c(int f) {
            this.f = f;
            return this;
        }

        public Notification build() {
            return new Notification(this);
        }
    }
}

빌더패턴을 사용하고 난 이후에는 Notification클래스에 각 매개변수별로 만들어져 있던

생성자가 사라지고, Builder클래스의 build메서드를 통해 단일창구로 Notification객체를

만들게 되었다.

그리고 각 서비스에서 notificationService에 전달할 Notification 객체를 빌더패턴을

사용해 생성하고 아래와 같이 전달한다.

Notification notification = new Notification.Builder(a, b, d).c(c).f(f).build();
Notification notification = new Notification.Builder(a, b, d).e(e).build();
Notification notification = new Notification.Builder(a, b, d).d(d).build();
notificationService.sendNotification(notification);

Notification객체를 생성하는 필요한 필수 멤버변수인 a, b, d는 Builder클래스의 생성자를

통해 전달하고, 나머지 선택 멤버변수인 c, e, f는 set메서드 역할을 하는 메서드를 사용해

필요할 때 호출해서 값을 세팅하고 마지막에 build메서드를 호출해 Notification 객체를

생성하게 된다.

이로 인해, Notification 객체를 생성해 전송하던 notificationService의 각기 다른

매개변수를 가지던 메서드는 사리지고, Notificaiton객체 하나만을 받는

메서드 하나로 리팩토링 되었다.

긴 매개변수를 받던 메서드들이 사라지고, Notification 객체 하나만을 받도록 리팩토링 한

아래 코드를 보면 전과 비교해 코드 가독성이 월등히 좋아졌음을 알 수 있다.

public void sendNotification(Notification notification) {
    //기존 Notification 객체생성부분 삭제
    sendAlarm(notification);
}

빌더패턴을 사용해 객체 생성부분을 리팩토링해서 얻은 결과는 아래와 같다.

  • 각기 다른 매개변수별로 중구난방으로 만들어져있던 notificationService의 메서드를 Notification객체를 받는 단 하나의 메서드로 통일시킴
  • Notification클래스에 각기 다른 매개변수별로 만들어져있던 생성자를 Builder객체를 이용해 객체를 만드는 방법으로 통일시킴
  • 각 서비스별로 notificationService의 각기 다른 매개변수들을 가진 메서드를 호출하고 있던 것을 빌더패턴을 이용해 Notification객체를 생성하고 Notification객체를 받는 하나의 메서드를 호출하는 것으로 통일시킴

무엇보다 제일 좋은 것은 리팩토링으로 얻은 코드 가독성이다.

전에는 쳐다보기조차 싫었던 코드가 빌더패턴을 사용해 객체생성부분을 수정함으로써,

다음에 또 수정사항이 생겨도 빠른 시간안에 대처할 수 있는 여력이 생겼다는 것이다.

역시나 일상 속에 지속적인 리팩토링과 이를 뒷받침하는 테스트코드의 중요함을 다시

한번 느끼는 하루였다.

혹시나, 빌더패턴에 대해 궁금하신 분은 Effective java 항목2를 읽어보면 많은

도움이 될 것이다.

[리팩토링]리팩토링 사례 공유 모임 후기

저번달에 범균님과 함께 리팩토링했던 스타크래프트 프로리그 앱에  대한 내용을

6월 27일 저녁에 오프라인에서 공유하는 자리를 가졌다.

해당 내용은 범균님 블로그에서 확인할 수 있다.

20130627_200654

리팩토링 내용은 크게 아래와 같다.

  • 클래스/인터페이스/메서드/필드등을 의미있는 이름으로 변경
  • 객체생성을 빌더패턴으로 전환함으로서 중복코드 제거
  • 역할과 책임을 명확히 하기 위해 HTML파싱과 관련된 일부기능 분리
  • 각 액티비티에서 직접적으로 의존하고 있던 데이터공급객체를 Factory클래스를  사용해서 의존성 제거함
  • 여기저기 흩어져있던 파일관련기능을 한 곳으로 모아 응집도 높임

모든 내용을 발표하는데 1시간 반정도 걸릴 것으로 예상했지만, 의외로 1시간도 되지 않아

발표가 끝났다. 그리고, 짧게나마 발표내용에 대한 토론이 이어졌다.

  • 왜 set메서드를 쓰면 좋지 않은가? 꼭 써야될 경우도 있지 않을까?
  • Lombok을 쓴 도메인 클래스는 왜 Entity클래스가 아니고 DTO에 가까울까?
  • Entity라고 부를만한 도메인클래스의 조건은 무엇인가?

개인적으로, 토론이 너무 짧은시간 동안 진행되서 아쉬웠다.

뭔가 서로간의 피드백을 주고 받으면서 깊은 대화들이 오갔으면 하는 바램이었지만,

아쉬움을 뒤로 하고 차근차근 한걸음씩 내딛는 것도 의미있다고 생각했다.

범균님 블로그 자료 맨 마지막 장에 보면 리팩토링을 하고 싶어도 같이 할 선후배 동료가

없을 때, “흔쾌히 같이 해보자“라고 하는 반가운 광고가 실려있다.

생각있으신 분은 연락해서 한번 직접 리팩토링을 같이 해보는 것도 좋은 경험이 되리라

생각한다. 내가 이번에 리팩토링을 하면서 많이 느끼고 배운것처럼 말이다.

그렇게 해서 조직에 있는 한사람 한사람이 조금씩 변화하고 성장해서, 그 조직에

긍정적인 영향을 주고 함께 발전해 나가는 범균님의 바램이 꼭 현실로 이뤄졌으면

하는 바램이다! ^^