Журнал

Как убить на мелкий скрипт кучу времени или история одного пулл-реквеста

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

Понадобилось мне дублировать информацию из markdown-заметок в телеграм-канал. Казалось быть, что тут рассусоливать — Ctrl+C и Ctrl+V в помощь. Однако выяснился маленький нюанс: markdown в телеге не совсем полноценный и ссылки в таком формате [text](http://example.com) клиент не поддерживает. Ладно, подумал я, попробуем что-то с этим сделать.

Пытаюсь победить врукопашную

Руками править ссылки, особенно когда их несколько — довольно муторно. Особенно с учетом бага декстопного клиента, из-за которого невозможно редактировать отложенные сообщения. С планшета можно отредачить, но это еще напряжнее.

Может, получится копированием из браузера или текстового редактора? Снова облом и еще один баг в телеге: при использовании буфера обмена форматирование сообщения теряется. Я заинтересовался, почему же так произошло.

В этом нам поможет утилита xclip, которая позволяет инспектировать содержимое буфера обмена. Для сообщения телеги она показывает такие доступные форматы:

text/plain
UTF8_STRING
STRING
TEXT
application/x-td-field-text
application/x-td-field-tags

Сравните это с теми, что предоставляет Firefox:

text/html
text/_moz_htmlcontext
text/_moz_htmlinfo
UTF8_STRING
COMPOUND_TEXT
TEXT
STRING
text/plain;charset=utf-8
text/plain
text/x-moz-url-priv

или какой-нибудь Libre Office Writer:

application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"
text/rtf
text/richtext
text/html
text/plain;charset=utf-16
application/x-openoffice-objectdescriptor-xml;windows_formatname="Star Object Descriptor (XML)";classname="8BC6B165-B1B2-4EDD-aa47-dae2ee689dd6";typename="LibreOffice 6.0 Text Document";viewaspect="1";width="16999";height="2995";posx="0";posy="0"
text/plain;charset=utf-8
UTF8_STRING
STRING
application/x-libreoffice-internal-id-5387

В общем, судя по всему, проблема достаточно глубокая. С учетом того, что я уже наткнулся на три бага декстопного клиента телеги, пора бы уже рассмотреть альтернативные решения. Конечно, можно попробовать сделать пулл-реквест, чтобы пофиксить один из двух багов, но что-то я посмотрел на исходники, вспомнил насколько я хорош с Qt да и с современными плюсам и подумал, что нет, как-нибудь в другой раз. Отложенные 5 минут я к этому моменту уже давно исчерпал, но уже интересно было из принципа хоть как-то облегчить себе задачу копипаста.

В любой непонятной ситуации пиши скрипт

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

Ок, нам нужен скрипт, который прочитает файлик и отправить его сообщением в телегу. Какие библиотеки для этого есть? На PyPI 31 страница с результатами по запросу "telegram". Выбрал Telethon — на хабре статьи с ним есть, и вообще, стильно, модно, молодежно, асинхронно, чистый питон, MTProto и так далее.

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

from telethon.sync import TelegramClient
import socks

api_id = 11111
api_hash = '...'
proxy=(socks.SOCKS4, '127.0.0.1', 9050)

with TelegramClient('anon', api_id, api_hash, proxy=proxy, timeout=60) as client:
	print(client.get_me().stringify())

Запускаем скрипт... и он не работает из-за того, что не может подключиться к телеге. Конечно же, я не HACKERMAN, чтобы он с первого раза заработал. Наверно, проблема с прокси.

We need to do deeper

Ок, проверим, работает ли Tor, который исполняет роль прокси:

$ curl -XGET httpbin.org/ip --socks5 localhost:9050

Получаем ответ с адресом, который не мой — все ок. Значит, проблема в том, как работает прокси в Telethon. Какую библиотеку он для этого использует? PySocks. Отлично, с этой библиотекой работал давным-давно. Там есть простенький скрипт, которым в том числе можно проверить работу прокси. Запускаем и получаем...

Traceback (most recent call last):
  File "test.py", line 110, in <module>
    print("HTTP: " + opener.open("http://httpbin.org/ip").read().decode())
  File "/usr/lib/python3.6/urllib/request.py", line 526, in open
    response = self._open(req, data)
  File "/usr/lib/python3.6/urllib/request.py", line 544, in _open
    '_open', req)
  File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "test.py", line 94, in http_open
    return self.do_open(build, req)
  File "/usr/lib/python3.6/urllib/request.py", line 1318, in do_open
    encode_chunked=req.has_header('Transfer-encoding'))
  File "/usr/lib/python3.6/http/client.py", line 1254, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib/python3.6/http/client.py", line 1300, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.6/http/client.py", line 1249, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib/python3.6/http/client.py", line 1036, in _send_output
    self.send(msg)
  File "/usr/lib/python3.6/http/client.py", line 974, in send
    self.connect()
  File "test.py", line 55, in connect
    ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),))
  File "/usr/lib/python3/dist-packages/socks.py", line 200, in create_connection
    proxy_username, proxy_password)
  File "/usr/lib/python3/dist-packages/socks.py", line 322, in set_proxy
    username.encode() if username else None,
