반복되는 예외처리(try-catch) 코드 템플릿으로 만들기

주싱·2022년 10월 27일
0

더 나은 코드

목록 보기
3/14

1. 문제 상황

  • dev 프로파일일 때만 서비스 프로세스 내에 로드되어 테스트를 돕는 하드웨어 API 서버 시뮬레이터 N개가 있다.
  • 시뮬레이터는 스프링 빈으로 등록되어 있고 생성자에서 TCP 서버 모듈 생성과 초기화를 한다.
  • dev 프로파일로 서비스를 실행할 때 종종 TCP 서버 포트가 이미 바인딩 되어있는 예외가 발생하고 전체 서비스가 올라가지 않는 문제가 발생한다.
@Component
@Profile("!ops")
public class AbcSimulator {
    private TcpServer server;

    public AbcSimulator() {
        start(12345);
    }

    public void start(int bindPort) {
        server = new TcpServer();
        AbcPipelineInitializer pipelineInitializer = new AbcPipelineInitializer(server);
        server.init(bindPort, pipelineInitializer);
        server.start();
    }
		... 
}

2. 원인

원인은 스프링 컨텍스트가 시뮬레이터 빈을 생성하다가 서버 포트가 이미 바인딩되어 있다는 예외를 만나게 되는데 예외를 어디에서도 처리하지 않기 때문이었다. 아마도 테스트용이라 무지성으로 간단히 처음 버전을 만들었는데 뒤이어 만드는 시뮬레이터들도 별 생각 없이 다 똑같이 만든 것 같다. (부끄러움)

3. 개선방향

서버 포트가 왜 이미 바인딩되어 있는지가 더 핵심적인 원인이지만 포트 바인딩은 서비스 외부적인 요인에 의해서 일어날 수 있는 일이고 하나의 시뮬레이터가 생성 실패했다고 모든 서비스가 실행되지 않는 일은 피해야 겠다. 일단 오류 메시지를 출력해 주고 해당 하드웨어 시뮬레이터만 로딩 실패하도록 만들어보자.

4. 템플릿/콜백 패턴으로 예외처리 하기

중복 예외처리

예외 처리를 해주려고 보니 N개의 시뮬레이터 서버 실행 코드에 아래와 같은 try-catcy 문장이 반복해서 들어가야 했다.

try {
    server = new TcpServer();
    AbcPipelineInitializer pipelineInitializer = new AbcPipelineInitializer(server);
    server.init(bindPort, pipelineInitializer);
    server.start();
    log.info(simulatorName + " : start success.");
} catch (Exception e) {
    log.error(simulatorName + " : start fails.", e);
}

간단한 반복이지만 변경되지 않는 템플릿 코드와 콜백 형태로 템플릿에 전달할 수 있는 변경되는 코드 부분이 극명하게 보였다. try-catch 문장과 로그를 남기는 코드는 변하지 않는 부분이라 할 수 있고, 서버를 생성하고 초기화 하는 부분과 로그로 남기는 시뮬레이터 이름은 변경되는 부분이라고 할 수 있겠다. 배웠던 템플릿/콜백 패턴을 적용해 보기 적합한 예제인 것 같다. 최근에 동료가 파일 IO를 처리하며 예외처리 지옥이라고 괴로워하던 생각도 났다.

템플릿

@Slf4j
@Component
public class ServerStartTemplate {
    public void work(Runnable serverStartCallback, String simulatorName) {
        try {
            serverStartCallback.run();
            log.info(simulatorName + " : start success.");
        } catch (Exception e) {
            log.error(simulatorName + " : start fails.", e);
        }
    }
}

콜백

@Component
@Profile("!ops")
@RequiredArgsConstructor
public class AbcSimulator {
    private final ServerStartTemplate serverStartTemplate;
    private TcpServer server;

    @PostConstruct
    public void start() {
        serverStartTemplate.work(() -> {
            start(12345);
        }, "Abc Simulator");
    }

    private void start(int bindPort) {
        server = new TcpServer();
	      AbcPipelineInitializer pipelineInitializer = new AbcPipelineInitializer(server);
        server.init(bindPort, pipelineInitializer);
        server.start();
    }
    ... 
}

5. 마치며

N번 반복해야 했던 변하지 않는 예외처리와 로깅 코드를 템플릿으로 분리하고 변경되는 서버 생성과 초기화 부분은 콜백으로 전달해서 깔끔하게 처리된 것 같다. 배운 것을 이렇게 적용해 보니 너무 기분이 좋다.

6. Reference

  • 토비의 스프링 3장 템플릿/콜백
profile
소프트웨어 엔지니어, 일상

0개의 댓글