Читать в телеге. Когда-то там были посты не только от меня.
Типы дат java.time
С java.time жить можно, хотя Joda Time у автора получился поудобнее. Памятка про отличия различных типов.
TLDR: LocalDateTime
с большой вероятностью бэкэндщику не нужен, а OffsetDateTime
покроет большинство бытовых кейсов.
Поиск неактуальных веток git
Продолжаю прибираться в проекте. На этот раз цель — избавиться от никому не нужных веток, которые были уже вмержены или забыты.
Главный помощник в этом деле — команда git ls-remote --heads [repoUrl]
, которая позволяет получить список веток репозитория без его клонирования. Удобно, когда тебе нужно 5+ реп обработать.
Со списком веток на руках можно сделать запрос к баг-трекеру, чтобы получить статус соответствующего тикета (если ветка содержит его идентификатор, разумеется). Мне не повезло и у меня Jira, хорошо хоть к ней есть подобие клиента. А дальше — закрываем ручками то, что в финальном статусе (можно автоматизировать и удаление, но как-то стремно). Полностью скрипт можно посмотреть в gist.
Если надо почистить локальные ветки, которые уже были удалены в удаленном репозитории — есть плагин, который это делает в пару кликов (VCS → Git → Delete old branches…). Правда он скорее всего будет одноразовый: вы же будете держать репу в чистоте после генеральной уборки?
Про Slack
Долгое время я скептически относился к слаку, считая, что телега лучше. И для личного общения/ботов/перекидывания стикерами она все равно лучше. Но для работы слак оказался удобнее. В мае моя команда на него перешла и возвращаться в телегу вряд ли будет.
- Киллер-фича — это треды. Особенно, когда в чате дохрена людей и тебе не нужно читать ВСЕ. Вдобавок, когда обсуждается несколько тем одновременно, тележный чат быстро превращается в помойку. 100 непрочитанных сообщений в секунду будут читать только психи. Чаты на 100+ человек в телеге даже более-менее на 1 тему — это рак. Я вообще не понимаю, какая от них польза, особенно когда туда заходят новые люди с типовыми вопросами. Конечно, к тредам надо приучать, но как показала практика, через пару недель игры в злого полицейского останутся только совсем отъявленные “не такие как все”.
- Настоящее отключение уведомлений. Не показывать сереньким, не показывать сереньким в архиве, а просто не показывать никак. Полезно для всяких чатов поддержки: понадобилось тебе что-то, зашел, спросил, в треде тебе ответили (и на это приходят уведомления разумеется), потом об этом чате забыл.
- Настоящие имена. Мелочь, но до переезда были случаи, когда пишешь кому-то в личку по рабочему вопросу и даже его пола не знаешь, не то что имени.
- Выключил рабочий комп = выключил работу. Никаких переписок в полночь о том, что надо бы завтра какую-нибудь фигню сделать.
Но по стикерам иногда скучаешь… Радужные попугаи — это круто, но огромную рожу Гарольда уже не вбросишь.
Дебри рефлексии
Картинка с двумя стульями у меня родилась не просто так. Довольно типичная рабочая ситуация, в которую я вляпался на прошлой неделе. Ближе к концу я уже сел на третий стул пулл-реквестов и сделал их аж три штуки, но расскажу о дебрях, в которые зашел, пытаясь решить проблему через рефлексию.
Пусть у нас есть публичный метод, в котором надо немного подшаманить:
@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 подробнее — тут, а узнать, зачем оно вообще нужно — тут.
Случайная выборка из csv
Безо всяких питонов сделать случайную выборку данных из текстового файла можно командой shuf
:
cat dataset.csv | shuf | head -n100 | tee random_sample.csv
Закрытие старых ревью в 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).