PICO
91 subscribers
45 photos
1 video
38 links
Логово киберайтишника
IT / FP / OOP / DevOps / .NET / SQL
@picolino
Download Telegram
Кекекек, тут десериализация в .NET стала второй топовой хакерской техникой рейтинга PortSwigger.

Ало, я думал .NET одна из самых безопасных сред для разработки ПО, а в документе на 100+ страниц перечислили просто нереальное количество векторов, как же можно ломануться из-вне на машинки, где крутятся .NET приложухи через бреши во внешних библиотеках. В страшное время живем...

👾 PICO
😁5🤔21🔥1
Data Oriented Design

Мы живём в мире, ориентированном на объекты. Есть сущности, они обладают определенными свойствами, поведением. Часть свойств можно увидеть из-вне, часть только при углубленном изучении, какие-то системы могут быть скрыты, поведение объектов может меняться в зависимости от обстоятельств...

Чистой воды объектно ориентированное программирование. Именно поэтому оно так популярно в айтишной среде, из-за своей обыденной простоты. "Ближе к народу", так сказать.

Мы уже давно отошли от процедурного программирования, перешли на следующей уровень абстракции, моделируем целые предметные области через призму реального мира (DDD), чтобы запускать успешные продукты. И это работает.

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

Например, существует целое направление, пришедшее к нам из игровой индустрии под названием Data Oriented Design (DOD).

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

Как часто вы наблюдаете тормоза при работе с современными продуктами? Они, к сожалению, часто являются следствием неоптимального применения DDD, где используется богатая модель, когда надо, допустим, собрать объект из 10 таблиц для выполнения операции, занимающей 1 наносекунду.

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

DOD заставляет посмотреть на ООП под другим углом, а после прочтения информации об архитектурах, применяемых в DOD, например об Entity Component System (тема одного из следующих постов, кстати), трудно не воскликнуть "А что, так можно было?"

Про DOD на Хабре
Книга по DOD

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3🤔3👍2💩1
Безграничная фильтрация в MongoDB (нет)

Люблю монгу. Правда, отличная СУБД для повседневных задач. Когда начинаешь работу с этим инструментом - как будто попадаешь в сказку. Все удобно, быстро, что ни захочешь - будет.

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

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

Как это сделать на монге? Подержите мое пиво, берём документ, денормализуем все входящие в него поля в один массив, например, props, следующим образом:


{
"_id": "000",
"DisplayName": "Label",

// ...

"props": [
{ "k": "_id", "v": "000" },
{ "k": "DisplayName", "v": "Label" }

// ...

]
}


Пишем механизм синхронизации этого поля, и все, что остается сделать - повесить индекс { "props.k": 1, "props.v": 1 } и перенацелить фильтрацию. В итоге мы получаем очень быстрый поиск по любой комбинации полей, при относительно небольших трудозатратах.

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

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

В общем случае решить эту проблему (с сохранением первоначальных требований) на монге просто невозможно. Такие "тупые" ограничения и становятся причиной появления технологических "зоопарков", в данном случае можно внедрять ElasticSearch и поддерживать еще тонну кода.

Печально.

Пока что единственная СУБД, которая ни разу не подводила в таких вещах - это PostgreSQL.

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥2💯2🤔1👾1
😁13👍2👀2
Entity Component System

Как-то года 4 назад я вбил в поиск гугла "хорошая архитектура". Вот так просто, без заморочек. Не "идеальная", чтобы не кликбейт, а вот просто "хорошая". Открыл вкладку с видео и начал смотреть.

На первой странице находился доклад Кирилла Надеждина про "архитектуру для всех" - ECS.

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

Так о чем эта архитектура?

Концепт достаточно прост, есть три типа "объектов":

- Сущность (entity). Это абстрактный объект, у которого есть только одно свойство - идентификатор (гуид, число - не важно). Можно представить, что это просто строка в базе данных с одним единственным полем - ID.

- Компонент. Это маркер, которым можно пометить сущность. На сущности могут находиться сколь угодно много компонентов самых разных типов. Если сущность можно было представить как строку в бд, то компонент - это колонка для этой строки.

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

Комбинация взаимодействий этих трёх объектов позволяет развивать сложные проекты достаточно большого масштаба. Overwatch, Operation Flashpoint, Raid: Shadow Legends, Baldur's Gate 3 - все эти проекты стали следствием применения паттерна ECS.

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

В ECS из коробки вы получаете data-oriented design, а следовательно, и performance. Наличие малого количества составных частей архитектуры (всего лишь три) гарантирует, что ваш код не превратится в ball of mud (хотя, если постараться, - все возможно)

