Pull to refresh

Проверочное состояние в Java

Reading time 2 min
Views 22K
Ключевое слово assert (проверка) появилось в Java 1.4. Мне кажется, многие до сих пор стараются его избегать, или заворачивать в утилитные статические методы с возможностью быстро поменять assert condition : message; на
if (!condition)
    throw new AssertionError(message);

по всему коду. Кто-то боится, что проверки недостаточно надежные, и если кто-то забудет их включить, какие-то баги останутся незамеченными. Кто-то, наоборот, маниакально думает о производительности: если кто-то включит проверки для подсистемы/библиотеки, написанной ребятами из первой группы, и забудет исключить пакеты или классы «производительной» библиотеки, исполнение будет замедленно бесполезными вычислениями.

Хотя, по-моему, ничего страшного в проверках нет, их можно и нужно расставлять по коду как можно щедрее. Во-первых, как я уже упомянул (но для кого-то это может оказаться в новинку), проверки можно гибко настраивать (включать/отключать в пакетах и отдельных классах) как из командной строки при запуске JVM, так и программно (через ClassLoader), так что если вы вдруг захотите включить проверки в одной системе и выключить — в другой, это уж точно решаемая проблема.

Во-вторых, иногда хочется проверять не тривиальные условия вроде какая-то булева переменная == false или true, а поддерживать некоторое проверочное состояние внутри класса и сверяться с ним в методах. С помощью трюка с assert можно добиться этого практически бесплатно при исполнении с отключенными проверками.

Трюк прост: инициализация и обновление проверочного состояния выносится в методы, которые по смыслу ничего не возвращают (void), а в коде — boolean и всегда true. Эти методы вызываются «через» assert. То есть, когда проверки для класса отключены, методы не вызываются, проверочное состояние не инициализируется и не обновляется, и в накладных расходах остается только одна null-ссылка в памяти объекта.

Пример:
import java.util.HashSet;
import java.util.Set;

public final class MyCoolSet<E> {

    private Object[] coolStorage;
    private transient Set<E> referenceSet;

    public MyCoolSet() {
        // ... init cool storage
        assert initReferenceSet();
    }

    private boolean initReferenceSet() {
        referenceSet = new HashSet<>();
        return true;
    }

    public int size() {
        // return the cool size
        return 42;
    }

    public boolean add(E e) {
        // .. add an element to the cool storage
        boolean added = true;
        assert addToReferenceSet(e);
        return added;
    }

    private boolean addToReferenceSet(E e) {
        referenceSet.add(e);
        checkSize();
        return true;
    }

    private void checkSize() {
        assert referenceSet.size() == size() :
                "Cool size diverged from reference size";
    }

    public boolean remove(Object o) {
        // ... remove an element from the cool storage
        boolean removed = true;
        assert removeFromReferenceSet(o);
        return removed;
    }

    private boolean removeFromReferenceSet(Object o) {
        referenceSet.remove(o);
        checkSize();
        return true;
    }
}
Tags:
Hubs:
+8
Comments 44
Comments Comments 44

Articles