개발일지

[devlog] 객체지향 구조로 데이터 불일치 해결하고 개발 생산성 개선하기 본문

NestJS, Node.js/#02 NestJS

[devlog] 객체지향 구조로 데이터 불일치 해결하고 개발 생산성 개선하기

lyjin 2024. 5. 25.

기능 설명

응시자가 온라인 시험에 응시하게 되면 다음과 같은 과정을 거쳐 최종 결과가 주최 측에 제공된다.

  1. 응시자가 촬영한 영상을 업로드 한다.
  2. AI 서버가 영상을 분석하여 감지된 데이터를 DB에 기록한다.
  3. BE 서버는 이 데이터를 기반으로 해당 응시자의 ‘분석결과(status)’를 도출한다.

내가 담당한 기능 중에서 가장 복잡하고 어려웠던 기능이었다.
여러 응시자의 분석결과를 여러군데에서, 다양한 형태로 제공 되어야 했다.
 
 


기존 코드 구조 문제

function method1(args) {
  let status;

  if (분석오류조건) {
    status = '분석오류';
  } else if (부정행위조건) {
    status = '부정행위';
  }
  
  if (status === '분석오류') {
    // logic...
  } else if (status === '부정행위') {
    // logic...
  }
  
  return status;
}

function method2(args) {
  if (분석오류조건) {
    // logic...
  } else if (부정행위조건) {
    // logic...
  }
}

 

문제점

  • status 판단 기준이 하드코딩 되어있음
  • 과도한 if-else문
  • 중복된 비즈니스 로직
  • 코드만 보고 ‘분석오류’ 또는 ‘부정행위’ 기준을 파악하기 어려움
  • 여러 API 에서 status가 다르게 도출되는 문제

전반적으로 가독성이 안좋았다. 그러다보니 기획 상 변경이 생겼을 때마다 많은 인적, 시간적 리소스가 필요했다. 예시로 기준이 헷갈릴 때마다 문서화 된 자료를 확인해야 했는데 이 기준이 십수개다 보니 자료 확인하는 시간이 더 걸릴 때도 있었다. 가장 문제가 됐던 건, 흔한 케이스는 아니지만 다중 API에서 다른 분석 결과가 도출되는 경우였다.
 
 

개선 목표

개선하고자 하는 목표는 다음과 같았다.

  • 분석결과의 일관성 및 정확성 확보
  • 즉각적인 수정 요구 반영
  • 가독성 향상 (문서 확인으로 낭비되는 시간 줄이기)

성능보다는 유지보수와 개발생산성을 우선적으로 고려했다. 타이트한 일정과 빈번한 변경 사항, 그에 비해 리소스는 한정되어 있었기 때문이다. 출시일에 맞춰 빠르고 정확하게 완성하는게 가장 중요하다고 생각했다. 다른 무엇보다도 빠르고 정확한 요구사항 반영이 핵심이었다.
 
 


개선1.  분석 로직 통합 및 모듈화

중복 로직을 통합하고자 공통 프로바이더를 추출했다. 먼저 분석결과 도출 프로세스를 크게 네 단계로 정리 했다.

1. 원본 룰셋 파일 내용을 서버에서 사용하기 적합한 구조로 가공한다.
2. 응시자의 사후 분석 데이터를 추출하고, 1번 룰셋과 맵핑 시킨다.
3-1. 2번 데이터를 바탕으로 응시자의 최종 분석 상태 “종합 소견”을 도출한다. (ex. 미응시자/부정행위/분석오류 등)
3-2. 2번 데이터를 바탕으로 상세 내역(ex. 타임라인, 룰셋 별 감지 횟수)에 나타낼 수 있는 형태로 가공한다.

 
2번까지는 베이스가 되는 정제된 데이터로 동일하게 진행된다. 최종 분석결과로 가공하는 클래스는 두 가지(3번)로 분류했다. 분석결과가 사용 되는 여러 케이스를 봤을 때 크게 ‘부정행위 횟수 등을 기반으로 하는 종합 소견이 필요한 경우’와 ‘상세 내역이 필요한 경우’로 나뉜다고 판단했기 때문이다.
 
공통 속성과 메서드는 슈퍼 클래스로 관리하고 각 특성에 맞는 서브 클래스를 생성했다.


개선2.  객체지향 구조 적용

‘분석결과’ 클래스를 추출하고, 그 안에서 다양한 기준과 최종 결과 반환 로직을 정의했다.

export class TesterAnalyzer {
    constructor(private args){}
    
    get status() {
      if (this.isError) return '분석오류';
      if (this.isCheat) return '부정행위';
    
      return '정상';
    }
    
    get isError(): boolean { 
      // 분석오류조건
    }
    get isCheat(): boolean { 
      // 부정행위조건
    }
}

// 예시
const tester = new TesterAnalyzer(args);
console.log(tester.status);

 
 


개선 효과

  • 유지보수성이 개선되었다. 문서 말고 소스 코드만으로도 판별 조건을 파악할 수 있게 되었고, 변경점이 생기더라도 해당 클래스만 수정하면 되기 때문에 개발 시간이 2배 가량 단축되었다.
  • 객체지향 구조를 도입함으로써 6개의 API에 흩어져 있던 중복 비즈니스 로직을 제거했다. 코드량 또한 약 57% 감소하여 가독성이 좋아졌다.
  • 데이터 불일치 이슈가 해결되었다. 단일 객체를 사용하게 되면서 여러 API에서도 동일한 결과값을 반환할 수 있게 되었다.
  • 협업 과정이 간결해졌다. 기존에는 기준이 변경될 때마다 팀원들과 맞춰가는 시간이 필요했다. 이제는 공통 객체만 수정하면 되기 때문에 불필요한 커뮤니케이션 비용을 줄일 수 있었다.