AttributeError: 'int' object has no attribute 'encode'

Ах ты ж... Какого черта? Я ведь сам туда хэндлер для https добавлял, должно работать! Что могло там поменяться за... 6 лет? Какой я старый :(

15-20 минут на клонирование репы и принт-деббагинг приводят к ответу, что проблема в неправильном порядке аргументов — в основной библиотеке поменяли аргументы, а во вспомогательном скрипте забыли поправить. Хорошо хоть пулл-реквеcт на фикс этой проблемы есть, и скрипт оттуда работает. Мораль проста: если список аргументов длинный, лучше использовать именованные параметры.

Ок, с этим разобрались, значит, проблема не в прокси. В чем же дело? И тут мне в голову приходит гениальнейшая идея про то, что может стоит включить дебаг-логи в Telethon? И одновременно осознание, какой же я даун, если не додумался до этого раньше. Пробуем с дебаг-логами:

DEBUG:telethon.network.mtprotosender:Connection attempt 2...
WARNING:telethon.network.mtprotosender:Attempt 2 at connecting failed: ProxyConnectionError: Error connecting to SOCKS4 proxy 127.0.0.1:9050: [Errno 115] Operation now in progress

О чем же говорит нам эта ошибка? По номеру находим код EINPROGRESS, а про него читаем уже маны ядра по функции connect. Если общими словами, то проблема заключается в том, что у нас неблокирующая операция (в нашем случае установка соединения) не успела выполнится мгновенно и намекает нам, что она все еще выполняется. Похоже на правду: Tor не очень быстр. Не ожидал конечно, что ради 10-строчного скрипта на питоне, который в наши времена любой школотрон пишет за 5 минут с закрытыми глазами, мне придется опуститься до уровня системых вызовов и вспоминать особенности работы с сокетами...

После того, как я понял, в чем проблема, выяснилось, что она на самом деле уже известна, правда сформулирована по-другому. И внезапно автор этого тикета в репозитории — создатель библиотеки Telethon. Есть еще баг с похожими симптомами в самом Telethon и подозрительно похожий на мою ситуацию тикет в PySocks.

Пора уже делать что-то полезное

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

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

- s.setblocking(False)
+ s.settimeout(timeout)

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

Возвращаемся назад

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

Как нормально передать параметры api_key и api_hash, которые нельзя отозвать/поменять, но можно заабузить? Посмотрел, как это делают в примерах Telethon — через переменные окружения. Неплохо, но где хранить тогда эти переменные окружения? В конфиге, чтобы подгружать их? А что если кто-то получит доступ к конфигу?

Тут я немного начал параноить (видимо сказывается профдеформация) и начал думать в сторону всяких Vault, шифрованных разделов, аппаратных ключей, начал смотреть про их уязвимости... Решил даже спросить совета у хорошего знакомого, который лучше разбирается в ИБ, чем я. Рассказал ему о проблеме. И тут он спрашивает про мой скрипт:

А зачем ему энтерпрайз левел секурити?)

...

... подумал я и впихнул чтение через keyring. Не plain-text, ничего разворачивать не надо, даже ставить не надо. Для моих целей действительно сойдет.

Заключение

Я надеюсь, что мне удалось на примере еще раз показать, что оценка времени на работу — непростое занятие, что для программиста иметь кругозор — хорошо, и что надо знать, когда стоит остановиться. Скрипт разросся до 50 строк, из которых полезны только 10, остальное — парсинг параметров, импорты да отступы. А пулл-реквест в итоге приняли на следующий день после небольшой дискуссии.

Надеюсь, что статья вас хотя бы позабавила, а если вы узнали что-то новое, то вообще шикарно. Спасибо за внимание!