Но паттерн подойдёт не всем. Я делал как минимум два подхода в попытке применить его к стандартным backend-сервисам, ориентированным на CRUD-операции. И каждый раз получается шляпа, все-таки не подходит ECS для стандартных бизнес-приложений, очередной "не silver bullet".

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

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

Отличная статья на хабре про ECS - базовые концепции и overview

Dev Talk разработчиков Overwatch про ECS и сетевой код на backend'e

Доклад от разработчика Larian (Baldur's Gate 3) про правильное применение ECS

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍2👾1
PT Dephaze

Фух, мы это сделали.

На прошедшем мероприятии Positive Security Day наша команда продемонстрировала крупнейшим компаниям РФ первого автопентестера компании Positive Technologies - Dephaze.

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

Собственно, это основная причина изменений в частоте публикаций на канале. Я по уши в работе 💃

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

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

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

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

Мы точно очень скоро вас удивим ✌️

А запись мероприятия доступна тут (Зал Молекула, есть таймкоды, PT Dephaze на 05:23:00):
https://psd.ptsecurity.com/#live

Обязательно посмотрите вступительную заставку в самом начале (00:30:00), она очень кайфовая.

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
5🔥7👍21
18👍1🔥1
Разработчики Visual Studio написали отдельный блог пост про то, что теперь в этой IDE можно КОПИРОВАТЬ ФАЙЛЫ между разными окнами приложения.

Шел 2024 год.

https://devblogs.microsoft.com/visualstudio/copy-files-across-instances-of-visual-studio/

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
👾4🔥1
Приколы межпроцессной буферизации

В операционных системах существует традиционное разделение потоков консольного вывода программ на stdout и stderr.

Stderr - поток для сигналов, что что-то идёт не так. Обычно туда попадают ошибки, исключения и различного рода крэши.

Stdout в свою очередь - это место, куда летит оставшийся вагон логов исполняемой программы.

Но кроме концептуальных различий между ними есть ещё кое-что. Буферизация.

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

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

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

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

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

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

А если речь идёт о процессе, который нужно повесить в фоновое исполнение надолго... То из родительской программы в реальном времени мы увидим логи примерно никогда.

Такое поведение можно переопределить, и в языках высокого уровня (например в dotnet) это "исправлено" из коробки: мы увидим построчный вывод даже из другого процесса. Но на низкоуровневых языках типа C/C++ разработчики сами принимают решение о "выносе мусора".

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

Так и живем.

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥52👾1
PT Dephaze - наш первый вебинар

Приглашаю всех заценить какой же все-таки кайф мы делаем 17 декабря в 14.00 (МСК) на вебинаре, посвященном Dephaze - флагманскому атакующему продукту Positive Technologies по автоматическому пентесту корпоративных инфраструктур!

А еще, мы запустили обратный отсчет до коммерческого запуска и старта первых пилотов. Смотрю на этот таймер и страшно становится. Но! Придуманная нами технология настолько перспективная, что приходится вырабатывать механизмы "сдерживания" этого монстра, чтобы он не сделал то, что не должен (а может он огого).

Кароч, завтра, 14.00 по мск, покажем реальную инфру, реальные атаки, без ерунды, будет интересно, залетай 😎

https://dephaze.ptsecurity.com/

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥8
Лучший скриптовый язык для .NET разработчика

Наверняка всем знакома история с bash/poweshell-скриптами для деплоя приложения на окружение.

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

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

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

C# обладает скриптовым диалектом, но жесткость синтаксических конструкций может приводить к появлению лишнего кода, а лишний код мы писать не хотим, особенно когда дело касается скриптов :)

Для такой задачи идеально подходит F#.

Во-первых, в F# вы (практически) никогда не словите NullReferenceException. Что бы вы не написали, если код компилируется - значит он будет исполнен так, как вы его написали, без всяких неявностей и потенциальных warning-ов. Добавим сверху ещё иммутабельность по-умолчанию и получим удобную и эффективную отладку.

Во-вторых, используя F#, вы получаете возможность использовать всю силу экосистемы dotnet. Нужно подключить в скрипт внешнюю .dll-библиотеку? Или вообще использовать внешний nuget-пакет? Легко, добавляем директиву #r и используем внешний код из коробки. А подключить код из другого скрипта поможет директива #load

В-третьих, F# очень давно заточен быть в том числе скриптовым языком, что способствовало появлению масштабных инструментов, ориентированных на использование в контексте скриптов. Например, F# Make (FAKE), с помощью которого можно разбивать скрипт на шаги и контролировать пайплайн его исполнения более декларативно. По удобству - почти как docker compose, с сохранением гибкости языка.

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

Активно пользуюсь F# скриптами в своей деятельности, и вам советую)

А начать свое погружение в удивительный мир функциональщины на F# рекомендую тут:
https://fsharpforfunandprofit.com/

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🤔5😁4
😁7👎2👍1🔥1
Социальный долг

На днях посчастливилось прочитать в книге Креативный программист (Ваутер Грунефелд) про один термин, который сильно привлек мое внимание - социальный долг.

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

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

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

Для этого он приводит очень интересные примеры "маркеров" социального долга, которые могут присутствовать в любом сообществе, объединенном общей целью, ниже примеры некоторых из них:

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

- Одинокие волки. Сотрудники, которые действуют не учитывая мнение других членов команды.

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


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

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

И даже, немного, собственному самоопределению в коллективе.

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

Можно, конечно, приклеить стикер на спину коллеги с подписью // Социальный долг, подобно тому, как мы оставляем тудушки в комментариях к техдолгу, но в таких важных вещах нужен более конструктивный подход :)

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
👍54👾3
MECE - Mutually Exclusive and Collectively Exhaustive

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

Не знаю, вдохновлялись ли аналитики, придумавшие MECE, ВУЗовским тервером, но в 1960-х они как будто бы переизобрели полную группу событий, но для бизнеса, накинули ярлык "принципа" и достаточно замысловато назвали: Mutually Exclusive (взаимно исключающее) and Collectively Exhaustive (совместно исчерпывающее).

Начнем рассмотрение принципа с очевидного примера, так будет проще.

Задача
Мне нужно быть в офисе завтра в 10.00 утра

Очевидные решения
- Пойти в офис пешком
- Поехать на машине
- Поехать на метро

Теперь попробуем применить MECE к данной задаче.

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

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

Теперь проверим, является ли пространство решений совместно исчерпывающим? Все ли решения мы рассмотрели? Как вариант, сегодня я мог бы остаться в офисе, чтобы завтра в 10.00 утра уже быть на месте (не является рекомендацией).

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

Задача
Мне нужно быть в офисе завтра к 10.00 утра

Решения по MECE
- Пойти в офис пешком
- Использовать транспорт
- Не покидать офис сегодня

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

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

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

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍42🔥1🤔1👾1
Упрощай

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

Часто можно услышать, что сложность кода (да и любого текста) можно измерить количеством появлений в голове фразы "what the f**k" за единицу времени при прочтении. По-моему это всё ещё лучший способ измерения когнитивной сложности, но есть и, безусловно, более формализованные инструменты.

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

Сложность кода противопоставляется многим вещам.

Например, требованиям. Нам за простоту кода не платят, а платят за реализацию бизнес-фич, описанных в задачах на разработку. Требования продиктованы рынком. А рынку, в целом то, плевать на чистоту реализации.

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

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

Что может сделать разработчик в такой ситуации?

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

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

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

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

P.S. Спасибо коллегам за пикчу для поста

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍111
Парадигма иммутабельности

Изменения - это гибко. Неважно о каких изменениях именно идёт речь. Меняем ли мы поле в строке базы данных, или свойство объекта в оперативной памяти - все эти процессы сопровождаются одним и тем же фактором: реакцией.

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

Чем больше изменений состояний объектов существует в программной системе, тем она тяжелее для восприятия и дальнейшей поддержки.

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

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

Возьмём в качестве примера простейший ООП-шный код формирования коллекции, на основе предыдущей, допустим нам нужно возвращать все четные числа из исходных:

int[] FilterEvenNumbers(int[] source)
{
    var result = new List<int>();
    foreach (var number in source)
    {
        if (number % 2 == 0)
        {
            // Прочие бизнес-правила
            result.Add(number);
        }
    }
    return result.ToArray();
}


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

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

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

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

Как проблема решается? На помощь приходит функциональное программирование.

Зачем бороться с чем-то, если от этого в принципе можно отказаться? - подумали ФПшники и изобрели парадигму иммутабельности: неизменяемость объектов по-умолчанию.

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

Взглянем на иммутабельный код:

IEnumerable<int> FilterAndTransformNumbers(IEnumerable<int> source)
{
    return source
        .Where(number => number % 2 == 0)
        // Прочие бизнес-правила
        .Select(number => number * 2)
        .Select(number => number / 3);
}


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

К тому же, мы получаем бесплатный бонус в виде потокобезопасности. Изменение одного объекта двумя разными потоками в одно и то же время более невозможно, так как изменяемости нет :)

