Как настроить зависимые выпадающие списки в MS Excel, используя СМЕЩ и СУММПРОИЗВ

В этой статье мы продемонстрирует простой подход по настройке выпадающего списка, зависящего от другого выпадающего списка. Например, мы выбираем страну в ячейке F1 и это изменяет список городов, доступных для выбора в ячейке F2, как показано на Рисунке 1.

Рисунок 1. Выбор города в стране
Рисунок 1. Выбор города в стране

Предположим, что мы уже настроили выпадающий список для страны, ссылающийся на диапазон A1:C1, тогда мы можем настроить список городов, используя формулу ниже, где:

  • СМЕЩ возвращает диапазон для зависимого выпадающего списка

  • A2 фиксирует начальную ячейку для функции СМЕЩ

  • 0 говорит функции СМЕЩ, что вертикального смещения нет

  • ПОИСКПОЗ(F1;A1:C1;0)-1 говорит функции СМЕЩ на сколько столбцов нужно сместиться вправо от начальной ячейки A2

  • СУММПРОИЗВ((F1=A1:C1)*(A2:C3<>»»)) сообщает функции СМЕЩ количество непустых ячеек (A2:C3<>»») в выбранном столбце (F1=A1:C1)

=СМЕЩ(A2;0;ПОИСКПОЗ(F1;A1:C1;0)-1;СУММПРОИЗВ((F1=A1:C1)*(A2:C3<>»»)))

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

  • (F1=A1:C1) – это массив {ЛОЖЬ;ИСТИНА;ЛОЖЬ}

  • (A2:C3<>»») – это массив {ЛОЖЬ;ИСТИНА;ИСТИНА:ЛОЖЬ;ЛОЖЬ;ИСТИНА}

  • (F1=A1:C1)*(A2:C3<>»») – это массив {0;1;0:0;0;0}, поскольку произведение ЛОЖЬ*ЛОЖЬ или ЛОЖЬ*ИСТИНА равно 0, тогда как произведение ИСТИНА*ИСТИНА равно 1

  • Функция СУММПРОИЗВ возвращает сумму массива {0;1;0:0;0;0}, равную 1 в нашем случае

Рисунок 2. Выбор города в Украине
Рисунок 2. Выбор города в Украине

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


ссылка на оригинал статьи https://habr.com/ru/post/680046/

Насколько хорошо у вас настроен OSPF/IS-IS или помогатор для сетевых инженеров

Коллеги-сетевики, привет. К написанию данной статьи меня сподвигли задачи, с которыми приходилось сталкиваться во время работы с OSPF/IS-IS и тот набор решений, к которому я в конечном итоге пришел. Речь идет о насущном вопросе сетевых инженеров, когда приходится применять настройки на живой сети (пусть и с программируемым откатом на крайний случай) без возможности посмотреть как это отразится на всей сети в целом. Если отдельные команды и сценарии еще можно проверить в лабе, то получить полную реплику сети практически невозможно. В связи с этим я задался вопросом о наличии инструмента, который позволял бы строить слепок сети и рассчитывать её реакцию на ранее примененные настройки. Об этом сегодняшний туториал.

Теория. Задачи. Практика

Весь слепок сети (правильнее сказать слепок area) уже содержится компактно на каждом L3-устройстве в Link-State DataBase (LSDB) OSPF/IS-IS протокола. Достаточно лишь подключиться к одному устройству, сохранить её в текстовый файл и все связности в рамках одной области (area) у вас уже есть. Если областей несколько, то собрать LSDB с каждой из них. Далее достаточно правильно распарсить и построить математический граф с L3 устройствами в качестве нод (vertex) и OSPF/IS-IS соседством в качестве линков (edge) между ними. Имея такой граф, мы можем у себя на ПК делать с ним все, что захотим: удалять линки и изменять метрики на них, удалять сами ноды, строить маршруты и все это не затрагивая реальную сеть. К слову сказать на реальной сети можно и так посмотреть как будет проходить путь через tracert/traceroute/mtr, но не все устройства, в частности файерволы, покажут себя в этом выводе (пример №1). А также нет никакой возможности посмотреть какой будет резервный (backup) маршрут, если тот или иной участок из этого пути упадет. Именно это покажет нам граф-модель, в которой достаточно удалить edge и построить маршрут между теми же самыми устройствами заново. Тогда наикратчайший маршрут в измененной модели будет являться резервным маршрутом для нашего первоначального слепка сети (пример №2).

Представим, что сеть представляет собой совокупность разных по дизайну топологий: hub-and-spoke для подключения удаленных офисов и full или partial mesh между hub-ами. Каждый удаленный офис имеет основной (Primary) и запасной (Secondary,Backup) маршрутизатор и вы планируете перезагрузить secondary устройство. Повлияет ли это как-то на активный трафик? Если это резервный девайс, то через него не должно ничего проходить, но как это проверить? Оказывается достаточно просто — необходимо из каждой удаленной локации построить наикратчайший маршрут до нашей локации, где мы планируем провести работы, и тем самым убедиться используется ли secondary устройство для активного трафика или нет (пример №3). Дальше-больше, что, если мы действительно обнаружили такой трафик с таким flow, как нам это поменять? Тогда нам не обойтись без изменения метрик (cost) на сети.

Много достоинств у Link-State протоколов и мы уже оценили одно из них, когда одно устройство содержит в себе всю информацию по конкретной области, но есть и особенности, с которыми приходится считаться. В частности метрика назначается и принадлежит интерфейсу, а не префиксу, как это обстоит у BGP. Поэтому если мы изменили метрику на одном интерфейсе, то могли повлиять на traffic flow во всей area. Но рано или поздно менять метрики приходится и если на интерфейсе на данный момент выставлена метрика 10, то как это повлияет на распределение трафика, если выставить не 10, а 9 или 11? Вы уже наверное догадались, что и в этот раз мы можем себе позволить сделать это на графе и посмотреть реакцию сети на наши изменения (пример №4).

Сеть как живой организм — все в нем внутри тесно связан, взаимодействует между собой и состояние на «вчера» может отличаться от состояния текущего. Поэтому было бы неплохо также иметь возможность сравнивать состояние сети в разные отрезки времени. Имея копии LSDB, переведенные в граф, мы теперь уже можем сравнивать их и понимать: какие новые появились подсети, какие, наоборот, пропали, а также выявлять новые и старые L3-устройства (пример №5).

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

Архитектура. Безопасность

Решение представляет собой веб сервис, состоящий из Open-Source проектов:

  • Nginx,

  • MongoDB,

  • Topolograph.

