🐵 왜 리팩토링을...
쿼카크루에 입사하고 코드베이스를 흝어보면서 수많은 문제점들을 발견했다.
특정 문제들은 간단한 수정만으로 고칠 수 있었지만
아키텍처적인 문제, 레거시에 의존하고 있는 부분들이 너무 많아 함부로 고칠 수 없는 문제들도 많았다.
"린하게 하자"라는 명목하에 코드들은 체계없이 누더기처럼 붙어있었고
수많은 안티패턴들을 발견할 수 있었다.
개발자가 "지금 우리 코드는 쓰레기같아요. 고칠 시간이 필요해요"
라고 말하는 것에 대해서 개발자 이외에는 누구도 공감해주지 않는다.
하지만 개발자 입장에서 정돈되지 않은 코드들은 장기적으로 보았을 때 생산성을 크게 떨어뜨리고
임계점에 도달하게 되면 개인의 초과근무 정도로는 해결할 수 없는 지경까지 가고 만다.
결국 결론은 이거다.
역설적이게 느껴질 수 있지만 빠르게 개발하기 위해서는 빠르게 개발해선 안된다.
그러나 이것을 해결하겠다고해서 며칠동안 서비스를 멈출 수는 없는 일
회사의 서비스는 매일 유저들에게 제공되어야하며 장애가 발생해서는 안될 것이다.
따라서 점진적으로 변경을 적용해나가면서 쌓여있는 기술부채들을 해소하고 향후 건강한 구조를 위해 나아가야한다
이를 위해서 어떤 전략을 취할 수 있을까? 에 대한 고민을 정리해보았다.
🐥 문제 인식
내가 생각하는 회사의 기존 코드 베이스의 가장 치명적인 문제는 이것이다.
코드 전반에서 너무 많은 any가 사용되고 있으며 함수의 return 문으로 작성된 any가 모든 코드로 전파되고 있다.
다행히 타입스크립트는 컴파일 타임에만 동작하고 실제 코드에 영향을 주는 일은 적기 때문에
빌드 실패만 되지 않도록 보장한다면 기존 서비스를 블락하지않으면서 점진적으로 타입을 개선해나갈 수 있을 것이다.
기존 any가 사용된 부분들을 개선하는것이 문제인데 사진에서 알 수 있듯이
상당히 많은 양의 any가 존재하고 있다.
그 외에도 치명적이지는 않지만 내 입장에선 문제라고 느끼는 부분들이 꽤 많이 있었는데
나열해보자면 다음과 같다.
1. switch 문과 option 패턴이 남용되고 있다.
switch 문 없이 구현하는게 어려운 경우도 물론 있다.
하지만 switch 문이 너무 많은 일을 하게 될 가능성이 높기 때문에
나는 switch 문은 매우 제한적으로 사용하는 편이다.
또한 ts-pattern 등을 이용해 패턴매칭을 이용하는 것이 더 적절한 경우도 있다고 생각한다.
현재는 switch 문 하나가 너무나도 많은 일을 하는 경우가 많다.
(내가 확인한 가장 긴 switch 문은 코드가 1500줄을 넘어갔다..)
불필요한 switch 문은 최종적으로는 없애는 것을 지향해야겠지만
당장 스위치에 의존하고 있는 여러 코드들이 문제가 생길 수 있다.
따라서 점진적인 변경을 위해 스위치문의 내부로직을 함수로 분리시키고
각 케이스는 케이스에 맞는 함수를 호출만 하는 식으로 개선해나가면 안전하게 고칠 수 있을 것이다.
const doNotUseSwitch = (option: 'plus' | 'minus') => {
const result = [100, 12, 231, 425, 2];
switch (option) {
case 'plus':
return result.reduce((acc, cur) => acc + cur);
case 'minus':
return result.reduce((acc, cur) => acc - cur);
}
};
가령 이런 코드가 존재한다고 가정해보자
이 doNotuseSwitch 함수를 의존하는 여러가지 코드가 존재할것이다.
그렇다면
const doNotUseSwitch = (option: 'plus' | 'minus') => {
const result = [100, 12, 231, 425, 2];
switch (option) {
case 'plus':
return plus();
case 'minus':
return minus();
}
};
const plus = () => {
const result = [100, 12, 231, 425, 2];
return result.reduce((acc, cur) => acc + cur);
};
const minus = () => {
const result = [100, 12, 231, 425, 2];
return result.reduce((acc, cur) => acc - cur);
};
외부에서 doNotUseSwitch를 참조하는 코드들이 문제없이 동작할 수 있도록하며
그와 동시에 로직들을 외부로 추출해낸다.
이후 doNotuseSwitch 문을 호출하고있는 코드들을 차근차근 plus(), minus()를 직접 호출하도록 바꿔주면 끝이다.
여기서 이후에도 원활한 변경을 고려하여 DI 를 하는 것처럼 레이어를 하나 더 두는 식으로
구조를 만드는 것도 고려해볼 수 있다.
interface PlusMinus {
plus():number
minus():number
}
class PlusMinus implements PlusMinus {
constructor(private plusFunc:() => number , private minusFunc:() => number) {}
plus() {
return this.plusFunc()
}
minus() {
return this.minusFunc()
}
}
const plus = () => {
const result = [100, 12, 231, 425, 2];
return result.reduce((acc, cur) => acc + cur);
};
const minus = () => {
const result = [100, 12, 231, 425, 2];
return result.reduce((acc, cur) => acc - cur);
};
이제 plus와 minus의 세부 구현을 얼마든지 고칠 수 있으며
지금 구현해둔 plus 함수가 아니라 다른 함수를 만들어서 끼워넣어도 문제없이 동작할것이다.
개인적으론 프론트엔드 개발에서 이정도까지 하는 것은 조금 투머치라고 생각하기도 했는데
한번 시도해보면 장기적인 관점에서 유지보수가 더 쉬워지긴 할 것 같다.
3. 모든 로직이 합쳐져 있어 로직을 확인하기가 너무 힘들다.
매우 치명적이다. 코드의 모듈화가 되지 않으면 유지보수는 더더욱 힘들어질것이며
이유없이 반복되는 코드들 사이에서 수정해야할 코드를 찾아헤매야한다.
4. Redux를 사용하고 있다.
이것 역시 문제는 아니지만.. Redux Toolkit이 아니라 Redux를 사용 중이며
현재 deprecated 된 createstore를 이용해 로직을 돌리고 있다.
이것 역시 문제는 아니지만..
dispatch, selector 들이 무분별하게 남용되며 코드를 추적하기가 매우 힘든 상태이다.
이 과정에서 코드베이스의 거의 모든 부분이 리덕스와 강하게 커플링되어버리는 결과가 있었다.
이것 역시 문제는 아니지만..
진짜 크리티컬한 문제는 현재 코드의 상태로는 리덕스의 상태가 너무 빈번하게 바뀌며
그와 동시에 너무 많은 전체 리렌더링을 야기하고 있다는 것이다.
이는 체감되는 수준의 성능문제로 직결되고 있으며 결국 사용자경험을 저해하고 있다.
문제는 redux를 건들게되면 redux-persist를 통해 관리되고 있는
유저정보에서 사이드이펙트가 발생할 여지가 매우 크다는 것이다
5. type 정의가 산발적으로 퍼져있다.
사실 타입정의만 퍼져있는 게 아니긴한데 타입 정의가 퍼져있는게 내 입장에선 좀 크리티컬해보인다.
코드가 응집되어있지않으면 문제를 파악하더라도 어디로 찾아가야할지가 모호해지기 때문이다.
6. Pages Router를 이용하고 있다.
이건 사실 문제라기엔 무리가 있지만 향후 앱라우터로 옮기게 될 것을 미리 걱정은 해두어야할 것 같다.
향후 앱라우터로의 전환이 필요해질 수 있다고 생각했기 때문이다.
🐼 next 측은 app router로의 점진적 마이그레이션도 가능하다고 주장한다.
결국 next 진영은 app 라우터를 밀어주기로 작정한 듯 하다.
물론 지금 당장 옮길 생각은 없기도하고 옮겼을 때 장점도 불투명하다.
우선 page 에서 app 으로 전환을 원하는 경우에 대한 문서를 참고할 수 있다.
https://www.youtube.com/watch?v=YQMSietiFm0
또한 점진적 전환을 고려하는 이들을 위한 16분 짜리 동영상도 제공해주고 있다.
레퍼런스
https://www.timegambit.com/blog/blog-log/app-router
https://velog.io/@naseriansuzie/TIL39
https://brunch.co.kr/@matthew-chang/28
'프로젝트 진행기' 카테고리의 다른 글
[연픽] SSG 되지 않아도 되는 컴포넌트 격리하기 (1) | 2023.09.27 |
---|---|
[연픽] interface 보강 기법을 통한 window 객체 확장 (0) | 2023.09.25 |
[Plip] 이메일 요청은 되도록 한번만 보내주세요 (0) | 2023.07.24 |
[PliP] 로그인의 restful 한 설계와 토큰 관리 전략 (1) | 2023.07.19 |
너무 복잡도가 높은 컴포넌트는 어떡하면 좋을까?(답변) (0) | 2023.07.07 |