1. 여러 기능에 분산된 휴게시간 계산 로직

수백 명 규모의 새로운 고객사에서는 우리 서비스가 기존에 제공하는 휴게 옵션으로 운영이 어려웠다. 앞으로 존재할 수 있는 잠재적인 고객사의 커버리지까지 높이기 위해 코드 구조를 파악했다.

레거시 코드들은 법령을 그대로 하드코딩하여 유연하게 옵션을 제공하지 않는 형태였고 새로운 옵션이 추가되었을 때도 확장이 불가능한 구조였다. 또한, 기존 휴게 옵션은 유틸리티 클래스로 작성되어 약 35곳에서 직접 호출 중이었다. 문제는 유틸리티 클래스 밖에서 적어도 11개 운영 클래스가 휴게 규칙을 해석 및 계산하는 로직을 중복으로 갖고 있었다.

과거에 개선 작업이 이뤄지지 않은 도메인 기능들은 대부분 테스트 코드가 작성되어 있지 않아서 운영 코드를 쉽게 리팩토링하거나 구조를 개선하기 어려운 상황이었다.

2. 테스트로 기존 동작을 고정하고 공통 정책으로 통합

여러 기능에 분산된 휴게시간 계산 로직들의 유스 케이스를 분석해서 종합했다. 결과적으로 다음 세 가지 유스 케이스로 패턴화할 수 있었다.

  1. 출근 시각과 소정근로시간이 주어지면 소정근로를 완료하기까지 포함된 휴게시간을 계산
  2. 출근 시각과 퇴근시각이 주어지면 포함된 휴게시간을 계산
  3. 하루 근로시간이 주어지면 포함된 휴게시간을 계산

이 세 가지를 인터페이스로 정의하고 클라이언트가 정의한 옵션에 따라 아웃풋을 산출하는 구현체를 만들 수 있도록 했다.

휴게시간 도메인 아키텍처

여러 휴게 클라이언트 도메인들은 런타임 시점에 스펙 클래스를 만들어서 리졸버에 전달하여 실제 구현체를 받아서 사용할 수 있는 구조다. 나중에 새로운 옵션이 생기는 경우 인터페이스를 구현하는 새로운 정책 클래스를 만들어 확장할 수 있게 했다.

리졸버에서는 lenient와 strict enum을 만들어서 일부 조회 화면에서는 lenient를 허용하도록 만들었다. 좀 더 설명하자면 특정 파라미터가 resolve() 메서드에 전달되지 않으면 에러를 발생시키는 것이 아닌 법정을 만족하는 휴게시간 정책으로 fallback하도록 했다. 반대로 저장하는 쪽은 모두 strict로 해서 시스템 내부로 들어가는 값들은 엄격하게 통제했다.

구현하기 전에는 매 단계에 Characterization Test를 통합테스트로 만들어서 넣었으며, TDD 방식으로 진행했다. 정책 클래스들은 순수하게 작성되어 단위 테스트로 빠르게 회귀를 파악할 수 있도록 했다.

3. 신규 고객사의 휴게 규칙을 반영, 레거시 계산 로직을 제거

수백 명 규모의 새로운 고객사가 휴게 규칙을 설정하여 적용 가능하게 했고, 추후에 계약할 수 있는 잠재적 고객사를 위한 유연성을 제공했다.

레거시 유틸리티, 서비스마다 중복된 휴게 규칙 해석과 계산 로직을 제거하여 8개의 도메인에서 같은 정책을 사용하도록 공통화했다.

또한 로직들을 순수한 자바 로직으로 분리하였고, 수정 전의 동작을 테스트로 고정했기 때문에 정책 변경 발생 시 유연하면서도 안전하게 대처할 수 있을 것으로 기대한다.

4. 방향성은 좋았지만 과한 작업 범위로 인한 협업 및 AI 효율성 하락

순수한 도메인 로직으로 분리하고 유스 케이스 단위로 public 진입점을 제한하는 방향성은 장기적인 유지보수성 향상에는 도움이 될 것으로 보았다. 다만 작업의 범위는 협업과 AI 관점에서는 좋지 못했다고 판단되었다.

작업을 완료한 뒤에는 수천 줄의 diff가 PR로 작성되었다. 어떻게 하면 의미를 해치지 않고 리뷰어에게 부담을 줄이면서 리뷰 효율성을 높일 수 있을까 고민해서 Stacked PR을 만들었다. 첫 번째 PR에는 새로운 정책과 공통 기능으로 묶는 작업을 포함시켰다. 두 번째 PR에는 이 통합된 기능을 실제로 활용하는 클라이언트 도메인에서의 변경 사항을 포함시켰다. 그럼에도 불구하고 물리적인 diff 숫자는 여전히 수천 줄에서 절반으로 줄어든 꼴이다. 그중에 절반 이상은 테스트 코드라서 어쩔 수 없는 부분이었지만 더 나은 방법을 고민해 볼 필요성은 있다.

좋은 소프트웨어 엔지니어링은 사람과 AI 구분 없이 협업에는 동일한 영향을 준다고 요새 느끼고 있다. AI를 이용한 작업에서는 어떤 점이 중요하고, 동료들과 협업에서는 어떤 점이 중요한지를 구분하는 건 의미 없다고 생각하기 때문이다. 다시 말해, 사람에게 좋다고 느껴지는 소프트웨어 엔지니어링은 AI에게도 효율적이고 효과적으로 다가온다.

이번 작업처럼 기능을 한 곳으로 몰아두고 명시적으로 이해하기 쉽게 작성하는 것은 사람에게도 그리고 AI에게도 도움이 되기 때문에 소프트웨어 엔지니어링은 AI 시대에도 꾸준히 학습할 필요가 있다고 생각된다.