정적 분석 커스터마이징하기

정적 분석 커스터마이징하기

분석 옵션 파일과 코드 주석을 사용해 정적 분석(static analysis)을 커스터마이징하는 방법을 안내하는 문서예요. analyzer를 활용해 버그를 예방하고 코드가 스타일 가이드에 맞는지 확인하는 법을 알려 드릴게요.

출처: Customizing static analysis

본문

정적 분석을 사용하면 코드를 한 줄도 실행하지 않고도 문제를 찾을 수 있어요. 이는 버그를 예방하고 코드가 스타일 가이드에 부합하는지 확인하는 강력한 도구예요.

analyzer의 도움으로 간단한 오타도 찾을 수 있어요. 예를 들어 if 문에 실수로 세미콜론이 들어갔다고 해볼게요.

void increment() {
  if (count < 10) ;
  count++;
}

제대로 구성하면 analyzer가 세미콜론을 가리키며 다음 경고를 만들어요.

info - example.dart:9:19 - Unnecessary empty statement. Try removing the empty statement or restructuring the code. - empty_statements

analyzer는 더 미묘한 문제도 찾아줘요. 예를 들어 sink 메서드를 닫는 것을 잊었을 수도 있죠.

var controller = StreamController<String>();
info - Unclosed instance of 'Sink'. Try invoking 'close' in the function in which the 'Sink' was created. - close_sinks

Dart 생태계에서 Dart Analysis Server와 그 밖의 도구들은 analyzer 패키지를 사용해 정적 분석을 수행해요.

Dart 언어 사양에 명시된 오류와 경고를 포함해 다양한 잠재적 문제를 찾도록 정적 분석을 커스터마이징할 수 있어요. 코드가 Dart Style Guide와 Effective Dart의 기타 권장 지침을 따르도록 린터 규칙도 구성할 수 있죠. dart analyze, flutter analyze, 그리고 IDE와 편집기 같은 도구는 analyzer 패키지를 사용해 코드를 평가해요.

이 문서는 분석 옵션 파일이나 Dart 소스 코드의 주석을 사용해 analyzer의 동작을 커스터마이징하는 방법을 설명해요. 도구에 정적 분석을 추가하고 싶다면 analyzer 패키지 문서와 Analysis Server API 사양을 참고하세요.

참고: 다양한 analyzer 진단과 설명 및 일반적인 수정 방법을 보려면 Diagnostic messages를 참고하세요.

분석 옵션 파일

분석 옵션 파일 analysis_options.yaml을 패키지의 루트, pubspec 파일과 같은 디렉터리에 두세요.

다음은 분석 옵션 파일의 예시예요.

include:
  - package:lints/recommended.yaml

