2014-02-12

Прикладная топология в исходном коде

Не развидеть

Бывают такие вещи, которые, однажды увидев, начинаешь замечать абсолютно везде. Для меня недавно таким открытием были теги {@see} и {@link} в джавадоках. Я неистово стал превращать исходники в такую вики. Параллельно сделал другое открытие, код не изотропен, связность пространства нарушается: попав из точки А в точку Б, я не всегда могу вернуться назад с той же скоростью - приходится обходить иерархию или вспоминать назание класса (черт возьми, у меня в активной памяти буфер на 7 элементов, названия класса из него выталкивается, даже если я пререшел из него десять секунд назад), чтобы по Ctrl+N перейти на него. Тут не спасают даже линки в джавадоках. Дальше я собрал подборочку таких моментов с реальными примерами.

Ссылки на файлы

В IntelliJ IDEA нельзя поставить ссылку на произвольный файл. Есть несколько фичреквестов, но статус их непонятен. Это позволило бы доступаться к файлам ресурсов хотя бы в одностороннем направлении.

Вообще ресуры - больная тема. Мы конечно можем себя убеждать, что ресурсы можно редактировать отдельно специальными людьми, но реальность прозаична. Поправив строчку в текстовом файлике с локализацией, мы пушим изменения на CI и через двадцать минут он выплевывает в репозиторий инсталлятор с полугиговым убер-варником.

Для локализации мы используем gwt-i18-server. Конечно мы тешим себя мыслью, что когда-нибудь у нас будет куча языков, но сейчас у нас их два и необходимо тратить время на синхронизацию изменений, перемещать их, если переместил интерфейс, создавать. Единственная оптимизация, которую я вижу - выделить имя класса и нажать ctrl+shift+n, чтобы быстро открыть оба два проперти файла с локализованными строкам. Здесь полный разрыв между двумя сущностями, которые должны быть рядом. Тут бы помогли аннотации (кстати идея для пулл реквеста).

Та же фигня со стилями в Vaadin. Есть файлик типа ReindeerTheme.java в котором куча строк вида public static final String MY_SUPER_BUTTON_STYLE = "my-super-button-style". Эти стили описаны в файле ресурсов style.css. Константы это, конечно, прикольно, но из этого файла никак не попасть в файл со стилями. А то, что написано в константах вообще никак не связано с тем, что написано в файле со стилями.

Статическая компиляция и reflection

Every time, you use reflection 
to workaround language limitation, 
somewhere, somehow God kills kitten

Вообще, мне очень нравится статически проверяемый код (до haskell я еще не дорос) и я здесь говорю гораздо шире чем типы. Вот между перечнем стилей и описанием стилей не было никакой связи. Компилятор никак не поможет, если программист где-то опечатался. Я бы лучше написал какой-нибудь генератор стилей в стиле fluent builder и был спокоен. Наличие же такой связи иногда позволяет избавиться от множественного редактирования.

Иногда рефлекшен это как головоломка, позволяет почесать правой пяткой за левым ухом. Иногда он приводит к очень элегантным решениям проблем. Но в большинстве случаев необдуманное использование reflection - это зло. Не потому, что, ой-ой, программа начинает тормозить - за многослойным наполеоном из спринбернейтов доступание к полям бина по имени дает пренебрежимо мало. Главное зло здесь в том, что рвется связь между именем поля и полем. Отличный пример Vaadin Bean Validation написанный поверх JSR-303. BeanValidationValidator.addValidator (Field field, Object propertyId, Class<?> beanClass). Между propertyId и beanClass нет никакой связи. И если вдруг разработчик исправил опечатку в имени поля, то валидатор не будет найдет. То же самое и с моделью данных в Vaadin. В java 7 появятся долгожданные лямбды и ссылки на функции, которые можно будет использовать в биндинге данных: указывать не проперти а ссылки на геттеры и сеттеры. Рефакторинг переименовывания поля (который также затрагивает геттеры и сеттеры) будет абсолютно безопасными.