Благодаря контейнеризации, все составные части описаны в одном конфигурационном файле и запускаются одной docker compose up -d командой на Linux или Windows хосте. Такой подход имеет еще одно преимущество — безопасность, поскольку все загруженные данные о сети сохраняются в локальной MongoDB базе. Также данный сервис может быть помещен в DMZ зону, где разрешены только входящие HTTP запросы до Nginx, но запрещены все исходящие запросы за пределы зоны.

Используемые open-source решения
Используемые open-source решения

На рисунке изображена архитектура решения, которая запускается в среде Docker. При этом сервис может и не иметь доступа к сети, поскольку для построения графа необходим лишь текстовый файл с описанием OSPF/IS-IS LSDB.

Визуализация OSPF/IS-IS сети

Сбор LSDB с устройств различных вендоров

Для получения математической модели (графа) сети, необходимо сперва получить Link-State DataBase (LSDB) с реального устройства. Разные производители по-разному смотрят на формат вывода LSDB, но стоит отметить, что подавляющее большинство из них поддерживают RFC 2328.

Vendor

LSA1

LSA2

LSA5

Cisco

show ip ospf database router

show ip ospf database network

show ip ospf database external

Quagga

show ip ospf database router

show ip ospf database network

show ip ospf database external

Juniper

show ospf database router extensive | no-more

show ospf database network extensive | no-more

show ospf database external extensive | no-more

Bird

show ospf state all

show ospf state all

show ospf state all

Nokia

show router ospf database type router detail

show router ospf database type network detail

show router ospf database type external detail

Mikrotik

/routing ospf lsa print detail file=lsa.txt

/routing ospf lsa print detail file=lsa.txt

/routing ospf lsa print detail file=lsa.txt

Huawei

display ospf lsdb router

display ospf lsdb network

display ospf lsdb ase

Paloalto

show routing protocol ospf dumplsdb

show routing protocol ospf dumplsdb

show routing protocol ospf dumplsdb

Ubiquiti

show ip ospf database router

show ip ospf database network

show ip ospf database external

Allied Telesis

show ip ospf database router

show ip ospf database network

show ip ospf database external

Таблица с перечнем команд для сбора OSPF Link State DB. LSA1 и LSA2 являются обязательными для построения графа, в то время как LSA5 — опционален.

Vendor

Command

Cisco

show isis database detail

Juniper

show isis database extensive

Nokia

show router isis database detail

Huawei

display isis lsdb verbose

Таблица с перечнем команд для сбора IS-IS Link State DB.

Построенный граф на основе OSPF/IS-IS вывода LSDB

Чтение LSDB, её парсинг и построение графа считается успешным, если после загрузки файла с Link-State базой, отобразится топология сети. Граф динамический и интерактивный, то есть его можно перетянуть в нужное место на поле или выбрать ноду, до/с которой построить маршрут, нажав на нужную ноду правой кнопкой.

граф сети
граф сети

Жирной линией на графе отображены множественные (2 и более) связности между двумя нодами.

Построение кратчайшего пути. Пример №1

В самом начале статьи описывалась задача с построением актуальных путей между двумя устройствами. Результат расчета пути представлен на рисунке ниже. Дополнительно описывается маршрут с указанием каждого L3-устройства на сети (в трассировке используется OSPF RID (router ID)), а также метрика маршрута. 

наикратчайшие пути с 123.14.14.14 до 123.123.30.30
наикратчайшие пути с 123.14.14.14 до 123.123.30.30

На картинке выше изображены 4 наикратчайших маршрута (ECMP, Equal cost multipath) от L3 устройства с RID 123.14.14.14 до 123.123.30.30 с метрикой 41.

Представим также, что Вы знаете только IP адрес отправителя и IP адрес получателя. Для построения маршрута нужно знать на каком из L3-устройств затерминирована подсеть отправителя и получателя, и чтобы не тратить время на поиск, можно сразу начать вводить IP адреса в поле Focus/From и To. Терминирующие их устройства подставятся автоматически.

Нахождение резервного маршрута. Пример №2

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

резервный путь от 123.14.14.14 до 123.123.30.30
резервный путь от 123.14.14.14 до 123.123.30.30

При потери связности между двумя устройствами, резервный путь будет состоять из четырех ECMP путей, иметь стоимость 50 и будет проходить через устройства 123.31.31.31 — 123.11.11.11.

Построение карты доступности устройства. Пример №3

Представим, что нода 123.30.30.30 основная, а 123.31.31.31 — резервная. Наша задача заключается в том, чтобы проверить доступность группы устройств слева от устройства 123.30.30.30 из любой точки нашей сети. Этого можно достичь при включенной опции «Print Minimum Shortest Tree (MST) for the node» и выбора меню «Build the shortest path to this node».

все наикратчайшие пути до ноды 123.123.30.30
все наикратчайшие пути до ноды 123.123.30.30

На рисунке выше изображены все наикратчайшие пути до ноды 123.123.30.30. Нода 123.30.30.30 действительно является основной для входящего трафика. 

Построение карты сетевой доступности до всех устройств сети

Продолжая пример с активным и резервным роутером выше, теперь давайте убедимся, что это справедливо и для исходящего трафика. Для этого построим наикратчайшие пути от устройства 123.123.30.30 до всех нод в сети при выборе «Build the shortest path from this node».

Все исходящие пути ноды 123.123.30.30
Все исходящие пути ноды 123.123.30.30

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

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

Доступные отчеты о сети
Доступные отчеты о сети

Все отчеты о сети помещены под графой Analytics. На момент написания статьи их представлено пять. Чуть подробнее об отчетах будет написано ниже.

Отчет о несимметричных путях на сети. Пример реальной сети

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

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

Реакция сети на изменения в ней

В примере №2 текущей статьи мы увидели какой будет резервный маршрут при падении связности между 123.10.10.10 и 123.30.30.30. Для получения этих данных нам пришлось построить наикратчайший маршрут между удаленными устройствами графа и с имитировать падение линка. Для случаев, когда нам хотелось бы увидеть картину в целом как перестроится OSPF/IS-IS граф, если мы на время выключим тот или иной линк, сделан режим NetworkReactionOnFailure. В этом режиме мы можем увидеть через какие устройства будут перенаправлены наикратчайшие пути после изменений на сети, а через какие устройства маршруты больше строиться не будут. Стоит отметить, что backend topolograph оперирует именно числом наикратчайших маршрутов через ноду и не привязывает это как-то к объему трафика, так как не располагает такой информацией. Однако мы можем предположить, что чем больше будет проложено маршрутов через ноду, то и трафика через неё будет в конечном итоге больше. Рассмотрим это на примере.

Реакция сети на потерю линка

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

реакция сети на потерю связности
реакция сети на потерю связности

