В чём ценность опытного разработчика? Способность видеть возможные проблемы. Интуиция, чуйка, "что-то мне здесь не нравится, давайте разберёмся".
Чтобы развить этот навык, нужно совершить много ошибок самому и изучать чужие ошибки. Прямо или косвенно, это всё идёт на пользу, и сегодня хочу поделиться одним неудачным случаем.
Default методы появились в java 8, чтобы упростить добавление методов в интерфейс. Подробный обзор можно прочитать в этом посте.
✅ Легко добавить новый метод
✅ Нет ошибок компиляции
✅ Методы при желании переопределяются
C java 8 в интерфейсе Collection появились методы по умолчанию spliterator(), stream(), parallelStream(), removeIf(…).
Какие могут быть проблемы?
❌ Реализация по умолчанию не подходит
❌ Разработчики не узнают, что добавился новый метод, который нужно переопределить. Ошибок компиляции нет, предупреждений тоже
❌ Нет тестов нового метода и интеграционных тестов
От этих проблем пострадали пользователи SynchronizedCollection из библиотеки Apache Commons.
Что произошло?
В SynchronizedCollection каждый метод синхронизирован по объекту lock:
synchronized (lock) {
return coll.remove(object);
}
Изменения последовательны, данные в безопасности и всегда актуальны. Что делает дефолтный метод removeIf? Берёт итератор, проверяет каждый элемент на соответствие условию и удаляет, если нужно.
Переложим на методы SynchronizedCollection. Синхронизация по lock берётся, отпускается, берётся, отпускается, и так несколько раз. При большой нагрузке управление перехватит другой поток, и произойдёт коллизия. Дефолтный метод не выполнит гарантий, заданных классом.
Ошибку легко исправить - переопределить метод removeIf:
synchronized (lock) {
return coll.removeIf(filter);
}
Проблема в том, что такие ошибки сложно обнаружить. Default метод появился в марте 2014, а класс обновили в июле 2019. 5 лет пользователи SynchronizedCollection пользовались ненадёжным методом.5 лет! Может новым методом никто не пользовался. Может поток данных через коллекцию был небольшим. Может никаких последствий не было. А может были, неизвестно.
Даже такая безобидная фича как "методы по умолчанию" привела к ошибке. Мы можем вынести из неё пару best practices:
▫️Если интерфейс используется только внутри системы, достаточно написать тесты для всех реализаций.
▫️Для общедоступных библиотек по возможности избегать методов по умолчанию.
#core