Telegram Web
Давайте поиграем в самообман

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

🔥 Так интереснее. Да, по граблям на костылях пройдусь, баги соберу (но это неточно), зато сам. Писать коды для чего-то, чем пользуются другие люди, что зарабатывает деньги тебе - самая клёвая работа. Может, это не по-предпринмательски, зато в кайф. Все эти тесты-шместы, в докер с пубернетисом завернуть, сборки и CI, cloudflare настроить, мониторинги и логи, вся эта шляпа - это промышленное программирование. Оно всё одинаковое, неинтересное, непрозрачное, очень многословное, энтерпрайзное, по best practices с SOLID - не люблю.

🥷 Уровень контроля. Я дико кайфую, когда могу делать что хочу и как хочу. Да, абстракций ещё пару слоёв нагородить придётся, запутаться в этом всём, переписать к чёрту. Зато получится красиво, как хотелось! Я лезу в редис и смотрю, кто там у меня на каком этапе регистрации застрял и исправляю баги не просто в проде, а прямо в стейте прода. Это почти как отладчиком в памяти шарить. В БД руками исправляю имя пользователя с опечаткой. Делаю новую платёжную ссылку для транзакции, которая подвисла - а код для этого у меня ещё не написан. Достаю пользователей с marketing_consent=True и рассылаю им что хочу. Круто же! Какая из существующих систем такое позволит?

😎 Такого, как я хочу, нет. Например, у нас своя уникальная система репостов-комментариев. Потому что мы хотим скрывать историю в основном канале Клуба, а телеграм этого не умеет с каналами. То есть либо скрытая история в обычном чате, либо канал с комментариями и открытой историей. И все делают каналы + обычные комментарии. А у нас круче, ни у кого такого нет пока. Или своя система отслеживания вовлечённости. Я её ещё не доделал, но, может быть, она поможет терять меньше клиентов каждый месяц. Ну и я столько ещё всего хочу навыдумывать и сделать, что это и армией чужих ботов не покрыть.

💩 Готовые решения часто ещё хуже. Написаны кем-то, кому дела до твоего бизнеса нет. Особенно телеграм-боты. Это ж не бизнес, где все серьёзно, QA, SLA, комплаенсы, аудиты и прочие страшные аббревиатуры из программы MBA. Хочу я стимулировать импульсивные покупки и просить email-ы уже после оплаты - да пожалуйста. Буквально: вызвать функции register и buy в обратном порядке. А в готовом боте кто это для меня сделает? Кто там баги поправит, если у меня люди идут в период продаж, а он глючит? Или я даже просто не пойму, что он глючит, а подумаю, что у нас продукт говно и конверсии говно, и это мы сами себе злые буратины.

🤘Я хочу владеть данными своих пользователей, потому что у нас на Клуб и связанные продукты большие планы. Допродавать тем, кто уже покупал, давать скидки за друзей, рассылать email-приглашения. Чужих ботов придётся либо прикручивать к собственному бэкенду, либо просто хранить все данные о клиентах там. Ок, если это страйп, это ещё можно пережить - и то source of truth для меня всё же в нашей БД. Но уж точно не чьи-то телеграмьи поделки! Так я хотя бы в один (почти) монолит полезный код пишу, а иначе буду зоопарком этих недосервисов через вебхуки и очереди жонглировать. Вот ещё.

🤬 Чужие поделки на порядок (без преувеличения) дороже. В комментарии Елена принесла один похожий сервис, так они берут комиссию 20%! Любимый креаторами (тьфу!) gumroad - 10%. У меня в страйпе не больше 3%, в лучшем случае около 1,7%. Ну это куда вообще годится? Ещё я нолог каким-то деятелям-погромистам, таким же, как я, не платил. Хватит с меня и англичанки.

Короче, все свои немалые усилия я воспринимаю не иначе как инвестиции. Для меня вопрос не в том, нужно ли их делать, а отобьются ли они - и когда. Лучшее ли это, во что я могу инвестировать своё время и усилия сейчас? Пока, на старте, кажется, что да. А как оно обернётся - мы вместе узнаем.
🔥16👍2🙏2🤔1
Добавленная ценность

Чем больше кодов для Клуба пишу, тем сильнее осознаю, что эти мои коды конкретно в нашем продукте почти не создают добавленной ценности 🤔 Ха-ха, привет, прошлый пост! И всем любителям моих смысловых качелей тоже привет!

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

