Category: архитектура

усы2

Монады против исключений

Я конечно не настоящий сварщик и про ФП мне Рабинович напел. Но я поработал в трех Clojure-командах и одной Erlang и видел, как люди пытаются для обработки ошибок слезть с исключений и перейти на Error монады (например, failjure, бывают и самописные, но принцип понятен). Считается, что исключения это такие грязь и пот для люмпенов, а монады из области чистых идей и разносят их на золотых подносах во дворцах.

А вот что показала практика: монады очень хрупкие и ошибку из них легко проглядеть, скушать, забить, вернуть как значение (которое вызывающая сторона уже выкинет т.к. не знает что его надо проверять, а результат ей был и не нужен) — и так далее. То есть я могу представить, как в идеальных условиях, в голове одного человека, можно небольшой сложности проектик как-то с таким подходом написать, если быть очень внимательным. Не очень понятно, какие из этого бенефиты, но можно.

А в производстве у нас что? Все спешат, тесты покрывают 25% случаев от силы, хорошо если хоть на какие-то failure cases тесты вообще есть, задачи и курс партии меняется еженедельно, коммуникация средняя, такая штука как коллективное внимание вообще можно сказать не существует (ну нельзя на каждый чих оповещать всех, ну нереалистично это). Задача производства ведь какая? Строить новое со скоростью большей, чем разваливается старое, чтобы выйти в конце в net positive. Подход быть внимательным, хоть я сам его и очень люблю и использую в домашних опен-сорс проектах, где всё разложено по полочкам и муха рядом чихнуть не может; такой подход ну не работает, нельзя на него полагаться от слова совсем.

Исключения в этом смысле инструмент для невнимательных, они компенсируют тот недостаток человеской головы, с которым невозможно ничего поделать, ну таков человек, ошибается и с этим надо помогать, а не требовать «ошибаться меньше». Исключения решают часть проблемы как раз тем, что на них не нужно обращать внимание специально и при этом их очень трудно потерять или проигнорировать. Разве что специально, но специально, будем считать, никто не пакостит.

Так что да, проблемы с исключениями я не вижу и очень их люблю даже.

усы2

Генератор идей

Который раз убеждаюсь, что все самые интересные идеи возникают от внешних стимуляторов — общение в кулуарах конференций, доклады, посиделки в барах, споры. Невозможно сидеть дома и что-то придумать. Сама идея, конечно, явно не звучит, но мозг в каком-то дискуссионном режиме подмечает тропки чужих мыслей и по сложным ассоциациям может вообразить какую-то проблему и тут же, на месте, придумать решение. И проблемы, и решения, всегда случайные, подмечаются (я бы даже сказал — генерируются, или возникают) в гораздо большем количестве, идут таким фоном вперемешку, бегущей строкой, загораются на секунду и тут же исчезают. Только если случайно сгенерированное решение _подойдет_ к случайно сгенерированной соседней проблеме, как входит в замок правильно наконец подобранный ключ из связки, происходит щелчок — сигнал в сознание, что нужно притормозить и обратить внимание. Остаток вечера, конечно, будет занят уже верчением этой _комбинации_ в голове, обдувом конструкции, первой проверкой на прочность. Если всё сложится, то дальше уже мысль надо где-то зафиксировать, а именно — произнести вслух, вербализировать, записать, пусть даже и в одиночестве. Здесь важен именно документальный факт, переход из мира чистых идей в объективную реальность. Когда это случается, дальше просто работа.

усы2

Надо поговорить о стримерах