На рисунке выше с эмулировано падение связности между нодой 123.30.30.30 и 123.10.10.10, при этом объем «трафика» в направлении от 123.11.11.11 до 123.31.31.31 увеличится на 200%, а в обратном направлении от 123.31.31.31 до 123.11.11.11 только лишь на 100%. В чем может быть причина?

Это связано с тем, что на линке 123.30.30.30 — 123.10.10.10 указаны разные метрики.

OSPF метрика между 123.30.30.30 и 123.10.10.10
OSPF метрика между 123.30.30.30 и 123.10.10.10

Из-за того, что стоимость до ноды 123.10.10.10 — 1, в то время, как в обратном направлении — 10, нижний линк через ноды 123.31.31.31 и 123.11.11.11 неравномеренно использовался. По этой причине на нем будет в два раза больше увеличение «трафика» при падении верхнего линка (отмеченного красным). Еще одной подсказкой где трафика станет больше или меньше может служить масштаб (ширина) стрелки, чем она шире, тем бОльше изменений.

Реакция сети на изменение метрики (cost). Пример №4

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

Имеем все тот же граф с асимметрично назначенными метриками и давайте посмотрим куда перенаправится трафик, если мы вместо стоимости 1, выставим стоимость 12 на интерфейсе.

реакция сети на измение метрики на интерфейсе
реакция сети на измение метрики на интерфейсе

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

Реакция сети на падение устройства

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

эмулирование падения ноды 123.123.101.101
эмулирование падения ноды 123.123.101.101

С эмулируем падение ноды 123.123.101.101 через соответствующий пункт контекстного меню правой кнопки мыши. 

реакция сети на падение 123.123.101.101
реакция сети на падение 123.123.101.101

Результат реакции сети показывает, что нагрузку на себя возьмет нода 123.123.100.100, но обратите внимание, что объем трафика не одинаков. К примеру, трафика с правой верхней ноды 123.123.110.110 в левую часть схемы через 123.123.100.100 будет больше, чем в обратном направлении с 123.10.10.10 через все тот же 123.123.100.100. Объясняется это все тем же несимметрично назначенной метрикой в левой части схемы на линке 123.10.10.10 — 123.30.30.30.

Карта зарезервированности Stub сетей

При анализе Link-State DB учитываюся и сети (stub), которые анонсированы в OSPF/IS-IS домен. Какую полезную информацию можно получить из полученных данных? Если мы видим, что устройство 123.14.14.14 анонсирует сеть 10.0.0.0/24 и в тоже время другое устройство — 123.15.15.15 также включает её в свои анонсы, то мы можем сделать вывод, что оба эти устройства находятся в HSRP паре и данная сеть защищена на случай выхода из строя одного из устройств. Если проверить таким же образом каждую сеть, то можно построить карту с использованием градиента красного цвета там, где больше всего сетей без резервирования, и с зеленым цветом, где зарезервированных сетей больше. 

Heatmap зарезервированных сетей
Heatmap зарезервированных сетей

На рисунке показаны все сети устройства 123.10.10.10, а также то, что сеть 10.99.0.0/21 и 99.99.99.0/24 настроена только на нем, т.е. без резервирования. Стоит отметить, что здесь могут быть исключения. К примеру, стековый свич представлен в OSPF/IS-IS домене как одно устройство с одним RID, но по факту он может обеспечить резервирование при выходе из строя одного из его юнитов, если имеет минимум два кабельных подключения в разные юниты.

Отчеты о сети

Для быстрого анализа настроек OSPF/IS-IS удобнее использовать отчеты, которые представляют собой набор стандартных проверок:

  1. наличие соединений с асимметрично настроенной метрикой

  2. наличие асимметричных путей

  3. проверка, что резервный путь не проходит через устройства другого региона

  4. резервирование сетей (stub)

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

Отчет о линках с асиметричной метрикой
Отчет о линках с асиметричной метрикой

Все линки с асимметричной метрикой помечаются красным.

API

Для того, чтобы загрузить граф, не обязательно это делать через сохранение вывода OSPF/IS-IS LSDB в файл. Возможно воспользоваться любимым NetDevOps инструментом наподобие Ansible, netmiko, nornir и проч., сохранить через них вывод и сформировать POST запрос в Topolograph. Ответ будет содержать следующие данные:

  • Разницу с ранее загруженными слепками сети: новые и старые L3 устройства, новые и старые связности (edge)

  • ссылка на запрос списка всех сетей

  • статус некоторых отчетов

Пример загрузки слепка OSPF домена сети представлен ниже.

POST запрос на загрузку LSDB
POST запрос на загрузку LSDB

Из вывода POST запроса видно, что по сравнению с ранее загруженным слепком OSPF домена, пропали 4 связности между устройствами. Старых, как и новых L3 устройств в сети не появилось. Однако появилась 1 новая сеть и 1 сеть теперь уже не анонсируется устройством 123.10.10.10. Из прочей статистики указано общее число нод — 13 и общее число сетей — 39. Таким образом можно мониторить основные характеристики OSPF/IS-IS домена сети. Для собственной проверки можно делать слепок каждый раз до и после работ на сети. Если после работ изменений не выявлено, то можно уверенно утверждать, что работы проведены успешно (если конечно работы не предполагали каких-либо изменений настроек на сети).

Мониторинг изменений в OSPF домене из единой точки на сети в режиме Online

Имея возможность получать diff сети, можно задаться целью собирать слепки настолько часто, чтобы мы могли детектировать аварийные случаи. Но тогда нужно определиться насколько часто подключаться и сохранять их, чтобы с одной стороны не нагрузить устройство, а с другой — отловить начало и конец аварии (на тот случай, если падение связности между устройствами было кратковременным). Но гораздо эффективней и рациональней предоставить возможность сервису читать актуальные служебные сообщения OSPF (LSA) в режиме реального времени и таким образом логировать изменения на сети. Такая задача покрывается в другом, но схожем, open-source проектe Ospfwatcher, целью которого является мониторинг изменений в OSPF топологии и экспорт данных в ELK (Elasticsearch, Logstash и Kibana).

На данный момент имеет следующую архитектуру:

  • Linux хост с docker, с которого устанавливается GRE туннель до любого активного L3 устройства в сети

  • Quagga для установления OSPF соседства через GRE туннель и дебаг OSPF LSA сообщений в docker volume

  • Watcher модуль, который читает OSPF debug сообщения и переводить их в структурированный вид

  • Logstash, который делает экспорт логов в стек Elastic-Logstash-Kibana

Компоненты ospfwatcher также описаны в едином конфигурационном файле для запуска их через docker compose.

