Нервный программист
123 subscribers
53 photos
1 video
1 file
30 links
Эмоционально об айти и программировании
Download Telegram
Отключил динамическую типизацию ночью - прод упал! 😢
4
Кстати про динамическую типизацию. Возникла тут задачка у меня одна. На вход приходит строка, которая может содержать int или float. Например, "4" или "3.14". И надо сконвертировать эту строку либо в int, либо во float соответственно. А пишу я код на php. И я че то забыл с этими питонами и (особенно) jsами, на сколько пхп хорош в своем приведении типов. Этот код

<?php

$a = '4';
$b = '3.14';

function test(int|float $t): void {
var_dump($t);
}

test($a);
test($b);


выдаст корректные

int(4)
float(3.14)


Если на вход придет неприводимая строка, то php выбросит TypeError, который можно поймать.

Обычно в коде я не использую strict_types, за что многие могут меня поругать. Поэтому код выше работает без проблем. Но на самом деле статический анализатор и так требует от меня соответствие типов, а там, где он выключен намеренно, приведение типов в php только радует. Но все же, что делать, если strict_types включен или анализатор ругается. Даже в этом случае php остается верен своим корням. Магический + 0 сделает свое дело.

<?php
declare(strict_types=1);

$a = '4';
$b = '3.14';

function test(int|float $t): void {
var_dump($t);
}

test($a + 0);
test($b + 0);


"Но это же кринге!" скажете вы. А я прошепчу вам на ушко в ответ "Покажи писькуЭто строгая динамическая типизация, братик. А кринге - это тип number в js 🤡". И мы разойдемся так и не узнав, что в голэнг придется ввести структуру с 4мя полями, чтоб решить эту задачу🥲
😁32
Приехал в Иннополис. Вижу возможность открытия школы golang.
3🔥2😁2
Послушал тут подкаст с Егором Бугаенко. Много что мог бы сказать по этому поводу, и люди в комментах там активно это делают. Но вместо этого, я бы хотел зацепиться за один конкретный кейс, который привлек мое внимание прям сразу во время прослушивания. Его кстати даже не Егор ввел, а ведущий Кирилл.

Говоря про концепцию null в ЯП, и ее плохость, Кирилл предложил пример с объектом юзер, который нужен в куче мест в коде. И по его мнению, проверки типа if user === null появляются повсеместно, что сильно огорчает. Поэтому Кирилл предлагает всегда иметь инстанцированный объект user, и в случае если пользователь не аутентифицрован, это будет какой-то guest user с пустыми значениями.

И я тут че-то не совсем согласен с Кириллом. Хотя я знаю, что такая концепция с дефолтным юзером много где используется.

Поскольку на бэкенде все завязано на циклах запрос-ответ, то возможные варианты развития событий можно классифицировать по типам ендпоинтов
* ендпоинты с обязательной аутентификацией
* ендпоинты без аутентификации
* ендпоинты с опциональной аутентификацией
Не благодарите вашего капитана🤡

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

Тем не менее, для 3й группы ендпоинтов мы обсуждаем 2 варианта, как действовать.
* Мы передаем default user и притворяемся, что у нас всегда "все хорошо"
* Мы передаем null, и тогда нам нужны проверки на null, которые так бесят Кирилла

И на эти два варианта я бы посмотрел не с точки "лучше-хуже", а с точки зрения цикломатической сложности и абстракции. Если у вас цикломатическая сложность вытарчивает прямо в ендпоинтском коде, т.е. у вас путь выполнения кардинально отличается в зависимости от того, аноним юзер или нет, то if-ов вам не избежать. И какая разница, будет это if user === null или if user.id === 0. Для читающего код, это примерно одинаковая когнитивная нагрузка.

Если же у вас цикломатическая сложность не видна сразу, то, кажется что default user - лучший вариант. Реально, избавляемся от чертовых if-ов прям тут. Ура! 🥳

Но в программировании ничего не бывает бесплатно. Так чем же мы платим тут? 🤔

А платим мы тут подтекающей, как старая жопа, абстракцией. Проблема в том, что авторизованный юзер и неавторизованный юзер в реальности не эквивалентны! Вот есть у вас метод public save(User user): void у репозитория. И как только вы завели в вашей системе дефолтного юзера, готовы ли вы поставить очко, что дефолтный юзер никогда не попадет в этот метод?

