본문 바로가기

IT/책읽는 개발자

[레거시 코드 활용 전략] CH6 고칠 것은 많고 시간은 없고 - 2

반응형

2020/02/03 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH1 소프트웨어 변경

2020/02/04 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH2 피드백 활용

2020/02/06 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH3 감지와 분리

2020/02/07 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH4 봉합 모델

CH5 는 단순한 테스트 도구와 관련된 이야기라 다루지 않습니다~

2020/02/10 - [IT/책읽는 개발자] - [레거시 코드 활용 전략] CH6 고칠 것은 많고 시간은 없고 - 1

ch 6 - 고칠 것은 많고 시간은 없고 - 2

포장 메소드

기존 메소드에 동작을 추가하는 것은 아주 간단하다.

하지만 이것이 항상 옳은 일일까? 반대로 옳지 않은 접근법일 때가 많다.

왜냐하면 나중에 추가되는 코드들은 단순히 기존 코드 동시에 실행되기 때문에 추가한 것뿐이다.

이러한 코드들의 관계는 관심사 관점으로 봤을 때는 그다지 강한 관계가 아니다

.

결국 이러한 두 개의 코드를 하나의 코드들로 나누고 싶어도 비대해진 메소드와 복잡성 때문에 쉽지 않다

따라서 동작을 추가할 때 간단한 기법들이 필요하다.

이제부터 설명할 기법들을 포장 메소드라고 부른다.

 

아래 간단한 예제를 보자

class Mail {
    ...
    public void sendMail(List<MailParam> mailParams){
        for(MailParam mailParam : mailParams){
            mailParam.put("fromAddress", FROM_ADDRESS);
        }
        mailSender.send(mailParams);
    }
    ...
}

이 메소드는 단순히 메일 전송 리스트를 받아 해당 메일 데이터에 발신자 주소를 추가하는 메소드이다.

하지만 이 메소드에 메일을 보낼 때마다 발신자와 수신자의 주소 히스토리를 남기는 기능을 추가하려면 어떻게 해야 할까?

 

쉬운 방법으로는 send 메소드 안에 히스토리를 남기는 로직을 추가하는 거다.

왜냐하면 발신자 주소를 추가하는 순간, 히스토리를 남기는 행위도 동시에 일어나야 하기 때문이다.

이를 다음과 같은 코드로 구현하면 어떻게 될까?

private void dispatchMail(List<MailParam> mailParams){
    for(MailParam mailParam : mailParams){
        mailParam.put("fromAddress", FROM_ADDRESS);
    }
    mailSender.send(mailParams);
}

public void sendMail(List<MailParam> mailParams){
    addFromAddress(mailParams);
    logMailAddress(mailParams);
}

private void logMailAddress(List<MailParam> mailParams){
   ...
}

sendMail() 메소드의 이름을 dispatchMail()로 변경하고 private 로 변경했다.

그다음에 dispatchMail를 호출하는 새로운 sendMail() 메소드를 작성했다.

새로운 sendMail() 메소드는 메일을 전송하고 전송된 메일 정보를 로깅 하는 메소드로 전달한다.

새로운 sendMail() 메소드를 호출하는 곳에서는 기존 sendMail() 메소드와의 변경을 알아차리지 못한다.

 

아래는 또 다른 포장 메소드의 형태이다. 해당 형태는 새로운 메소드를 추가하고자 할 때 사용할 수 있다.

private void loggedSendMail(List<MailParam> mailParams){
    sendMail(mailParams);
    logMailAddress(mailParams);
}

public void sendMail(List<MailParam> mailParams){
    ...
}

private void logMailAddress(List<MailParam> mailParams){
   ...
}

이제 해당 클래스는 두 가지 방법으로 메일을 전송할 수 있다.

 

포장 메소드는 새로운 기능을 추가하면서 봉합부를 도입할 수 있는 좋은 방법이다.

그러나 이 기법은 네이밍 선택에 조금 고민이 필요한 부분이 있다.

 

포장 기법은 발아 메소드 / 클래스와는 다르게 기존 메소드의 코드가 추가되지 않는다.

또한 신규 기능이 기존 로직에 섞이지 않고 단독으로 만들어진다는 장점이 있다.

다만 앞서 말한 것과 같이 네이밍 문제를 일으키지 않기 위해 많은 고민이 필요하다.

포장 클래스

포장 메소드를 클래스 단위로 확장한 것이 포장 클래스이다.

새로운 기능을 기존 메소드의 추가할 수도 있지만, 그 새로운 기능을 다른 클래스에 추가할 수도 있다.

 

최초의 Mail Class이다.

class Mail {
    ...
    public void sendMail(List<MailParam> mailParams){
        for(MailParam mailParam : mailParams){
            mailParam.put("fromAddress", FROM_ADDRESS);
        }
        mailSender.send(mailParams);
    }
    ...
}

전송되는 메일에 대한 로깅을 하고 싶다고 가정하자.

이때 사용할 수 있는 방법 중 한 가지가 sendMail() 메소드를 가지는 별도의 클래스를 작성하는 것이다.

그 클래스는 Mail 객체를 갖고 sendMail() 메소드 내부에서 로깅을 진행하며, 기존 sendMail()을 수행하기 위해 Mail 객체에 처리를 위임한다.

 

다음 코드는 데코레이터 패턴을 활용한 포장 클래스이다.

class LoggingMail extends Mail {

    public LoggingMail (Mail mail){
        this.mail = mail;
    };
        ...
    public void sendMail(List<MailParam> mailParams){
        mail.sendMail(mailParams);
        logMailAddress(mailParams);
    }
    private void logMailAddress(List<MailParam> mailParams){
   ...
    }
}

만약 sendMail() 메소드가 단 한 곳에서만 호출이 된다면 데코레이더 패턴을 대신해서 별도의 클래스를 두고 여기에서 Mail 객체를 전달받아 메일을 전송한 후 로깅을 진행할 수도 있다.

class LoggingMailDispatcher {
    private Mail mail;

    public LoggingMailDispatcher (Mail mail){
        this.mail = mail;
    };
        ...
    public void sendMail(List<MailParam> mailParams){
        mail.sendMail(mailParams);
        logMailAddress(mailParams);
    }
    private void logMailAddress(List<MailParam> mailParams){
   ...
    }
}

포장 클래스의 핵심은 신규 동작을 기존 클래스에 추가하지 않으면서 시스템에 추가할 수 있다는 점이다.

기존 코드 호출이 많은 경우 데코레이터 패턴이 효과적이다.

반대로 기존 코드 호출이 없을 경우 다른 방법에 포장 클래스 호출도 효과적이다.

 

발아 메소드와 포장 메소드의 차이점은 기존 메소드로터 신규 메소드를 호출하고 싶을 때 발아 메소드를 사용한다.

반면에 기존 메소드의 호출과 새로운 메소드의 호출을 분리하고 싶을 경우 포장 메소드를 사용할 수 있다.

 

기존 메소드의 알고리즘이 분명히 이해가 될 때에는 발아 메소드, 추가될 신규 기능이 기존 기능과 동등한 수준일 경우 포장 메소드를 사용하면 좋을 것 같다.

반응형