А чтобы не сойти с ума и в билетиках не запутаться, эксельку для учёта с интерфейсом в виде телеграм-бота накодировал.

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

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

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

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

В конце концов, эти рассуждения подводят меня к моему любимому вопросу: почему мне кажется более привлекательным предпринимательство, а от программирования иногда воротит? Да потому что программировать скучно. Скакнуть из 0 в 1 с помощью кодов - круто. Из единицы вырасти в двоечку-троечку, когда всё работает как часы - ладно, под пиво попрёт. А заниматься очередной развесистой системой годами точно не кайф.

Поэтому и тянет меня всё сильнее в важное: в ценность и продукт, в ценообразование и разные тарифы, в создание работающего сообщества, в маркетинг, в понимание целей и задач наших клиентов, чтобы лучше им помогать (и больше продавать).
7💯4🤔3👍2
Привет 👋

Мы с моим другом Виктором Карповым работаем над новым образовательным проектом.

Если вы проходили онлайн-обучение программированию за последние 2 года, пожалуйста, заполните наш коротенький опросник:

👉 https://forms.gle/wWDzDFitHSTYRQ316

А если ещё и готовы поболтать около часа про ваш опыт обучения программированию и работы, то поставьте галочку в конце формы 🔥

В качестве благодарности за разговор будет консультация + письменные рекомендации от меня и/или Виктора по любым вопросам - от алгоритмов до софт-скиллов. Спасибо! 🙏
12
Программист на свободе

Шесть лет назад я уволился из Яндекса, оставив хороший незавестившийся опцион и отличное, свое место и роль тимлида команды разработки партнёрского кода РСЯ, чтобы "покорять америку". Изрядно нагревшись под калифорнийским солнцем, я вдруг осознал: а можно же ведь было не работать! И мир не останавливается, и смысл жизни не пропадает. Деньги, конечно, нужны, но это ведь не самое главное.

Не бездельничать с пинаколадой на пляже, нет. Просто можно заниматься чем-то не столько потому, что за это тебе платят, а потому что в кайф. При этом я очень нежно вспоминаю свои годы в Яндексе и люблю всех бывших коллег, с которыми, увы, не общаюсь и 1/10 того, сколько хотелось бы.

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

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

Виновата ли в этом компания? Нет, просто даже тимлидить в корпорации мне мало. А уж писать код и подавно.

Три года назад, когда я уже работал в Фейсбуке в Лондоне, а весь мир уже закрылся на карантин, я наконец-то начал делать что-то своё. У меня была идея, что нужно сделать словарик, основанный на произношении - так появился Quiken.

Проект, кстати, очень мне нравился, потому что там была простая и прикольная механика, а также я делал всё - от картинок для хром-стора и логотипа (рисовал прям в тетрадке, потом обводил в кривых) до кодирования и деплоев.

На тот момент у меня за плечами было около 8 лет карьеры (Топтал, Кларна, Яндекс, Иннова) и несколько лет фриланса, всё в веб-разработке, из которых почти 5 лет - тимлидства. И я тогда настолько прирос к своему профессиональному самоопределению - тимлид и фронтенд, - что первой, спустя годы уже очень странной, мыслью было: "а я вообще сам что-то смогу сделать от и до?".

Ужасное ощущение для программиста! Особенно попавшего в профессию по любви, когда денег тут никаких ещё не было.

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

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

И вот, я вырвался. Сел делать по вечерам то, что мне было интересно, что я придумал сам - и делал как хотел. Кайфовал.

Потом были ещё какие-то работы, проекты, идеи, но в целом я понимаю, что из меня теперь любовь к творческой свободе ничем не вытравить.

Мечтаю ли я программировать всю жизнь? Нет. Люблю ли я писать тесты? Нет. Нравится ли мне исправлять баги? Нет. Деплоить, конфигурировать, отлаживать, мониторить, ретроспектировать, обсуждать требования - ну изредка да, но чаще нет.

Тем не менее, программирование - прекрасный способ творить.

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

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

Вот такой вот программист на свободе.
👍4018🤔3🥱2💯2🤝2
Как я по сишке упарывался

В начале года, когда Стильный клуб и наши с Витей задумки ещё не появились, мне было скучновато.

Одним из ответов на вопрос "как вернуть интерес к работе" была идея попробовать низкоуровневое программирование. В конце концов, именно с си и плюсов (тьфу-тьфу, чур меня!) началось моё прямо увлечение программированием в юности.

