Lint란?

Android 스튜디오에 있는 Lint라는 코드 스캔 도구를 사용하면 앱을 실행하거나 테스트 사례를 작성하지 않고도 코드의 구조적 문제를 식별하고 수정할 수 있습니다. 도구에서 탐지된 각 문제는 설명 메시지 및 심각도 수준과 함께 보고되므로 개선이 시급한 순서대로 신속히 우선순위를 결정할 수 있습니다. 또한 프로젝트와 관련이 없는 문제를 무시해서 문제의 심각도 수준을 낮추거나 특정 문제를 강조하여 심각도 수준을 높일 수 있습니다.

Lint 도구는 잠재적 버그를 찾아내고 정확성, 보안, 성능, 사용성, 접근성 및 국제성을 높이기 위해 Android 프로젝트 소스 파일을 검사합니다. Android 스튜디오를 사용할 경우 앱을 빌드할 때마다 구성된 Lint와 IDE 검사가 실행됩니다. 하지만 검사를 수동으로 실행하거나 명령줄에서 Lint를 실행할 수도 있습니다.

Lint가 버그를 찾기 위한 코드 탐색 워크플로는 아래와 같습니다.

 

 

안드로이드 스튜디오의 프로젝트에서는 기본적으로 포함된 lint가 활성화 되어있기 때문에, 문제가 발생할 상황에 있어서 오류를 검출하고 메시지를 출력합니다. 예를들어 Toast 객체를 만들기만 하고 show()를 호출 하지 않아. 화면에 토스트가 노출되지 않는 실수를 종종하곤 합니다. 이런 실수를 예방하기 위해 기본적으로 lint를 통해 다음과 같은 메시지를 출력하고있습니다.

프로젝트에 Custom Lint 설정하기

NOTE:아직 확정된 API들이 아니기 때문에 다음버전에는 변경될 수도 있습니다.

  1. custom lint를 위한 checker 모듈 생성
  2. 앱 모듈의 build.gradle 에 다음 내용을 추가하기
    dependencies {
        lintChecks project(':checker')// 모듈 이름 확인
    }
    
  3. 추가한 custom lint 모듈에 다음 내용을 추가하기
    apply plugin: 'java' //코틀린 사용자는 kotlin으로 변경
    dependencies {
        compileOnly "com.android.tools.lint:lint-api:26.5.0"
        compileOnly "com.android.tools.lint:lint-checks:26.5.0"
    }

    최신 버전은 이곳에서 확인
    https://mvnrepository.com/artifact/com.android.tools.lint/lint-gradle-api

  4. IssueRegistry 서브 클래스 생성하기
    public class CustomIssueRegistry extends IssueRegistry {
    
        @Override
        public int getApi() {
            return ApiKt.CURRENT_API;
        }
    
        @NotNull
        @Override
        public List<Issue> getIssues() {
            /**
             * 이슈 등록은 이곳에서
             */
            return Arrays.asList();
        }
    }
    
  5. custom lint 모듈의 build.gradle에 IssueRegistry 서브 클래스 등록하기
    dependencies{ ... }
    jar {
        manifest {
            attributes("Lint-Registry-v2": "com.charlezz.checker.CustomIssueRegistry")
        }
    }

    CanonicalName을 정확하게 적는다.

  6. lint 검사를 하고 싶은 이슈 클래스를 만들어서 등록 하기

이슈(Issue) 생성하기

이슈를 생성하기 위해서는 Issue.create(…) 메소드를 사용해야합니다.

메소드에서 다음의 매개변수들을 갖습니다.

  • id : 이슈의 고유 식별 문자열
  • briefDescription : 5-6 문자로 표현하는 문제의 짧은 요약을 적습니다.
  • explanation : 이슈에 대한 전체 설명과 제안사항을 이곳에 적습니다.
  • category : 이슈와 연관된 카테고리를 적습니다. 예 ) Category.CORRECTNESS
  • priority : 이슈의 우선순위를 결정합니다. 1~10 사이의 정수를 적습니다.
  • severity : 이슈의 심각도를 지정합니다.
    확정된 API는 아니지만 현재 다음과 같은 심각도를 사용할 수 있습니다.

    Severity.FATAL
    Severity.ERROR
    Severity.WARNING
    Severity.INFORMATIONAL
    Severity.IGNORE
  • implementation : 문제사항을 검출하는 비즈니스 로직을 포함한 Implementation 클래스를 구현합니다.

