리팩토링은 앱의 유지보수성, 확장성, 가독성을 향상시키기 위해 필요하다라고 하면 굉장히 식상하지만 프로젝트를 진행하다 보면 이런 부분을 굉장히 자연스럽게 느끼게 된다.
특히 몇 년 유지(또는 방치)된 프로젝트 코드를 보다 보면 왜 리팩토링이 필요한지 누가 아무 말 안 해도 절감하게 된다.
그럼 리팩토링을 잘 하는 방법이 따로 있는가?
1. 모듈화와 컴포넌트 분리
지금 내가 만지고 있는 프로젝트는 모듈화와 컴포넌트 분리가 '하나도' 안 되어 있다.
파일 하나(클래스 하나)에 코드가 1,500줄이 넘는 건 기본이고 어떤 건 2,000줄 가깝다.
로직이 어떻게 돌고 있는지 이해하기 쉽지 않는 건 물론이고 스크롤한다고 손가락 아파 죽겠다.
UI는 작은 위젯으로 분리해야 한다.
가능한 한 작게 만들고 다른 곳에서 재사용한다면 재사용한다.
코드는 (되도록) 중복되면 안 된다.
각 위젯의 책임을 명확히 하면(기능을 명확히 하면) 개발하는 입장에서도 유지보수하는 입장에서도 일이 훨씬 수월해진다.
2. 상태관리 전략 결정
프로젝트 규모에 따라 어떤 상태관리 방법을 사용할지도 리팩토링에 많은 영향을 미치는 것 같다.
내 경험 상으로는 초반에 도입한 상태관리 방법은 나중에 바꾸기 어렵다.
MVP식으로 낸 프로젝트가 성공했거나 오랜 기간 유지되어와 규모가 큰 경우 특히 그렇다.
(getX 사용하는 개발자 뽑는 플러터 회사가 이에 해당하지 않나 나는 생각하고 있다...)
그래서 프로젝트의 현 상황에 맞게 상태관리 방법을 선택하는 것도 좋지만
미래의 상황도 고려할 필요가 있다고 생각한다.
이런 부분 없이 그냥 개발해 버리면 나중에 마이그레이션 할 수 없는 것 같다.
규모가 엄청 큰 getX 프로젝트를 몇 년 간 진행했는데
나중에 getX 마음에 안 들 때 그거 어떻게 마이그레이션 할 것인가?
(기술적으로 할 수는 있겠지. 내 말은 '상황'을 말하는 거다...)
여튼, 상태관리 전략에 따라서도 리팩토링 방법에 영향이 갈 수 있으니 이 부분을 생각할 것.
3. 코드 분리와 재사용성 향상
앞의 모듈화와 컴포넌트 분리 부분과 일맥상통한다.
결국 가능한 한 단순하게 깔끔하게 만들라는 거다.
여기서 개념이 더 나아가면 추상화, 인터페이스 등까지 얘기가 나가겠지.
비즈니스 로직은 UI에서 분리되어야 한다.
MVC, MVVM, BLoC과 같은 디자인 패턴을 적용할 수 있다면 적용하는 게 좋다.
패턴이 잘 적용된 프로젝트는 개발, 유지보수가 쉽다(고 생각한다).
이러면 코드의 재사용성이 높아지고 테스트하기도 낫다.
4. 매직 넘버와 문자열은 제거하자
매직 넘버: 코드 내에서 직접 표시된 숫자 값. 의미나 목적이 명확하지 않다.
int isActive = 1; // 1을 '활성화됨'의 의미로 사용
if (isActive == 1) {
// 활성화 상태일 때 실행할 코드
}
주석은 내가 넣은 거고, 프로젝트에서 주석이 없다고 치자.
저런 코드 딱 보면 '아...'하지 않겠는가?
1/0, 1/-1 등으로 true, false 표현하는 게 매직 넘버의 예고 절대로 해서는 안 되는 행동이다.
매직 넘버 얘기는 진짜 많이 할 수 있어서 여기서 끊는다.
프로퍼티에 문자열을 하드코딩하는 것도 지양하는 게 좋다고 생각한다.
유지보수하기 너무 힘들다.
하드코딩할 수 있는 문자열은 상수로 대체하고 그런 상수들을 다 모아 놓는 게 좋다 생각한다.
나는 그를 위해 별도로 consts 폴더 안에 파일을 관리한다.
네이밍 컨벤션 유지하고, 코드 스타일 가이드(effective dart 같은 거) 따르고 lint 적용할 수 있으면 하고 하는 것도 방법이라 생각한다.
그리고 좀
Use `lowercase_with_underscores` when specifying a library prefix.
이런 에러 뜨면 좀 저런 거 좀 지켜 줬으면 좋겠다.
IDE에서 스네이크 케이스 쓰라는데 굳이 카멜 케이스 쓸 이유가 뭔가?
5. 불필요한 위젯 및 코드 제거
사용되지 않는 위젯, 함수, 라이브러리 등은 정기적으로 검토하고 안 쓰면 지우는 게 맞다.
나는 아무도 책임자(담당자)가 없는 프로젝트 코드를 만지고 있어서 그 안에 있는 주석 처리 된 코드를 쉬이 못 지우고 있다.
지웠다가 나중에 꼭 필요하면 어쩔 것인가...
추적하기도 어려운 상황이 생길 수도 있으니 지우지를 못 한다.
6. 테스트와 문서화
테스트가 리팩토링 과정에서 무슨 중요한 역할을 하는가?
1. 테스트를 통해 리팩토링 전후에 코드가 예상대로 유지되는지 확인할 수 있다.
2. 테스트를 하면 리팩토링으로 인해 발생할 수 있는 문제를 사전에 발견하고 수정할 수 있다.
3. 작은 코드 조각이나 함수의 동작을 검증하는 유닛 테스트, 플러터의 UI 위젯이나 사용자 인터페이스의 일부분을 테스트하는 위젯 테스트 등을 하면 각 기능이나 위젯이 기대한 대로 동작하는지 확인할 수 있다.
4. 리팩토링은 기존 코드의 구조를 개선하는 과정에서 기능적 변경을 최소화해야 한다. 유닛 테스트와 위젯 테스트를 하면, 리팩토링 후에도 애플리케이션의 핵심 기능이 안정적으로 동작하는지 검증할 수 있다. 테스트가 실패하면, 리팩토링 과정에서 의도치 않은 변경이 발생한 것이다.
5. 리팩토링 후에 이전에 해결되었던 문제가 다시 발생하는 것을 리그레션 (버그)라고 한다. 유닛 테스트와 위젯 테스트는 리팩토링 과정에서 리그레션이 발생하지 않도록 해 준다. 테스트 케이스가 충분히 커버하고 있다면, 리팩토링으로 인해 과거에 수정된 버그가 다시 발생하는 것을 사전에 감지할 수 있다.
문서화의 역할은?
코드에 대한 문서화는 리팩토링 과정에서 코드의 목적과 동작을 명확히 하는 데 도움이 된다.
테스트 코드 자체도 일종의 문서 역할을 할 수 있다.
테스트 케이스는 해당 기능이나 위젯이 어떤 입력에 대해 어떤 출력을 내야 하는지 명확하게 보여주므로, 코드의 의도를 이해하는데 큰 도움이 된다.
결국 핵심은 이런 과정을 통해
애플리케이션의 성능을 최적화하겠다는 것이다.
앱의 성능 최적화를 위해 리팩토링할 때, 플러터에서는 특히 위젯의 빌드 과정을 최적화하고 필요 이상으로 상태 업데이트가 일어나지 않도록 주의할 필요가 있다.
불필요한 위젯 리빌드가 일어나지 않게 하는 것은 정말 정말 중요하다.
const 쓰기: 위젯에 const를 쓰면 위젯 트리가 리빌드 돼도 해당 위젯은 재생성 안 된다. 기존 인스턴스가 사용된다. 이는 리소스 사용을 줄이고 성능을 향상시킨다.
StatelessWidget과 StatefulWidget 바로 쓰기: 상태가 없으면 stl을, 상태가 있으면 stf를 쓴다. 상태가 변경될 때만 stf이 리빌드되도록 해야 불필요한 리빌드를 방지할 수 있다.
위젯 분리: 복잡한 위젯을 더 작은 위젯으로 분리하면, 상태 변경이 있을 때 전체 위젯이 아닌 변경된 부분만 리빌드되도록 할 수 있다. 이렇게 필요한 부분만 업데이트하면 성능이 개선된다.
Key 사용: 리스트나 콜렉션 내의 위젯이 재정렬되는 경우, 각 위젯에 고유한 Key를 할당하면 플러터가 위젯을 올바르게 재사용할 수 있다고 한다... 이는 동적 리스트에서 위젯의 리빌드를 최적화하는 데 유용하다는데...
ListView(
children: <Widget>[
for (var item in items) ListTile(key: ValueKey(item.id), title: Text(item.title)),
],
);
...을 찍은 건 '딱히, 과연 그럴까?'싶어서였는데 동적 리스트에서는 그럴 '수'도...
Builder 위젯 사용: Scaffold의 body 안에서 상태 변경이 필요한 부분만 Builder로 감싸면 해당 부분만 리빌드되도록 할 수 있다.
AnimationController 리소스 관리: 애니메이션을 사용할 때는 AnimationController를 적절히 관리해야 한다. stf의 dispose 메서드에서 컨트롤러를 dispose해야 하는 것도 기본이다. 그러지 않으면 불필요한 리소스가 계속 사용된다.
CustomPainter와 RepaintBoundary: 복잡한 그림을 그리는 커스텀 위젯을 사용할 때는 CustomPainter를 사용하고, 불필요한 리페인트를 방지하기 위해서는 RepaintBoundary 위젯으로 감쌀 수 있다.
GlobalKey 사용 최소화: GlobalKey는 필요할 때만 사용해야 한다. 과도하게 사용하면 성능 저하 발생한다.
마지막으로...
불필요한 패키지 의존성은 제거한다.
필요한 패키지 버전은 되도록 최신으로 유지한다.
리팩토링은 방 청소하는 거랑 정말 같다.
'플러터 > 플러터 사용기' 카테고리의 다른 글
프로바이더 뽀개기 프로젝트 (1) | 2024.02.03 |
---|---|
플러터-프로바이더-라이브러리-정리 (0) | 2024.02.02 |
플러터-프로바이더-궁금했던-내용들 (0) | 2024.02.02 |