Опрос: оправдана ли трата нескольких часов на этот скрипт?

  • Да
  • Зависит, от ситуации, но скорее да
  • Зависит, от ситуации, но скорее нет
  • Точно нет
СсылкаКомментировать

Программист — существо социальное

Работа в команде

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

Взаимодействие с другими отделами

Частая причина "войн" отделов — непонимание. И это замкнутый круг: из-за непонимания возникают проблемы, ответственность сваливают друг на друга, и оборот вражды делает ещё один круг. Чтобы разорвать порочный круг, надо просто сесть и поговорить, наладить общение (даже если ненавидите этих уродов людей), понять, какие у них трудности и в чем их проблемы. "Возлюби ближнего своего". Можно разработать сотни регламентов, но если вас на том конце будут ненавидеть, то и качество будет "на отвали" (итальянская забастовка).

Развитие себя и своей команды

Обратная связь, метод «360 градусов», встречи 1:1, да даже корпоративы — это все про общение, налаживание контактов и социальные взаимодействия. И хоть в каком-то виде они в организации должны быть, иначе сохранение существующего строя становится целью компании, а люди превращаются в винтики внутри системы: без обратной связи и реагирования на неё все покатится в бездну, как в каких-нибудь сильно бюрократизированных организациях, где система — прежде всего.

Лучшие практики из мировой истории

Один из наилучших способов оценить свою работу — обратная связь от коллег, как внутри компании (от команды, лида/руководителя), так и вне её (общение с другими специалистами в отрасли). Однако для объективной оценки надо выстраивать доверительные отношения, иначе получится как в социальных сетях/конференциях — красивая картинка, а на самом деле та ещё дичь. Без обратной связи команда не понимает правильности сделанных шагов и выбранного направления, а, значит, и эффективности проделанной работы, что неминуемо приведет к спаду мотивации.

Прокачка навыков тимлида

Обратите внимание на тимлидские конференции. Про что там доклады? Про общение, налаживание связей, софт-скиллы, онбординг, мотивацию, выгорание и не только. Встречаются выступления про процессы, «залётные» доклады про декомпозицию задач и планирование, но самые интересные всегда про людей. Характер людей менять тяжело, их понять-то не всегда просто, а без этого никак. По этому поводу, кроме докладов с конференций (например, TeamleadConf), не лишним будет прочитать книги Дж. Ханка Рейнвотера «Как пасти котов. Наставление для программистов, руководящих другими программистами» и Марка Гоулстона «Как разговаривать с мудаками. Что делать с неадекватными и невыносимыми людьми в вашей жизни». Первая довольно легкая, вторая более фундаментальна, но не забывайте про разумный скепсис.

Будь позитивен

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

Лучшие практики и с чем их едят

Стоит понимать, что общие принципы фундаментальны и особо не зависят не то что от языка, но даже и от парадигмы программирования. На этих принципах уже строятся более конкретные вещи. И по мере того, как программист становится матерым, он из этих фундаментальных принципов может выводить уже прикладные решения. Поэтому всякие трюки "специально для Java" или "специально для Python", конечно, полезны тем, что дают вам знание на уровне "а что, так можно было?" или "о, так это так работает", но лучше все-таки более системно подходить к этому вопросу. Языки меняются, «кишки» — тоже дело переходящее, фундаментальные принципы — редко.

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

Через призму такого зрения возникнет вопрос: а как писать хорошо на новом языке программирования и понять его философию? Самый простой ответ — посмотреть существующий код из плюс-минус доверенных источников, желательно, разумеется, от создателей языка. Но большинство мейнстримовых языков сейчас мультипарадигменные и довольно мощные, поэтому в одном языке можно написать одни и те же вещи очень по-разному, и, опять же, на это могут сильно влиять окружение проекта, его цели и т. п., например, код на Kotlin для Android, для Enterprise-бэкэнда, для IOS и для фронта могут выглядеть настолько по-разному, что как будто написаны на разных языках. И "лучшая практика" для IOS-кода может быть тем еще костылем в мире энтерпрайза и наоборот. Поэтому надо затачивать практики под свое окружение и проверять их, а не делать из них культ карго.

Стоит еще вопрос задать — а зачем вам практики нужны? Что конкретно плохо в текущем коде? Как бы вы это решили в хорошо знакомом языке? Можно ли так же решить в этом? Да, возможно, неидиоматично получится, но почему это вас должно волновать? Код решает конкретную проблему, к нему есть требования по качеству. Идиоматичность — приятно, но совсем не главное. Узкие вещи по языку лучше изучать планомерно и глубоко, а не на основе коротышей с поверхностными трюками.

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