Обратите внимание, что это не про "злой null". Вместо null здесь мог бы быть тип AnonymousUser и юнион тип. Это просто очередной вопрос компромиссов, повсеместно встречающийся в разработке.

Че думаете? Как у вас с юзерами на проектах?
3👍1
This media is not supported in your browser
VIEW IN TELEGRAM
К недавнему посту о чтении доки)
😁5👍1🤣1
сук, я не хотел вообще этот пост писать. Наоборот, у меня контент по плану было кое-что позитивное. Но у меня щас просто разбомбило сракатан в пух и прах!😡

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

Вот у меня есть generic класс

class Result[T]:
pass

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

def transform(self, transformer):
data = transformer(self.__data)
return Result(data)


Давайте поможем Даше найти, как расставить типы в объявлении метода!

ну что вы напишите в этом случае? ну наверное что-то типа

def transform[V](self, transformer: Callable[[str], V]) -> Result[V]:
pass

Обратите внимание, как же сука говяно описываются колаблы. Ну это ладно. Этот код впринципи некорректный!

А знаете какой код корректный? Вот такой

def transform[V](self, transformer: Callable[[str], V]) -> "Result[V]":
pass


Задача: найти два отличия.

Ответ: левая кавычка и правая кавычка.

Причем, сука задаешь этот вопрос дипсику и perplexity, так он тебе старый способ объявления дженериков в ответ ставит, который еще более уебский с пежней типа


T = TypeVar('T')

class Result(Generic[T]):
pass


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

Извинити, просто реал бомбануло
👍4😁4
Тут на меня навалилось всякое, и я что-то не нашел времени отметить знаменательное событие. Ведь на прошлой неделе самому умирающему языку программирования исполнилось 30 лет🥳 с днем рождения php!

Какой бы топ ЯП вы не взяли, php стабильно где-то в жепе! Наверное трудно найти какой-то другой язык, который хейтили бы больше, но который также активно использовался бы в продакшне. А между тем множество из хейтеров вкатывались в АЙТИ именно через php. Но ощущение такое, что php развивался, а хейтеры нет. Ведь за последнюю декаду в языке появилось столько всего, что удобство разработки и качество кода перешло на новый уровень. Перечислю список новых фич за последние 10 лет, которые, на мой взгляд, оказали наибольшее влияние на процесс разработки:
* строгая типизация в рантайме
* enums
* JIT компиляция
* атрибуты
* стрелочные функции
* first-class callable
* readonly свойства и классы, проперти хуки
* распаковка массивов, деструктурирующее присваивание (прям как в этом вашем js)
* файберы

И это я перечислил только то, что мне пришло на ум, и то, чем я пользуюсь. Там еще до хера всего. И вы можете сказать "но постой, это есть во всех других современных языках". Так в этом и соль, что php - это нормис. Это хороший современный язык! И он отлично справляется с работой, для которой он и создавался, а именно веб приложения. Его "умирающая" модель исполнения идеально подходит для веба, где большая часть взаимодействия построена по принципу request-response.

Кстати про умирающую модель. И здесь у пхп большие подвижки. На момент написания этого поста у пхп есть уже 2 продакшн реди решения (roadrunner и frankenphp), где используется воркерная модель, когда php процессы живут "вечно". А еще есть асинхронные библиотеки openswoole и reactphp. Так что даже в этом плане php такой же нормис.

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

Короче говоря за 10 лет php проделал огромный путь. У меня ощущение, что php 5 и php 8 это два разных языка - уж на столько их возможности отличаются. Будем надеяться, что следующая декада пройдет не менее активно. Нам тут уже обещают дженерики и async-await, но в целом, я был бы рад если бы в язык встроили дебаггер 🤡

Короче говоря, не умер и ладно!
🔥53
Посчу шутки про AI, пока он меня не заменил😅
😁71
Как не зайдешь в эти ваши интернеты, везде одни истории успеха😔Один навайбкодил стартап, другой выкатил обнову для своей библиотеки, третий на подкаст пришел и рассказал, какой он крутой. И никто сука не рассказывает о факапах и тупняках. Поэтому об этом буду рассказывать я😊

