수정의 일괄 적용(Bulk application of fixes)
수정의 일괄 적용(Bulk application of fixes)
분석 문제 여러 개를 한 번에 고치는 방법을 소개할게요. 편집하면서 진단 결과 하나씩 자동으로 고쳐 주는 기능은 이미 살펴봤는데요, 때로는 그것마저 느려서 진단 결과 여러 개를 한 번에 고치고 싶을 때가 있어요. 다행히 그걸 지원하는 기능도 있는데, 이 글에서 함께 살펴볼게요.
본문
진단 결과를 여러 개 고치는 방법은, 얼마나 많은 진단을 고치고 싶은지에 따라 몇 가지로 나뉘어요. 각각을 따로 살펴볼게요.
같은 진단이 여러 번 나타난 경우 고치기
클래스를 다른 파일로 옮기면서 코드를 리팩터링해 본 적 있나요? 그렇다면 어떤 import는 옮겨진 클래스 안의 참조 때문에만 필요했을 가능성이 꽤 있어요. 그래서 안 쓰는 import가 남게 되는 거죠.
같은 파일 안에서 두 번 이상 적용할 수 있는 수정은, 도구가 그 파일 전체에 적용하는 옵션을 제공해요.
이런 수정은 다른 수정들과 같은 자리에 나타나고, 보통 단일 위치용 수정과 비슷한 이름이 붙지만 'in file (파일 내)' 같은 말이 덧붙어요. 예를 들어 IDE에서 이 수정은 이렇게 보여요.
'in-file' 수정을 고르면 예상한 대로 동작해요. 파일 안의 안 쓰는 import를 전부 지우는 거죠.
파일 안의 모든 진단 고치기
좋긴 한데, 이 방식은 한 종류의 진단만 고치고, 수정을 호출하려면 진단이 보고되는 위치(에디터나 problems 뷰)를 찾아야 해요. 도구가 더 많은 걸 할 수 있을 것 같죠.
그리고 Visual Studio Code에서는 실제로 더 많은 걸 할 수 있어요. 단일 파일 안의 모든 진단에 수정을 적용할 수 있거든요. 방법은 위와 같은 예시로 살펴볼게요.
우클릭해서 컨텍스트 메뉴를 열면 다음이 보여요.
여기서 Source Action…을 고르면 두 번째 메뉴가 열려요.
Fix All을 고르면 에디터 안의 모든 진단에 수정이 적용돼서 이렇게 돼요.
Fix All 명령은 명령 팔레트에서도 호출할 수 있어요. 명령 팔레트는 View 메뉴에서 Command Palette…를 고르거나, 단축키로 열 수 있어요(모든 플랫폼에서 F1, Windows와 Linux는 Control+Shift+P, macOS는 Command+Shift+P). 명령 이름을 입력하면 목록을 필터링할 수 있죠. 명령 팔레트는 대략 이렇게 생겼어요.
디렉터리 안의 모든 진단 고치기
진단을 고치는 데 쓸 수 있는 우리 도구 중 가장 완전한 것은 dart fix 명령줄 도구예요. 주어진 디렉터리 안의 모든 진단을 고칠 수 있어서, 패키지나 애플리케이션 전체를 한 번에 고치는 데 쓸 수 있죠.
물론 주의할 점이 몇 가지 있어요. 모든 진단을 고쳐 주는 건 아니에요. IDE에서 쓰는 것과 같은 퀵 픽스를 적용하기 때문에, 퀵 픽스가 없는 진단은 고칠 수 없거든요. 또한 문제를 고칠 방법이 똑같이 유효한 여러 가지인 경우에도 퀵 픽스를 적용하지 않아요. 어떤 수정을 선호하는지 도구가 알 수 없으니까요. 이 주의점들은 앞 절에서 설명한 Fix All 명령에도 동일하게 적용돼요.
이 도구의 필요성이 자주 생기지는 않지만(그래서 잘 모를 수도 있겠죠), 특히 유용한 상황이 두 가지 있어요.
첫째, core, recommended, flutter 같은 규칙 세트 중 하나처럼, 새 린트 하나 혹은 린트 묶음을 활성화하고 싶은 경우를 생각해 볼게요. 패키지에 린트 규칙 위반이 적어도 몇 개는 있을 가능성이 높아요. 린트에 관련된 수정이 있다면, 각 위반에 하나씩 적용하는 대신 그 수정을 한 번에 모든 곳에 적용하는 게 편하겠죠.
둘째, Flutter의 한 릴리스에서 다른 릴리스로 업그레이드하는 경우를 생각해 볼게요. 두 릴리스 사이에 API가 바뀌었다면, 코드를 최신 릴리스에 맞게 업그레이드해 주는 수정이 있을 가능성이 높아요. 이런 변경들도 단일 작업으로 모두 적용할 수 있으면 편하겠죠.
그게 바로 dart fix 명령이 등장하는 자리예요. 여러 파일의 여러 진단에 퀵 픽스를 적용할 수 있거든요.
이 명령에는 두 가지 모드가 있어요. 드라이 런(dry-run) 모드는 어떤 진단을 고칠 수 있고 몇 개의 변경을 적용할 수 있는지 알려 주지만 파일은 수정하지 않아요. 적용(apply) 모드는 변경을 실제로 적용해요.
고칠 파일이 들어 있는 디렉터리 경로를 줄 수 있고, 디렉터리를 생략하면 현재 디렉터리와 하위 디렉터리의 모든 파일을 재귀적으로 고쳐요.
prefer_single_quotes 린트를 활성화했고, 다음과 같은 파일이 있다고 가정해 볼게요.
import 'package:logging/logging.dart';
void main() {
var logger = Logger("myApp");
logger.info("Entering myApp");
// Do stuff.
logger.info("Exiting myApp");
}
이 파일이 있는 디렉터리에서 --dry-run 옵션과 함께 dart fix 명령을 실행하면 이런 출력이 나와요.
$ dart fix --dry-run
Computing fixes in __bin__ (dry run)... 4.3s
3 proposed fixes in 1 file.
demo.dart
prefer_single_quotes • 3 fixes
도구가 린트 위반 세 개를 찾았고, 셋 다 고칠 수 있다고 보고하지만, 파일을 수정하지는 않아요.
이번에는 --apply 옵션을 붙여 dart fix 도구를 다시 실행하면 거의 같은 출력이 나와요.
$ dart fix --apply
Computing fixes in __bin__... 5.3s
Applying fixes... 0.0s
3 proposed fixes in 1 file.
demo.dart
prefer_single_quotes • 3 fixes
3 fixes made in 1 file.
다만 이번에는 파일이 이렇게 갱신돼 있어요.
import 'package:logging/logging.dart';
void main() {
var logger = Logger('myApp');
logger.info('Entering myApp');
// Do stuff.
logger.info('Exiting myApp');
}
드라이 런 모드는 제안되는 변경을 실제로 볼 수 있는 diff 출력을 만들어 내지 않아요. 그래서 --apply 옵션으로 도구를 실행하기 전에는 변경 사항을 먼저 커밋해 두는 걸 권해요. 그러면 도구가 실제로 어떤 변경을 했는지 더 쉽게 확인하고, 마음에 안 드는 변경은 되돌리기도 쉽거든요.
도구 실행 전에 커밋하고 싶지 않고 IntelliJ IDEA를 쓰고 있다면, File → Local History 기능을 써서 수정 적용 전과 후의 상태를 비교할 수 있어요. (자세한 내용은 Add labels to specific states Local History에서 볼 수 있어요.)
dart fix 명령이 모든 문제를 고쳐 주지는 못해요. 하지만 고칠 수 있을 때는, 모든 곳에 수동으로 수정을 적용하는 것보다 훨씬 쉬워요.
요약
여러 수정을 한 번의 작업으로 적용할 수 있게 되면 퀵 픽스의 힘이 커질 뿐 아니라, 새 린트 규칙을 활성화하거나 최신 Flutter 버전으로 업그레이드하는 것 같은 더 큰 규모의 변경도 더 쉬워져요.
언제나 그렇듯, 이 도구들을 개선할 아이디어가 있다면 dart-lang/sdk 이슈를 만들어 알려주세요.