Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lint в помощь / Григорий Джанелидзе (Одноклассники)

171 views

Published on

РИТ++ 2017, AppsConf
Зал Касабланка, 6 июня, 15:00

Тезисы:
http://appsconf.ru/2017/abstracts/2819.html

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

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

Lint в помощь / Григорий Джанелидзе (Одноклассники)

  1. 1. Григорий Джанелидзе Одноклассники 2017 Lint в помощь
  2. 2. 2 Mars Climate Orbiter – ожидания
  3. 3. 3 Mars Climate Orbiter - реальность
  4. 4. 4
  5. 5. 5 Ловим проблемы Проблема Инструмент Важность Codestyle Checkstyle 🤔 Потенциальные баги Тесты, статический анализ, ... 😵 раньше узнаем ≈ раньше исправим
  6. 6. 6 Lint ./gradlew lint :app:lint MainActivity.java:22: Error: Call requires API level 21: andro 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
  7. 7. 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>
  8. 8. 8 Lint
  9. 9. 9 Настраиваем Lint android { lintOptions { abortOnError true warningsAsErrors false quiet false htmlReport true xmlReport false textReport true explainIssues false ignoreWarnings false lintConfig file("lint.xml") } }
  10. 10. 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>
  11. 11. 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-base
  12. 12. 12
  13. 13. 13 BatchApiRequest batchApiRequest = BatchApiRequest .batchBuilder() .id(loginRequest.METHOD_ID) .add(loginRequest) .add(syncRequest) .build();
  14. 14. 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' } }
  15. 15. 15 Пишем собственные инспекции public class OkIssueRegistry extends IssueRegistry { @Override public List<Issue> getIssues() { return Arrays.asList( MissingBatchIdDetector.ISSUE ); } }
  16. 16. 16 Пишем собственные инспекции 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) );
  17. 17. 17 Пишем собственные инспекции public class MissingBatchIdDetector extends Detector implements JavaPsiScanner { @Override public List<String> getApplicableMethodNames() { return Arrays.asList(BUILD); } }
  18. 18. 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"); }
  19. 19. 19 Пишем собственные инспекции boolean isBatchCreation(JavaContext context, PsiMethod method) { 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; }
  20. 20. 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; }
  21. 21. 21 PsiViewer
  22. 22. 22 Тестируем public class MissingBatchIdTest extends LintDetectorTest { @Override protected Detector getDetector() { return new MissingBatchIdDetector(); } @Override protected List<Issue> getIssues() { return Collections.singletonList( MissingBatchIdDetector.ISSUE); } }
  23. 23. 23 Тестируем public void testMissingBatchId() throws Exception { lint().files( java("..."), java("...")) .run() .expect("src/test/pkg/SampleClass.java:7: Error: Missing batch idn" + " .build();n" + " ~~~~~n" + "src/test/pkg/SampleClass.java:18: Error: Missing batch idn" + " builder.build();n" + " ~~~~~n" + "2 errors, 0 warnings"); }
  24. 24. 24 Тестируем: особенности • Можно дебажить во время тестов • Все используемые классы должны быть в classpath’е • нужные Android’ные классы – нужен $ANDROID_HOME • нужны свои классы – начинается веселье
  25. 25. 25 Включаем • Пакуем всё в .aar, прописываем в модуле зависимость • Нужно явно прописывать зависимость – а если хочется иметь инспекцию независимо от модуля? ¯_(ツ)_/¯ • Пакуем .jar и кладём в ~/.android/lint • Каждый разработчик должен сделать это сам – а если забудет/поменяет компьютер/…?
  26. 26. 26
  27. 27. 27 А Kotlin? • Использует переписанный Lint внутри – вместо Psi внутри uast • Пока про миграцию на uast ничего не слышно
  28. 28. 28 Заключение • Меньше багов – это всегда хорошо • Чем раньше мы выявим баг, тем раньше сможем его исправить • Lint предоставляет удобные инструменты для раннего выявления потенциальных проблем, плохую документацию и хорошую обратную совместимость
  29. 29. 29 Что почитать? android.googlesource.com/platform/tools/base/+/studio-master-dev/lint/libs/
  30. 30. Cпасибо за внимание. Вопросы?

×