Я тут подсел на Сэра Троглодита (пусть псевдоним вас не пугает), который играет в Героев Меча и Магии 3. В общем-то играет неплохо, и к игре я не равнодушен, но подсел я в первую очередь на харизму, потом на скилл и нехилый такой саспенс (см. https://www.youtube.com/watch?v=DN1fc_Jb3es), а потом обнаружился гораздо более интересный эффект.

Герои вообще игра стратегическая, и если ты играешь хорошо, то она очень быстро превращается в очень условное соревнование столбиков чисел: 100 скелетов лучше, чем 5 вампиров, потому что хитпоинтов больше, но есть еще атака/защита/особые свойства, так что всё не так однозначно™ — но всё еще это набор чисел, которые всего лишь комплексно и ситуативно сравниваются. Соответственно скилл в игре — умение оценивать и максимизировать эти числа. Тактика, стратегия, экономика. И очень легко найти стримы занудных достаточно папок, которые и играют получше, и объясняют по-подробнее, но для них это передвижение шахмат по доске, а из чего там сделаны фигуры и доска — без разницы.

Так вот, у Сэра Троглодита происходит важная штука — он берет всё происходящее за чистую монету. Гноллы у него — не просто юниты первого уровня из замка типа «Оплот», это действительно бестолковые прямоходящие собаки, которые живут почему-то в болотах, и он пытается как-то, через шутки в основном, осмыслить, почему они там живут и зачем. Феи это не просто юнит, который производит атаку и наносит N дамага, ему важно, как именно они этот дамаг наноят: пыльцу токсичную распыляют? Каждая деталь оживает и обретает смысл. Больше всего любви своим героям, конечно — он их и по именам помнит, и в аватарки вглядывается, и прозвища ласковые придумывает, и задним числом какую-никакую мотивацию подводит. Пришлось по игре зайти в снега Академии? — Джен пошла морозить ноги, захватила шубу. Ну и так далее. Внезапно выясняется, что у замков в игре всё это время были названия, но всем как-то было пофиг, ну потому что действительно, выигрывать они никак не помогают. Троглодит же их зачитывает и подводит легенду, как то или иное название могло случиться. Избушка колдуньи, чисто утилитарное строение на карте, выдающее случайный вторичный навык, у него абсолютно серьезно полноценный дом, в котором живет почти что живой человек и помогает по мере возможностей.

И так далее. Конечно, и утилитарность, и столбики цифр, и стратегия — это всё тоже есть, механика самой игры совершенно обычная, просто фигуры имеют значение. За этот счет пространство мира как бы расширяется, ты получаешь возможность посмотреть на него вглубь и при небольшом усилии действительно можно представить, что было вот такое вот королевство, ему тысячи лет, жизнь в нем так вот причудливо сложилась, территории поделили, замков понастроили, расы такие вот корявенькие, одни в лесах, другие в горах, третьи под землей, кое-как уживались, армии набирали из кого придется, конфликтовали, притирались и вот сейчас в нем происходят такие вот бои между доблестными (и нонсенсными по составу) армиями во главе с выдающимися полководцами, иногда живыми, а иногда и не очень. А то что вы видите на экране — это такая очень условная реконструкция этих событий, что ли. Ну правда, когда вы видите в одном войске условных церберов, мошек из болота и скелетов с луками (а Герои в этом смысле — идеальный генератор таких вот ситуаций), неужели вам не хочется узнать предысторию??? И что они друг о друге думают, стоя плечом к плечу против, например, пегасов и ходячих деревьев?

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

усы2

ФП в Киеве есть

Был две недели назад в Киеве, прочитал на митапе в Grammarly доклад, продолжение «ФП в браузере». Доклад про то, как сделать векторный редактор на React.js и Immutable.js.

Коротко:

На JS жить можно, с правильной архитектурой писать вполне терпимо. Я ожидал больше страданий. Запилил мультиметоды и атомы, впрочем.

Immutable.js нормальная штука, не хватает только сериализации/десериализации структур в строку, очень странно что они это упустили. Пришлось писать самому.

Еще смешно: Immutable.js портировали реализацию персистентных структур из ClojureScript, при этом писали на чистом JS и умудрились сделать ее в 2-3 раза медленнее, чем на ClojureScript. Вот вам и про преимущества ручного кода перед компилируемым.

Код: https://github.com/tonsky/vec (650 loc)

Живая версия:



Видеозапись:
http://www.youtube.com/watch?v=lDkrXTDwbJQ (разбор кода)
http://www.youtube.com/watch?v=tUtLe1VlkYc (ответы на вопросы)

В Киеве классно:

История одного безумия

После предыдущего поста я, признаться, решил уже, что хоть проект и в ужасном состоянии, но мы достигли дна и потихоньку все будет налаживаться. Начальство опомнится, что уже два спринта мы ничего не производим, программистские рефакторинги начнут наконец упрощать и повышать надежность, и т.д., и т.п. О, как я ошибался!

Начнем издалека. Про то, что народ много знает, я в предыдущем посте, конечно, приукрасил. Технологии мы познавали в процессе. Итак, изначально были hibernate и spring. Hibernate пропатчили, чтобы научить хранить в базе объекты, меняющиеся во времени (т.н. версии). Архитектор, придумавший это, то ли сошел с ума, то ли попал в дурку — меня тогда еще не было, жаль, не встретились. Ни транзакций, ни управления hibernate sessions не было, точнее, они были написаны, и все думали, что они есть, хотя они не работали. Народ ловил LazyInitializationException, тихонько матерился и дописывал все больше джойнов в метод loadDetails(id). Чтобы, ткскзть, данные-то вытащились сразу и все. Ну, например:

    protected DetachedCriteria applyAdditionalJoins(DetachedCriteria criteria) {
        return criteria
//                 TODO (dao)  DRY (see applySearchEntity)
                .createAlias("provider", "pa")
                .createAlias("owner", "owner")
                .createAlias("subscriber", "sa")
                .createAlias("sa.document", "sa.document")

                .createAlias("services", "services", Criteria.LEFT_JOIN)
                .createAlias("services.templateService", "templateService", Criteria.LEFT_JOIN)
                .createAlias("templateService.serviceName", "serviceName", Criteria.LEFT_JOIN)
                .createAlias("services.inactivityPeriods", "inactivityPeriods", Criteria.LEFT_JOIN)
                .createAlias("registrations.person", "person", Criteria.LEFT_JOIN)
                .createAlias("person.privileges", "privileges", Criteria.LEFT_JOIN)
                .createAlias("privileges.privilegeCategory", "privilegeCategory", Criteria.LEFT_JOIN)
                .createAlias("registrations.person.document", "document", Criteria.LEFT_JOIN)
                .createAlias("registrations.registrationType", "type", Criteria.LEFT_JOIN)
                .createAlias("registrations.departurePeriods", "departurePeriods", Criteria.LEFT_JOIN)

                .createAlias("address", "address", Criteria.LEFT_JOIN)
                .createAlias("address.building", "building", Criteria.LEFT_JOIN)
                .createAlias("building.street", "street", Criteria.LEFT_JOIN)
                .createAlias("street.city", "city", Criteria.LEFT_JOIN)
                .createAlias("city.district", "district", Criteria.LEFT_JOIN)
                .createAlias("district.area", "area", Criteria.LEFT_JOIN)
                .setFetchMode("services", FetchMode.JOIN)
                .setFetchMode("services.templateService", FetchMode.JOIN)
                .setFetchMode("templateService.serviceName", FetchMode.JOIN)
                .setFetchMode("registrations", FetchMode.JOIN)
                .setFetchMode("services.inactivityPeriods", FetchMode.JOIN)
                .setFetchMode("registrations.departurePeriods", FetchMode.JOIN);
    }


Везде, где нужна была сущность, мы вытаскивали и все ее связи до N-го колена. А что, лучше лишнего вынуть, чем нужного не вынуть. Радует также комментарий про DRY. Если вам интересно, что написано в applySearchEntity, то там то же самое и симметричный комментарий про DRY. Понятно, что стоило Васе добавить/убрать джоин, как что-нибудь отваливалось у Пети. Отстрел таких вот багов был основным времяпрепровождением народа в то время. Ладно. Сессии с транзакциями я починил, и стало внутри @Transactional методов хорошо и lazy. Постепенно пришло осознание того, что вся глубина замысла свалившего архитектора не укладывается в наши скромные по меркам его амбиций возможности. А в том виде, что он оставил, ничего не работало, так что от патченного hibernate мы тихонько отказались и реализовали версионирование стандартными средствами.

Все хорошо, и мы даже успели несколько задач для бизнеса сделать. Но на коде все еще сохранился отпечаток того безумия, который магическим образом подчинял себе всех, кто этот код прочитал. В какой-то момент захотели мы SessionPerRequest. Замечательный типовой прием (© rainman_rocks), спору нет. Но тут вдруг полезло. Один этот SessionPerRequest включил и недолго думая закоммитил сразу в транк. Тестеры в панике, они не могут найти работающей страницы. Начали разбираться. Оказалось, у нас сохраняемый объект, он детачнутый, в каких-то проверках перед update достается в сессию, и оба-на: a different object with the same identifier, не могу сохранить, я не я и привет от хибернейта. Казалось бы, ну чини! И цель благородная, и обществу польза. Но наш герой решает, что чинить — дело не барское. Назад отступать тоже как-то не в его идеалах. И делает вызов update в отдельной транзакции. От греха подальше. Посовещавшись с архитектором. Я, к горю своему, тоже участвовал в этом обсуждении, хотя и стараюсь сидеть в наушниках и беседы за архитектуру игнорировать — тяжело я переношу все эти нервные потрясения последнего времени. Предложения разобраться, починить, изучить, откатить, были отвергнуты: ся сделаем начальника всё хоросо будет дазе не заметись. В конце концов, спринт короткий, а есть ведь и более важные задачи™ (переложить ресурс бандлы из одного файла в другой, например — но об этом позже). Собственно, этот случай показателен в двух смыслах: а) у нас начисто отсутствует какое-либо стремление к стабильности кода на всех уровнях, от разработчиков до архитектора, другими словами, костыли и ямочный ремонт — рекомендуемая линия партии, и б) печально, что библиотека таки оказалась сильнее разработчика и превратилась из инструмента, сокращающего путь к цели, в помеху на этом пути. Хоть лично я никогда не считал хибернейт технологией сильно сложной, кажется, он действительно может стать непобедимым монстром, если проигнорировать при знакомстве с ним документацию. Хотя, блин, ну казалось бы. Чай, не Глобус Тулкит. И теперь либо отказываться в пользу чего-то более простого, либо всех собирать и читать доку вслух. А что делать? Жду проблем с освоением log4j.

