-
Зачем спринты?
За все время работы в айти я не понял, зачем нужны спринты.
Как правило, спринт — это период от двух недель до месяца. В него набирают задач и пытаются их сделать к концу спринта. Потом спринты оценивают: этот был лучше, чем в прошлый раз, а этот хуже. Тут собрали двадцать стори-поинтов, а тут восемнадцать. Проводят ретро, которые убивают время.
Все это кажется мне бредом. Зачем делить время на равные участки? Зачем считать стори-поинты? Какое знание дает цифра 18 стори-поинтов? Чем это хуже, чем 20 поинтов?
В чем проблема, если задача из одного спринта переносится в другой? Кто от этого пострадал? Если задача не умещается в спринт, то может просто не впихивать невпихуемое?
Нужно обсудить процесс? Так проведи серию звонков 1 на 1 с разработчиками. Не обязательно ждать ретро.
В моем понимании процесс управляется релизами. Скажем, поставили мы задачу выкатить новый релиз через два месяца. Договорились, что в релиз войдет то, се, пятое-десятое. Не успеваем? Что-то упрощаем, что-то откладываем в следующий релиз.
Зачем нам спринты? Почему все следуют карго-культу гуглов-амазонов? Нужно думать своей головой, а не как менеджер Гугла.
UPD: сюда же относятся эстимейты. Воистину, самое тупое и бесмыссленое, что есть в айти. Эстимейтить все и вся, ошибаться на порядок и потом рвать задницу, чтобы успеть.
-
Шкала ООП
Когда говорят про ООП, упоминают всякие солиды, наследование и прочее. И не говорят вот про что (а надо бы).
Если в языке присутствует ООП, то типизация объектов размывается. Только структуры и функции дают четкие типы; объекты, наоборот, разрушают их путем абстракций. Они замыливают глаз.
Скажем, у нас есть классы
Dog
иCat
, унаследованные отAnimal
. Пока мы передаем собак и котов явно, все хорошо. Но когда мы передаем их как животных (Animal
), там может быть что угодно. В итоге на одни и те же данные смотришь по-разному.Если предположить, что
Animal
наследуется еще от чего-то, то получится шкала отObject
кDog
/Cat
, и как смотреть на объект — зависит от контекста. В некоторых случаях делают даун-каст и ап-каст: передают кота в виде объекта, а потом проверяют: если это кот, то одно, если собака, то другое, иначе эксепшен.Эту шкалу (
Object
—Animal
—Mammal
—Dog
) важно держать в голове и знать, где находишься сейчас. Лично мне она доставляет много хлопот, когда я пишу на ООП-языках, например Джаве. Шкала — это не вкл/выкл, а некая доля, позиция в в дереве, следить за которой сложнее.Может быть, кто-то лучше выразил эту мысль, но либо я не слышал, либо забыл.
-
Не люблю регулярки
Признаться, я не особо люблю регулярные выражения. Я знаю их где-то на троечку, время от времени применяю, но стараюсь, чтобы их было меньше.
Дело в том, что регулярные выражения — это сверхплотный, сверхкраткий язык описания шаблонов в тексте. Из его главного преимущества — краткости — следует главный недостаток. Когда регулярка больше какого-то порога, ее понимание и поддержка резко идут вниз. В этом случае следует отказаться от регулярки, однако в проекте запросто может оказаться маньяк, который накрутит еще пару этажей регулярок.
Другой момент — с регуляркой часто оказывается, что ты не все учел. Предположим, нужно искать в тексте числа с плавающей запятой. Вы написали регулярку, которая ищет числа вроде
-12.0042
. А потом оказалось, что у чисел может не быть целой части, например-.0042
. А еще оказались числа в научной нотации:-1.2E9
. И регулярку нужно допиливать, допиливать и накидывать тесты.У регулярок есть хорошее применение: ими удобно разбивать текст. Как правило, я разбиваю текст на части и проверяю, что их количество и содержимое чему-то соответствует. Это проще отлаживать, это лучше в плане сообщения об ошибке. Если текст не натягивается на монструозную регулярку, ты не можешь объяснить, что пошло не так. А если обрабатывать текст по частям, легко сказать, в чем проблема.
В одном из проектов у нас была библиотека для генерации регулярок. Это когда декларативно указываешь: либо это слово, либо это, либо то, но не это и не то, и библиотека строит одну регулярку. В ней учитываются общие начала слов, например для
fuck
иfuckoff
получимfuck(?=off)
. Выбрал такой пример, потому что фильтровали сообщения в чате. Было много других слов, значения которых я не знал и смотрел в словаре.Этот подход интересен тем, что регуляркам отводится служебная часть. Конфигурация делается словарями и списками, все ясно и прозрачно.
Вместо регулярок мне больше нравится парсинг грамматикой. Пару лет назад я прочитал пару статей про комбинаторные парсеры и решил сделать свой. В результате я написал огрызок, который назвал Ostap (потому что великий комбинатор). В библиотеке были простые парсеры и те, что составляются из других (and, or). Можно задать грамматику словарем и получить из нее парсер.
В результате у меня получилось составить грамматику JSON, и парсер разбирал произвольный JSON-документ. Правда, это было медленней джавного Jackson раз в десять, но за скоростью я не гнался. Почему-то зачесались руки сесть и переписать его на Джаве и собрать парсеры для JSON, INI, Tolm и других форматов.
Словом, регулярки хороши, но я предпочитаю держать их на расстоянии вытянутой руки. Чем они ближе, тем строже я слежу за ними.
-
Jira
Я считаю, работать в компании, которая пилит Джиру, это примерно то же самое, что работать в военкомате или Роскомнадзоре. Нормальному человеку, даже если он туда попал, будет стыдно, и он уйдет в другое место.
Современная Джира — это просто заповедник багов и косяков. Каждый ее квадратный сантиметр несет бред. Без шуток, по мотивам Джиры можно написать небольшую книгу о том, как делать не надо.
Из сегодняшнего: заполняю тикет, прикладываю кусочек кода. Пишу:
{code:clоjure} (dеfn do-some-shit [a b] (do-this {:a b})) {code}
Сохраняюсь и вижу:
Unable to find source-code formatter for language: clоjure. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, relange, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, swift, visualbasic, xml, yaml(dеfn do-some-shit [a b] (do-this {:a b}))
Иными словами: сообщение о том, что подсветка Clоjure не поддерживается, внедрилось прямо в код! Причем даже без переноса строки: код следует сразу за
...yaml
.Сначала я проклял всеми словами фронтендеров, но посмотрел в Network и убедился: это серверный рендер. Клиент посылает фрагмент, а сервер возвращает HTML. Поскольку это не JSON, некуда положить нотис о том, что язык не поддерживается, и ребята такие — давайте просто засунем его в код.
Каким же мудаком надо быть, чтобы протащить это в прод — я просто не знаю. И куда глядела армия QA, разные бета-тестеры и вообще сами сотрудники фирмы? Фееризм космического дна.
-
Сишные строки
Одна из самых худших вещей в айти — это сишные нуль-терминированные строки. Другими словами, цепочка байтов, которая завершается нулем. Керниган и Ричи были гениями, никто не спорит, но решение с нулем на конце трудно назвать удобным.
Беда в том, что, читая эту строку, никогда не знаешь, где конец. Сколько байтов резервировать — 32 или килобайт? Немало ошибок в сишных программах связано с тем, что неверно определяется конец строки.
Интересно наблюдать сишные строки в бинарном протоколе Postgres. В оригинальной его части строки передаются как в Си — то есть неизвестной длины с нулем на конце. Скажем, сообщение StartupMessage строится по принципу
u s e r n a m e 0 d a t a b a s e 0
А в расширениях, которые появились позже, строки передаются нормально: сначала длина в int32, потом байты без нуля:
0 0 0 5 h e l l o
Пример — расширение
hstore
, в нем строки передаются с фиксированной длиной. Видимо, разработчики поняли, что жить с нулем на конце нельзя и сделали нормально.Чтение строки известной длины — это две строчки кода. Чтение неизвестной строки — это цикл, выделения буферов, конкатенация и прочий бред.
Общий тезис таков: главное должно быть первым. В строке чаще всего нас интересует длина, поэтому она должна быть в начале. Бывает, содержимое вообще неважно, нужно только переложить строку в другое место и не ошибиться на байт туда-сюда. Сишные строки делают все, что такая ошибка случилась.
С любовью вспоминаю Паскаль, где строки были правильными: сначала байт с длиной, потом содержимое. Правда, из-за этого строки не могли быть больше 256 байтов. Разные компиляторы предлагали строки, где под длину выделялось два байта или четыре.
Можно долго рассуждать, что было и по каким причинам, но к делу это отношения не имеет. Важно запомнить, что главное должно быть первым — как на экране, так и в байтиках.
-
Бусти и почта
Подписался на одного человека в Бусти — первый раз пользовался этой платформой. Заодно наблюдал котовасию с подтверждением почты.
Дело было так: захожу через Гугло-учетку, все нормально, пришло письмо “здравствуй, дорогой Ivan”. А как начал читать, появилась плашка: чел,
ivan@grishaev.me
— это твой емейл?Вы серьезно? Гугл уже подтвердил, что это моя почта, что вам еще нужно? Как вы это представляете: зашел через Гугл, а почта не моя? Тыкаю Yes, чтобы отвязался. Перехожу в соседнюю вкладку, а там вылезла та же плашка. На этот раз я промахнулся и нажал No. В результате моя почта пропала из профиля, и Бусти пишет — пока не подтвердишь почту, покоя не дам.
Хоть я и не видел кода Бусти, легко понять, почему так происходит. Когда создается учетка, у нее стоит флаг “почта не подтверждена” — нормальное поведение для регистрации по email. Но разработчики не учли, что в случае с Гуглом почта подтверждается автоматом и выносят мозг подтверждением.
На ровном месте устроили бурю в стакане: подтверди, хотя никакого подтверждения не нужно. Большой и денежный сервис, а такие косяки.
Интересный факт об авторизации, который я не знаю, куда приткнуть, поэтому пусть будет здесь. Когда пользователь входит через Фейсбук, он может отказать в передаче емейла. В результате вам прилетит джейсончик, где всякие токены-шмокены, а почты нет. Если почта критична (а это чаще всего так), приходится заворачивать пользователя со словами “не быкуй, галочку не снимай”. Требует много кода и тестов.
-
Приглашаю на митап
В следующий четверг (28 ноября) Health Samurai проводят очередной кложурный митап. Я буду рассказывать про свою библиотеку PG2 — это которая клиент к Постгресу. Начало в 19:00, нужна регистрация. Ссылка на страницу организаторов.
Из анонса может показаться, что доклад будет про библиотеку, но на самом деле я бы хотел поговорить о другом. Пока я работал над ней, то столкнулся со спецификой, о которой раньше не думал, и теперь хочу ей поделиться. Кроме самой библиотеки, расскажу о следующих вещах:
- как устроен Postgres Wire Protocol
- зачем браться за свое решение, когда есть другие, проверенные годами
- сопоставление типов Postgresql и Java/Clоjure
- заморочки с парсингом
- мысли об API и дизайне
Подойдет всем, кто как-то связан базами данных. Запись на Ютубе будет через несколько дней после митапа.
-
Можешь поправить
Есть одна обидная вещь, я называю ее “можешь поправить”. Это когда находишь ошибку, которой много лет и которую воспринимают как данность. И когда обращаешь внимание, тебе отвечают — можешь поправить.
Текст ошибки совершенно нечитаем? Можешь поправить. Сервис делает тысячу запросов вместо одного? Можешь поправить. Хрупкие билды? Можешь поправить. Не собирается под твоей операционкой? Можешь поправить. Не работает в Фаерфоксе? Можешь поправить.
Ну вы поняли: “можешь поправить” — это вежливая форма “е…сь сам”. Ощущение, что на полу лежит какашка, и все старательно ее обходят, дожидаясь, кто вступит первым. Как правило, первым вступаю я.
Повторюсь, это очень обидная вещь, и вдобавок тревожный маркер о состоянии команды. В данном случае нужно править не только технические косяки, но и отношение к ним в команде.
-
Пробел в урлах
Хотя интернету 55 лет, мы до сих пор не починили пробел в урлах.
На выходных была ситуация: упали крон-джобы, и вообще произошла какая-то фигня. Файлы в S3 есть, а клиенты жалуются, что нету.
Смотрю — кто-то поменял название папки в S3. Раньше было
Daily_Reports
, а теперьDaily Reports
(с пробелом). Половина клиентов пишет файлы нормально. Но есть другие клиенты на питоне и баше, которые кодируют урлы дважды. В результатеDaily Reports
становится новой папкойDaily%20Reports
в S3. Один клиент пишет вDaily Reports
и ему ок. Второй клиент ищет файлы вDaily%20Reports
, не находит и падает.Увы, мой быдлокод тоже упал. У меня такая задача: прилетает S3-урл вида
s3://some.bucket.com/path/to/file.txt
и мне нужно вытащить из него бакет, в данном случае хост. Делаю так:
(-> s3-url java.net.URI. .getHost)
Но когда в урле оказался пробел, класс
URI
валится — вай, не по стандарту, не знаю-не могу. Поменял на классURL
— он парсит урлы с пробелами нормально, но теперь ему не нравится схемаs3://
— опять не по стандарту. Сделал так: беру урл, меняю схему автозаменой наhttp://
, оборачиваю в URL и достаю хост. Обмазал тестами.“Какая шаткая система, если её может разрушить пригоршня ягод!” (с)
-
О переменных среды
Про переменные среды нужно знать две вещи. Первая — если не было значения по умолчанию, функция должна бросить исключение, где написано, какой именно переменной не нашлось. Вторая — нельзя читать переменные посреди программы. Нужно сделать это один раз на старте. Эти два правила улучшают код на порядок.
Теперь подробнее. Как правило, если переменная среды не установлена, то попытка ее прочесть вернет пустую строку или
null
. Это неправильно. Должно выскочить исключение с примерно таким текстом:System.getenv("DB_PASSWORD") => RuntimeException "env variable DB_PASSWORD is not set"
Если вместо исключения будет
null
, то он провалится в дальнейшие вычисления, например, в формирование урла или бакета. Постоянно вижу такое в коде:host = "api." + getenv("ENV_PREFIX") + ".acme.com"
Если
ENV_PREFIX
не задан, то получитсяapi.null.acme.com
. Из-за этого HTTP-клиент пойдет на левый хост и кинет непонятное исключение. То же самое с бакетом в S3: Амазон скажет, мол, нет такого бакета, а вы будете рвать волосы.Простой фикс — написать свою функцию, которая бросит исключение. Потом заменить все коробочные getenv на ваш. Пример:
(dеfn env! ([varname] (or (System/getenv varname) (throw (new RuntimeError ...)))) ([varname dеfault] (or (System/getenv varname) dеfault)))
Исключение не кидается, если передан дефолт.
Второе. Чтение переменной среды — это грязная операция. Формально никакого IO не происходит, потому что переменные уже в памяти программы. Но это сторонняя зависимость, которую ваш код не контролирует. По факту чтение переменной не отличается от чтения файла. Нет файла — программа сломается; нет переменной — тоже.
Поэтому, если уж вы связались с переменными среды, читайте их на старте в какую-то мапку. Передавайте эту мапку в функции, чтобы они были чистыми. Это легко тестировать: подал на вход то, это, пятое-десятое. А когда код завязан на getenv, с ним невозможно работать.
Приходилось работать в проекте, который писали одни чудики. Они прочитали The Twelve-Factor App и особенно прониклись пунктом насчет переменных среды. Результат можно описать одним словом: пи…ц. Представьте проект на 600 файлов, где на каждый чих читается переменная среды, да к тому же приводится к нужному типу. На старте ничего не проверяется: запустил код без переменной — узнал об этом в продакшене. Какой-то чел добавил глобальный кэш переменных и целый ворох связанных с ним проблем.
Чудики вынесли настройки в переменные среды, чтобы быть свободными от конфигурации. Так им сказали в The Twelve-Factor App. А потом написали ENV-файлы на три экрана. Было несколько ENV-файлов, которые загружались в особом порядке, переопределяя значения друг друга. Например, сначала базовый энв, потом энв текущего окружения (тест, прод, стейджинг), потому энв текущей машины. Удачной отладки.
Из этого вывод: много чего можно прочитать в интернете, но если нет своей головы, оно не поможет. Нужно делать так, чтобы было удобно, а не как написано в The Twelve-Factor App или на Хакер-Ньюз.