$30 off During Our Annual Pro Sale. View Details »

EqualsVerifier, ErrorProne и все-все-все (Гейзенбаг Питер 2018)

EqualsVerifier, ErrorProne и все-все-все (Гейзенбаг Питер 2018)

https://asatarin.github.io/talks/equals-verifier-and-error-prone/

Самый лучший вид тестов — это тесты, которые почти не надо писать, но которые при этом находят баги. Расскажем о двух инструментах для Java, которые позволяют приблизиться к этому идеалу. Первый из них — библиотека EqualsVerifier для тестирования контракта методов equals() и hashCode(). Второй инструмент — ErrorProne от Google, надстройка над компилятором Java, которая позволяет находить типичные ошибки в вашем коде.

Доклад будет полезен тестировщикам и разработчикам, разрабатывающим проекты на Java.

Andrey Satarin

May 19, 2018
Tweet

More Decks by Andrey Satarin

Other Decks in Technology

Transcript

  1. EqualsVerifier, ErrorProne 

    и все, все, все …
    Андрей Сатарин

    @asatarin

    View Slide

  2. О чем я сегодня расскажу
    • EqualsVerifier
    • Зачем он нужен
    • Как он может вам помочь
    • ErrorProne
    • Что это
    • Какие проблемы решает
    2

    View Slide

  3. Зачем все это нужно?
    • Есть куча низкоуровневых ошибок, которые сложно найти
    • Эти ошибки могут очень сильно влиять на видимое поведение
    • Так же эти ошибки приводят к общему “гниению” кода
    3

    View Slide

  4. @Test
    public void setSizeIsTwo() {
    final Set set = new HashSet!<>();
    final Value a = new Value(0, 0);
    final ChildValue b = new ChildValue(0, 0, -29791);
    set.add(a);
    set.add(b);
    assertEquals(2, set.size());
    }
    4

    View Slide

  5. @Test
    public void setSizeIsTwo() {
    final Set set = new HashSet!<>();
    final Value a = new Value(0, 0);
    final ChildValue b = new ChildValue(0, 0, -29791);
    set.add(a);
    set.add(b);
    assertEquals(2, set.size());
    }
    5

    View Slide

  6. @Test
    public void setSizeIsTwo() {
    final Set set = new HashSet!<>();
    final Value a = new Value(0, 0);
    final ChildValue b = new ChildValue(0, 0, -29791);
    set.add(b);
    set.add(a);
    assertEquals(2, set.size());
    }
    6

    View Slide

  7. Часть I
    EqualsVerifier
    7

    View Slide

  8. Object!::equals
    public boolean equals(Object obj) {
    return (this !== obj);
    }
    8

    View Slide

  9. final Object a = new Object();
    final Object b = new Object();
    assertTrue(a !== a);
    assertTrue(a.equals(a));
    assertFalse(a !== b);
    assertFalse(a.equals(b));
    9

    View Slide

  10. Integer!::equals
    public boolean equals(Object obj) {
    if (obj instanceof Integer) {
    return value !== ((Integer)obj).intValue();
    }
    return false;
    }
    10

    View Slide

  11. final Integer x = new Integer(43534);
    final Integer y = new Integer(43534);
    assertTrue(x !== x);
    assertTrue(x.equals(x));
    assertFalse(x !== y);
    assertTrue(x.equals(y));
    11

    View Slide

  12. final Integer x = new Integer(43534);
    final Integer y = new Integer(43534);
    assertTrue(x !== x);
    assertTrue(x.equals(x));
    assertFalse(x !== y);
    assertTrue(x.equals(y));
    12

    View Slide

  13. • Иногда в нашем коде приходится реализовывать equals()
    • Где есть реализация, там нужны и тесты
    13

    View Slide

  14. Кто помнит три свойства, которым
    должна удовлетворять корректная
    реализация equals()?
    14

    View Slide

  15. I know Java 

    equals contract
    15

    View Slide

  16. Три Четыре свойства
    • Рефлексия (reflexive)
    • Симметрия (symmetric)
    • Транзитивность (transitive)
    • Консистентность (consistent)
    16

    View Slide

  17. Рефлексия
    Для любого a, всегда верно a.equals(a)
    a !== a
    17

    View Slide

  18. Симметрия
    Если верно a.equals(b)

    !=> тогда верно b.equals(a)
    a !== b !=> b !== a
    18

    View Slide

  19. Транзитивность
    Если верно a.equals(b) и b.equals(c) 

    !=> тогда верно a.equals(с)
    !=>
    a !== b
    b !== c
    a !== c
    19

    View Slide

  20. Консистентность
    Если a.equals(b) сейчас 

    !=> тогда a.equals(b) в будущем
    a !== b !=> a !== b
    20

    View Slide

  21. Josh Bloch, Effective Java
    “A common source of bugs is the failure to override the
    hashCode method. You must override hashCode in every class
    that overrides equals. ”
    21

    View Slide

  22. Поэтому дальше я говорю про оба метода
    • Object::equals
    • Object::hashCode
    equals() + hashCode()
    22

    View Slide

  23. Пример класса
    public class ParsedURL {
    private final Protocol protocol;
    private final String hostname;
    private final int port;
    private final String path;
    private final Map parameters;

    }
    23

    View Slide

  24. Как все это тестировать?
    24

    View Slide

  25. Вариант 1
    Никак не тестировать:
    • Просто и быстро
    • Не надежно
    25

    View Slide

  26. Вариант 2
    Очень простые тесты:
    • Относительно просто и быстро
    • Все еще не надежно
    26

    View Slide

  27. Примеры “простых” тестов
    • 1 на равенство двух не идентичных объектов
    • 5 на неравенство двух разных объектов
    • 1 на рефлексию объекта с самим собой
    • 1 на симметрию двух равных объектов
    • 1 на консистентность (как это проверить вообще)?
    27

    View Slide

  28. @Test
    public void testSimple() {
    final ParsedURL u1 = new ParsedURL(:));
    final ParsedURL u2 = new ParsedURL(:D);
    assertNotEquals(u1, u2);
    }
    “Простой” тест
    28

    View Slide

  29. • Получилось примерно 10 тестов
    • Достаточно ли этого?
    29

    View Slide

  30. Таких тестов недостаточно
    • Что с классами-потомками и классами-предками?
    • Что с double/float полями?
    • Что с nullable полями?
    • Что будет при изменении класса?
    30

    View Slide

  31. Таких тестов недостаточно
    • Что с классами-потомками и классами-предками?
    • Что с double/float полями?
    • Что с nullable полями?
    • Что будет при изменении класса?
    • Мы забыли про Object::hashCode
    31

    View Slide

  32. Вариант 3
    Тесты с EqualsVerifier:
    • Просто и быстро
    • Очень надежно
    32

    View Slide

  33. Пример теста с EqualsVerifier
    @Test
    public void testEqualsContract() {
    EqualsVerifier
    .forClass(Value.class)
    .verify();
    }
    33

    View Slide

  34. • Один (!) тест с EqualsVerifier дает 100% покрытие по строкам кода
    • Если это не так — EqualsVerifier выдаст ошибку
    34

    View Slide

  35. Пример 1

    Хороший equals при плохом классе
    35

    View Slide

  36. public final class Value {
    private int x;
    private int y;
    public Value(int x, int y) {
    this.x = x;
    this.y = y;
    }

    @Override
    public boolean equals(Object o) {
    if (this !== o) return true;
    if (!(o instanceof Value)) return false;
    Value value = (Value) o;
    return x !== value.x !&& y !== value.y;
    }
    }
    36

    View Slide

  37. java.lang.AssertionError:
    Mutability: equals depends on mutable field x.
    37

    View Slide

  38. Чем это опасно?
    @Test
    public void testValueStaysInSet() {
    final Set set = new HashSet!<>();
    final Value a = new Value(123, 789);
    set.add(a);
    assertTrue(set.contains(a));
    a.setX(0);
    assertFalse(set.contains(a));
    }
    38

    View Slide

  39. Как починить?
    1. Починить код
    2. Рассказать тестам, что так и надо
    39

    View Slide

  40. public final class Value {
    private final int x;
    private final int y;
    public Value(int x, int y) {
    this.x = x;
    this.y = y;
    }

    @Override
    public boolean equals(Object o) {
    if (this !== o) return true;
    if (!(o instanceof Value)) return false;
    Value value = (Value) o;
    return x !== value.x !&& y !== value.y;
    }
    }
    40

    View Slide

  41. 2. Рассказать тестам, что так и надо
    @Test
    public void testLooseEqualsContract() {
    EqualsVerifier
    .forClass(Value.class)
    .suppress(Warning.NONFINAL_FIELDS)
    .verify();
    }
    41

    View Slide

  42. Пример 2

    Сын за отца в ответе
    42

    View Slide

  43. public class ChildValue extends Value {
    private int z;
    public ChildValue(int x, int y, int z) {
    super(x, y);
    this.z = z;
    }
    @Override
    public boolean equals(Object o) {
    if (this !== o) return true;
    if (!(o instanceof ChildValue))
    return false;
    if (!super.equals(o)) return false;
    ChildValue that = (ChildValue) o;
    return z !== that.z;
    }
    43

    View Slide

  44. java.lang.AssertionError: Symmetry:

    ChildValue{z=1, x=1, y=1} 

    does not equal superclass instance

    Value{x=1, y=1}
    44

    View Slide

  45. java.lang.AssertionError: Symmetry:

    ChildValue{z=1, x=1, y=1} 

    does not equal superclass instance

    Value{x=1, y=1}
    45

    View Slide

  46. Тест, который провел EqualsVerifier
    final Value a = new Value(1, 1);
    final ChildValue b = new ChildValue(1, 1, 1);
    assertTrue("a, b", a.equals(b));
    assertFalse("b, a", b.equals(a));
    46

    View Slide

  47. Чем это опасно?
    @Test
    public void setSizeIsTwo() {
    final Set set = new HashSet!<>();
    final Value a = new Value(0, 0);
    final ChildValue b = new ChildValue(0, 0, -29791);
    set.add(a);
    set.add(b);
    assertEquals(2, set.size());
    }
    47

    View Slide

  48. @Test
    public void setSizeIsTwo() {
    final Set set = new HashSet!<>();
    final Value a = new Value(0, 0);
    final ChildValue b = new ChildValue(0, 0, -29791);
    set.add(b);
    set.add(a);
    assertEquals(1, set.size());
    }
    Чем это опасно?
    48

    View Slide

  49. Как чинить асимметрию?
    public boolean canEqual(Object other) {

    }

    https://www.artima.com/lejava/articles/equality.html
    49

    View Slide

  50. Типичная ошибка — реализуем
    equals() специально для тестов
    50

    View Slide

  51. • Очень хотим вот так писать

    assertEquals(expected, actual);
    • Это плохой equals():
    • Не несет смысл, важный для доменной области
    • Будет незаметно использован в ПРОД коде
    51
    Реализуем equals() для тестов

    View Slide

  52. Вот так хорошо
    assertTrue(
    EqualsBuilder.reflectionEquals(
    expected, actual
    )
    );
    52

    View Slide

  53. А вот так лучше
    assertThat(
    actual,

    Matchers.reflectionEquals(expected)
    );
    53

    View Slide

  54. Типичные возражения
    54

    View Slide

  55. Типичные возражения
    • Я использую EqualsBuilder и HashCodeBuilder
    • Я использую кодогенерацию из IDEA
    55

    View Slide

  56. Часть проблем остается
    • Проблемы с мутабельными полями остаются
    • Проблемы с наследниками остаются
    • Возможные ошибки при добавлении полей остаются
    • Возможно ошибок нет сейчас, но они могут появится в будущем
    56

    View Slide

  57. Альтернативные решения
    • Google Auto Value
    • @EqualsAndHashCode из Lombok
    • Дождаться появления value классов в Java
    57

    View Slide

  58. EqualsVerifier выводы
    • 100% покрытие одной строчкой кода
    • Не надо писать новые тесты при изменении класса
    • (Почти) Не надо помнить детали контракта equals()
    • Все это гарантируется не только сегодня, но и в будущем
    • Бесплатно как “бесплатное пиво”
    58

    View Slide

  59. Отлично, есть относительно простой способ искать ошибки в очень
    небольшой части кода. 


    А хочется очень простой способ искать ошибки по всему коду.
    59

    View Slide

  60. Часть II
    ErrorProne
    60

    View Slide

  61. Статические анализаторы
    • FindBugs
    • SonarQube
    • PMD
    61

    View Slide

  62. ErrorProne — компилятор на
    стероидах
    62

    View Slide

  63. Как работает ErrorProne
    Исходный

    код
    Байт код 

    в jar’ах
    Компилятор 

    (javac)
    Класс файлы
    63

    View Slide

  64. Как работает ErrorProne
    Исходный

    код
    Байт код 

    в jar’ах
    Компилятор 

    (javac)
    Класс файлы
    Компилятор 

    (javac + ErrorProne)
    Класс файлы

    (еще раз)
    64

    View Slide

  65. Интеграция
    • IntelliJ IDEA
    • Ant
    • Maven
    • Gradle
    • Bazel
    65

    View Slide

  66. http://errorprone.info/
    “Using Error Prone to augment the compiler’s type analysis, you
    can catch more mistakes before they cost you time, or end up
    as bugs in production. 

    We use Error Prone in Google’s Java build system 

    to eliminate classes of serious bugs from entering our code,
    and we’ve open-sourced it, so you can too!”
    66

    View Slide

  67. http://errorprone.info/
    “Using Error Prone to augment the compiler’s type analysis, you
    can catch more mistakes before they cost you time, or end up
    as bugs in production. 

    We use Error Prone in Google’s Java build system 

    to eliminate classes of serious bugs from entering our code,
    and we’ve open-sourced it, so you can too!”
    67

    View Slide

  68. Doug Lea
    “Definitely embarrassing. I guess I’m back to liking Error Prone
    even though it sometimes annoys me :-)”
    68

    View Slide

  69. Уровень 1

    Подключил, починил и забыл
    69

    View Slide

  70. AndroidInjectionBeforeSuper

    AndroidInjection.inject() should always be invoked before calling super.lifecycleMethod()

    ArrayEquals

    Reference equality used to compare arrays

    ArrayFillIncompatibleType

    Arrays.fill(Object[], Object) called with incompatible types.

    ArrayHashCode

    hashcode method on array does not hash array contents

    ArrayToString

    Calling toString on an array does not provide useful information

    ArraysAsListPrimitiveArray

    Arrays.asList does not autobox primitive arrays, as one might expect.

    AsyncCallableReturnsNull

    AsyncCallable should not return a null Future, only a Future whose result is null.

    AsyncFunctionReturnsNull

    AsyncFunction should not return a null Future, only a Future whose result is null.

    AutoValueConstructorOrderChecker

    Arguments to AutoValue constructor are in the wrong order

    BadShiftAmount

    Shift by an amount that is out of range

    BundleDeserializationCast

    Object serialized in Bundle may have been flattened to base type.

    ChainingConstructorIgnoresParameter

    The called constructor accepts a parameter with the same name and type as one of its caller's parameters, but its caller doesn't pass that parameter to it. It's likely that it was intended to.

    CheckReturnValue

    Ignored return value of method that is annotated with @CheckReturnValue

    CollectionIncompatibleType

    Incompatible type as argument to Object-accepting Java collections method

    ComparableType

    Implementing 'Comparable' where T is not compatible with the implementing class.

    ComparisonOutOfRange

    Comparison to value that is out of range for the compared type

    CompatibleWithAnnotationMisuse

    @CompatibleWith's value is not a type argument.

    CompileTimeConstant

    Non-compile-time constant expression passed to parameter with @CompileTimeConstant type annotation.

    ComplexBooleanConstant

    Non-trivial compile time constant boolean expressions shouldn't be used.

    ConditionalExpressionNumericPromotion

    A conditional expression with numeric operands of differing types will perform binary numeric promotion of the operands; when these operands are of reference types, the expression's result may not be of the expected type.

    ConstantOverflow

    Compile-time constant expression overflows

    DaggerProvidesNull

    Dagger @Provides methods may not return null unless annotated with @Nullable

    DeadException

    Exception created but not thrown

    DeadThread

    Thread created but not started

    DoNotCall

    This method should not be called.

    EqualsNaN

    == NaN always returns false; use the isNaN methods instead

    EqualsReference

    == must be used in equals method to check equality to itself or an infinite loop will occur.

    ForOverride

    Method annotated @ForOverride must be protected or package-private and only invoked from declaring class, or from an override of the method

    FormatString

    Invalid printf-style format string

    FormatStringAnnotation

    Invalid format string passed to formatting method.

    FunctionalInterfaceMethodChanged

    Casting a lambda to this @FunctionalInterface can cause a behavior change from casting to a functional superinterface, which is surprising to users. Prefer decorator methods to this surprising behavior.

    FuturesGetCheckedIllegalExceptionType

    Futures.getChecked requires a checked exception type with a standard constructor.

    GetClassOnAnnotation

    Calling getClass() on an annotation may return a proxy class

    GetClassOnClass

    Calling getClass() on an object of type Class returns the Class object for java.lang.Class; you probably meant to operate on the object directly

    GuardedBy

    Checks for unguarded accesses to fields and methods with @GuardedBy annotations

    GuiceAssistedInjectScoping

    Scope annotation on implementation class of AssistedInject factory is not allowed

    GuiceAssistedParameters

    A constructor cannot have two @Assisted parameters of the same type unless they are disambiguated with named @Assisted annotations.

    GuiceInjectOnFinalField

    Although Guice allows injecting final fields, doing so is disallowed because the injected value may not be visible to other threads.

    HashtableContains

    contains() is a legacy method that is equivalent to containsValue()

    IdentityBinaryExpression

    A binary expression where both operands are the same is usually incorrect.

    Immutable

    Type declaration annotated with @Immutable is not immutable

    ImmutableModification

    Modifying an immutable collection is guaranteed to throw an exception and leave the collection unmodified

    IncompatibleArgumentType

    Passing argument to a generic method with an incompatible type.

    IndexOfChar

    The first argument to indexOf is a Unicode code point, and the second is the index to start the search from

    InexactVarargsConditional

    Conditional expression in varargs call contains array and non-array arguments

    InfiniteRecursion

    This method always recurses, and will cause a StackOverflowError

    InjectMoreThanOneScopeAnnotationOnClass

    A class can be annotated with at most one scope annotation.

    InvalidPatternSyntax

    Invalid syntax used for a regular expression

    InvalidTimeZoneID

    Invalid time zone identifier. TimeZone.getTimeZone(String) will silently return GMT instead of the time zone you intended.

    IsInstanceOfClass

    The argument to Class#isInstance(Object) should not be a Class

    IsLoggableTagLength

    Log tag too long, cannot exceed 23 characters.

    JUnit3TestNotRun

    Test method will not be run; please correct method signature (Should be public, non-static, and method name should begin with "test").

    JUnit4ClassAnnotationNonStatic

    This method should be static

    JUnit4SetUpNotRun

    setUp() method will not be run; please add JUnit's @Before annotation

    JUnit4TearDownNotRun

    tearDown() method will not be run; please add JUnit's @After annotation

    JUnit4TestNotRun

    This looks like a test method but is not run; please add @Test and @Ignore, or, if this is a helper method, reduce its visibility.

    JUnitAssertSameCheck

    An object is tested for reference equality to itself using JUnit library.

    JavaxInjectOnAbstractMethod

    Abstract and default methods are not injectable with javax.inject.Inject

    LiteByteStringUtf8

    This pattern will silently corrupt certain byte sequences from the serialized protocol message. Use ByteString or byte[] directly

    LoopConditionChecker

    Loop condition is never modified in loop body.

    MislabeledAndroidString

    Certain resources in android.R.string have names that do not match their content

    MissingSuperCall

    Overriding method is missing a call to overridden super method

    MisusedWeekYear

    Use of "YYYY" (week year) in a date pattern without "ww" (week in year). You probably meant to use "yyyy" (year) instead.

    MockitoCast

    A bug in Mockito will cause this test to fail at runtime with a ClassCastException

    MockitoUsage

    Missing method call for verify(mock) here

    ModifyingCollectionWithItself

    Using a collection function with itself as the argument.

    MoreThanOneInjectableConstructor

    This class has more than one @Inject-annotated constructor. Please remove the @Inject annotation from all but one of them.

    MustBeClosedChecker

    The result of this method must be closed.

    NCopiesOfChar

    The first argument to nCopies is the number of copies, and the second is the item to copy

    NonCanonicalStaticImport

    Static import of type uses non-canonical name

    NonFinalCompileTimeConstant

    @CompileTimeConstant parameters should be final or effectively final

    NonRuntimeAnnotation

    Calling getAnnotation on an annotation that is not retained at runtime.

    NullTernary

    This conditional expression may evaluate to null, which will result in an NPE when the result is unboxed.

    OptionalEquality

    Comparison using reference equality instead of value equality

    OverlappingQualifierAndScopeAnnotation

    Annotations cannot be both Scope annotations and Qualifier annotations: this causes confusion when trying to use them.

    OverridesJavaxInjectableMethod

    This method is not annotated with @Inject, but it overrides a method that is annotated with @javax.inject.Inject. The method will not be Injected.

    PackageInfo

    Declaring types inside package-info.java files is very bad form

    PreconditionsCheckNotNull

    Literal passed as first argument to Preconditions.checkNotNull() can never be null

    PreconditionsCheckNotNullPrimitive

    First argument to Preconditions.checkNotNull() is a primitive rather than an object reference

    PredicateIncompatibleType

    Using ::equals as an incompatible Predicate; the predicate will always return false

    PrivateSecurityContractProtoAccess

    Access to a private protocol buffer field is forbidden. This protocol buffer carries a security contract, and can only be created using an approved library. Direct access to the fields is forbidden.

    ProtoFieldNullComparison

    Protobuf fields cannot be null

    ProtocolBufferOrdinal

    To get the tag number of a protocol buffer enum, use getNumber() instead.

    ProvidesMethodOutsideOfModule

    @Provides methods need to be declared in a Module to have any effect.

    RandomCast

    Casting a random number in the range [0.0, 1.0) to an integer or long always results in 0.

    RandomModInteger

    Use Random.nextInt(int). Random.nextInt() % n can have negative results

    RectIntersectReturnValueIgnored

    Return value of android.graphics.Rect.intersect() must be checked

    RestrictedApiChecker

    Check for non-whitelisted callers to RestrictedApiChecker.

    ReturnValueIgnored

    Return value of this method must be used

    SelfAssignment

    Variable assigned to itself

    SelfComparison

    An object is compared to itself

    SelfEquals

    Testing an object for equality with itself will always be true.

    ShouldHaveEvenArgs

    This method must be called with an even number of arguments.

    SizeGreaterThanOrEqualsZero

    Comparison of a size >= 0 is always true, did you intend to check for non-emptiness?

    StreamToString

    Calling toString on a Stream does not provide useful information

    StringBuilderInitWithChar

    StringBuilder does not have a char constructor; this invokes the int constructor.

    SuppressWarningsDeprecated

    Suppressing "deprecated" is probably a typo for "deprecation"

    ThrowIfUncheckedKnownChecked

    throwIfUnchecked(knownCheckedException) is a no-op.

    ThrowNull

    Throwing 'null' always results in a NullPointerException being thrown.

    TruthSelfEquals

    isEqualTo should not be used to test an object for equality with itself; the assertion will never fail.

    TryFailThrowable

    Catching Throwable/Error masks failures from fail() or assert*() in the try block

    TypeParameterQualifier

    Type parameter used as type qualifier

    UnnecessaryTypeArgument

    Non-generic methods should not be invoked with type arguments

    UnusedAnonymousClass

    Instance created but never used

    UnusedCollectionModifiedInPlace

    Collection is modified in place, but the result is not used

    70

    View Slide

  71. AndroidInjectionBeforeSuper

    AndroidInjection.inject() should always be invoked before calling super.lifecycleMethod()

    ArrayEquals

    Reference equality used to compare arrays

    ArrayFillIncompatibleType

    Arrays.fill(Object[], Object) called with incompatible types.

    ArrayHashCode

    hashcode method on array does not hash array contents

    ArrayToString

    Calling toString on an array does not provide useful information

    ArraysAsListPrimitiveArray

    Arrays.asList does not autobox primitive arrays, as one might expect.

    AsyncCallableReturnsNull

    AsyncCallable should not return a null Future, only a Future whose result is null.

    AsyncFunctionReturnsNull

    AsyncFunction should not return a null Future, only a Future whose result is null.

    AutoValueConstructorOrderChecker

    Arguments to AutoValue constructor are in the wrong order

    BadShiftAmount

    Shift by an amount that is out of range

    BundleDeserializationCast

    Object serialized in Bundle may have been flattened to base type.

    ChainingConstructorIgnoresParameter

    The called constructor accepts a parameter with the same name and type as one of its caller's parameters, but its caller doesn't pass that parameter to it. It's likely that it was intended to.

    CheckReturnValue

    Ignored return value of method that is annotated with @CheckReturnValue

    CollectionIncompatibleType

    Incompatible type as argument to Object-accepting Java collections method

    ComparableType

    Implementing 'Comparable' where T is not compatible with the implementing class.

    ComparisonOutOfRange

    Comparison to value that is out of range for the compared type

    CompatibleWithAnnotationMisuse

    @CompatibleWith's value is not a type argument.

    CompileTimeConstant

    Non-compile-time constant expression passed to parameter with @CompileTimeConstant type annotation.

    ComplexBooleanConstant

    Non-trivial compile time constant boolean expressions shouldn't be used.

    ConditionalExpressionNumericPromotion

    A conditional expression with numeric operands of differing types will perform binary numeric promotion of the operands; when these operands are of reference types, the expression's result may not be of the expected type.

    ConstantOverflow

    Compile-time constant expression overflows

    DaggerProvidesNull

    Dagger @Provides methods may not return null unless annotated with @Nullable

    DeadException

    Exception created but not thrown

    DeadThread

    Thread created but not started

    DoNotCall

    This method should not be called.

    EqualsNaN

    == NaN always returns false; use the isNaN methods instead

    EqualsReference

    == must be used in equals method to check equality to itself or an infinite loop will occur.

    ForOverride

    Method annotated @ForOverride must be protected or package-private and only invoked from declaring class, or from an override of the method

    FormatString

    Invalid printf-style format string

    FormatStringAnnotation

    Invalid format string passed to formatting method.

    FunctionalInterfaceMethodChanged

    Casting a lambda to this @FunctionalInterface can cause a behavior change from casting to a functional superinterface, which is surprising to users. Prefer decorator methods to this surprising behavior.

    FuturesGetCheckedIllegalExceptionType

    Futures.getChecked requires a checked exception type with a standard constructor.

    GetClassOnAnnotation

    Calling getClass() on an annotation may return a proxy class

    GetClassOnClass

    Calling getClass() on an object of type Class returns the Class object for java.lang.Class; you probably meant to operate on the object directly

    GuardedBy

    Checks for unguarded accesses to fields and methods with @GuardedBy annotations

    GuiceAssistedInjectScoping

    Scope annotation on implementation class of AssistedInject factory is not allowed

    GuiceAssistedParameters

    A constructor cannot have two @Assisted parameters of the same type unless they are disambiguated with named @Assisted annotations.

    GuiceInjectOnFinalField

    Although Guice allows injecting final fields, doing so is disallowed because the injected value may not be visible to other threads.

    HashtableContains

    contains() is a legacy method that is equivalent to containsValue()

    IdentityBinaryExpression

    A binary expression where both operands are the same is usually incorrect.

    Immutable

    Type declaration annotated with @Immutable is not immutable

    ImmutableModification

    Modifying an immutable collection is guaranteed to throw an exception and leave the collection unmodified

    IncompatibleArgumentType

    Passing argument to a generic method with an incompatible type.

    IndexOfChar

    The first argument to indexOf is a Unicode code point, and the second is the index to start the search from

    InexactVarargsConditional

    Conditional expression in varargs call contains array and non-array arguments

    InfiniteRecursion

    This method always recurses, and will cause a StackOverflowError

    InjectMoreThanOneScopeAnnotationOnClass

    A class can be annotated with at most one scope annotation.

    InvalidPatternSyntax

    Invalid syntax used for a regular expression

    InvalidTimeZoneID

    Invalid time zone identifier. TimeZone.getTimeZone(String) will silently return GMT instead of the time zone you intended.

    IsInstanceOfClass

    The argument to Class#isInstance(Object) should not be a Class

    IsLoggableTagLength

    Log tag too long, cannot exceed 23 characters.

    JUnit3TestNotRun

    Test method will not be run; please correct method signature (Should be public, non-static, and method name should begin with "test").

    JUnit4ClassAnnotationNonStatic

    This method should be static

    JUnit4SetUpNotRun

    setUp() method will not be run; please add JUnit's @Before annotation

    JUnit4TearDownNotRun

    tearDown() method will not be run; please add JUnit's @After annotation

    JUnit4TestNotRun

    This looks like a test method but is not run; please add @Test and @Ignore, or, if this is a helper method, reduce its visibility.

    JUnitAssertSameCheck

    An object is tested for reference equality to itself using JUnit library.

    JavaxInjectOnAbstractMethod

    Abstract and default methods are not injectable with javax.inject.Inject

    LiteByteStringUtf8

    This pattern will silently corrupt certain byte sequences from the serialized protocol message. Use ByteString or byte[] directly

    LoopConditionChecker

    Loop condition is never modified in loop body.

    MislabeledAndroidString

    Certain resources in android.R.string have names that do not match their content

    MissingSuperCall

    Overriding method is missing a call to overridden super method

    MisusedWeekYear

    Use of "YYYY" (week year) in a date pattern without "ww" (week in year). You probably meant to use "yyyy" (year) instead.

    MockitoCast

    A bug in Mockito will cause this test to fail at runtime with a ClassCastException

    MockitoUsage

    Missing method call for verify(mock) here

    ModifyingCollectionWithItself

    Using a collection function with itself as the argument.

    MoreThanOneInjectableConstructor

    This class has more than one @Inject-annotated constructor. Please remove the @Inject annotation from all but one of them.

    MustBeClosedChecker

    The result of this method must be closed.

    NCopiesOfChar

    The first argument to nCopies is the number of copies, and the second is the item to copy

    NonCanonicalStaticImport

    Static import of type uses non-canonical name

    NonFinalCompileTimeConstant

    @CompileTimeConstant parameters should be final or effectively final

    NonRuntimeAnnotation

    Calling getAnnotation on an annotation that is not retained at runtime.

    NullTernary

    This conditional expression may evaluate to null, which will result in an NPE when the result is unboxed.

    OptionalEquality

    Comparison using reference equality instead of value equality

    OverlappingQualifierAndScopeAnnotation

    Annotations cannot be both Scope annotations and Qualifier annotations: this causes confusion when trying to use them.

    OverridesJavaxInjectableMethod

    This method is not annotated with @Inject, but it overrides a method that is annotated with @javax.inject.Inject. The method will not be Injected.

    PackageInfo

    Declaring types inside package-info.java files is very bad form

    PreconditionsCheckNotNull

    Literal passed as first argument to Preconditions.checkNotNull() can never be null

    PreconditionsCheckNotNullPrimitive

    First argument to Preconditions.checkNotNull() is a primitive rather than an object reference

    PredicateIncompatibleType

    Using ::equals as an incompatible Predicate; the predicate will always return false

    PrivateSecurityContractProtoAccess

    Access to a private protocol buffer field is forbidden. This protocol buffer carries a security contract, and can only be created using an approved library. Direct access to the fields is forbidden.

    ProtoFieldNullComparison

    Protobuf fields cannot be null

    ProtocolBufferOrdinal

    To get the tag number of a protocol buffer enum, use getNumber() instead.

    ProvidesMethodOutsideOfModule

    @Provides methods need to be declared in a Module to have any effect.

    RandomCast

    Casting a random number in the range [0.0, 1.0) to an integer or long always results in 0.

    RandomModInteger

    Use Random.nextInt(int). Random.nextInt() % n can have negative results

    RectIntersectReturnValueIgnored

    Return value of android.graphics.Rect.intersect() must be checked

    RestrictedApiChecker

    Check for non-whitelisted callers to RestrictedApiChecker.

    ReturnValueIgnored

    Return value of this method must be used

    SelfAssignment

    Variable assigned to itself

    SelfComparison

    An object is compared to itself

    SelfEquals

    Testing an object for equality with itself will always be true.

    ShouldHaveEvenArgs

    This method must be called with an even number of arguments.

    SizeGreaterThanOrEqualsZero

    Comparison of a size >= 0 is always true, did you intend to check for non-emptiness?

    StreamToString

    Calling toString on a Stream does not provide useful information

    StringBuilderInitWithChar

    StringBuilder does not have a char constructor; this invokes the int constructor.

    SuppressWarningsDeprecated

    Suppressing "deprecated" is probably a typo for "deprecation"

    ThrowIfUncheckedKnownChecked

    throwIfUnchecked(knownCheckedException) is a no-op.

    ThrowNull

    Throwing 'null' always results in a NullPointerException being thrown.

    TruthSelfEquals

    isEqualTo should not be used to test an object for equality with itself; the assertion will never fail.

    TryFailThrowable

    Catching Throwable/Error masks failures from fail() or assert*() in the try block

    TypeParameterQualifier

    Type parameter used as type qualifier

    UnnecessaryTypeArgument

    Non-generic methods should not be invoked with type arguments

    UnusedAnonymousClass

    Instance created but never used

    UnusedCollectionModifiedInPlace

    Collection is modified in place, but the result is not used

    Список проверок

    Не надо читать
    71

    View Slide

  72. Пример 3
    Все животные равны
    72

    View Slide

  73. Object[] a = new Object[]{1, 2, 3};
    Object[] b = new Object[]{1, 2, 3};
    if (a.equals(b)) {
    System.out.println("arrays are equal!");
    }
    Error:(14, 27) java: [ArrayEquals] Reference equality used
    to compare arrays
    (see http:!//errorprone.info/bugpattern/ArrayEquals)
    Did you mean 'if (Arrays.equals(a, b)) {'?
    73

    View Slide

  74. Object[] a = new Object[]{1, 2, 3};
    Object[] b = new Object[]{1, 2, 3};
    if (a.equals(b)) {
    System.out.println("arrays are equal!");
    }
    Error:(14, 27) java: [ArrayEquals] Reference equality used
    to compare arrays
    (see http:!//errorprone.info/bugpattern/ArrayEquals)
    Did you mean 'if (Arrays.equals(a, b)) {'?
    74

    View Slide

  75. Object[] a = new Object[]{1, 2, 3};
    Object[] b = new Object[]{1, 2, 3};
    if (a.equals(b)) {
    System.out.println("arrays are equal!");
    }
    Error:(14, 27) java: [ArrayEquals] Reference equality used
    to compare arrays
    (see http:!//errorprone.info/bugpattern/ArrayEquals)
    Did you mean 'if (Arrays.equals(a, b)) {'?
    75

    View Slide

  76. Пример 4

    No toString() attached
    76

    View Slide

  77. int[] a = {1, 2, 3};
    System.out.println("array a = " + a);
    77

    View Slide

  78. int[] a = {1, 2, 3};
    System.out.println("array a = " + a);
    Error:(23, 43) java: [ArrayToString] Calling toString
    on an array does not provide useful information
    (see http:!//errorprone.info/bugpattern/
    ArrayToString)
    Did you mean 'System.out.println("array a = " +
    Arrays.toString(a));’?
    78

    View Slide

  79. int[] a = {1, 2, 3};
    System.out.println("array a = " + a);
    Error:(23, 43) java: [ArrayToString] Calling toString
    on an array does not provide useful information
    (see http:!//errorprone.info/bugpattern/
    ArrayToString)
    Did you mean 'System.out.println("array a = " +
    Arrays.toString(a));’?
    79

    View Slide

  80. Пример 5
    Форматировал, форматировал, 

    да не отфармотировал
    80

    View Slide

  81. PrintStream out = System.out;
    out.println(String.format("Hello, %s%s", "World!"));
    out.println(String.format("Hello, $s", “World!"));
    81

    View Slide

  82. PrintStream out = System.out;
    out.println(String.format("Hello, %s%s", "World!"));
    out.println(String.format("Hello, $s", “World!"));
    Error:(14, 39) java: [FormatString] missing argument
    for format specifier '%s'
    (see http:!//errorprone.info/bugpattern/
    FormatString)
    Error:(15, 39) java: [FormatString] extra format
    arguments: used 0, provided 1
    (see http:!//errorprone.info/bugpattern/
    FormatString)
    82

    View Slide

  83. PrintStream out = System.out;
    out.println(String.format("Hello, %s%s", "World!"));
    out.println(String.format("Hello, $s", “World!"));
    Error:(14, 39) java: [FormatString] missing argument
    for format specifier '%s'
    (see http:!//errorprone.info/bugpattern/
    FormatString)
    Error:(15, 39) java: [FormatString] extra format
    arguments: used 0, provided 1
    (see http:!//errorprone.info/bugpattern/
    FormatString)
    83

    View Slide

  84. Уровень 2
    Аннотировал, починил и забыл
    84

    View Slide

  85. Уровень 2
    • Придется немного поработать
    • Результаты того стоят
    85

    View Slide

  86. Пример 6
    86

    View Slide

  87. public class SelfSynchronized {
    @GuardedBy(“this")
    private final List lst = new ArrayList!<>();
    public synchronized void add(final int x) {
    lst.add(x);
    }
    public int size() {
    return lst.size();
    }
    !!...
    Error:(19, 16) java: [GuardedBy] This access should be guarded
    by 'this', which is not currently held
    (see http:!//errorprone.info/bugpattern/GuardedBy)
    87

    View Slide

  88. public class SelfSynchronized {
    @GuardedBy(“this")
    private final List lst = new ArrayList!<>();
    public synchronized void add(final int x) {
    lst.add(x);
    }
    public int size() {
    return lst.size();
    }
    !!...
    Error:(19, 16) java: [GuardedBy] This access should be guarded
    by 'this', which is not currently held
    (see http:!//errorprone.info/bugpattern/GuardedBy)
    88

    View Slide

  89. public class SelfSynchronized {
    @GuardedBy(“this")
    private final List lst = new ArrayList!<>();
    public synchronized void add(final int x) {
    lst.add(x);
    }
    public synchronized int size() {
    return lst.size();
    }
    !!...
    Error:(19, 16) java: [GuardedBy] This access should be guarded
    by 'this', which is not currently held
    (see http:!//errorprone.info/bugpattern/GuardedBy)
    89

    View Slide

  90. Пример 7
    Пожалуйста, закрывайте двери
    90

    View Slide

  91. private static class Resource implements AutoCloseable {
    @MustBeClosed
    public Resource() {}
    @Override
    public void close() throws Exception {}

    }
    public void doWorkWithResource() {
    final Resource r = new Resource();
    r.doWork();
    }
    Error:(39, 28) java: [MustBeClosedChecker] The result of this
    method must be closed.
    (see http:!//errorprone.info/bugpattern/MustBeClosedChecker)
    91

    View Slide

  92. private static class Resource implements AutoCloseable {
    @MustBeClosed
    public Resource() {}
    @Override
    public void close() throws Exception {}

    }
    public void doWorkWithResource() {
    final Resource r = new Resource();
    r.doWork();
    }
    Error:(39, 28) java: [MustBeClosedChecker] The result of this
    method must be closed.
    (see http:!//errorprone.info/bugpattern/MustBeClosedChecker)
    92

    View Slide

  93. Уровень 3
    93
    WARNING => ERROR

    View Slide

  94. Преимущества ErrorProne
    • Его нельзя проигнорировать
    • Очень низкий уровень false positive
    • Можно постепенно “закручивать” гайки
    • Бесплатный как “бесплатное пиво”
    94

    View Slide

  95. Заключение
    95

    View Slide

  96. Что с этим делать?
    1. Обсудить доклад с разработчиками
    2. Использовать EqualsVerifier для проверки ваших equals()
    и hashCode()
    3. Включить ErrorProne для поиска гадких ошибок
    4. Аннотировать ваш код для усиления проверок ErrorProne
    96

    View Slide

  97. Контакты
    https://www.linkedin.com/in/asatarin


    https://twitter.com/asatarin


    [email protected]
    97

    View Slide

  98. Ссылки
    • http://jqno.nl/equalsverifier/
    • “Not all equals methods are created equal” by Jan Ouwens

    https://youtu.be/pNJ_O10XaoM
    • https://www.artima.com/lejava/articles/equality.html
    • http://www.drdobbs.com/jvm/java-qa-how-do-i-correctly-implement-th/
    184405053
    98

    View Slide

  99. Ссылки
    • http://errorprone.info/
    • https://github.com/google/auto/blob/master/value/userguide/index.md
    99

    View Slide