В Vaadin 6 разработчики еще не вкурили генерики (может была и другая причина) и фигачили все на Object, из-за чего постоянно приходилось конвертить типы. В седьмой версии финны одумались и вкорячили генерики в модель данных, растоптав надежду обратную своместимость. 

Мощь генериков начала до меня доходить только недавно, жаль что в 8-ке не осилили убрать type erasure - это бы сильно облегчило жизнь и не надо было бы использовать хаки с наследованием, которые закладывались на жесткий порядок типов, когда неосторожное наследование приводит к трудноотлавливаемым ошибкам.

Еще один плагин, который и привел к идее ссылок org.vaadin.mvp. События описываются в шине как 

@Event(handlers = { MenuPresenter.class })
public void selectMenu(ValueChangeEvent event);


Вся эта магия работает через рефлекшен. Проблема в том, что в презентере должен быть метод onSelectMenu(...). Это рвет связь между методом и подписчиком. Опечатки, ручные переименования, копирование и вставка с переименование. Метод обязательно дожен быть публичным и прочее веселье. Чтобы не было предупреждений, метод нужно помечать @SuppressWarnings("unused"), что опять же плохо, ведь он может перестать использоваться на самом деле. Гуавовская шина мне импонирует гораздо больше.

JPA

Главная проблема - где хранить запросы к базе. Проблема связности меня беспокоила с самого начала карьеры, хотя я и не мог её сформулировать, но понимал, что что-то здесь не так. Итак, мы пришли к анемичной доменной модели: есть классы, в которых хранятся значения и самая минимальная бизнес-логика и сервисный слой, который оперирует с доменами. Нам говорят: храните запросы в @NamedQuery в доменных объектах. Но в нашей ситуации запросы используются в сервисном слое. Типичный воркфлоу: добавляем запрос в домен, добавляем метод в сервисный класс. Когда попал в сервис, чтобы посмотреть на запрос нужно открывать домен, если исправил запрос в домене нужно открывать сервис и править там. Вотафак.

Почему не перенести запросы туда, где они используются (because fuck you, критический баг открыт 8 лет назад). На самом деле можно сервис аннотировать @MappedSuperclass, что позволит переместить запросы в один файл с тем местом где они используются. Хотелось бы переместить их непосредственно в метод, но увы, мы тогда потеряем валидацию JPQL на этапе компиляции, а этого очень не хочется.

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

Тесты

Нельзя поставить ссылку из класса на тест для этого класса, хотя это очень естественное желание - после редактирования исходника, перейти в файл с тестами чтобы посмотреть на варианты использования, либо поправить сценарии, но IDEA не видит тестовых классов.

FXML

Декларативный гуй для JavaFX внутри XML. Если не брать во внимание, что он дичайше тёк и парсил XML на каждую отрисовку компонента, то это отличная иллюстрация разрыва. Разрыва между двумя сущностями, которые должны быть рядом. Я добавил компонент, добавил клик-лисенер. Потом мне нужно найти класс, вспомнить сигнатуру!, прописать её руками. Да нахрен оно мне надо Button b = ButtonBuilder.create().onClick(...). Билдеры для каждого компонента вообще мегаудобная штука, позволяет писать многоуровневые цепочки в стиле JQuery. Где-то даже видел расстройство: мол разработчики фигачать гуй в коде и не используют такой удобный Scene Builder. Потому и не используют, что небольшая экономия на старте обернется большими затратами времени в сопровождении.

Потеря контекста в тредах

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

public class Task implements Runnable {
    private Exception context = new Exception();

    public void run() {
        try {
            ...
        } catch (Exception e) {
            log.error("Cannot upload", e);
            log.debug("Context", context);
        }
    }
}

Мусор

Из-за разрывов мы не можем быстро и с уверенностью делать вывод о том, что конкретный ресурс или строка локализации не используется. Спустя какое-то время в проекте начинает копиться мусор, чтобы его вычистить нужно затратить значительные услилия, хотя обычно забивают.

 Выводы

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

Статическая типизация увеличивает связность и уменьшает количество ошибок из-за рассинхронизации сущностей.