analyzer:
  exclude: [build/**]
  language:
    strict-casts: true
    strict-raw-types: true

linter:
  rules:
    - cancel_subscriptions

이 예시는 가장 흔한 최상위 항목을 보여줘요.

  • include:를 사용해 다른 파일이나 패키지(package:lints/recommended.yaml이나 package:flutter_lints/flutter.yaml 같은)의 공유 분석 옵션을 가져와요. 단일 URI 또는 URI 목록을 지정해 여러 파일을 포함할 수 있어요. analyzer는 포함된 옵션을 순서대로 병합하며, 로컬 옵션이 우선해요.
  • analyzer:를 사용해 더 엄격한 타입 검사 활성화, 분석에서 파일 제외, 특정 규칙 무시, 규칙 심각도 변경, 실험 활성화 같은 정적 분석 검사를 커스터마이징해요.
  • linter:를 사용해 코딩 표준을 강제하고 잠재적 버그를 잡으며 모범 사례를 유지하기 위해 린터 규칙을 구성해요.

경고: YAML은 공백에 민감해요. YAML 파일에서는 탭을 사용하지 말고, 각 들여쓰기 수준에 공백 2개를 사용하세요.

analyzer가 패키지 루트에서 분석 옵션 파일을 찾지 못하면 디렉터리 트리를 거슬러 올라가며 찾아요. 파일이 없으면 analyzer는 표준 검사를 기본값으로 사용해요.

큰 프로젝트의 다음 디렉터리 구조를 생각해 보세요. analyzer는 file #1을 사용해 my_other_packagemy_other_other_package의 코드를, file #2를 사용해 my_package의 코드를 분석해요.

더 엄격한 타입 검사 활성화하기

Dart 타입 시스템이 요구하는 것보다 더 엄격한 정적 검사를 원한다면 strict-casts, strict-inference, strict-raw-types 언어 모드를 활성화하는 것을 고려해 보세요.

analyzer:
  language:
    strict-casts: true
    strict-inference: true
    strict-raw-types: true

이 모드들을 함께 사용할 수도 있고 따로 사용할 수도 있어요. 모두 기본값은 false예요.

strict-casts: <bool>

true 값은 타입 추론 엔진이 dynamic에서 더 구체적인 타입으로 암시적 캐스트를 절대 수행하지 않도록 보장해요. 다음 유효한 Dart 코드는 jsonDecode가 반환한 dynamic 값에서 List<String>으로의 암시적 다운캐스트를 포함해 런타임에 실패할 수 있어요. 이 모드는 잠재적 오류를 보고하며 명시적 캐스트를 추가하거나 코드를 조정하도록 요구해요.

void foo(List<String> lines) {
  ...
}

void bar(String jsonText) {
  foo(jsonDecode(jsonText)); // Implicit cast
}
error - The argument type 'dynamic' can't be assigned to the parameter type 'List<String>'. - argument_type_not_assignable

버전 참고: strict-casts 모드는 Dart 2.16에서 도입되었어요. 이전 SDK 릴리스에서 유사한 검사를 활성화하려면 현재는 더 이상 사용되지 않는 implicit-casts 옵션을 고려해 보세요.

analyzer:
  strong-mode:
    implicit-casts: false

strict-inference: <bool>

true 값은 타입 추론 엔진이 정적 타입을 결정할 수 없을 때 dynamic 타입을 절대 선택하지 않도록 보장해요. 다음 유효한 Dart 코드는 타입 인자를 추론할 수 없는 Map을 만들어 이 모드가 추론 실패 힌트를 내도록 만들어요.

final lines = {}; // Inference failure
lines['Dart'] = 10000;
lines['C++'] = 'one thousand';
lines['Go'] = 2000;
print('Lines: ${lines.values.reduce((a, b) => a + b)}'); // Runtime error
warning - The type argument(s) of 'Map' can't be inferred - inference_failure_on_collection_literal

팁: strict-inference 모드는 추론 실패를 초래하는 많은 상황을 식별할 수 있어요. 추론 실패 조건의 전체 목록은 strict inference failure 조건을 참고하세요.

strict-raw-types: <bool>

true 값은 타입 인자가 생략되어 정적 타입을 결정할 수 없을 때 타입 추론 엔진이 dynamic 타입을 절대 선택하지 않도록 보장해요. 다음 유효한 Dart 코드는 raw 타입의 List 변수를 가지고 있어 이 모드가 raw 타입 힌트를 내도록 만들어요.

List numbers = [1, 2, 3]; // List with raw type
for (final n in numbers) {
  print(n.length); // Runtime error
}
warning - The generic type 'List<dynamic>' should have explicit type arguments but doesn't - strict_raw_type

린터 규칙 활성화 및 비활성화하기

analyzer 패키지는 코드 린터도 제공해요. 아주 다양한 린터 규칙을 사용할 수 있어요. 린터는 논파(宗派)를 가리지 않는 경향이 있어서 규칙들이 서로 일치할 필요가 없어요. 예를 들어 어떤 규칙은 일반 Dart 패키지에 더 적합하고, 다른 규칙은 Flutter 앱을 위해 설계되었죠. 정적 분석과 달리 린터 규칙은 오탐(false positive)이 있을 수 있다는 점에 유의하세요.

Dart 팀이 권장하는 린터 규칙 활성화하기

Dart 팀은 lints 패키지에서 두 세트의 권장 린터 규칙을 제공해요.

  • Core rules — Dart 코드를 실행하거나 사용할 때 문제로 이어질 가능성이 높은 중대한 이슈를 식별하는 데 도움을 줘요. 모든 코드는 이 린터 규칙을 통과해야 해요. pub.dev에 업로드된 패키지는 이 규칙 통과 여부를 일부 기준으로 하는 패키지 점수가 있어요.
  • Recommended rules — Dart 코드를 실행하거나 사용할 때 문제로 이어질 수 있는 추가 이슈를 식별하고, 하나의 관용적인(idiomatic) 스타일과 형식을 강제해요. 모든 Dart 코드가 이 규칙을 사용하기를 권장하며, 이 규칙은 core 규칙의 상위 집합이에요.

팁: Flutter 코드를 작업한다면 lints 패키지 대신 권장 규칙의 상위 집합을 제공하는 flutter_lints를 사용하세요.

두 세트 중 하나를 활성화하려면 개발 의존성으로 lints 패키지를 추가하세요.

$ dart pub add --dev lints

그 다음 analysis_options.yaml 파일을 편집해 원하는 규칙 세트를 포함하세요.

include: package:lints/<RULE_SET>.yaml

예를 들어 다음과 같이 권장 규칙 세트를 포함할 수 있어요.

include: package:lints/recommended.yaml

중요: lints의 새 버전이 게시되면 이전에 분석을 통과했던 코드가 분석을 통과하지 못할 수 있어요. 새 규칙에 맞게 코드를 업데이트하는 것을 권장해요. 다른 방법으로는 개별 린터 규칙을 명시적으로 활성화하거나 개별 규칙을 비활성화하는 거예요.

참고: 옵션 파일 포함에 대한 자세한 내용은 Including shared options 섹션을 확인하세요.

개별 규칙 활성화하기

단일 린터 규칙을 활성화하려면 분석 옵션 파일에 최상위 키로 linter:를 추가하고, 두 번째 수준 키로 rules:를 추가하세요. 다음 줄에 적용할 규칙을 대시(YAML 목록 문법)로 접두사로 붙여 지정하세요. 예를 들면:

linter:
  rules:
    - always_declare_return_types
    - annotate_redeclares
    - cancel_subscriptions
    - close_sinks
    - combinators_ordering
    - comment_references
    - invalid_case_patterns
    - only_throw_errors
    - prefer_single_quotes

개별 규칙 비활성화하기

lints 같은 분석 옵션 파일을 포함한다면 포함된 규칙 중 일부를 비활성화하고 싶을 수 있어요. 개별 규칙 비활성화는 활성화와 비슷하지만 rules: 항목의 값으로 목록 대신 맵을 사용해야 해요. 그래서 각 줄에는 규칙 이름 뒤에 : false: true가 와야 해요.

lints의 모든 권장 규칙을 사용하되 avoid_shadowing_type_parameters만 제외한 분석 옵션 파일의 예시예요. 또한 await_only_futures 린트를 활성화해요.

include: package:lints/recommended.yaml

linter:
  rules:
    avoid_shadowing_type_parameters: false
    await_only_futures: true

참고: YAML 제한 때문에 같은 rules 항목에 목록과 키-값 문법을 섞을 수 없어요. 포함된 파일에서는 다른 문법을 규칙에 사용할 수 있어요.

공유 옵션 포함하기

분석 옵션 파일은 다른 옵션 파일이나 심지어 다른 옵션 파일 목록에 지정된 옵션을 포함할 수 있어요. 최상위 include: 필드를 사용해 그런 파일을 지정할 수 있어요.

include: package:flutter_lints/recommended.yaml

포함된 옵션 파일은 package: URI나 상대 경로로 지정할 수 있어요. 여러 분석 옵션 파일을 포함하려면 목록을 사용하세요.

include:
  - package:flutter_lints/recommended.yaml
  - ../team_options.yaml

여러 파일을 포함할 때 analyzer는 옵션을 목록 순서대로 병합해요.

  1. 첫 번째 포함 파일이 재귀적으로 포함하는 파일을 포함해 평가돼요.
  2. 각 후속 포함 파일이 순서대로 적용되어 새 옵션을 추가하거나 충돌하는 옵션을 덮어써요.
  3. 마지막으로 현재 analysis_options.yaml 파일에 직접 정의된 옵션이 포함된 옵션을 덮어써요.

옵션을 병합할 때 analyzer는 전체 섹션을 교체하는 대신 호환 가능한 설정을 결합해요. 예를 들어 나중 파일에서 개별 린터 규칙을 활성화하면 포함된 규칙 세트의 나머지는 버리지 않고 이전 파일의 규칙에 추가하거나 덮어써요.

예를 들어 다음과 같은 옵션 파일이 있다고 해볼게요.

include: two.yaml
# ...

그리고 이들을 포함하는 최종 옵션 파일:

include:
  - one.yaml
  - three.yaml
# ...

그러면 analyzer는 one.yaml, two.yaml, three.yaml, 마지막으로 analysis_options.yaml의 옵션을 순서대로 적용해요.

분석기 플러그인 활성화하기 (실험적)

참고: 이것은 레거시 플러그인 시스템으로, 향후 릴리스에서 더 이상 사용되지 않을 예정이에요. 새 개발에는 새 분석기 플러그인 시스템을 사용하세요.

analyzer는 레거시 플러그인에 대한 실험적 지원을 가져요. 이 플러그인들은 analyzer와 통합되어 새 진단, 빠른 수정, 커스텀 코드 완성 같은 기능을 추가해요. analysis_options.yaml 파일당 플러그인 하나만 활성화할 수 있어요. 레거시 분석기 플러그인을 활성화하면 analyzer가 사용하는 메모리가 늘어나요.

다음 조건 중 하나에 해당하면 레거시 분석기 플러그인을 사용하지 마세요.

  • 16GB 미만의 메모리를 가진 개발 머신을 사용하는 경우.
  • pubspec.yamlanalysis_options.yaml 파일이 10개가 넘는 모노레포를 사용하는 경우.

pub.dev에서 몇 가지 레거시 분석기 플러그인을 찾을 수 있어요.

레거시 플러그인을 활성화하려면:

  1. 플러그인이 포함된 패키지를 개발 의존성으로 추가하세요.
$ dart pub add --dev <your_favorite_analyzer_plugin_package>
  1. analysis_options.yaml 파일을 편집해 플러그인을 활성화하세요.
analyzer:
  plugins:
    - your_favorite_analyzer_plugin_package

새 진단 같은 특정 플러그인 기능을 활성화하려면 추가 설정이 필요할 수 있어요.

분석에서 코드 제외하기

때로는 어떤 코드가 분석을 통과하지 못해도 괜찮은 경우가 있어요. 예를 들어 내가 소유하지 않은 패키지가 생성한 코드에 의존할 수 있는데, 생성된 코드가 작동하지만 정적 분석 중 경고를 만들어 낼 수 있어요. 아니면 억제하고 싶은 오탐을 린터 규칙이 만들 수도 있죠.

분석에서 코드를 제외하는 방법은 몇 가지가 있어요.

  • 분석에서 파일 전체를 제외.
  • 특정 비오류 규칙이 개별 파일에 적용되지 않도록 중지.
  • 특정 비오류 규칙이 개별 코드 줄에 적용되지 않도록 중지.

모든 파일에 대해 특정 규칙을 비활성화하거나 규칙의 심각도를 변경할 수도 있어요.

파일 제외하기

파일을 정적 분석에서 제외하려면 exclude: 분석기 옵션을 사용하세요. 개별 파일이나 glob 패턴 문법을 나열할 수 있어요. glob 패턴의 모든 사용은 analysis_options.yaml 파일이 있는 디렉터리를 기준으로 해야 해요.

analyzer:
  exclude:
    - lib/client.dart
    - lib/server/*.g.dart
    - test/_data/**

파일에 대한 진단 억제하기

특정 파일에서 특정 비오류 진단을 무시하려면 해당 파일에 ignore_for_file 주석을 추가하세요.

// ignore_for_file: unused_local_variable

이것은 주석 앞뒤의 파일 전체에 적용되며 특히 생성된 코드에 유용해요.

둘 이상의 진단을 억제하려면 쉼표로 구분된 목록을 사용하세요.

// ignore_for_file: unused_local_variable, duplicate_ignore, dead_code

모든 린터 규칙을 억제하려면 type=lint 지정자를 추가하세요.

// ignore_for_file: type=lint

버전 참고: type=lint 지정자 지원은 Dart 2.15에서 추가되었어요.

코드 줄에 대한 진단 억제하기

특정 Dart 코드 줄에서 특정 비오류 진단을 억제하려면 코드 줄 위에 ignore 주석을 두세요. 언어 테스트에서 하듯 런타임 오류를 일으키는 코드를 무시하는 예시예요.

// ignore: invalid_assignment
int x = '';

둘 이상의 진단을 억제하려면 쉼표로 구분된 목록을 제공하세요.

// ignore: invalid_assignment, const_initialized_with_non_constant_value
const x = y;

또는 ignore 주석을 적용되는 줄 끝에 붙일 수도 있어요.

int x = ''; // ignore: invalid_assignment

pubspec 파일에서 진단 억제하기

pubspec.yaml 파일에서 analyzer의 비오류 진단을 억제해야 한다면 해당 줄 위에 ignore 주석을 추가하세요.

다음 예시는 flutter 의존성을 맨 앞에 두려 하는 sort_pub_dependencies 린트를 무시해요.

dependencies:
  flutter:
    sdk: flutter

  # ignore: sort_pub_dependencies
  collection: ^1.19.0

버전 참고: pubspec.yaml 파일의 ignore 주석 지원은 Dart 3.3에서 추가되었어요. Dart 3.2 이하를 사용하면 무시된 진단이 여전히 발생해요.

분석 규칙 커스터마이징하기

각 analyzer 진단과 린터 규칙은 기본 심각도를 가져요. 분석 옵션 파일을 사용해 개별 규칙의 심각도를 변경하거나 일부 규칙을 항상 무시할 수 있어요.

analyzer는 세 가지 심각도 수준을 지원해요.

  • info — 분석을 실패시키지 않는 정보성 메시지예요. 예: dead_code
  • warning — analyzer가 경고를 오류로 취급하도록 구성되지 않는 한 분석을 실패시키지 않는 경고예요. 예: invalid_null_aware_operator
  • error — 분석을 실패시키는 오류예요. 예: invalid_assignment

규칙 무시하기

errors: 필드를 사용해 특정 analyzer 진단과 린터 규칙을 무시할 수 있어요. 규칙 뒤에 : ignore를 나열하세요. 예를 들어 다음 분석 옵션 파일은 분석 도구에게 TODO 규칙을 무시하도록 지시해요.

analyzer:
  errors:
    todo: ignore

규칙의 심각도 변경하기

특정 규칙의 심각도를 전역적으로 변경할 수 있어요. 이 기법은 일반 분석 문제와 린트 모두에 적용돼요. 예를 들어 다음 분석 옵션 파일은 분석 도구에게 잘못된 할당을 경고로, 누락된 반환을 오류로 취급하고, 죽은 코드에 대해 정보(경고나 오류가 아닌)를 제공하도록 지시해요.

analyzer:
  errors:
    invalid_assignment: warning
    missing_return: error
    dead_code: info

dart format 구성하기

분석 옵션 파일에 formatter 섹션을 추가해 dart format의 동작을 구성할 수 있어요.

구성 가능한 옵션

page_width

선호하는 page_width(줄 길이)를 지정할 수 있어요. 기본값은 80자예요.

자세한 내용은 포맷터 page width 구성에 대해 읽어 보세요.

trailing_commas

trailing_commas 옵션을 사용해 포맷터가 인자 및 매개변수 목록에서 후행 쉼표를 처리하는 방식을 구성할 수 있어요. 다음 두 값 중 하나를 받아요.

  • automate (기본값) — 포맷터가 주변 구성을 분할하기로 결정한 것에 따라 후행 쉼표를 추가하고 제거해요.
  • preserve — 후행 쉼표가 주변 구성을 강제로 분할해요. 포맷터는 구성을 분할할 때 후행 쉼표를 추가하지만 제거하지는 않아요.
formatter:
  page_width: 120
  trailing_commas: preserve

리소스

다음 리소스를 사용해 Dart의 정적 분석에 대해 더 배워 보세요.

더 알아보기