На работе делаем супер продвинутого ИИ помощника (на питоне, разумеется). Как истинный хороший программист, я решил внедрить статусное программирование (спасибо Кириллу Мокевнину с его докладами). Это означает, что сессия пользователя может находиться только в одном из доступных состояний. Например, есть состояние awaiting, когда помощник готов принимать запрос, или processing, когда происходит обработка запроса пользователя. Переходы между состояниями запрограммированы через finite state machine (fsm).

Однако в какой-то момент потребовалось научиться "ждать" определенного перехода между состояниями. Например, приходит запрос на создание новой сессии, но предыдущая сессия находится в состоянии summarizing, когда llm подгатавливает саммари по ней. И надо дождаться, когда состояние станет finished, прежде, чем продолжать операцию.

И вот трое нас, я, чатгпт и дипсик, родили совершенно потрясающее решение, основанное на Lockах и Eventах. Я даже тесты написал!🥳

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

И тут бы мне остановиться придумывать херню, ведь можно сделать классический поллинг - просто периодически долбить базу с запросом "ну че там, статус поменялся?". Но нет, случился очередной ступор мозговины. Ведь мне нужно КРАСИВОЕ решение.

И тут начинается полезная инфа для вас. Если вы работаете с postgres, то подобную задачу можно решить используя механизм LISTEN / NOTIFY. То есть в одной сессии подпиcываетесь на определенный ивент с помощью команды LISTEN event, а в другой сессии можно уведомить подписоту с помощью NOTIFY event. Еще payload можно передать. Ну круто же!

Но есть одна сложность. Допустим вы прочитали значение из бд, оно вам не понравилось, и вы хотите подождать другого. Если вы просто оформляете подписку после чтения, значение уже может измениться в этот момент. Это классическая гонка данных. Поэтому и listen, и notify важно делать в транзакциях с локами. В частности в транзакции, где у вас notify, нужно сделать select ... for update, а там, где listen должно быть select ... for share.

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

Вывод: не выпендривайтесь, делайте просто! 👆

Из бонусов, я кайфанул от того, что написал тесты, а затем полностью переписал реализацию, а тесты почти не трогал! Почти оргазм! 🥹
💅7
Не вайбкодю, потому что хочу сам решать, когда добавлять уязвимости в проект!
😁61👍1
Штош, шутки шутками, но настало время более серьезно поговорить про вайбкодинг, а точнее, про влияние вайбкодинга на разработчика. Так получилось, что сам я не выйбкодю. И далее по тексту вы увидите мои оправдашки, почему так.

В качестве поддержки своего мнения, я бы хотел обратиться к офигенной статье Why I stopped using AI code editors. На момент написания поста оригинал статьи почему-то недоступен, но есть копия в вебархиве. Я советую прочитать, очень отрезвляюще. Особенно прикольна аналогия с теслой, когда долгое использование автопилота привело к деградации навыка вождения у автора.

Почему же я не вайбкодю? Я отвечаю себе на этот вопрос так: прекращение работы над простыми вещами делает сложные вещи еще сложнее. В статье об этом говорится в части про Fingerspitzengefühl.

Приведу свою налогию. Какое-то время назад я занимался единоборствами. Одним из ключевых упражнений в тренировках было бить по мешку один и тот же удар определенное количество раз. Ну например, надо 100 раз ударить прямым ударом руки по мешку. Затем поменять руку и стойку и также 100 раз ударить. Потом тоже самое с другими видами ударов руками и ногами. Скучно кабзда! Но мне кажется, мало кто сомневается в полезности таких упражнений. Тренер всегда объяснял нам, зачем это делать. Нужно довести наши удары до автоматизма в "тепличных" условиях. В спаринге не будет таких условий. В спаринге часто оказываешься в неудобной позиции, тебя бьют, ты устаешь. Но ключ к победе в том, чтоб "прошел" один единственный удар, который ты наносишь, находясь в супер неудобных условиях. Вот для этого ты и долбишь часами один и тот же удар. И даже самые опытные бойцы продолжают бить эти самые удары и связки ударов. Потому что это важно.

Теперь представьте, что у вас появляется ИИ в тренировке. И он добавляет "множитель" к вашим ударам. Например, множитель 5: теперь вместо 100 ударов, надо сделать всего 20. Формально ты выполнишь тренировку, нанеся всего 20 ударов. Сделает ли это твою жизнь проще? Конечно сделает! Но в спарринге, человек, который честно набил 100 ударов будет иметь преимущество. У него и удары техничнее, и руки "набиты" лучше.

