Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Make Lint Great Again

Make Lint Great Again

Кодревью – как много в этом слове! Согласитесь, было бы здорово, если бы кодревью было сосредоточено чисто на архитектурных проблемах и потенциальных багах в логике, забыв про всякие небольшие нюансы в духе контрактов определенных классов. И как было бы здорово, если бы про эти нюансы можно было бы намекнуть разработчику ещё в процессе разработки, при этом не стоя у него за плечом и не заглядывая в его монитор. И тут на помощь приходит Lint с огромным числом проверок из коробки. Но на этом Lint не заканчивается – его можно дополнить своими проверками, с одним нюансом – сперва нужно понять что и как делать, потому что с документацией всё не очень радостно.

В основном про расширяемость Lint'а мы и поговорим – зачем оно вообще нужно, как оно было раньше, как оно есть сейчас, как всё это дело тестировать и дебажить.

MOSDROID

May 21, 2017
Tweet

More Decks by MOSDROID

Other Decks in Programming

Transcript

  1. 4

  2. 5 Ловим проблемы Проблема Инструмент Важность Codestyle Checkstyle Потенциальные баги

    Тесты, статический анализ, ... раньше узнаем ≈ раньше исправим
  3. 6 Lint ./gradlew lint :app:lint MainActivity.java:22: Error: Call requires API

    level 21: android.view.View#setTranslationZ [NewApi] fab.setTranslationZ(10f); ~~~~~~~~~~~~~~~ Ran lint on variant release: 4 issues found Ran lint on variant debug: 4 issues found Wrote HTML report to /app/build/reports/lint-results.html Wrote XML report to /app/build/reports/lint-results.xml :app:lint FAILED ./gradlew lintDebug
  4. 7 Lint <?xml version="1.0" encoding="UTF-8"?> <issues format="4" by="lint 3.0.0-alpha1"> <issue

    id="NewApi" severity="Error" message="Call requires API level 21: android.view.View#setTranslationZ" category="Correctness" priority="6" summary="Calling new methods on older versions" explanation="Very long explanation of what you’ve done wrong" errorLine1=" fab.setTranslationZ(10f);" errorLine2=" ~~~~~~~~~~~~~~~" quickfix="studio"> <location file=".../MainActivity.java" line="22" column="13"/> </issue> </issues>
  5. 9 Настраиваем Lint android { lintOptions { abortOnError true warningsAsErrors

    false quiet false htmlReport true xmlReport false textReport true explainIssues false ignoreWarnings false lintConfig file("lint.xml") } }
  6. 10 Config <?xml version="1.0" encoding="UTF-8"?> <lint> <issue id="IconMissingDensityFolder" severity="error" />

    <issue id="NewApi" severity="ignore" /> <issue id="ObsoleteLayoutParam"> <ignore path="res/layout/registration.xml" /> <ignore path="res/layout-sw360dp/registration.xml" /> </issue> </lint>
  7. 11 Baseline android { lintOptions { baseline file("lint-baseline.xml") } }

    :app:lintDebug Wrote HTML report to /app/build/reports/lint-results-debug.html Wrote XML report to/app/build/reports/lint-results-debug.xml Lint found no new issues (1 error filtered by baseline lint- baseline.xml)
  8. 12

  9. 14 Пишем собственные инспекции apply plugin: 'java' repositories { jcenter()

    } dependencies { compile 'com.android.tools.lint:lint-api:25.3.0' testCompile 'com.android.tools.lint:lint-tests:25.3.0' } jar { manifest { attributes 'Manifest-Version': 1.0 attributes 'Lint-Registry': 'ru.ok.lint.OkIssueRegistry' } }
  10. 15 Пишем собственные инспекции @SuppressWarnings("unused") public class OkIssueRegistry extends IssueRegistry

    { @Override public List<Issue> getIssues() { return Arrays.asList( MissingBatchIdDetector.ISSUE ); } }
  11. 16 Пишем собственные инспекции public static final Issue ISSUE =

    Issue.create( "MissingBatchId", "Batch request doesn't have id", "Every batch request need id due to statistics", Category.CORRECTNESS, 6, Severity.FATAL, new Implementation(MissingBatchIdDetector.class, Scope.JAVA_FILE_SCOPE) );
  12. 17 Пишем собственные инспекции public class MissingBatchIdDetector extends Detector implements

    JavaPsiScanner { private static final String BUILD = "build"; @Override public List<String> getApplicableMethodNames() { return Arrays.asList(BUILD); } }
  13. 18 Пишем собственные инспекции @Override void visitMethod(@NonNull JavaContext context, @Nullable

    JavaElementVisitor visitor, @NonNull PsiMethodCallExpression call, @NonNull PsiMethod method) { if (isBatchCreation(context, calledMethod)) { if (isIdSetInChainedCalls(context, node) || checkIdPresent(context, node, calledMethod)) { return; } context.report(ISSUE, node, context.getNameLocation(node), "Missing batch id"); }
  14. 19 Пишем собственные инспекции boolean isBatchCreation(JavaContext context, PsiMethod method) {

    final String methodName = method.getName(); if (BUILD.equals(methodName)) { PsiClass containingClass = method.getContainingClass(); JavaEvaluator evaluator = context.getEvaluator(); return evaluator.extendsClass(containingClass, BATCH_BUILDER, false); } return false; }
  15. 20 Пишем собственные инспекции boolean isIdSetInChainedCalls(@NonNull JavaContext context, @NonNull PsiMethodCallExpression

    node) { Queue<PsiElement> childQueue = new ArrayDeque<>(node.getChildren()); while (!childQueue.isEmpty()) { PsiElement child = childQueue.remove(); if (child instanceof PsiMethodCallExpression && isSetIdCall(context, (PsiMethodCallExpression) child)) { return true; } childQueue.addAll(child.getChildren()); } return false; }
  16. 22 Тестируем public class MissingBatchIdTest extends LintDetectorTest { @Override protected

    Detector getDetector() { return new MissingBatchIdDetector(); } @Override protected List<Issue> getIssues() { return Collections.singletonList( MissingBatchIdDetector.ISSUE); } }
  17. 23 Тестируем public void testMissingBatchId() throws Exception { lint().files( java("..."),

    java("...")) .run() .expect("src/test/pkg/SampleClass.java:7: Error: Missing batch id\n" + " .build();\n" + " ~~~~~\n" + "src/test/pkg/SampleClass.java:18: Error: Missing batch id\n" + " builder.build();\n" + " ~~~~~\n" + "2 errors, 0 warnings"); }
  18. 24 Тестируем: особенности • Можно дебажить во время тестов •

    Все используемые классы должны быть в classpath’е • нужные Android’ные классы – нужен $ANDROID_HOME • нужны свои классы – начинается веселье
  19. 25 Включаем • Пакуем всё в .aar, прописываем в модуле

    зависимость • Нужно явно прописывать зависимость – а если хочется иметь инспекцию независимо от модуля? ¯\_(ツ)_/¯ • Пакуем .jar и кладём в ~/.android/lint • Каждый разработчик должен сделать это сам – а если забудет/поменяет компьютер/…?
  20. 26

  21. 27 А Kotlin? • Использует переписанный Lint внутри – вместо

    Psi внутри uast • Пока про миграцию на uast ничего не слышно
  22. 28 Заключение • Меньше багов – это всегда хорошо •

    Чем раньше мы выявим баг, тем раньше сможем его исправить • Lint предоставляет удобные инструменты для раннего выявления потенциальных проблем, плохую документацию и хорошую обратную совместимость