В принципе, на один спринт мне хватило бы потрясений. Но интересно же, в пользу чего решили отказываться от починки SessionPerRequest. Это должна была быть какая-то важная бизнес-задача, да? Не на тех напали. Я уже писал как-то, что у нас работа с базой идет через два слоя, Dao и Service. По паре на каждую сущность. Выглядит это примерно так (Service):

public class CompanyServiceImpl extends LongIdServiceImpl<Company, CompanyDAO> implements CompanyService {

    public CompanyServiceImpl(CompanyDAO dao) {
        super(dao);
    }

    public List<Company> findByName(String name) {
        return getDao().findByName(name);
    }

    public List<Company> findBySimilarName(String name) {
        return getDao().findBySimilarName(name);
    }

    public List<Company> findByOwner(Company owner) {
        return getDao().findByOwner(owner);
    }

    public List<Company> findByName(String name, CompanyFilterEnum filterItem) {
        return getDao().findByName(name, filterItem);
    }

    public List<Company> findBySimilarName(String name, CompanyFilterEnum filterItem) {
        return getDao().findBySimilarName(name, filterItem);
    }

    public List<Company> findByAddress(Address address) {
        return getDao().findByAddress(address);
    }

    public List<Company> getSecurityPage(EntityFilter<Long, Company> filter) {
        return getDao().getSecurityPage(filter);
    }

