2013-04-09

Код с привидениями

Есть в коде места, заросшие паутиной, куда разработчики редко заглядывают. При этом, такие места иногда напоминают о себе в самых неожиданных местах.

Когда мы активно готовились к выставке и тестировали систему, Hibernate начал бросать странное исключение collection [] was not processed by flush() triggering for unknown reasons с пометкой this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session. Гуглинг сразу вывел на закрытые тикет HHH-3876, который тоже не сильно помог. В данном сущности была парочка мутабельных коллекций, которые лениво подгружались. Стоило поставить жадную подгрузку, как исключение пропадало. Тут взгляд упал на строку:

@EqualsAndHashCode(exclude = {"tags"})

которая подменяла стандартные методы equals и hashCode. Эта аннотация принадлежит великолепной библиотеке lombok, позволяющей при компиляции генерировать boilerplate код: toString, getter, setter, equalsAndHashCode.

Тем не менее, подменять хэшкод в мутабельных классах - очень плохая идея, ведущая к крайне веселым последствиям. Как оказалось, при персисте, гибернейт дергал метод equals, тот в свою очередь пробегался по полям и задевал ленивый прокси, прокси инициализировался и изменял хэш объекта, гибернейт видел это и сходил с ума от внезапного изменения уже обработанной коллекции.

На фикс бага ушло несколько часов. Самое веселое началось, когда мы открыли историю:

2013-02-19Удалено
2012-11-28@EqualsAndHashCode(exclude = {"tags"})
2012-10-14@EqualsAndHashCode(exclude = {"users"})
2012-10-12@EqualsAndHashCode(exclude = {"users", "tags"})
2012-05-25@EqualsAndHashCode(exclude = {"users"})
2012-05-03@EqualsAndHashCode)

А теперь, самое интересное, результаты раскопок: добавлен этот метод был для удобства тестирования. На тот момент, сущность не содержала коллекций и результаты тестов было очень удобно сравнивать с ожидаемыми объектами. Но спустя некоторое время, объект стал сложным и сравнение было переписано на toString, необходимость в подмене equals отпала. Тем не менее equals жил еще несколько месяцев, порождая трудноуловимые баги. А сколько еще таких привидений живет в коде?