За кресты-плюсы я бы в полном здравии не взялся (хотя полагаю, что писать своё на ограниченном подмножестве языка всё же лучше, чем читать или отлаживать чужие ужасы), а вот к сишке у меня давняя и очень крепкая любовь.

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

Плюс он потрясающие компактный. В си буквально ничего нет, кроме десятка типов, стандартных управляющих конструкций (циклы, условия и т.п.), функций, указателей, структур и стандартной библиотеки. Ну, ещё директивы препроцессора, чтобы файлы склеивать-нарезать по условию ещё до компиляции и линковки - и то, есть мнение, что лучше бы его не было 🙂 Нет даже исключений!

Всё остальное реализуется на основе этих конструкций. Хочешь писать свой malloc - да на здоровье. Главное понимать, что без стандартной библиотеки код перестаёт быть кроссплатформенным.

Есть даже кайфовейшая книжка Object-Oriented Programming with ANSI-C, где автор как раз объекты-наследование-полиморфизм и прочее делает сам, с нуля. Рекомендую.

Но что на сях писать-то? Вот тут у меня проблемы. Есть ардуино и идея самостоятельно напрограммировать управление коптером. Но тут больше пайки и ручной возни, чем программирования, плюс у меня всех деталей под рукой нет. Есть что-то низкоуровневое и производительное, типа SQLite или nginx. Но это огромные проекты!

В итоге написал свой парсер JSON 🤣

И, хочу вам сказать, это офигенный опыт! Я теперь ещё больше, чем прежде, убеждён, что учить computer science надо частично на примере низкоуровневых языков.

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

Но почему?

Я тоже помню, включая, как мне кажется, и причины - и пишу, чтобы скопировать строчку:

char* copy = malloc(strlen(original) + 1);
*copy = *original;

Где-то там определён char* original = ..., конечно же.

Помню про выделение памяти и нулевой байт, и пишу malloc(strlen(copy) + 1). Так в си заканчиваются строки, т.к. нет никакого объекта, чтобы хранить её длину - это просто последовательность байт в памяти.

И про указатели с разыменованием тоже помню, и поэтому пишу в *copy = *original. Конструкция *ptr в си означает, что надо взять значение, находящееся по адресу, лежащему в ptr (и длиной в ее тип - но это от меня уже ускользает).

Но одного не помню, что копирование строки - это O(n) операция, если не считать выделения памяти, и её не сделать так же, как копирование чего-то помещающегося в регистры процессора.

То есть моё выражение *copy = *original превращается в несколько инструкций, означающих "возьми значение по адресу original длиной в 8 бит (т.к. это char* ptr, символ) и положи его в ячейку по адресу copy".

Это меня высокоуровневые языки совсем уже с толку сбили.

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

Вот кто ещё вам скажет, что тест - это просто функция, которая вызывает другую функцию и проверяет результат? Да, до этого можно додуматься, но ведь надо подумать. Я даже поначалу искал "unit testing framework" для си, а потом додумался просто положить тесты рядышком и запускать через make.

Короче, кайф. Если хотите почувствовать себя властелином железяки, рекомендую!

Пока они не стали нашими властелинами 🤣
👍16😁21🔥1
Поразил один респондент 🤯

Мы сейчас проводим исследование о том, как, когда и почему люди покупают курсы. В более широком смысле, как, когда, чему и зачем учатся.

Плюс-минус по JTBD (jobs to be done, есть такой фреймворк для описания проблем и их решений в формате "работ"), но, конечно, направить диалог в правильное русло не всегда удаётся. Да и, наверное, в общем случае не нужно. Нужно понять людей, их ситуации, чувства, мысли - а потом решения и результаты.

Меня поразил один респондент. Парень, хорошо за 30, взялся изучать PHP фактически с нуля и - внимание! - сам решил брать от курса всё: выполнял все практические задания "со звёздочкой", не жалея времени и не оглядываясь на продолжительность курса. Интуитивно или с чьей-то подсказкой, он решил набивать руку, решая много однотипных задач.

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

Это чертовски правильный подход! 🔥

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

А вот несколько месяцев активной практики, когда я целенаправленно хотел набить руку в SQL, очень помогли. Хотя, конечно, я бы в идеале и впрямь условный SQLite написал с поддержкой какого-то подмножества ANSI SQL, чтобы понять, как это работает.

По теме: вот, например, классная бесплатная книжка по SQL для совсем начинающих и куда более интересная платная.