Архитектруа Ospfwatcher
Архитектруа Ospfwatcher

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

На рисунке выше представлена выборка логов по изменению OSPF метрики на сети в Elastic-e, а именно:

  • какое устройство обнаружило изменение,

  • какая была старая метрика и какая — новая.

Как это использовать

Приходя утром в офис Подключившись по удаленке через VPN, можно прчто было c OSPF за ночь. Или когда поступают жалобы в виде «сеть начала тормозить, когда как 5 минут назад все работало хорошо» можно посмотреть перестраивался ли OSPF, если нет, то следовать по привычным шагам далее — смотреть мониторинг, дашборды, уточнять детали. Если же граф перестраивался, то это даст подсказку где и что произошло.

Заключение

Этой статьей я хотел суммировать подходы к исследованию настроек OSPF/IS-IS сети с помощью доступных инструментов, с целью привнесения некоторой аналитики в legacy сети, которые по умолчанию этот сервис не предоставляют. Попробуйте применить озвученные подходы анализа к вашей сети, сделайте отчеты, будут ли у вас несимметричные пути?) Возможно, у кого серые IP адреса на Router ID и кто может показать свою топологий — также welcome, вместе посмотрим какой дизайн сети применяется чаще всего.


ссылка на оригинал статьи https://habr.com/ru/post/680048/

Spring security: без фильтров по умолчанию, как и что из этого получится

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


Введение

Решая тестовое задание, когда доступ к ресурсу предоставляется не всем, я выбрал Spring Security, чтобы помочь себе: использолвать готовое, возможно увидеть код, который может меня чему-то научить, лучше разобраться в теме удостоверения и предоставления прав (authentication, authorization). Мне показалось неуютным, когда глядя на примеры в сети, очень многие примеры, я оставляю бесконтрольным, что сейчас подключено, а что нет. Это по меньшей мере включенный лишний функционал. Я постарался овладеть настройкой того, что уже подключено по умолчанию.

