저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링4 입문

스프링 4

DDD Start

객체 지향과
디자인 패턴

JSP 2.3

JPA 입문

기존 코드의 중복 코드: 흐름 제어 중복


최근에 정리한 코드는 다음과 같은 형태를 가졌다.


private ErrorLogger errorLogger;


public Response chooseMeeting(Request req) {

    String apiId = "E0004";

    String code = null;

    String jupno = null;

    String transt = null;

    try {

        jupno = request.getJupno();

        changeTranst(jupno, transt = "0", req.getId(), req.getIp()); // 전송전 상태

        Result result = tranService.chooseMeeting(req); // 전송 처리

        code = eocsResult.getCode();

        changeTranst(jupno, transt = "1", req.getId(), req.getIp()); // 전송완료 상태

    }catch (EocsException ex) {

        transt(jupno, transt = "2", req.getId(), request.getIp()); // 전송오류 상태

        code = ex.getEocsErrorCode();

        errorLogger.append(apiId, code, jupno, transt, req, req.getId(), req.getIp(),....);

    }catch (Exception e) {

        code = "C00003";

        errorLogger.append(apiId, code, jupno, transt, req, req.getId(), req.getIp()....);

    }

    return new Response(apiId , code);

}


private changeTranst(String jupno, String transt, String id, String ip) {

    ...

}


chooseMeeting() 메서드는 다음 흐름을 갖는다.

  1. 전송전 상태 변경: changeTranst()
  2. 실제 전송 업무 처리: tranService.chooseMeeting()
  3. 전송완료 상태 변경: changeTranst()
    1. 전송오류 발생시 전송오류 상태 변경 후 로그 기록
    2. 이 외 익셉션 로그 기록
  4. 결과 리턴

chooseMeeting() 메서드만 이런 코드를 가진 게 아니다. 이와 동일한 흐름을 갖는 메서드가 3개가 더 있었다. 차이점은 apiId와 전송 처리 코드뿐이었다. 예를 들어, 다른 코드는 아래와 같다.


public Response choosePip(PipRequest req) {

    String apiId = "E0003";

    String code = null;

    String jupno = null;

    String transt = null;

    try {

        jupno = request.getJupno();

        changeTranst(jupno, transt = "0", req.getId(), req.getIp()); // 전송전 상태

        Result result = tranService.choosePip(req); // 전송 처리

        code = eocsResult.getCode();

        changeTranst(jupno, transt = "1", req.getId(), req.getIp()); // 전송완료 상태

    }catch (EocsException ex) {

        transt(jupno, transt = "2", req.getId(), request.getIp()); // 전송오류 상태

        code = ex.getEocsErrorCode();

        errorLogger.append(apiId, code, jupno, transt, req, req.getId(), req.getIp(),....);

    }catch (Exception e) {

        code = "C00003";

        errorLogger.append(apiId, code, jupno, transt, req, req.getId(), req.getIp()....);

    }

    return new Response(apiId , code);

}



메서드 추출로 하려다가...


처음엔 대충 다음과 같은 모양을 상상하면서 메서도 추출로 가려했다.


public Response chooseMeeting(Request req) {

    String apiId = "E0004";

    return runTrans(apiId, req.getJupno(), req.getId(), req.getIp(), 

                         () -> tranService.chooseMeeting(req));

}


public Response choosePip(PipRequest req) {

    String apiId = "E0003";

    return runTrans(apiId, req.getJupno(), req.getId(), req.getIp(), 

                         () -> tranService.choosePip(req));

}


private Response runTrans(String apiId, 

                          String jupno, String id, String ip, // 상태처리나 로그 기록에 필요

                          Supplier<Result> transition) {

    String code = null;

    String transt = null;

    try {

        jupno = request.getJupno();

        changeTranst(jupno, transt = "0", id, ip); // 전송전 상태

        Result result = transition.get(); // 전송 처리

        code = eocsResult.getCode();

        changeTranst(jupno, transt = "1", id, ip); // 전송완료 상태

    }catch (EocsException ex) {

        transt(jupno, transt = "2", id, ip); // 전송오류 상태

        code = ex.getEocsErrorCode();

        errorLogger.append(apiId, code, jupno, transt, id, ip,....);

    }catch (Exception e) {

        code = "C00003";

        errorLogger.append(apiId, code, jupno, transt, id, ip,....);

    }

    return new Response(apiId , code);

}


private changeTranst(String jupno, String transt, String id, String ip) {

    ...

}


runTrans() 메서드는 로그를 남기거나 상태 변경을 처리하는 코드에서 필요한 값을 파라미터로 받는데, 파라미터가 다소 많다. 로그를 남길 때 사용할 jupno, id, ip를 구하는 코드가 달라서, 이 세 개 값을 파라미터로 전달해야 했다.


이렇게 되면 코드를 추가로 정리할 때 번잡함이 발생한다. 예를 들어, runTrans()에서 changeTranst()에 의미를 더 부여하기 위해 다음과 같이 변경한다고 하자.