Detector 구현하기

Detector는 특정문제들을 검출하는 클래스인데, XmlScanner 또는 SourceCodeScanner 인터페이스와 함께 구현되지 않으면 동작하지 않습니다.

XmlScanner는 xml 파일 내의 코드에서 오류사항을 검출하고, SourceCodeScanner는 java파일 내의 코드에서 오류사항을 검출합니다.

설명을 위해 간단한 예제를 만들어 보겠습니다. 데이터바인딩 라이브러리의 사용을 강제하기 위해 Activity.setContentView() 호출을 막고,  DatabindingUtil.setContentView()를 사용하도록 유도하는 예제를 작성하겠습니다.

Detector와 SourceCodeScanner를 상속 하는 서브 클래스를 생성합니다.

public class SetContentViewDetector 
        extends Detector 
        implements SourceCodeScanner {

}

메소드이름이 setContentView이라는것에 초점을 맞춰 해당 메소드 명을 검출하는 코드를 다음과 같이 작성합니다.

@Nullable
@Override
public List<String> getApplicableMethodNames() {
    return Arrays.asList("setContentView");
}

getApplicableMethodName()은 반환하는 리스트에 있는 메소드 이름을 소스코드로 부터 검출 하는 역할을 합니다.

해당 메소드를 검출시 수행하는 코드를 작성하겠습니다.

public static final Issue ISSUE = Issue.create(
        SetContentViewDetector.class.getSimpleName(),
        "Prohibits usages of setContentView()",
        "Prohibits usages of setContentView(), use DataBindingUtil.setContentView() instead",
        Category.CORRECTNESS,
        5,
        Severity.ERROR,
        new Implementation(SetContentViewDetector.class, Scope.JAVA_FILE_SCOPE)
);

@Override
public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) {
    if (context.getEvaluator().isMemberInClass(method, "androidx.databinding.DataBindingUtil")) {
        return;
    }
    context.report(
            ISSUE,
            node,
            context.getLocation(node),
            "Use DataBindingUtil.setContentView() instead"
    );
}

visitMethodCall은 getApplicableMethodNames에서 반환된 메소드가 호출되면 호출되는 콜백메서드 입니다. JavaContext, UCallExpression, PsiMethod를 매개변수로 갖는데 각 매개변수에 대한 설명은 다음과 같습니다.

  • JavaContext : 분석한 자바 파일 에 대한 정보들을 가지고 있습니다.
  • UCallExpression : 호출된 메소드의 노드정보입니다.
  • PsiMethod : 호출된 메소드를 표현합니다.

setContentView가 검출되었다면 context.report(…) 호출을 통해 lint에게 에러를 알립니다. 주의해야할 점은 DatabindingUtil.setContentView 도 메소드 이름이 같기 때문에 데이터바인딩 클래스의 경우 report하지 않기 위해 Evaluator로 부터 메소드가 DatabindingUtil클래스에 속해 있는지 확인합니다.

이제 마지막 단계입니다. 생성한 이슈를 CustomIssueRegistry에 등록합니다.

public class CustomIssueRegistry extends IssueRegistry {
    ...
    @NotNull
    @Override
    public List<Issue> getIssues() {
        return Arrays.asList(
                SetContentViewDetector.ISSUE
        );
    }
}

모든 과정이 끝났다면 Clean Project를 하여 초기화를 한번 시켜줍니다. 이제 Activity.setContentView()를 호출하면 lint에러가 발생되는것을 확인하실 수 있습니다.

위 예제는 https://github.com/Charlezz/LintCheck 에서 확인 가능합니다.

TL;DR

Lint를 사용하여 빌드 및 런타임 이전에 오류를 검출할 수 있기 때문에, 개발의 생산성을 높이고 프로그램을 안정화 시킬 수 있습니다.

간단히 Java 코드를 정적 분석하는 예제를 소개했지만, xml 코드도 마찬가지로 XmlScanner통해 분석을 하고 오류를 검출 할 수 있습니다.

xml 코드 분석을 포함한 다양한 예제 및 코틀린 사용예제는

https://github.com/alexjlockwood/android-lint-checks-demo 에서 확인 가능합니다.

카테고리: JavaTutorial

1개의 댓글

dhparkz · 2020년 2월 4일 5:49 오후

신기하네요. 잘 읽고 갑니다 🙂

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.