Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Типы дат java.time

С java.time жить можно, хотя Joda Time у автора получился поудобнее. Памятка про отличия различных типов. TLDR: LocalDateTime с большой вероятностью бэкэндщику не нужен, а OffsetDateTime покроет большинство бытовых кейсов.

СсылкаКомментировать

Поиск неактуальных веток git

Продолжаю прибираться в проекте. На этот раз цель — избавиться от никому не нужных веток, которые были уже вмержены или забыты.

Главный помощник в этом деле — команда git ls-remote --heads [repoUrl], которая позволяет получить список веток репозитория без его клонирования. Удобно, когда тебе нужно 5+ реп обработать.

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

Если надо почистить локальные ветки, которые уже были удалены в удаленном репозитории — есть плагин, который это делает в пару кликов (VCS → Git → Delete old branches…). Правда он скорее всего будет одноразовый: вы же будете держать репу в чистоте после генеральной уборки?

СсылкаКомментировать

Про Slack

Долгое время я скептически относился к слаку, считая, что телега лучше. И для личного общения/ботов/перекидывания стикерами она все равно лучше. Но для работы слак оказался удобнее. В мае моя команда на него перешла и возвращаться в телегу вряд ли будет.

  1. Киллер-фича — это треды. Особенно, когда в чате дохрена людей и тебе не нужно читать ВСЕ. Вдобавок, когда обсуждается несколько тем одновременно, тележный чат быстро превращается в помойку. 100 непрочитанных сообщений в секунду будут читать только психи. Чаты на 100+ человек в телеге даже более-менее на 1 тему — это рак. Я вообще не понимаю, какая от них польза, особенно когда туда заходят новые люди с типовыми вопросами. Конечно, к тредам надо приучать, но как показала практика, через пару недель игры в злого полицейского останутся только совсем отъявленные “не такие как все”.
  2. Настоящее отключение уведомлений. Не показывать сереньким, не показывать сереньким в архиве, а просто не показывать никак. Полезно для всяких чатов поддержки: понадобилось тебе что-то, зашел, спросил, в треде тебе ответили (и на это приходят уведомления разумеется), потом об этом чате забыл.
  3. Настоящие имена. Мелочь, но до переезда были случаи, когда пишешь кому-то в личку по рабочему вопросу и даже его пола не знаешь, не то что имени.
  4. Выключил рабочий комп = выключил работу. Никаких переписок в полночь о том, что надо бы завтра какую-нибудь фигню сделать.

Но по стикерам иногда скучаешь… Радужные попугаи — это круто, но огромную рожу Гарольда уже не вбросишь.

СсылкаКомментировать

Дебри рефлексии

Картинка с двумя стульями у меня родилась не просто так. Довольно типичная рабочая ситуация, в которую я вляпался на прошлой неделе. Ближе к концу я уже сел на третий стул пулл-реквестов и сделал их аж три штуки, но расскажу о дебрях, в которые зашел, пытаясь решить проблему через рефлексию.

Пусть у нас есть публичный метод, в котором надо немного подшаманить:

@Override
String exampleMethod(){
    String firstPart = privateMethodToModify();
    String secondPart = super.exampleMethod();
    String thirdPart = anotherPrivateMethod();
    return firstPart + secondPart + thirdPart;
}

Первая реакция будет: “Чё тут делать-то? Отнаследуйся да переопредели!”. Но есть нюанс: super будет уже не тот. А беспокоить дедушек в java нельзя.

Ок, может тогда получим указатели на exampleMethod и anotherPrivateMethod, и в месте вызова ручками все подергаем? Однако полиморфизм будет суров, и exampleMethod вызовется из дочернего класса, даже если указатель вы получали у родителя.

Что же делать? Сдаться и скопипастить дочерний класс, изменив в нем что нужно? Скорее всего да, но если вы хотите сидеть на пиках до конца, то есть еще один способ — MethodHandles. Это новый API, который не является заменой рефлексии, а дополнением к ней, в основном для всяких низкоуровневых манипуляций для разработчиков языков под JVM. С ним можно вызвать родительский exampleMethod для ребенка:

MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Child.class, MethodHandles.lookup());
MethodHandle baseHandle = lookup.findSpecial(Parent.class, "exampleMethod", MethodType.methodType(String.class), Child.class);
return (String) baseHandle.invoke(child);