private Response runTrans(String apiId, String jupno, 

                                   String id, String ip, Object req, Supplier<Result> transition) {

    String code = null;

    String transt = null;

    try {

        jupno = request.getJupno();

        transt = beforeTranst(jupno, id, ip); // 전송전 상태

        Result result = transition.get(); // 전송 처리

        code = eocsResult.getCode();

        transt = afterTranst(jupno, id, ip); // 전송완료 상태

    }catch (EocsException ex) {

        errorTranst(jupno, id, ip); // 전송오류 상태

        code = ex.getEocsErrorCode();

        errorLogger.append(apiId, code, jupno, transt, req, id, ip,....);

    }catch (Exception e) {

        code = "C00003";

        errorLogger.append(apiId, code, jupno, transt, req, id, ip,....);

    }

    return new Response(apiId , code);

}


private String beforeTranst(String jupno, String id, String ip) {

    changeTranst(jupno, "1", id, ip);

    return "1";

}

...afterTranst()와 errorTranst()도 비슷하게 구현


private changeTranst(String jupno, String transt, String id, String ip) {

    ...

}


runTrans() 메서드는 에러 로그를 기록하는데 transt 로컬 변수를 사용한다. 그래서 beforeTranst() 메서드는 상태 변경 실행후 transt 로컬 변수에 할당할 값을 리턴해야 한다. 게다가 beforeTranst() 메서드는 changeTranst() 메서드를 호출하기 위해 필요한 값을 파라미터 3개로 받고 있다.


흐름 처리를 수행하는 클래스 작성


메서드 추출로 중복을 없앨 수는 있지만, 그 결과로 만들어진 코드가 이쁘지 않다. 로컬 변수나 파라미터 개수가 많기에 이를 별도 객체로 뽑아서 중복을 없애는 것으로 방법을 바꿨다. 먼저 다음과 같이 프로세스를 처리하는 클래스를 만들었다.


public class TransitionProcess {

    private ErrorLogger errorLogger;


    private String apiId;

    private String jupno;

    private String id;

    private String ip;


    private String transt;

    private String code;


    @Builder

    public TransitionProcess(

            ErrorLogger errorLogger, String apiId,

            String jupno, String id, String ip) {

        this.errorLogger = errorLogger;

        this.apiId = apiId;

        this.jupno = jupno;

        this.id = id;

        this.ip = ip;

    }

    

    public Response runWith(Supplier<Result> transition) {

        try {

            beforeTranst();

            Result result = transition.get();

            code = result.getCode();

            afterTranst();

        } catch (EocsClientException ex) {

            transtError();

            code = ex.getEocsErrorCode();

            appendErrorLog(ex);

        } catch (Exception ex) {

            code = "C00003";

            appendErrorLog(ex);

        }

        return new Response(apiId , code);

    }


    private void beforeTranst() {

        transt = "0";

        changeTranst();

    }


    ...


    private void changeTranst() {

        ... // apiId, jupno, code, transt 등 필요한 값이 필드에 존재

    }


    private void appendError(Exception ex) {

        errorLogger.append(apiId, code, jupno, transt, id, ip, ex);

    }

}


TransitionProcess 클래스는 생성자를 통해서 로그 기록이나 상태 변경에 필요한 값을 받는다. runWith() 메서드는 전송 처리 기능을 함수형 인터페이스로 전달받는다.


상태 변경을 수행하는데 필요한 값(jupno, id, ip, transt)이 필드에 존재하므로 changeTranst() 메서드는 파라미터가 필요없다. 동일하게 beforeTranst() 메서드나 afterTranst() 메서드도 파라미터가 필요 없다.


에러 로그를 남기기 위한 appendError()도 동일하게 에러 로그를 기록하는데 필요한 값 중 Exception을 제외한 나머지는 필드에 존재한다. 그래서 파라미터로 Exception만 받으면 된다.


사실, TransitionProcess의 beforeTrans(), afterTrans(), errorTrans(), appendError() 메서드는 처음부터 존재한 것이 아니고 클래스로 추출한 뒤에 리팩토링하는 과정에서 생긴 것이다. 파라미터로 필요한 값을 전달할 필요가 없기 때문에 코드를 정리하기 더 쉽고, 메서드 호출에 파라미터가 없으므로 코드도 덜 복잡하다.


결과


다음은 흐름 처리 객체를 이용햇서 변경한 코드이다.


public Response chooseMeeting(Request req) {

    String apiId = "E0004";

    TransitionProcess process = TransitionProcess.builder()

        .errorLogger(errorLogger)

        .apiId(apiId)

        .jupno(req.getJupno())

        .id(req.getId())

        .ip(req.getIp())

        .build();

    return process.runWith(() -> tranService.chooseMeeting(req));

}


public Response choosePip(PipRequest req) {

    String apiId = "E0003";

    TransitionProcess process = TransitionProcess.builder()

        .errorLogger(errorLogger)

        .apiId(apiId)

        .jupno(req.getJupno())

        .id(req.getId())

        .ip(req.getIp())

        .build();

    return process.runWith(() -> tranService.choosePip(req));

}


// changeTranst 메서드 제거. TransitionProcess로 이동.


개인적으로는 처음 메서드 추출로 중복을 제거하려고 했던 것보다 더 깔끔하고 프로세스 처리가 별도 클래스로 빠져서 프로세스 분석이나 수정이 용이해진 것 같다.

Posted by 최범균 madvirus

댓글을 달아 주세요

페이스북 친구들과 댓글을 공유하고 싶다면 아래를 이용해주세요.