Любая аналогия ложна, в том числе и эта. Все таки тренировка не работа разработчиком, а спаринг не построение архитектуры ПО. Но я все равно считаю, что каким бы ремеслом ты не занимался (а программирование - это ремесло), важно продолжать делать "простые" вещи, если хочешь оставаться в этом ремесле и не терять квалификацию. При этом эти самые простые вещи можно делать с помощью современного инструментария: крутой IDE, фреймворка или даже ИИ. Но главное, делать.

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

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

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

В завершении отмечу, что я тут не говорю о влиянии на бизнес, о корректности работы самого ИИ, о влиянии ИИ на код и индустрию. Я думаю, никто не сможет сказать, как это будет через год, два или пять лет. Но в одном я уверен: пока ИИ оперирует обычными ЯП, пока валидация решения остается на уровне обычного кода, никуда программистские навыки не денутся, кто бы там что не говорил.
5🔥4👍1
ИИ уничтожает StackOverflow.

Также StackOverflow:
🗿4
Лан. Предыдущий мемас не залетел. Может этот проканает?)
😁11
Корочи, на работе продолжаем пилить жесткую тему связанную с ИИ🤖Для этого у нас в команде есть специально обученный (нет) человек, чтоб заниматься непотребствами с llm и всячески их тюнить. Ибо тема эта новая, и, кажется, человечество создало нечто такое, в чем само поитогу разобраться не может. Требуются бесконечные эксперименты с промптами и всякими агентами.

Разумеется, то тут, то там, начали вылезать фреймворки для разработки "expressive, customizable agent workflows". Суть их в том, чтоб ускорить накидывание говна в llm, в надежде получить че-то хорошее на выходе. По сути - ускорить обратную связь. Одним из таких фреймворков является набирающий популярность сейчас langgraph. Решили попробовать часть функционала переписать с помощью него. Потому что на бумаге все звучит очень круто🤩

Основная идея, на которой построен langgraph - представление логики обработки запроса пользователя в виде графа. В этом графе каждый узел - это какая-то функция, которая может сходить в llm или вызвать локальный инструмент (tool) или запросить помощь от пользователя. Можно определить условия перехода в тот или иной узел. А еще langgraph позволяет работать с состоянием. Каждая нода ведет себя как сучька редьюсер в react redux - принимает на вход старое состояние и возвращает новое. Ну не красота ли!

Но по факту опыт получился смешанный. Потому что нахуевертили!

Если вы начнете погружаться в langgraph, то обнаружите, что там довольно много документации. Ситуация осложняется еще и тем, что разработчики предоставили миллиард способов сделать одну и ту же херню. Да там даже 2 разных независимых API существует зачем-то! Короче, готовьтесь пару дней тратить только на чтение буков🤓

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

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

Проблема в том, что я могу НЕ хотеть сохранять промежуточный мусор пока запрос не выполнился полностью. Потому что если на середине пути у меня вывалилась ошибка, то я хочу просто начать сначала без предыдущих потуг. И langgraph не особо дает это сделать. Более того, разработчики, видимо поняв, что идея с чекпоинтерами не слишком гибкая, добавили еще один вид памяти long term. И это какая то невнятная дичь. Вдовесок ко всему, модель хранения langgraph по сути скрыта. Например, захотите вы проанализировать транскрипт пользователя - ну удачи вам.

В итоге, мы решили использовать langgraph только как способ организовать код в виде графа. Это реально облегчило прототипирование, поскольку позволило отвязать весь наш остальной код от логики работы агента. Мы сами храним и состояние, и транскрипт, и общую память о пользователе, а в langgraph бегаем просто как в black box. На вход ему подаем наши загруженные данные + запрос пользователя. После обработки запроса графом, мы используем результат для обновления состояний, метрик, транскрипта и т.п.

Я считаю, что в этом и состоялась основная польза от использования langgraph. Именно отделение кода логики агента от остального кода - основная ценность. Поскольку этими двумя частями заведуют разные люди в команде.

А сам по себе langgraph на 5.5 из 10. Много что есть сразу, но не гибко, сложно, потно!😰Будем надеяться, что это просто первые попытки человечества создать фреймворк под написание агентов, и дальше будет лучше.
5👍2
Когда вместо реляционной СУБД решил использовать NoSQL
🌚62😁1😢1
Внимание! В сеть утекли исходники 11 винды!
😁7🔥1
В прошлый раз рассказывал про langgraph. а сегодня расскажу о том, почему у меня сгорела дыра из-за него🌚

