inv2004 Dev Blog
309 subscribers
76 photos
4 videos
75 links
Он всегда был не прочь подкрепиться. Кроме того, он был поэт
Download Telegram
Чистый кот

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

Ещё заранее стало понятно, что это про около-SOLID концепт "чистый код". Я не против и не за, у него есть и плюсы и минусы, но, по некоторым аспектам у меня возникают вопросы:

- Все статьи на эту тему начинают с одного и того же примера: делаем интерфейс доступа к базе (шаблон repository) чтобы можно было его имплементировать для нескольких баз. Мотивируется это так: вдруг вы с postgres на монгу переехать захотите. Подобный пример, конечно, можно ожидать в реале, но, как по мне, всё равно является довольно рыхлым. Я не верю в простой переезд с sql на nosql и обратно для какого-то проекта более чем todo. Мотивация номер два - реализовать и для postgres и для sqlite, потому как программисту тяжело запускать postgres локально. С этого момента уже хочется начать хихикать, представляя какое количество дополнительных проблем принесёт подобный подход, где фундамент проекта отличается при разработке и эксплуатации кардинально. Хочется добавить, что, наверняка в языке и так есть абстракция для sql баз, где для замены базы надо заменить только дравер.

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

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

Можно бы добавить про поли- и моно-морфизм - дженерики и утиную типизацию go, но и так много получилось

— added —
Хороший пример удобства данной концепции, который мне привели - кеширование

#golang
👍12🤔1
Радио-Т: Rust против Go

Хотя очень редко слушаю Радио-Т, но, мимо такого пройти не мог. В пятёрочке очень удивлялись почему я стою пол-часа с рукой у лица возле полки с дошираком, но, для этого был повод.

В этой теме участвуют трое ведущих, и, если, один из них изображает непонимание и скепсис, то кринж от прослушивания вызывали двое других, изображающих из себя типичных растоманов первой волны - не писав на расте чего-то больше hello-world, что очень видно по тому как они путаются в базовых вещах, они, даже до некой обиды на первого ведущего, доказывают то как всё правильно и хорошо при написании веба на расте. При том, перепрыгивая и путаясь в Clone, Box, dyn, макросах и подобном. Дальше - лучше, начинают спец-олимпиаду - смотреть бенчмарки. И происходит такое: смотрите, rust обгоняет go-echo на 10%, эти 10% - это же миллионы и миллиарды долларов - говорит один из них => раст экономит миллиарды. На что другой (скептикам проще - они просто смотрят цифры) говорит, вот есть же go-fiber, и он, о боже, обгоняет rust на 10%. Но, если это стоит миллионы, может быть стоило использовать этот fiber, который всё равно быстрее?

В общем-то это больше смешно, но есть пару выводов, которые я сделал для себя:
1) Чем меньше я пишу на расте, тем меньше у меня осталось к нему раздражения, растоманы новой волны куда лучше понимают его проблемы и проблему его позиционирования и являются куда меньше фанатиками RIIR (rewrite it in rust)
2) Перестал слушать Радио-Т потому как их профессиональной областью является обзор макбуков, а если говорить про dev-ориентированное, то лучше заменить, или совместить, с чем-то другим

https://t.me/radio_t_podcast/218

#rust #golang #podcast
😁9👍51👎1
Golang отравлен чистым кодом

Неожиданно то, о чём я писал про яву https://t.me/inv2004_dev_blog/111, оказалось применимо и к golang. И, если на яву можно махнуть рукой — она уже прочно присохла к лепке энтерпрайза из песка и палок. То для golang, на котором большая часть кода это микро-микро сервисы читающие из базы, применение «лучших практик» вызывает непонимание.

Пример — запрос из 50 полей, но надо же делить на слои: транспорт, сервис, репа. И вот, в каждом слое мы делаем практически полную копию структуры этого запроса (слои не должны пересекаться), причём настолько увлечённо, что всё ещё больше раздувается дублированием зависимых типов, enum и прочим. В golang нет кодогенерации — её здесь не очень любят — вырастают конверторы структур из слоя в слой. Но нам этого мало — хорошо бы скопировать ещё раз для слоя репозитория.

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

Мне-то всегда наивно казалось, что программирование это вообще больше про обобщение, а вот оно как оказывается.

Выводы не меняются уже годами: go катится в говно В IT превалирует курс обезьянничества карго-культа лучших практик больших команд больших компаний. Не учитывается специфика конкретной работы. Не приветствуется, и даже поощряется, раздувания кода - большой PR выглядит солиднее. Так-то я себя ещё в руках держу и громко об этом не заявляю, что мне, после APL, переодически кажется, что меньше кода - почти всегда лучше
fibN=: (-&2 +&$: <:)^:(1&<) M."0


Не говорю о том, что чистый код прямо совсем не нужен, для него есть причины, но 1) SOLID уже не совсем, чем был во времена повального ООП 2) Это всё применимо для команд хотя бы от 20 человек, а, в микросервисе, который увидит максимум три человека за всё время его существования - нет.