Такой подход, безусловно, несёт и определенные минусы. Например, в C# с иммутабельными коллекциями тяжело работать в асинхронных методах без дополнительных библиотек (таких как System.Linq.Async), иммутабельность может очень сильно расходовать оперативную память, а ограничения в изменениях иногда вынуждают добавлять новые промежуточные типы.

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

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
43👍3
Фрактальная геометрия в проектировании систем

DotNext дропнули увлекательнейший доклад Влада Хононова. Горячо рекомендую всем, кто хоть сколько-нибудь занимается программированием.

С доклада выходишь со 100% пониманием:
- Откуда взялся принцип "loose coupling - high cohesion"
- Почему любая комплексная система стремится к своему росту
- Почему умирают легаси системы
- Как именно бороться с растущей сложностью

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

https://youtu.be/NV-TvnfDSd4?si=VbQVa3vVlRfh2iK0

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥41
Хвостатые функции

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

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

int[] FilterWithLoop(int[] input, Func<int, bool> predicate)
{
List<int> result = [];
foreach (var item in input)
{
if (predicate(item)) result.Add(item);
}
return [.. result];
}


Но как мы выяснили ранее, мутабельность - зло. Как же быть? На помощь приходит рекурсия.

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

int[] FilterWithRecursion(int[] input, Func<int, bool> predicate)
{
if (input.Length == 0) return [];
var rest = FilterWithRecursion(input[1..], predicate);
return predicate(input[0]) ? [input[0], .. rest] : rest;
}


В функциональном программировании постоянно пользуются рекурсией. Она эффективно может заменить циклы и особенно хорошо показывает себя на деревьях, где листья повторяют сами себя (например родитель-потомок). А некоторые СУБД поддерживают рекурсивные CTE, чтобы получать древовидные данные было проще.

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

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

Но ФПшники продолжают пользоваться рекурсией. Почему?

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

Но самый крутой трюк - это tail call optimization (TCO). Если последнее выражение в функции является рекурсивным вызовом - то компилятор может переиспользовать фрейм стека текущей функции при вызове нового цикла рекурсии.

int[] FilterWithRecursionTail(int[] input, Func<int, bool> predicate, int index, List<int> acc)
{
if (index >= input.Length) return [.. acc];
if (predicate(input[index])) acc.Add(input[index]);
return FilterWithRecursionTail(input, predicate, index + 1, acc);
}


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

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

У кого-то это работает только в релизной конфигурации сборки, у кого-то только с повешенным на метод атрибутом [MethodImpl(MethodImplOptions.AggressiveOptimization)]. Детали реализации данной оптимизации покрыты туманом и без нескольких часов за просмотром исходного кода компилятора и JIT-оптимизатора, видимо, не обойтись.

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

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥4👍1
Текучие строки. Как не отлететь по памяти?

Что будет с приложением, которое читает десятки тысяч повторяющихся строк из базы данных и работает с ними в параллельных потоках на протяжении нескольких реальных секунд?

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

Большое количество строк произвольного размера может в какой-то момент просто не поместиться в оперативку и мы получим жирнющий красный лог с заголовком OutOfMemoryException.

Как быть?

Для начала врубаем профилировщик (типа dotMemory) и смотрим а что же за строки реально находятся у нас в оперативе в самых критических, жирных секциях. Можно словить нехилое удивление, обнаружив там десятки, а то и сотни инстансов одинаковых строк, нещадно поглощающих драгоценную ОЗУ, не давая простора другим важным объектам.

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

Дело в том, что интернирование в C# (как и в Java) применяется исключительно для строк, которые компилятор считает литералами. То есть это те строки, которые пишутся в двойных кавычках. Остальные же строки, прочитанные, например, из файлов или из баз данных, ведут себя как стандартные ссылочные типы, то есть могут иметь копии себя в куче и никто им ничего за это не предъявит.

Можно, конечно, "приказать" строке улететь в таблицу интернирования и переиспользоваться с помощью конструкций string.Intern(...) и string.IsIntern(...). Только вот снова есть нюансы. Во-первых, пул интернирования не ограничен сверху. А во-вторых, он не чистится Garbage Collector-ом. Все, что попадает в таблицу интернирования, остается в таблице интернирования до конца работы всего приложения, а для нас это означает, что если вариативность данных со временем становится выше - приложение совершенно точно улетит в ООМ, возможно, даже быстрее, чем было до применения интернирования.

Остается последний рубеж - StringPool.

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

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

Подключаем этот пул к критическим секциям (например, при чтении строк из базы данных) и полностью избавляемся от дублирующихся строк в памяти. PROFIT.

А если в приложении помимо строк присутствует частая вычитка большого количества неизменяемых объектов, то стоит посмотреть в сторону ObjectPool.

👾 PICO
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥41👍1