    public int getSecurityCount(EntityFilter<Long, Company> filter) {
        return getDao().getSecurityCount(filter);
    }

    public Company findByCode(String code) {
        return getDao().findByCode(code);
    }
}


Ну и считается, что нужно везде использовать Service, а не Dao. А вдруг где-то это не так? Вдруг какой-то негодяй вызвал Dao в обход Service? Проверить код? Да в этой куче черт ногу сломит. Разъяснить разработчикам? Все равно ведь в обход полезут. В конце концов, слишком легко это все. Решили написать Чекер™. Чтоб рушил все нахрен, если какой-то партизан не за тот провод дернул. Причем как бы с благой целью повышения стабильности, только видится мне, что и в Чекере™ баги, и ситуации неоднозначные будут. Бог с ним, тут интересно другое. В МСС то же самое было, но с секьюрити, я хорошо помню: как сделать такую систему, чтобы другой, тупой разработчик, то есть не я, не смог написать некорректный код. Этот симптом, он даже не про то, что неладно что-то в Датском королевстве, он про то, что все пиздец закрывайте лучше лавочку. Началась война. Верить нельзя никому. Каждый считает себя умнее другого. Менять можно только свой собственный код, либо писать новый. Победит тот, кто первый посадит соперника в клетку. И вот тогда заживееем.......

Я как бы всегда считал себя человеком сильно спокойным, но что-то в последнее время то на смех истерический сорвусь, то за голову хватаюсь, то проклятия вполголоса бормочу. Дайте мне, что ли, другой какой-нибудь глобус. Только не Глобус Тулкит.