본 글은 사내 업무 효율성 향상을 목적으로 펌웨어 검증 과정을 자동화하기 위한 프로그램 개발 경험을 공유합니다. 특히, Payload
객체의 구현과 검증 로직 개발 과정에서 발생한 다양한 문제들과 이에 대한 해결 방안을 중심으로 논의합니다. 이 과정에서 여러 Payload
클래스들을 효과적으로 검증하는 방법에 대한 고민과 그 해결책을 제시하고자 합니다.
먼저 28개의 클래스 중 하나인 ChangeSomethingPayload
클래스를 예시를 살펴보겠습니다.
해당 클래스는 Payload
클래스입니다. 요청과 응답을 규정하고 있는 클래스로서 필드는 OCPP1.6
프로토콜에서 정의한 내용을 포함하고 있습니다. 요청 페이로드는 key와 value 두 개의 필드를 가지고 있고, 두 필드 모두 필수값이면서 문자열 길이 50 이내라는 제한 사항을 갖고 있습니다. 한편 응답 페이로드는 status
라는 하나의 필드를 가지고 있습니다. status
는 설정 변경 요청에 대한 응답 상태를 나타내며, SomethingStatus
라는 Enum
클래스의 값 중 하나여야 힙니다.
위와 같은 제약 조건에 대해 애노테이션을 사용하지 않고 검증 로직을 개별 클래스 내에 구현하는 경우 다음과 같은 코드를 작성할 수 있을 것입니다.
public class ChangeSomethingPayload extends Payload{
private String key;
private String value;
@Override
public boolean isValid() {
return keyIsValid() && valueIsValid();
}
private boolean keyIsValid() {
return key != null && !key.trim().isEmpty() && key.length() <= 50;
}
private boolean valueIsValid() {
return value != null && !value.trim().isEmpty() && value.length() <= 50;
}
public static class Response extends Payload {
private String status;
@Override
public boolean isValid() {
return statusIsValid();
}
private boolean statusIsValid() {
return status != null && SomethingStatus.contains(status);
}
}
}
SomethingStatus
Enum
은 다음과 같이 정의합니다.
public enum SomethingStatus {
ACCEPTED("Accepted"),
REJECTED("Rejected"),
...
;
private final String value;
public static boolean contains(String request){
return Arrays.stream(values()).anyMatch(status -> status.getValue().equals(request.trim()));
}
}
이렇게 구현할 경우 무엇이 문제일지 생각해 봅시다. 만약 Payload
클래스가 이것 하나라면 크게 문제될 일이 없을 것 같습니다. 그런데 이와 같은 Payload
가 28개나 존재하며, 내부에 가진 클래스까지 더하면 그보다 훨씬 많은 수의 클래스가 존재한 상황입니다. 각각의 Payload
는 비슷한 제약 조건, 즉 필수여부, 문자열 제한, Enum
타입 필드 등을 갖고 있기 때문에 중복되는 로직을 매번 복사 붙여넣기 해야하는 문제가 생깁니다.
결과적으로 각 필드의 유효성을 검사하는 로직이 클래스 내에 흩어져 있어 코드의 가독성이 떨어지고, 중복된 코드가 발생합니다. 만에 하나 필드의 유효성을 검사하는 조건이 변경된다면 어떨까요? 해당 필드를 검증하는 모든 코드를 찾아서 수정해야 합니다.