TG Telegram Group Link
Channel: Эшу быдлокодит
Back to Bottom
И завершу тему прошедшего отпуска небольшой подборкой фоточек с Селигера.

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

В сумме я провел на воде около 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. Принцип подстановки Барбары Лисков: объекты в программе должны быть заменяемы их наследниками без изменения корректности программы.

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

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

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

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

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

#собес
HTML Embed Code:
2025/07/04 20:02:51
Back to Top