Возвращаясь к нашему респонденту, сразу заметны его результаты и уровень. Со своим практически нулевым опытом человек реально умеет писать код, делать несложные веб-сервисы, ориентироваться в сложных для новичка концепциях и абстракциях. Главное, что он уже умеет решить проблему самостоятельно, когда её понимает.

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

Я даже не понимаю, что это за чудо такое: в дополнение ко всему, он рассказал, что ему очень нравится бэкенд и заходит писать логику, "я бы API-шки делал и горя не знал". Хотя обычно, и это как будто бы предсказуемо и очевидно, начинающих больше привлекает фронтенд, ведь там сразу видно результат. Когда ты ещё не привык к абстрактному мышлению (а это лежит в самой сути программирования), фронтенд даётся проще. Первые шаги и результаты так точно.

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

А вы проходили онлайн-курсы?

У нас уже больше 70 ответов в форме, но нужно больше тех, кто уже работает и продолжает повышать свой профессиональный уровень. Заполняйте анкету - я выберу ещё минимум человек 10 для интервью, в конце которого разберу любой запрос и дам устную консультацию по теме с письменной рекомендацией 🔥
👍11🔥31🤔1🤪1
Посты с начала мая

1️⃣ Добавленная ценность
Нужен ли код и программисты в нетехнологическом продукте? А если подумать?

2️⃣ Программист на свободе
Работать, чтобы жить, жить, чтобы работать - или вообще не работать?

3️⃣ Как я по сишке упарывался
Помогут ли десятки собранных segfault вернуть интерес к работе?

4️⃣ Поразил один респондент
Разве на онлайн-курсах программирования реально можно научиться программировать?

А между ними есть ещё опросы. Кто голосует - молодец! 🥳
👍8
Сложность

У нас в боте для Стильного клуба есть много пока ещё отсутствующих фич. И, конечно же, даже такая маленькая и простая система за такой короткий срок (первый из 277 комитов я сделал в начале марта) уже успела порасти мхом техдолга 🫣

Я то старательно игнорирую этот факт, как настоящий стартапер, то, как программист-перфекционист, принимаюсь всё тщательно переделывать, выдумывая новые и переделывая существующие абстракции.

Есть, например, BaseFlowHandler. Это такой базовый класс, потомкам которого передаётся управление для обработки пользовательских сценариев. Например, сценарий регистрации или сценарий покупки. Их можно комбинировать, и за это отвечает PrivateFlowController, который знает, как реагировать на сообщение или колбэк от пользователя в каждый (ну, почти каждый) момент.

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

Помучив это дело несколько дней и написав приблизительно половину кода, я упёрся в свои же абстракции. Все эти базовые классы и контроллеры я делал для того, чтобы обрабатывать один, непрерывный по сути пользовательский сценарий. Там они работают. А вот для публикации множества сообщений, каждое из которых можно отдельно настроить в любой момент, уже нет.

И настолько мне не захотелось всё это добро переписывать, что я просто всё это выкинул и забил 🤪И решил сделать по-другому, в 10 раз проще и во столько же раз быстрее. По-стартаперски. Конечно, настолько же гибко и удобно, как могло бы быть в теории, не получится, но зато наверняка получится проще - а от этого ещё лучше.

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

Это включает в себя и систему запланированных а-ля cron задач (celery - кривая, текущая во все стороны и хреново задокументированная библиотека с абсолютно непрозрачным поведением в некоторых ситуациях, и сделать без своего планировщика не получилось), и правильную схему данных, чтобы не ошибаться с отправкой, корректно считать статистику и т.п.

Самое интересное для меня - посмотреть, как это повлияет на продажи. Потому что, теоретически, чем больше "касаний" потенциальных клиентов мы делаем, объясняя им ценность, показывая примеры и отзывы, просто напоминая о себе, тем лучше они будут покупать. Ужасно интересно проверить на практике!

В чём же разница между этими фичами? 🤔

Понятная, достаточно сложная и по-технически увлекательная история про новый способ обработки сценариев с одной стороны. Я более-менее понимаю, как её сделать, но нужно придумать, написать и переписать кучу кода. Сложного кода, с кучей разных состояний. Или более простая, но ещё менее понятная, с потенциально более высоким ROI, система рассылок - с другой.

Кажется, что в первой фиче есть и inherent complexity (т.е. естественная сложность, свойственная самой задаче), и accidental complexity (случайно привнесённая во время реализации, мной). И если с естественной сложностью я сладить ещё могу, случайно привнесённая меня просто бесит! 🤬 Даже в своих проектах, что уж говорить о чужих.