Но, как всегда, есть нюансы. Во-первых, если java меньше 9 версии, то придется еще и рефлексию использовать, чтобы в приватные методы залезть (выглядит стремно). Во-вторых, в kotlin это работать из коробки пока не будет без специальных флагов, но в 1.4 эти флаги включат по умолчанию. 100% интеропа такие 100%.

Посмотреть примеры подробнее можно github. Познакомиться с API подробнее — тут, а узнать, зачем оно вообще нужно — тут.

СсылкаКомментировать

Закрытие старых ревью в Upsource

К сожалению, теорию разбитых окон можно наблюдать и в разработке. Поэтому периодически стоит наводить чистоту в коде, трекерах задач (можно называть это модной фразой “груминг бэклога”) и прочих штуках.

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

Внезапно, у Upsource есть API, но есть нюанс: названия методов придется угадывать самостоятельно по названию DTO. В остальном все относительно просто: берем список ревью по подобранному методом тыка запросу, и закрываем по одному. Все вместе можно посмотреть в gist.

СсылкаКомментировать

Форматирование строки в python

Я тот еще консерватор и не очень люблю новое. Поэтому статьи про

"hello, {}!".format("world")
"hello, {0} and {0} again!".format("world")
"hello, {name}".format(name="world")

вызывали у меня легкое раздражение: я не видел никаких существенных преимуществ перед старым добрым "hello %s" % "world". Но пора бы очнуться от летаргического сна, и посмотреть на Python 3.6+ (выпущенный в 2016).

Там есть f"hello, {name}!", у которого в скобках будет полноценное выражение, т.е. можно сделать f"hello, {''.join(map(chr, [87, 111, 114, 108, 100]))}!". Этот вариант хорош для большинства случаев, особенно с учетом того, что почти во всех распространенных языках строковая интерполяция плюс-минус так и работает. Кроме Java, но она всегда была немного “особенной”.

Пример случая, когда такая интерполяция не очень уместна: фиксированный формат, который используется несколько раз и вынесен в “константу”. Например, у меня в скриптах довольно часто встречается такое:

XX_PATH = "http://%s/some/api/path/%s"
...
response = requests.get(XX_PATH % (host, param), ...)

Тут придется садиться на один из стульев: % или format.

СсылкаКомментировать

Поиск удаленного в истории git

Искать в текущей версии любой дурак может, а как найти что-то, что было удалено в истории git?

git --no-pager grep -i "search term" $(git rev-list --all)

--no-pager уже может быть знаком по journalctl, -i, как и в обычном grep, говорит об неважности регистра, а вызов $(git rev-list --all) дает список всех ревизий. Будет вывод в формате хэш коммита:/путь/к/файлу: совпавшая строка. А дальше переключаемся на нужную ревизию и ищем как раньше.

СсылкаКомментировать

Что означает поле update в результатах update by query в Elasticsearch?

Смотрим в документацию:

updated
  The number of documents that were successfully updated.

Внимание, вопрос: учитываются все затронутные документы или все измененные? Вроде как все затронутые, да и обновления могут быть без изменений документа (для того, чтобы обновить документ в индексе, например). Но хочется побольше уверенности.

Роемся в исходниках, недолгий поиск по ключевому слову updated приводит сначала к WorkerBulkByScrollTaskState, а потом к AbstractAsyncBulkByScrollAction:

switch (item.getOpType()) {
    case CREATE:
    case INDEX:
        if (item.getResponse().getResult() == DocWriteResponse.Result.CREATED) {
            worker.countCreated();
        } else {
            worker.countUpdated();
        }
        break;
    case UPDATE:
        worker.countUpdated();
        break;
    case DELETE:
        worker.countDeleted();
        break;
}

Тут видно, что в подсчете вообще не учитывается, был ли изменен документ — интересен только тип операции. Соответственно в поле updated ответа будет число всех затронутых документов, даже если они не менялись. Отличаться от total оно будет только тогда, когда создаются или удаляются документы. Т.е. total = updated + deleted + created.

Люблю Open Source продукты за то, что можно при желании разобраться почти в любой проблеме.

СсылкаКомментировать

Из чего только не строят графы

Прикольная статья про то, как чувак строил графы по автодоплнению поисковой выдачи гугла ключевое слово vs. Перевод для нихтферштейнов.

Кто-то сделал даже страничку, чтобы поиграться самому. Запрос к гуглу идет напрямую из браузера, так что персонализация поиска скорее всего будет работать (BB is watching you).

СсылкаКомментировать