Дело в том, что langgraph пытается казаться на столько удобным решением, что они даже запилили собственную IDE, и назвали ее langgraph studio (далее lgs). Очень блять оригинально.

Как же выглядит IDE для разработки агентов? Нужно ли там писать код? Можно ли там вайбкодить с помощью агента, которого сам же пишешь? 🤔 Все эти вопросы сразу остро встают на повестке дня.

Но все гораздо прозаичнее (см. картинку). Эта присрачка позволяет запускать каждый узел в графе (функцию) отдельно, мейнтейнить стейт, менять входные параметры. Короче говоря, позволяет всячески эксперементировать с узлами без необходимости работать с полным циклом запрос-ответ. Удобно? Не то слово! Идея просто бомбическая.

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

Сначала все было хорошо. Сплошные востороженные возгласы. Но внезапно мне прилетает тревожное сообщение "В lgs не работает подключение к бд"😱

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

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

ТО ЕСТЬ, разрабы lgs дали способ работать с каждым узлом изолированно от всего остального кода, но не подумали, что может потребоваться ЧТО-ТО от этого остального кода. Гении ебать!

В общем ключе задача формулируется так: "У библиотеки или тулзы отсутствует нужный api для взаимодействия, а вам позарез надо вмешаться в ее модель выполнения". Звучит конечно так себе. Поэтому добавим "...для разработки или тестирования"😏

Это был пост-затравка. В следующем посте я расскажу, как решал эту проблему в python. Но подобный метод может применяться практически в любых интерпретируемых ЯП. В комментах пишите, как бы вы решали подобную проблему👇
5
Итак, я обозначил проблему. Вкратце: у нас библиотека или тулза, которая настолько говяная, что к ней не подступиться через публичный api, но недостаточно говяная, чтоб от нее отказываться. А нам позарез надо что-то поменять в ее поведении.

В комментах никто не написал, как бы это делал (эх 😔), поэтому дам ответ сам: monkey patch - это возможность менять код в рантайме.

Как же это выглядит в моем случае. У меня тулза - это вонючий langgraph studio (lgs), и мне надо, чтоб во всем коде, при запуске через lgs было доступно подключение к бд.

Напоминаю, что все питонисты обмазываются декораторами. Я решил не отставать. Написал декоратор db_aware такой, что

@db_aware
async def some_func():
# db стопроц будет тут
db = get_db()
# далее в коде юзаем db в хвост и гриву


Пусть точка входа в наш граф находится в файлике my_agent/graph.py, его мы подаем на вход lgs. В файлике my_agent/nodes.py находится код всех нод графа. Напоминаю, что ноды - это просто функции. Тогда можно написать новую точку входа только для lgs

# Исходный пакет с нодами
NODES_MODULE: Final[str] = "my_agent.nodes"

class DbAwareNodes(ModuleType):
def __init__(self, module: str):
super().__init__(module)
# Сохраняем исходные функции
self._original_nodes = importlib.import_module(NODES_MODULE)

# __getattr__ вызывается когда интерпретатор импортирует имя из модуля
def __getattr__(self, name):
# Находим исходную функцию
node = getattr(self._original_nodes, name)
# Декоратор работает только с корутинами
if iscoroutinefunction(node):
# Декорируем исходную функцию
return db_aware(node)
return node

# Здесь подменяем дефолтный модуль на наш пропатченный
sys.modules[NODES_MODULE] = DbAwareNodes(NODES_MODULE)

# Экспортируем наш граф, будто это точка входа.
from pain_agent.graph import graph
__all__ = ["graph"]


Выглядит потно, согласен. Но на самом деле, главное здесь - это подход. Ведь он применим ко всем популярным интерпертируемым языкам программирования: python, php, ruby, js.

Например, в php можно тупо перегрузить stream для чтения файлов, и менять код прямо во время чтения интерпретатором исходников. Именно так работает пакет dg/bypass-finals, позволяющий мокать final классы.

Самое главное, помните, что monkey patch следует использовать только при разработке! В продакшне использовать подобное - это надо быть очень смелым, либо очень тупым. Лучше не надо.
3👍3🙈2
Кто понял, тот понял
😁4