Teamlead: постановка задач

Исполнитель должен понимать контекст задачи

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

Надо хорошо понимать исполнителя

Если вы в одном контексте, и исполнитель хорошо знает тему, то и описание ему можно дать достаточно короткое. "Петрович, дай ту фиговину, чтобы я ее прихреначил на эту приблуду". Петрович в контексте, он и так знает, что надо без 1000 слов. А если вместо Петровича придет стереотипная Софочка, то ей придется долго рассказывать про фиговины, хреначение и приблуды. А если подавать фиговины больше некому - что ж, лучше потратить время на то, чтобы Софочку ввести в контекст, писать первое время подробно все и разжевывать, а потом постепенно уменьшать уровень подробностей. В идеале Софочка превратиться в Петровну и без проблем сможет подавать даже хреновины для прифигачивания, а не только фиговины для прихреначивания. Чем опытнее исполнитель, тем больше свободы ему стоит давать (и меньше лишних подробностей).

Еще один аспект про понимание исполнителя

Сейчас уже некоторые люди книжку не могут прочитать, а чтиво на 5 минут - уже лонгрид. Можно сделать суперподробное описание, но какой-нибудь зумер с клиповым мышлением скажет TLDR и прочитает только заголовок. Тут либо человека образумить (хороший вариант), либо находить к нему подход - кинуть голосовое в чатик, пантомиму с рисованием у доски сделать и т.п. А если серьезно, то есть вариант обсуждения - дайте краткое описание задачи, спросите человека, как будет делать, обсудите, и пусть сам потом запишет это в описание тикета (разумеется, потом надо проверить). Ему уже и по башке можно будет с чистой совестью дать, если сам себе задачу описал и сам же неправильно сделал.

Адекватно декомпозируйте задачи

Зависит от многих вещей, но в среднем по больнице задача дольше дня уже должна быть декомпозирована. Не обязательно отдельным тикетом, в формате TODO-шек в тикете и/или чеклистов тоже сойдет, зависит от вашего workflow. И декомпозировать может не только руководитель, но и исполнитель. Ежедневные agile-митинги примерно про это - что сделал, какие проблемы и т.п. (но они не панацея, и не всегда нужны). Это нужно, чтобы держать руку на пульсе и перенаправить исполнителя в нужное русло. Иначе получится, что человек неделю варился в собственном соку и на выходе совсем не то. Если будет привита практика регулярного отписывания по тикетам - то там и варианты решения можно обсудить на ранних стадиях.

Исполнитель должен знать неявные условия задачи

Окружение, проект и его цели, стек технологий, сложившиеся практики, стиль кода и т.п.. Чтобы это все было как данность и незамусоривало сам тикет. Отпадут проблемы когда весь стек на java, а чувак написал сервис на rust просто потому что ("ну а че, в тикете написано же не было"). Как вы определяете, что "сделано не то"?

Определите, по каким критериям будет приниматься задача. Знает ли их исполнитель? А он как раз должен. Определите, что критично - напишите в тикет это. Подумайте о definition of ready (DOR) и definition of done (DOD) (а если не знаете, что это такое - стоит почитать, например, тут).

И помните, что garbage in => garbage out.

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

НПиО 2019

Доклад для студентов. Очень поверхностный.

Тони Хоар, создатель быстрого алгоритма сортировки, как-то сказал: "Внутри каждой большой программы находится маленькая программа, пытающаяся вырваться наружу" (Inside every large program is a small program struggling to get out). Программирование - это не только реализация алгоритмов и "фич". Очень много работы программиста заключается в подготовке, валидации, индексации данных и перемещении их из одного места в другое. Причем обычно это должно быть быстро и масштабируемо, а еще чтобы было без багов, можно было нетрудно поменять и легко развернуть новую версию на прод. Это может звучать немного скучно, особенно на фоне работы Дата Сайентиста, который обучает нейронки, чтобы писать сценарии для новых фильмов Бетмена или прилепить лицо Трампа на мексиканца. Однако proof-of-concept - это одно, а раскатать модель это на прод "чтобы работало" и чтобы к ней приходили качественные данные - уже инженерная задача, причем не всегда тривиальная. Сравнительно недавно появилось модное название для программистов, которые делают эту работу для Дата Сайентистов - Дата Инженеры. В чем заключается "рутина" программирования? Что делают Дата Инженеры, какие навыки им нужны и какие инструменты они используют?

Презентация

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