Кажется, что этот пост и про шарпистов тоже.

#golang
👍11😢2🤡1
Самое скучное go-сообщество генерирует максимально увлекательный контент 🎥

@ntuzov нащупал блоггерскую жилу и продолжил развивать ранее упомянутую тему https://t.me/inv2004_dev_blog/131

На этот раз свёл жабу 🐸 с гадюкой 🐍 в прямом эфире. Если в двух словах, то собеседующих менеджеров, «представляющих сообщество», отсобеседовали с применением типично менеджерских приёмов. Даже отчасти стресс-интервью, судя по странному хамоватому поведению одной из сторон с самого начала (не одобряю). В конце Николай даже добавил контента, оттоптавшись на проигравших, но, справедливости ради, вовремя опомнился.

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

#golang

https://www.youtube.com/live/nkcUzameNtY
🔥3👎2👍1😁1💯1
Концепт, который пока не удалось дотащить до логического конца: errchan

Есть запрос к базе, но который не выполняется мгновенно, а возвращает данные постепенно, по мере обработки. Вопрос: как это запихнуть в чистейший код? Как оказалось, каналы для этого не считаются чем-то нормальным. Хотя, я никогда сильно разделял то, что возвращает функция: []int или int, или лямбду, или канал - всё это first-class citizens (как перевести?) в golang.

Решение, которое сразу пришло в голову:

- Самописный итератор.
Минусы: 1) самописные итераторы в go не то чтобы такая простая для использования конструкция, как хотел бы чистый код. 2) исчезает _бесплатная_ буферизация, которую дают каналы — буферизацию надо писать отдельно в сервисном слое.
Плюсы: 1) легко ловить ошибки из repo-слоя.

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

- Итератор iter. В чате подсказали, что, оказывается, в go всё же есть итераторы https://pkg.go.dev/iter, хотя в реальном мире я их не встречал. Решение ок: формально, как в 1 и 2 — мы оберегли repo от использования канала, избежали самописного итератора что тоже хорошо. Но проблема (2) осталась. Логика того, как этот итератор прокидывает ошибку наверх — не самая прямолинейная. https://go.dev/play/p/7V7631Mr0yB

- Решение без чистого кода. https://go.dev/play/-YdPQ1ufQpV-
Плюсы: бесплатная буферизация. Функция repo, возвращающая канал, что, имхо, сразу показывает, что данные надо обрабатывать по мере прихода.
Минусы. Не ясно, как прокидывать ошибку, хотя и можно накостылить чуть более сложный тип у канала или передавать errgroup, что опять не приветствуется в чистейшем.

Для себя я пришёл к выводу, что всё же без чистого кода решение лучше, дополню то, что это не всегда канал, это может быть io.Writer или что-то подобное между сервисным и api слоем. А попытка избежать этого приведёт к ещё одной порции костылей в каждом слое, который с этим взаимодействует.

Но, при этом, мне почему-то кажется, что в go не хватает конструкции, которая бы объединила errgroup + chan. Ведь когда-то сделали errgroup и считают её достаточно стандартной. Предположим, назвать её errchan. Будь такая конструкция и repo могла бы её возвращать — это было бы самым понятным решением, которое, в то же время, не теряет своей эффективности. Но, не исключаю, что я, вероятно, не до конца понимаю, зачем так рьяно оберегать repo на предмет возврата только простых типов, особенно если это вредит и простоте, и даже производительности. Кстати, может, такое уже есть?

#golang
👍5👎2🤔1
inv2004 Dev Blog
Концепт, который пока не удалось дотащить до логического конца: errchan Есть запрос к базе, но который не выполняется мгновенно, а возвращает данные постепенно, по мере обработки. Вопрос: как это запихнуть в чистейший код? Как оказалось, каналы для этого…
Накидал по-быстрому PoC

https://go.dev/play/p/GBWen9_0UQI

Наверное для возврата error вообще канал не нужен - сразу в структуру ставить просто, заодно танцы с закрытием каналов убирает ... посмотрел x/sync/errgroup - там так и есть

— added —
через поле https://go.dev/play/p/1wTm8hCoy5s , но, конечно, 5 раз надо подумать, что тут может сломаться

— added —
Из примера не очевидно, что .Go вызывается только один раз и, что канал может быть буферизированным

#golang
👍3👎2
errchan.Chan: всё оказалось немного сложнее

@pragus в https://t.me/inv2004_dev_blog/150 накинул хороший пример, о том, что вызов .Err() не гарантирован => не гарантированы закрытие + drain канала. А сделать это через defer тоже _красиво_ не получится - потому как вызывать его надо на другом уровне.

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

Пример обновил: https://go.dev/play/p/dI40gU9DeZT

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

#golang
🤡2👍1
😁10😢3👎2💯2