최근 자바스크립트로 작성되어있던 코드들을 리팩터링과 함께 타입스크립트로 마이그레이션을 시작하였다.
테스트 코드를 작성하기 쉬운 유틸리티 함수부터 리팩터링하였고 타입스크립트로 적용해나갔다. 하다보니 프로젝트 전체를 타입스크립트로 바꾸고 있었고 그러던 중간에 앱 업데이트를 진행하게 되었다.
그리고 발생하는 오류에 재발을 방지하는 반성의 기록을 남긴다.
리팩터링
다음 프로젝트 진행을 위해 기존 코드들을 정리하는 의미에서 리팩터링을 하고 있었다. 리팩터링 하기 전 테스트 코드를 작성하여 안전성을 확보하고 시작하였기에 로직이 기존과 다르게 변경되어 유발되는 오류는 미리 잡을 수 있었다.
기능 개선이 아닌 중복을 최소화하고 가독성과 유지보수성에 주제를 두고 시작하였으며 하다보면 점점 방대해지는 것을 원래의 목적을 계속 상기시키며 진행하였다.
단순하게는 변수명이 i, a, b처럼 작성되어있거나 userId로 되어있으나 유저의 앱 아이디인지 유저가 등록한 카드의 아이디인지 구분이 명확하지 않는 부분들도 수정하였다.
eslint의 react-hooks/exhaustive-deps
을 적용하면서 useEffect에서 누락된 dependency를 명시해주는 작업도 진행하였다.
해당 룰을 적용 전 개발 당시 이 룰을 지키려고 의존성을 추가하면 의도치 않게 코드가 호출된다
라는 의견이 있었으나 의존성을 추가하면서도 의도를 유지할 수 있는 방법을 찾으면서 룰을 적용하게 되었고 아직 수정되지 않은 코드들은 이번에 추가하고자 하였다.
타입스크립트
리팩터링을 하면서 타입스크립트로 변환하는 작업도 진행을 하였다. 이미 테스트 코드가 작성되어있던 유틸리티 파일들을 먼저 진행하였고 컴포넌트, 리듀서, 사가 순으로 작업 순서를 가져갔다.
타입스크립트로 한번에 여러 파일을 변환하면 겉잡을 수 없는 타입에러가 방대하게 발생하여 최대한 의존성이 낮고 확인이 쉬운 파일부터 진행하고자 하였다.
이 과정에서 몇몇 라이브러리가 아직 타입스크립트를 지원하지 않아 직접 타입을 작성해주어야했다.
사가 액션 헬퍼
에서 REQUEST, SUCCESS, FAILURE
각각 받는 payload
의 타입을 제네릭으로 받아오도록 하였고 그렇지 않은 경우에는 디폴트로 사용중인 형식에 맞게 커스텀하였다.
REQUEST와 SUCCESS는 payload가 객체이거나 undefined
라고 명시하였으며 FAILURE는 문자열
을 받는다고 명시하였다.
사가에 타입스크립트를 적용하면서 제네릭을 굳이 전부 다 명시해야할까?
하는 생각을 했다. 그리고 디폴트 타입을 객체로 명시뒀으니깐 괜찮아 보이는데, 타입에러도 없고
하면서 컴파일 단계에서 나타나지 않기에 제네릭은 없이 진행하였다.
런타임 에러
앱 업데이트를 위해 테스트 빌드를 해야하는데 이전 빌드와 변경된 점이 타입스크립트 적용이라면 QA 브랜치가 필요할까?
하는 잘못된 판단을 하게 되었고 브랜치는 develop에서 바로 release로 분기되었다.
release 브랜치에서 앱 업데이트를 위한 테스트를 진행하던 중 생각치 못한 오류들이 나타나기 시작하였다.
아무것도 바뀐 것이 없다고 생각했던 화면에서 갑자기 앱이 종료됩니다: 커스텀 에러 핸들러
에러 팝업이 노출되었다.
이게 무슨일이야
하며 bugsnag
를 확인해보니 undefined에서 구조분해
를 하고 있으니 오류가 발생하였다. 되던 것이 안되는 상황에 당황스러움도 잠시였고 설마 하며 문제가 예상되는 지점을 찾아 가보니 사가 REQUEST에 제네릭도 명시하지 않고 payload는 undefined로 넘어가고 있는데 해당 액션의 리듀서에서는 payload를 구조분해하여 값이 있는 경우를 찾고 있었다.
action.REQUEST();
...
const { value } = payload;
if (value) {
...
}
payload를 구조분해를 할 수 있고 그 분해한 내용에 value가 존재해야만 하는 아래 코드가 실행된다. 그러나 payload는 undefined였고 undefined는 분해 자체가 되지 않기에 오류가 발생하였다.
이 문제를 해결하기 위해 액션에 payload를 {}
로 넘겨주거나 payload?.value
처럼 코드를 수정하였다.
그리고 사가 헬퍼를 어떻게 수정할 수 있을까 고민하던 중 명시하지 않은 제네릭을 추가
하는 것으로 생각하는 순간 다음 오류가 발생하였다.
혹시나 역시나
이번에는 화면이 넘어가야하는데 넘어가지 않고 계속 그 자리에 머물러 있는 상황이 발생하였다. 이건 에러 트래킹도 되지 않아 디버그 모드로 상황을 재현해봐야했다.
useEffect에 의존성을 추가하면서 의도와는 다르게 훅이 업데이트 되고 있었다. 의존성 추가에 대해 의도치 않은 변경에 대한 의문점은 다시 재기되었다.
오판과 오만
타입스크립트로 변경하면서 컴파일 에러가 발생하지 않았으니 실행해보지 않아도 되겠다고 생각했던 잘못된 판단이 에러를 가져왔다.
기존 코드가 작성된 의도가 있을 것이고 의존성이 추가되지 않은 부분에도 의미가 있을 텐데 이를 무시하고 룰을 적용하고자 변경했던 것이 오류를 발생시켰다.
기능적으로 변경된 것은 없으니 QA브랜치가 필요하지 않다고 판단해버리며 바로 release를 열어버린 커밋 히스토리는 오류가 나타날때마다 수정 후 머지되고 이를 다시 당겨오는 방식으로 거미줄이 되어가고 있다.
결국 뒤늦게 QA브랜치를 열었고 테스트가 완료되면 머지 후 release 빌드를 할 예정이다.
결론
테스트 코드가 없거나 테스트를 직접 하지 않았던 부분들은 어김없이 에러를 반환한다.
모든 것들을 테스트 할 수 있다면 어떨까?
에러가 없을것이라 오만했던 상황을 반성하며 시간을 아끼고 싶다면 돌아가지 말고 정문으로 들어가자.