Зависимости Gradle
dependencies {   // необходимые     implementation 'org.springframework.boot:spring-boot-starter-security'     implementation 'org.springframework.boot:spring-boot-starter-web'          // для красоты, удобства и тестов     compileOnly 'org.projectlombok:lombok'     developmentOnly 'org.springframework.boot:spring-boot-devtools'     annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'     annotationProcessor 'org.projectlombok:lombok'     testImplementation 'org.springframework.boot:spring-boot-starter-test'     testImplementation 'org.springframework.security:spring-security-test' }

Настройка

Одним куском такую информацию так нигде и не встретил, пришлось поработать в ручную, пробами и ошибками. Итак, есть два предлагамых Спригом способа настройки безопасности через код (можно ещё через файл конфигурации): это через наследование классу WebSecurityConfigurerAdapter (с версии 5.7 признан устаревшим), либо через класс конфигурации. На оба рекомендовано навесить аннотацию

‘@EnableWebSecurity’,
@Retention(RetentionPolicy.RUNTIME)  @Target({ElementType.TYPE})  @Documented  @Import({org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.class,org.springframework.security.config.annotation.web.configuration.SpringWebMvcImportSelector.class,org.springframework.security.config.annotation.web.configuration.OAuth2ImportSelector.class,org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.class})  @EnableGlobalAuthentication  @Configuration  public @interface EnableWebSecurity extends annotation.Annotation

которая вбирает в себя и хорошо известную аннотацию @Configuration и @EnableGlobalAuthentication (помечает, что класс может быть использован для построения экземпляра AuthenticationManagerBuilder — строитель того, что используют филтры, о которых идёт здесь речь).

Оказалось, что всё, что уже включено, это 11 фильтров, все кроме можно легко выключить или перенастроить, кроме WebAsyncManagerIntegrationFilter. Вот некоторые подробности о каждом из них.

Филтры, подключенные по умолчанию:
org.springframework.security.web.authentication.       AnonymousAuthenticationFilter org.springframework.security.web.csrf.                 CsrfFilter org.springframework.security.web.session.              DisableEncodeUrlFilter org.springframework.security.web.access.               ExceptionTranslationFilter org.springframework.security.web.header.               HeaderWriterFilter org.springframework.security.web.authentication.logout.LogoutFilter org.springframework.security.web.savedrequest.         RequestCacheAwareFilter org.springframework.security.web.servletapi.           SecurityContextHolderAwareRequestFilter org.springframework.security.web.context.              SecurityContextPersistenceFilter org.springframework.security.web.session.              SessionManagementFilter org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter

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

Код настройки безопасности, выключающий каждый достпуный для выключения фильтр:
import lombok.val; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain;  @EnableWebSecurity public class SecurityFilterChainImpl {     @Bean     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {         return chain = http                 .anonymous(AbstractHttpConfigurer::disable)         // AnonymousAuthenticationFilter                 .csrf(AbstractHttpConfigurer::disable)              // CsrfFilter                 .sessionManagement(AbstractHttpConfigurer::disable) // DisableEncodeUrlFilter, SessionManagementFilter                 .exceptionHandling(AbstractHttpConfigurer::disable) // ExceptionTranslationFilter                 .headers(AbstractHttpConfigurer::disable)           // HeaderWriterFilter                 .logout(AbstractHttpConfigurer::disable)            // LogoutFilter                 .requestCache(AbstractHttpConfigurer::disable)      // RequestCacheAwareFilter                 .servletApi(AbstractHttpConfigurer::disable)        // SecurityContextHolderAwareRequestFilter                 .securityContext(AbstractHttpConfigurer::disable)   // SecurityContextPersistenceFilter                 .build();     } }

Выключить можно и устаревшим (как выражается документация Спринга: без лямбд, ведь это менее удобно) вызовом: .anonymous().disable().and()

Как видите, выключаемые и настраиваемы классы иногда даже отдалённо не напоминают названием имена методов стоителя настрое цепи безопасности (см. код и комментарии, я нарочно разместил методы настроек и настраиваемые фильтры в том же порядке). Это ещё не всё, когда захочется настроить защищённый доступ к некоторым ресурасм, но хотя бы один ресурс (для регистрации, например) оставите с досупом для всех, окажется, что понадобится AnonymousAuthenticationFilter. Ещё странным кажется, что некоторым методам строителя не соотвествует ни один фильтр: .userDetailsService() и .portMapper(). К нюансам можно привыкнуть, но время!..

Фильтры, сколько бы их ни было, слагают шаблон «цепочка ответственности» косвенно рекурсивно вызывают один другого: стандартные — по списку, добавленные (нами) — на месте springSecurityFilterChain, по значению их порядка:

Некоторые фильтры, предопределённые Спригом, и их порядковые номера

пакет

класс

порядковый номер

org.springframework.security.web.session.

DisableEncodeUrlFilter

100

org.springframework.security.web.session.

ForceEagerSessionCreationFilter

200

org.springframework.security.web.access.channel.

ChannelProcessingFilter

300

org.springframework.security.web.context.request.async.

WebAsyncManagerIntegrationFilter

500

org.springframework.security.web.context.

SecurityContextHolderFilter

600

org.springframework.security.web.context.

SecurityContextPersistenceFilter

700

org.springframework.security.web.header.

HeaderWriterFilter

800

org.springframework.web.filter.

CorsFilter

900

org.springframework.security.web.csrf.

CsrfFilter

1000

org.springframework.security.web.authentication.logout.

LogoutFilter

1100

org.springframework.security.oauth2.client.web.

OAuth2AuthorizationRequestRedirectFilter

1200

org.springframework.security.saml2.provider.service.servlet.filter.

Saml2WebSsoAuthenticationRequestFilter

1300

org.springframework.security.web.authentication.preauth.x509.

X509AuthenticationFilter

1400

org.springframework.security.web.authentication.preauth.

AbstractPreAuthenticatedProcessingFilter

1500

org.springframework.security.cas.web.

CasAuthenticationFilter

1600

org.springframework.security.oauth2.client.web.

OAuth2LoginAuthenticationFilter

1700

org.springframework.security.saml2.provider.service.servlet.filter.

Saml2WebSsoAuthenticationFilter

1800

org.springframework.security.web.authentication.

UsernamePasswordAuthenticationFilter

1900

org.springframework.security.openid.

OpenIDAuthenticationFilter

2100

org.springframework.security.web.authentication.ui.

DefaultLoginPageGeneratingFilter

2200

org.springframework.security.web.authentication.ui.

DefaultLogoutPageGeneratingFilter

2300

org.springframework.security.web.session.

ConcurrentSessionFilter

2400

org.springframework.security.web.authentication.www.

DigestAuthenticationFilter

2500

org.springframework.security.oauth2.server.resource.web.

BearerTokenAuthenticationFilter

2600

org.springframework.security.web.authentication.www.

BasicAuthenticationFilter

2700

org.springframework.security.web.savedrequest.

RequestCacheAwareFilter

2800

org.springframework.security.web.servletapi.

SecurityContextHolderAwareRequestFilter

2900

org.springframework.security.web.jaasapi.

JaasApiIntegrationFilter

3000

org.springframework.security.web.authentication.rememberme.

RememberMeAuthenticationFilter

3100

org.springframework.security.web.authentication.

AnonymousAuthenticationFilter

3200

org.springframework.security.oauth2.client.web.

OAuth2AuthorizationCodeGrantFilter

3300

org.springframework.security.web.session.

SessionManagementFilter

3400

org.springframework.security.web.access.

ExceptionTranslationFilter

3500

org.springframework.security.web.access.intercept.

FilterSecurityInterceptor

3600

org.springframework.security.web.access.intercept.

AuthorizationFilter

3700

org.springframework.security.web.authentication.switchuser.

SwitchUserFilter

3800

Каждый фильтр внутри springSecurityFilterChain имеет свой порядок. Начальный задается внутри экземпляра HttpSecurity переменной класса FilterOrderRegistration, в её конструкторе. И вы либо переопределяете существующий фильтр, либо добавляете новый, указывая где он должен быть размещён, относительно остальных.

Контроллер

Теперь добавим простенький контроллер

Код контроллера
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;  @RestController public class Controller {      @GetMapping     public String get() {         return "Hello!";     } }

Тесты

…И проверим

Код теста
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;  import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse;  import static org.junit.jupiter.api.Assertions.assertEquals;  @SpringBootTest public class ControllerIT {      @Test     public void test() throws IOException, InterruptedException {         final HttpRequest request = HttpRequest.newBuilder()                 .uri(URI.create("http://localhost:8080"))                 .GET()                 .build();         final HttpClient client = HttpClient.newHttpClient();          final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());          assertEquals("Hello!", response.body());     } } 

Всё работает.


Кстати, для самостоятельного ознакомления, можно воспользоваться советом из документации Спринга:

…adding a debug point in FilterChainProxy is a great place to start.

В цепи обработки

/* FilterChainProxy#doFilter(ServletRequest request, ServletResponse response, FilterChain chain)):  переменная chain#filters содержит  0 = {ApplicationFilterConfig@7248} "ApplicationFilterConfig[name=characterEncodingFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter]" 1 = {ApplicationFilterConfig@7249} "ApplicationFilterConfig[name=formContentFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedFormContentFilter]" 2 = {ApplicationFilterConfig@7250} "ApplicationFilterConfig[name=requestContextFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter]" 3 = {ApplicationFilterConfig@7251} "ApplicationFilterConfig[name=springSecurityFilterChain, filterClass=org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1]" 4 = {ApplicationFilterConfig@7252} "ApplicationFilterConfig[name=Tomcat WebSocket (JSR356) Filter, filterClass=org.apache.tomcat.websocket.server.WsFilter]" springSecurityFilterChain, его собственно мы и настраиваем */


ссылка на оригинал статьи https://habr.com/ru/post/680052/

В чём процессорная сила, брат?

Долгое время, начиная, фактически, с 80-х годов 20-го века и до нынешнего момента, архитектура x86 доминировала на рынке десктопных, а потом и серверных решений и ноутбуков. Для многих жителей планеты Земля слова «компьютер» и «компьютер на базе процессора x86» стали синонимами. Но в последние годы позиции архитектуры x86 перестали выглядеть столь незыблемыми. Виной тому несколько причин: недооценка компанией Intel и в итоге проигрыш мобильного рынка процессоров компании ARM; скукоживание рынка десктопных решений опять-таки по причине роста мобильных устройств; потеря технологического лидерства Intel в разработке самых передовых нанометров, где пальму первенства захватила компания TSMC; недостаточная гибкость бизнес-модели компании Intel, являющейся классической Integrated Device Manufacturing компанией во времена, когда сложность разработки растёт и требует всё большего разделения труда. В итоге, на горизонте у архитектуры x86 появились конкуренты, бросающие ей вызов. В первую очередь это архитектуры Arm и RISC-V. Но несмотря на все сложности текущего положения архитектуры x86, есть важнейший фактор, который ещё долго будет мешать конкурентам скинуть её с трона серверного и десктопного рынков. Этот фактор – колоссальная по объёму программная экосистема, разработанная за десятилетия существования x86. В данной статье хотелось бы кратко осветить вопрос, почему переход с одной процессорной платформы на другую столь болезнен, почему нельзя просто взять и перекомпилировать весь необходимый софт на новую архитектуру и где нас ожидают подвохи и подводные камни.

Немного истории

Начну с краткого исторического экскурса. В отечественном инфополе существует одно достаточное лубочное объяснение того, почему СССР проиграл процессорную гонку США. Звучит оно примерно следующим образом: «в 1960-х годах был взят курс на копирование зарубежных архитектур, что привело к деградации отечественной школы процессоростроения с соответствующими последствиями». Часто этот тезис сопровождается дальнейшими поисками  врагов народа, где степень накала таких рассуждений, как правило, обратно пропорциальна уровню понимания вопроса. Мы не будем вдаваться в дебри столь сложной темы, тем более, что за давностью лет многие важные моменты и нюансы остались только в памяти непосредственных участников тех судьбоносных решений, коих уже нет в живых. Но в тех далёких баталиях есть один важный момент, который имеет прямое отношение к нашему вопросу. Благодаря книге Бориса Николаевича Малиновского «История вычислительной техники в лицах» мы имеем возможность прочитать стенограмму заседания декабря 1969-го года в Минрадиопроме, где принималось решение о том, какую систему выбрать в качестве базовой для дальнейших разработок. Из приведённого обсуждения (и предшествующих ему дискуссий) видно, что ключевым фактором, определяющим выбор облика будущей единой архитектуры в СССР, был именно вопрос размера доступной программной экосистемы. По этому поводу в книге есть такая цитата:

Опыт разработки сложных и объемных операционных систем показал, что на их создание требуется труда даже больше (тысячи человеко-лет), чем на разработку собственно технических средств

Таким образом, уже в конце 60-х годов 20-го века, размер программной экосистемы был доминирующим фактором в вопросе выбора аппаратных решений. Недаром, если судить по стенограмме, то фактически, корифеями отечественной микроэлектрониики и профильными чиновниками обсуждался выбор между всего двумя вариантами – нелегальное копирование системы IBM-360 на базе задела работ, созданного коллегами из ГДР или лицензирование у английской компании ICL ЭВМ «Система-4», являющейся в свою очередь легальным клоном той же IBM-360 (в итоге был выбран первый вариант). Т.е. в обоих случаях была цель переиспользовать уже разработанное для IBM-360 программное обеспечение в том или ином виде. Что же говорить о нынешней ситуации, когда десятки миллионов программистов ежегодно на протяжении уже более 30 лет разрабатывают ПО, предназначенное для работы на платформе x86.

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

Языки программирования

Начнём с вопроса того, какой стек языков программирования вы используете для создания своего программного продукта. Рождение новой архитектуры сопровождается созданием базового набора системного ПО – компиляторов, библиотек, операционных систем и т.д. Но всё это в первую очередь — для C/C++. Далее идёт разработка ПО для поддержки других популярных языков программирования. Но ясно, что если у вас в стеке используется что-то не столь распространнённое, то вы не сможете просто физически свой софт скомпилировать. А ждать момента, когда такая поддержка появится, можно годы. Например, для той же Java (одного из важнейших ЯП) поддержка архитектуры RISC-V в OpenJDK появилась только в этом году. Что уж говорить о чём-то более экзотическом, условных Haskell или D, их поддержки можно вообще не дождаться. Поэтому, проблема отсутствия должной поддержки широкой номенклатуры используемых языков программирования и сопутствующего им системного ПО является важнейшим фактором, препятствующим распространению новой архитектуры.

ОС Windows

Есть второй, очень важный момент – это операционная система, на которой работает ваше ПО. В том случае, если это Windows, то ситация становится драматической. Ведь недаром экосистему x86 часто называют Wintel – ОС Windows и архитектура x86 настолько шли друг с другом рука об руку, что разработка под  Windows фактически означает жёсткую привязку к x86. Ведь фактически, экосистема MS Windows доступна только для архитектуры x86. Да, есть версии Windows под Arm, но:

  1. Только для определённых устройств (а поддержки интересующих вас, например, на базе условного Байкал-М, там не будет)

  2. Кроме самой ОС нужна ещё инфраструктура библиотек и прочего ПО, которое для ARM портировано в минимальном объёме

Для остальных архитектур (той же RISC-V) ОС Windows нет физически, и всё, что вам остаётся – это портирование своего ПО на Linux. А эта задача по сложности и трудозатратам может быть сопоставима с разработкой ПО с нуля. В определённых случаях можно попробовать обойтись утилитой Wine, но мой опыт работы с данным программным продуктом показывает, что это костыль и более-менее вменяемо он работает только для несложного ПО и игр. А для более серьёзных программных продуктов он просто неприменим.

Именно поэтому, кстати, переход на какую-либо отечественную платформу должен в первую очередь сопровождаться стимулированием разработчиков портировать своё ПО на Linux, вплоть до запрета попадания в различные «реестры отечественного ПО» в Windows-версий приложений. Особенно это актуально в нынешних условиях, когда компания Microsoft официально покинула российский рынок.

ОС Linux

Но допустим, ПО разработано (или портировано) под Linux. Написано на C/C++, т.е. вы изначально имеете доступ к достаточно обширному стеку системного ПО, позволяющего вести разработку под новую архитектуру. Всё хорошо? Нажимаем кнопку «скомпилировать проект» и наслаждаемся результатом? К сожалению, нет, нас ждут потенциально следующие проблемы:

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

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

  2. Переход на новый компилятор. Даже если вы собирали свой проект условным gcc, то простая замена x86 версии на Arm, скорее всего, приведёт к появлению ошибок компиляции, с которыми придётся разбираться. Это происходит из-за того, что два компилятора даже под одну архитектуру, но разных версий (например, gcc 9.3.0 и gcc 10.3.0) имеют отличия, и то, что без ошибок скомпилируется на одной версии, не будет компилироавться на другой. У компиляторов под другую архитектуру может быть своя специфика, закладки на какие-то undefined behavior и поэтому сгенерированный код может вести себя немного по разному. Вот, например, обзорная статья от Microsoft о нюансах перекомпиляции кода под архитектуру Arm. На моей памяти, ни один переход на новую версию компилятора не прошёл «бесшовно» — всегда приходилось править какие-то ошибки.

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

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

    По моему опыту, это массовая и достаточно трудоёмкая проблема

  4. Проприетарный софт. Тут всё понятно. Если вы в том или ином виде завязаны на проприетарный софт, то либо вам придётся от него отказываться и искать заменители(что может быть крайне затратно), либо ждать, когда владельцы портируют его на нужную вам платформу.

    Данная проблема усугубляется тем, что поставщикам конечных решений, использующих ПО от разных производителей и занимающихся его интеграцией, необходимо, чтобы все элементы их конечного решения были портированы на нужную платформу. Если же нет хотя бы одной части, то с решением на базе новой архитектуры возникает проблема, и приходится ставить старый привычный x86, на котором «всё работает». И как правило, чем более сложные решения , тем  больше всякого проприетарного софта, который существует только для x86 и для которого нет  адекватной замены. Возникает проблема курицы и яйца — владельцы софта не видят смысла тратить кучу ресурсов на портирование сложного ПО на новое железо, если его продажи минимальны, а продажи железа с новой архитектурой минимальны, потому что под него нет ПО. Круг замыкается.

Поддержка кода

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

Производительность

Бытует мнение, что переходу на новую архитектуру также мешает большое снижение производительности при перекомпиляции кода, т.к., дескать, «всё ПО оптимизировано под x86». И поэтому требуются большие затраты на оптимизацию кода под новую архитектуру.

Тут хочется отделить зёрна от плёвел. Вообще говоря, сам по себе код на языке программирования высокого уровня не имеет привязки к конкретной архитектуре и обладает  примерно равным потенциалом по скорости для развитых RISC/CISC архитектур, коими являются x86, Arm, Risc-V (это не совсем так, например, для VLIW-архитектур, но этот вопрос широко обсуждался в предыдущих статьях). Поэтому, если ваш проект компилировался под x86 с помощью gcc/clang, то с высокой вероятностью при перекомпиляции на Arm, скорость работы ПО будет идентичной по модулю различий производительности аппаратуры. Но есть, конечно же, нюансы. Разница может возникнуть в следующих случаях:

  1. Вы использовали компилятор icc. Это проприетарный компилятор от Intel, позволяющий выжать максимальную производительность из интеловских процессоров. На целочисленных задачах разницы с gcc практически не будет скорее всего, но на HPC нагрузках компилятор icc вполне может добавить 10-20% к скорости работы программы.

  2. Вы разрабатываете под Risc-V на C/C++. Открытые компиляторы (gcc, clang+llvm) в данный момент генерируют примерно равный по производительности код для архитектур x86 и ARM. В случае же архитектуры RISC-V бэкенд ещё не столь «взрослый» и есть немалая вероятность, что в определённых случаях можно наткнуться на неоптимальности и, как следствие, потерять от нескольких единиц до нескольких десятков процентов производительности в самых худших случаях.

  3. Вы используете в разработке что-то отличное от C/C++.  В таком случае, ситуация в общем случае будет следующей  – производительность под Arm будет приближаться к x86, местами сравниваясь с ней, Risc-V сейчас будет проигрывать, но гандикап от конкурентов будет быстро уменьшаться в ближайшие годы по мере взросления архитектуры.

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

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

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

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

В качестве резюме

При разработке любого железа, надо всегда помнить одну простую, но исключительно важную истину:


ссылка на оригинал статьи https://habr.com/ru/post/680070/

Статический анализ кода в современной Java-разработке

Привет, Хабр!

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

Когда-то (очень давно, на четвёртом-пятом годах карьеры) я носил лычки «проектировщика программного обеспечения» и готовил многостраничные гайды по оформлению кода, правильных инструментах и библиотеках. Это были красивые всеобъемлющие документы, с прочтения которых начинался онбординг любого разработчика на проекте. А затем я тщательно следил за соблюдением этих правил на этапе code review. И знаете, сейчас я понимаю, что это было полное абсолютное стопроцентное дерьмо.

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

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

Зачем вообще следить за качеством кода?

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

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

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

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

Зачем нужен архитектор или техлид?

На каждом проекте должен быть человек, играющий роль архитектора или технического лидера. Этот человек несёт персональную ответственность за технические решения, принимаемые на проекте, а также следит за качеством кода. Делает он это посредством CI пайплайна, который нужно настроить на максимально тщательную проверку программного кода. Весь код, который не соответствует принятым критериям качества, должен отвергаться и никогда не должен попасть в основную ветку разработки (master/development).

Какие проверки кода могут быть?

Проверяться могут любые аспекты кода. В целом, чем больше проверок, тем лучше.

Все проверки можно разделить на две категории — автоматические и экспертные. Экспертные — это code review. Это самые медленные, дорогие и ненадёжные проверки, которые только могут быть, но, тем не менее, они должны быть.

Автоматические проверки включают в себя:

  • компиляцию кода;

  • выполнение тестов;

  • статический анализ;

  • проверку на уязвимости и т.д.

Зачем нужен Checkstyle и EditorConfig?

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

Checkstyle позволяет зафиксировать и отслеживать во время сборки проекта соответствие кода принятым стандартам оформления. Больше не будет споров по типу: табуляция или пробелы; 2 или 4 пробела для отступа и т.д. За этим будет следить Checkstyle.

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

Checkstyle работает на основе построение AST и содержит очень много проверок. Одни из моих любимых: RedundantModifier, SingleSpaceSeparator, EmptyLineSeparator и IllegalImport. Пример конфига, который я использую для своих проектов, можно найти на GitHub.

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

Пример конфигурации для Gradle:

plugins {     id 'checkstyle' } test {     useJUnitPlatform()     dependsOn checkstyleMain, checkstyleTest  } checkstyle {     toolVersion '10.3.1'     configFile file("config/checkstyle/checkstyle.xml")     ignoreFailures = false     maxWarnings = 0     maxErrors = 0 } checkstyleMain {     source ='src/main/java' } checkstyleTest {     source ='src/test/java' }

Пример конфигурации для Maven:

<plugin>     <groupId>org.apache.maven.plugins</groupId>     <artifactId>maven-checkstyle-plugin</artifactId>     <dependencies>         <dependency>             <groupId>com.puppycrawl.tools</groupId>             <artifactId>checkstyle</artifactId>             <version>10.3.1</version>         </dependency>         <dependency>             <groupId>com.thomasjensen.checkstyle.addons</groupId>             <artifactId>checkstyle-addons</artifactId>             <version>6.0.1</version>         </dependency>     </dependencies>     <configuration>         <encoding>UTF-8</encoding>         <consoleOutput>true</consoleOutput>         <violationSeverity>warning</violationSeverity>         <failOnViolation>true</failOnViolation>         <failsOnError>true</failsOnError>         <linkXRef>false</linkXRef>         <includeTestSourceDirectory>true</includeTestSourceDirectory>         <sourceDirectories>             <directory>${project.build.sourceDirectory}</directory>             <directory>${project.build.testSourceDirectory}</directory>         </sourceDirectories>         <checkstyleRules>            ...            <!-- Правила можно описать прямо в parent pom. Удобно для многомодульных проектов -->         </checkstyleRules>     </configuration>     <executions>         <execution>             <id>check</id>             <phase>validate</phase>             <goals>                 <goal>check</goal>             </goals>         </execution>     </executions> </plugin> 

Зачем нужен PMD?

PMD — это ещё один статический анализатор, гармонично дополняющий Checkstyle. Он так же строит AST, но помимо этого использует байт-код, поэтому его нужно запускать после компиляции проекта. А ещё PMD позволяет достаточно легко добавлять свои собственные проверки. Подробнее об этом можно почитать в статье от Wrike.

Моя любимая проверка — JUnitTestsShouldIncludeAssert. Я о ней недавно рассказывал в статье про AssertJ; почитайте, если ещё нет.

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

К Gradle проекту PMD можно подключить так:

plugins {     id 'pmd' } test {     useJUnitPlatform()     dependsOn pmdMain, pmdTest } pmd {     consoleOutput = true     toolVersion = "6.47.0"     ruleSetFiles = files("config/pmd/pmd.xml") // Исключения только через внешний файл     ruleSets = [] }

Maven плагин также не позволяет настраивать исключения прямо в parent pom, поэтому приходится использовать файл с конфигом:

<plugin>     <groupId>org.apache.maven.plugins</groupId>     <artifactId>maven-pmd-plugin</artifactId>     <configuration>         <failOnViolation>true</failOnViolation>         <includeTests>true</includeTests>         <linkXRef>false</linkXRef>         <printFailingErrors>true</printFailingErrors>         <rulesets>             <ruleset>config/pmd/pmd.xml</ruleset> <!-- Есть нюансы для многомодульных проектов -->         </rulesets>     </configuration>     <executions>         <execution>             <id>pmd-check</id>             <phase>test</phase> <!-- Желательно наличие байт-кода; см. https://maven.apache.org/plugins/maven-pmd-plugin/faq.html -->             <goals>                 <goal>check</goal>             </goals>         </execution>     </executions> </plugin> 

Зачем нужен SpotBugs?

SpotBugs — тоже статический анализатор, но ориентированный больше на поиск ошибок в байт-коде. Он является преемником FindBugs и имеет свыше 400 эвристик, на основании которых работает. Он может обнаруживать потенциальные NPE или игнорирование возвращаемого методом значения (смотрите статью про AssertJ, которую я упоминал ранее).

Из недостатков стоит отметить, что SpotBugs достаточно параноидален и часто жалуется на вещи, которые мы не хотим исправлять (много false positive по отдельным правилам). Такие эвристики можно проигнорировать, используя excludeFilterFile.

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

Конфигурация для Gradle:

plugins {     id 'com.github.spotbugs' version '5.0.9' } spotbugs {     showProgress = true     effort = 'max'     reportLevel = 'low'     excludeFilter = file("config/spotbugs/exclude.xml") }

Maven вариант конфига:

<plugin>     <groupId>com.github.spotbugs</groupId>     <artifactId>spotbugs-maven-plugin</artifactId>     <version>4.5.3</version>     <configuration>         <includeTests>true</includeTests>         <effort>Max</effort>         <threshold>Low</threshold>         <xmlOutput>false</xmlOutput>         <excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>     </configuration>     <executions>         <execution>             <id>check</id>             <phase>test</phase>             <goals>                 <goal>check</goal>             </goals>         </execution>     </executions> </plugin>

Почему бы не использовать только SonarQube?

На многих проектах (особенно в «кровавом энтерпрайзе») уже может использоваться SonarQube. И тут возникает резонный вопрос: не достаточно ли только его?

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

Признаюсь честно, я не поклонник Sonar’а. И тем не менее, на всех своих проектах я стараюсь использовать SonarQube, но при этом предпочитаю рассматривать его как последнюю линию обороны.

Зачем JaCoCo в проекте?

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

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

Пример настройки правил для Gradle:

plugins {     id 'jacoco' } test {     useJUnitPlatform()     finalizedBy jacocoTestReport     finalizedBy jacocoTestCoverageVerification } jacocoTestReport {     reports {         html.enabled true     } } jacocoTestCoverageVerification {     dependsOn test     violationRules {         rule {             limit {                 counter = 'CLASS'                 value = 'MISSEDCOUNT'                 maximum = 0             }         }         rule {             limit {                 counter = 'METHOD'                 value = 'MISSEDCOUNT'                 maximum = 0             }         }         rule {             limit {                 counter = 'LINE'                 value = 'MISSEDCOUNT'                 maximum = 0             }         }         rule {             limit {                 counter = 'INSTRUCTION'                 value = 'COVEREDRATIO'                 minimum = 1.0             }         }     } } check.dependsOn jacocoTestReport, jacocoTestCoverageVerification

И аналогичные правила для Maven:

<plugin>     <groupId>org.jacoco</groupId>     <artifactId>jacoco-maven-plugin</artifactId>     <version>0.8.8</version>     <configuration>         <excludes>             <!-- excludes generated code -->             <exclude>**/*MapperImpl.class</exclude>         </excludes>     </configuration>     <executions>         <execution>             <id>prepare-agent</id>             <goals>                 <goal>prepare-agent</goal>             </goals>         </execution>         <execution>             <id>report</id>             <phase>test</phase>             <goals>                 <goal>report</goal>             </goals>         </execution>         <execution>             <id>check-minimal</id>             <phase>package</phase>             <goals>                 <goal>check</goal>             </goals>             <configuration>                 <rules>                     <rule>                         <element>BUNDLE</element>                         <limits>                             <limit>                                 <counter>INSTRUCTION</counter>                                 <value>COVEREDRATIO</value>                                 <minimum>0.80</minimum>                             </limit>                             <limit>                                 <counter>CLASS</counter>                                 <value>MISSEDCOUNT</value>                                 <maximum>0</maximum>                             </limit>                             <limit>                                 <counter>METHOD</counter>                                 <value>MISSEDCOUNT</value>                                 <maximum>0</maximum>                             </limit>                             <limit>                                 <counter>LINE</counter>                                 <value>MISSEDCOUNT</value>                                 <maximum>0</maximum>                             </limit>                         </limits>                     </rule>                 </rules>             </configuration>         </execution>     </executions> </plugin>

А какие проверки можно использовать ещё?

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

Если вы используете БД, то можно (и нужно!) проверять её структуру и код ваших миграций на соответствие лучшим практикам и типовые ошибки. У меня есть такой инструмент — pg-index-health — набор проверок для PostgreSQL. Очень рекомендую.

Ну, и напоследок

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


ссылка на оригинал статьи https://habr.com/ru/post/680018/