Я убивать был готов, когда в фейсбуке несколько дней провозился с модулем CookieMonster на полтыщи строк, чтобы просто тупо куку поставить и прочесть. Там и конфиг поправь, и типы, и codegen запусти, и во всех этих фабриках разберись. Как будто кто-то специально напихал всё, что можно, чтобы получить ачивку overengineer 80 lvl на ревью.

А кто-то именно ради сложных задач до красноты глаз литкод мучает (я тоже это проходил 3-4 года назад) и в фанги любой ценой пробивается. Причём не ясно, то ли ради естественной сложности, то ли ради того, чтобы в слоях абстракций копошиться сутки напролёт, как палеонтолог.

Вот вам как даётся управление сложностью?
🔥5👍21
В комментарии к прошлому посту про сложность Ваня инженер принёс полезный подход. Ниже мой краткий пересказ.

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

Это позволяет выйти из ступора, когда не понимаешь, с чего начать, и выработать только необходимые для бизнеса абстракции. А не понаделать странных вещей, в которые сам потом упрёшься.

Я попробовал, и мне подход понравился 🔥 Напоминает UML-моделирование или задачку по систем-дизайну, где вместо прямоугольничков и стрелочек реальный, но ещё пустой код. При желании можно наверное даже запустить такого франкенштейна, чтобы посмотреть на стектрейсы и прикинуть, правильно ли оно работает.

От себя добавлю, что при конструировании новых программ я обычно делаю что-то из этого (редко всё):

1️⃣ Прототипирую, пока у меня в коде не получится внятная модель реальности-задачи. Это особенно важно, когда у программы или её части много возможных состояний, которые нужно где-то хранить и как-то обрабатывать, и уж тем более, когда состояние изменяется во времени

2️⃣ На сложные и неочевидные части кода, особенно зависящие от внешних систем либо с большим количеством возможных состояний, пишу тесты. Часто заранее, чтобы проверять себя, когда не получается удержать в уме все движущиеся части

3️⃣ Стараюсь двигаться от простого к сложному: начинать с более тривиальной версии задачи и минимального количества кода. Как раз чтобы сократить количество возможных состояний и сначала разобраться, как система будет работать

Последний пункт объясню отдельно, потому даётся мне это не всегда. Да, я не скатываюсь до уровня абстрактных фабрик в личных проектах (да и в большинстве рабочих тоже), но вот нафигачить классов вместо функций, чтобы просто сложить код в одну "коробочку" - это иногда случается.

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

Для них заготовлены килограммы горячих углей за шиворот: а не много ли тут методов, не слишком ли большой получился класс? Вот это положить в инстанс, либо сделать аргументом методов? Не нарушены ли принципы SOLID? А вот тут мне нужен миксин или лучше сделать базовый класс? Какое время жизни у объектов этого класса? А уж если есть строгая типизация и дженерики - держись, перфекционист!

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

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

Но рефакторинг, чтобы получилось сделать недиалоговые сценарии взаимодействия с ботом, я пока отложил. Занимаюсь более важным и срочным, чем красота кода.
👍95👨‍💻1
Telegram API и внутренняя/привнесённая сложность

Закончим тему сложности примером inherent (внутренней) и accidental (привнесённой) complexity.

Если у вас есть бот, и этот бот добавлен в канал/чат с определённым набором прав, он будет получать от телеграма обновления. Одно сообщение или событие (пользователь вошёл в чат, вышел, запинили сообщение и т.п.) - одно обновление. Классы немного многословные, иногда нелогичные - прослеживается, что система развивалась итеративно, и теперь все эти новые поля как кольца вековых деревьев.

И вот, делаю я репосты из одного чата в другой (такая механика есть в Клубе). Приходит одно сообщение - вызываешь copyMessage, передавая from_chat_id, message_id и chat_id - бот отсылает такое же сообщение в другой чат. Пока всё просто и логично.

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

Что же делает Телеграм, когда приходят такие сообщения? Он добавляет в Update.message поле media_group_id и присылает обновление для каждой фотографии или видео! Учитывая то, что в теории эти обновления могут доходить с задержкой или даже не по порядку (чего, к чести олимпиадников Дурова, никогда не происходило), их нужно где-то копить, ждать какое-то время, вытаскивать из каждого обновления file_id и собирать новый набор параметров для метода sendMediaGroup.

Можно догадаться, что произошло, когда у Телеграма возникло желание добавить поддержку нескольких фото в сообщении. Олимпиадники покумекали и приняли правильное инженерное решение: оставить схему данных практически нетронутой, реализовав всё это на уровне клиентов. Именно они умеют показывать 10 сообщений как одно, красиво группируя и расставляя фотографии по сетке и делая подпись из первого сообщения общей для всех.

И точно так же вынуждены делать боты. Что ужасно неудобно: теперь вместо одного вызова API в момент получения Update нужно положить обновление куда-то (у меня в redis), следующие обновления с тем же media_group_id складывать туда же, подождать получения 10 обновлений, либо какого-то умозрительного TTL - в моём случае, 5 секунд, а когда это время пройдёт, вытащить id файлов из обновлений, не забыть по пути caption и caption_entities - и вызвать sendMediaGroup. Всё это уже в другой фоновой задаче, чтобы не блокировать worker на время ожидания, а значит привет, race conditions и лишний промежуточный state!

Да, даже подпись к фото или видео лежит не в поле text, а в caption. А разметка не в entities, а в caption_entities. Ещё один пример вековых колец развития системы 🙂

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

Всё это хорошие примеры inherent complexity задачи "переслать сообщение из чата в чат" для меня, как для пользователя Bot API. И, в то же время, это пример accidental complexity для программистов Телеграма, которые вынуждены были пожертвовать элегантностью системы в пользу простоты реализации и обратной совместимости. Решение скорее всего правильное, но это неудобство с нами (и с ними) теперь уже навсегда.
👍7🙈54🤔2
Привет, что вам больше заходит?
Anonymous Poll
48%
Про коды
16%
Про некоды
36%
🍿
🍓4
Как в продакшене память потекла

Захожу вчера в консоль Digital Ocean и вижу там такое. Глаза по пять рублей сразу - ну как так?! Вроде ж нормально программировал, а оно вон как повернулось.

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

Как думаете, поможет этот подход обнаружить, где днище протекает? 🤔
🤔1
Скоро вернусь к вам с историей про память, а пока принимаю поздравления, т.к. сдал местный экзамен по вождению 😁
🔥32🎉14👏74👍2
Наконец-то НУЖНЫЙ эй-ай подвезли 😁
😁7👍3🔥2
Что же случилось с памятью?

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

Закопался в делах: и работы привалило (взял классный проект на несколько месяцев), и куча любопытного и интересного приключилось - скоро расскажу! 😍

Итак, увидев 96% съеденной оперативки, я удивился и испугался одновременно. Где я мог настолько сильно накосячить в питоне с его garbage collection? Посмотрел на графики, а там ступеньки. Ещё более таинственно: что такого может приводить к одномоментному скачку в потреблении памяти?

Обычно, если течёт, то течёт постепенно, а тут по 10-15 процентных пунктов за раз. И так уже на протяжении нескольких месяцев, то есть даже с какими-то последними изменениями это не связать 🤯
4
Решил отлаживать. Поднял локальный стенд в продакшен-режиме, начал туда наливать запросов: 10,000 фейковых запросов от телеграма, 10,000 запросов из страйпа. Хотя там и близко столько не бывает, у нас сотни человек пользуются ботом, а не десятки тысяч.

Не течёт. Чудеса в решете! Коллеги-друзья накидали вариантов, что какие-то древние питонячьи сетевые либы могут течь - но и это, в моём понимании, не очень-то объясняет ступеньки. Плюс у меня-то uvicorn + джанга, вроде как без палеонтологии. Ничего не понимаю.

А потом как понял!

Я много запускаю django shell прямо в проде. Что-то посмотреть, вручную сообщения отправить, поменять какие-то поля в модельках, которые в админке read-only. Каждый запуск - это ipython в веб-консоли (да, пока самый большой минус PaaS от Digital Ocean в отсутствии SSH). И консоль эта крайне кривая: скролл тупит, выделение нормально не работает, зависает ещё постоянно 🤬

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

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

Вот так вот просто всё разрешилось. Спасибо хоть чуйка на ступеньки сработала, и кучу времени не потратил!

Как вчера, например, когда час убил на подвисающие миграции в prisma (это такая модная ORM для ноды) - а оказалось, что она просто запускалась в интерактивном режиме и ждала от меня названия миграции. А я этого просто не видел, потому что запускаю всё в докере локально 🤣

А у вас какие банальные ошибки при отладке приключались?
🔥15😁8👍5
2025/07/09 11:00:10
Back to Top
HTML Embed Code: