Telegram Web
На выходных был на рыбалке на Рыбинском водохранилище, открывал летний водомоторный сезон.

В сумме я провел на воде около 16 часов, кидая спиннинг и исследуя место впадения речки Рени в Рыбинское водохранилище. Ехал я за щукой, но она не клевала, совсем: ни единой отметины зубов на приманках я не увидел. Клевал окунь, два (грамм по 300) оказались достаточно голодными, чтобы проглотить щучью приманку.

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

#природа
Последние полтора года я работал в банке ПСБ, но пришло время расставаться. Был получен крайне интересный опыт, правда больше организационный: я увидел как может быть организована разработка в крупной организации с серьезным контролем качества и сохранности данных.

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

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

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

Первая нормальная форма: в таблицах есть первичные ключи, отсутствуют дубли, к тому же нет составных данных, то есть не допускается хранение данных в одной записи в виде массива или просто утрамбованных в текстовый тип данных с разделителем. Про то, как я мучался с дублями в таблице в ~500Гб можно почитать тут. А еще у меня на одном из рабочих проектов был опыт, когда одно из хранимых мной полей могло быть long, long[], guid или objectId (монговский формат "уникального" id). Сохранение в виде строки не проходило по требованиям к объему базы, раскидывание по разным строкам - по требованиям к производительности. В итоге я изобразил свой бинарный формат и хранил тупо байтики.

Поясню: long - id сущности из другой системы, например - "инфаркт" (система была для медтеха). long[] - жесткая сцепка двух понятий, например - "инфаркт в анамнезе", что несет несколько другой смысл для принятия врачебных решений (делалась СППВР).

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

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

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

Четвертая нормальная форма
- база пребывает во всех предшествующих нормальных формах, а в дополнение в таблицах отсутствуют данные, зависящие только от первичного ключа, но при этом не имеющие между собой логических связей. Этот принцип я нарушал множество раз и с особым цинизмом. Один из примеров - был у меня маленький проект, связанный с системой построения отчётов FastReports (я писал о ней). Мне нужно было решить проблему хранения файлов отчётов и информации о них: название, тег (идентификатор отчёта, по которому его запрашивает фронт), группа, сам файл макета отчёта, сериализованный в base64 (да, хранить такое в базе - дикость, но иногда приходится).

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

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

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

Изначально все вызовы я пустил строго последовательно, рассудив, что отладка параллельных вызовов превратится в ад (так в последствии и оказалось).

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

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

#архитектура
Распишу один из самых часто задаваемых вопросов на собеседованиях - принципы SOLID.

S: Single Responsibility Principle (Принцип единственной ответственности). Класс должен решать только какую-то одну задачу. Видел в интернетах много высосанных из пальца примеров, попробую дать примеры из жизни. К основному нарушению этого принципа я бы отнес замешивание в одном месте бизнесового и инфраструктурного кода. Имеем класс с методом с какой-то ядреной бизнес логикой, и тут, ВНЕЗАПНО нам нужно получить дополнительные данные. Не выходя из метода открываем подключение к БД, формируем запрос, читаем ответ, продолжаем бизнес логику. Чувствуете запах?:) Или другой вариант - замешиваем в одном классе получение данных из брокера и их бизнесовую обработку.
Ну и классический пример - антипаттерн singleton. Плоха не единственность экземпляра класса, а самоплальная логика обеспечения единственности.

O: Open-Closed Principle (Принцип открытости-закрытости). Классы должны быть открыты для расширения и закрыты для модификации. Самый простой пример, попирающий этот принцип - использование else if бесконечных размеров, куда по мере развития добавляются новые вариации ветвлений со своей логикой. Самое интересное начинается спустя несколько лет, когда приходится спиливать лишние загибы ветвлений или что-то изменять, оставляя часть функционала неизменной. Альтернатива - больше использовать следующий принцип.

L: Liskov Substitution Principle (Принцип подстановки Барбары Лисков). Объекты в программе должны быть заменяемы их наследниками без изменения корректности программы. В целом - самый очевидный принцип, который осознанно нарушают достаточно редко.

I: Interface Segregation Principle (Принцип разделения интерфейса). Классы не должны реализовывать методы только для совместимости с абстракцией предка. Самый простой путь нарушить предыдущий принцип - реализовывать часть методов только для сохранения совместимости. Как правило, это делается спустя рукава или без полного погружения в контекст.

D: Dependency Inversion Principle (Принцип инверсии зависимостей). Классы должны зависеть не от других классов, а от интерфейсов, чтобы в любой момент можно было подменить реализацию. Очевидная на бумаге вещь, но до чего же лениво ей следовать!

#собес
#solid
Эшу быдлокодит
Распишу один из самых часто задаваемых вопросов на собеседованиях - принципы SOLID. S: Single Responsibility Principle (Принцип единственной ответственности). Класс должен решать только какую-то одну задачу. Видел в интернетах много высосанных из пальца…
По горячим следам пример к букве I - разделению интерфейсов.

Например: пишем бота в телеграмме, у нас есть сущность - сообщение. Хочется (если честно - не очень) сделать какой-то интерфейс для типичных методов для работы с ним IMessage с методами GetText, GetAttachedMedia, LogToDB и т.д.

Но у нас есть нюанс: сообщения бывают входящие и исходящие. Исходящие после формирования надо отправлять в телегу, то есть нужен метод Send.

Но добавление этого метода в общий интерфейс заставит нас делать какую-то нерабочую фигню во входящем сообщении, потому интерфейсы надо разделить: ICommonMessage, а от него наследуются два интерфейса: IIncomeMessage и IOutcomeMessage, объявляющие свои специфические методы.

#собес
#solid
Поступила идея ответить на следущий вопрос интервьюера: приведите примеры ситуаций, когда нарушение каждой из букв оправдано.

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

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

Начали выполнение - добавили запись в БД. Следущий шаг - ещё запись, в другое место.

Если все шаги выполнены успешно - коммитнули транзакцию.

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

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

#собес
#solid
А вообще, мы в отпуске. Стартовали вчера в составе жена, я, два ребенка - 6 и 3.5 лет и племянница 14 лет. Мы поехали на машине в Мурманск и окрестности. Первый перегон - Москва - Волхов. Заночевали в посуточно сдающейся квартире. Несмотря на неказистый вид дома позапрошлого века постройки, для квартиры за 2тыс/сутки прям отлично. И детям громить особенно нечего:)

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

#отпуск
Перейдем к ситуациях, когда допустимо попирать следущую букву.

O. Принцип открытости-закрытости: классы должны быть открыты для расширения, но закрыты для модификации.

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

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

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

#собес
#solid
Перейду к описанию кейсов, когда нарушение принципа, соответствующего следующей букве в аббревиатуре #solid оправдано.

L. Принцип подстановки Барбары Лисков: объекты в программе должны быть заменяемы их наследниками без изменения корректности программы.

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

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

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

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

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

#собес
Сразу после пробуждения упаковались, посмотрели на миниатюрную Волховскую ГЭС и отправились в Карелию. Десять лет назад мы уже были тут, это было наше первое автомобильное путешествие.

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

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

Выложу видео сямозерского прибоя и немного зверей из зоокомплекса.

А теперь движемся дальше: север ждёт!

#отпуск
Пришел через нарушить следущий из принципов #solid - буковку I - Interface segregation - принцип разделения интерфейсов, который я иллюстрировал вышел.

Рассмотрим ситуацию: мы - банк. У нас много денег и высока цена ошибки. Айти огромное, а системы крайне сложны.

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

Итого на выходе мы имеем шаблон, в котором предусмотрено единообразным способом:
1. Логирование ошибок и основных этапов, система метрик, трассировка используемых запросов.
2. Простой механизм использования кэша, скрывающий особенности реализации: методы Put и Get
3. Механизм для проведения и отката распределенной транзакции.
4. Механизм увязывания операций в цепочки.
5. Механизм авторизации и аутентификации.

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

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

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

#собес
Сегодня совершили переезд из Петрозаводска за полярный круг, в окрестности Кандалакши.

По пути посетили занятный арт-объект в Петрозаводске - памятник карельскому комару, сделанный из трактора.

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

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

Заночевали в придорожном мотеле чуть недоезжая Кандалакши. При заселении меня попросили расписаться за пожарную безопасность (!) и стребовали согласия на обработку персональных данных от всех членов экипажа.
2025/07/06 06:10:54
Back to Top
HTML Embed Code: