Як за допомогою статичних аналізаторів коду на основі Roslyn підвищити якість розробки

Доброго дня! Мене звуть Антон Широких, я full-stack-розробник і зараз займаю позицію Tech Lead в компанії Beetroot.

У цій статті ми розглянемо статистичні аналізатори, завдання, які вони можуть виконувати, покрокове керівництво по їх впровадженню на базі Roslyn та конфігурації, а також підводні камені, які можуть вас чекати.

Цілі статті: розповісти про статичних аналізаторах для мови C# на платформі Roslyn і про те, як і з якою метою їх можна використовувати, а також про деякі нюанси і можливі проблеми.

Стаття, на мій погляд, може бути цікава всім C#-розробникам, а питання впровадження і конфігурації — розробникам на lead-позиціях.

Вступ

Більшість розробників при описі ідеального проекту зазвичай згадують 3 пункту: юніт-тести, CI/CD і статичні аналізатори. Якщо перші 2 пункти присутні в багатьох сучасних проектах, а також існує маса вступних статей і мануалів по цим темам, то статичні аналізатори в знайомих мені проектах застосовуються досить рідко і вступних статей на цю тему дуже небагато. Моя мета — створення вступної статті, яка б описувала, що таке статичні аналізатори в цілому, які є можливі варіанти для C#, а також прості кроки впровадження Roslyn-аналізаторів у ваш проект і можливі проблеми, які можуть при цьому виникнути.

Еволюція процесу рев'ю коду

Почати хотілося б з невеликою ретроспективи щодо того, як у моїй кар'єрі з'явився і еволюціонував процес рев'ю коду.

Почалося все, мабуть, як і у багатьох, з його відсутності. Будучи студентом, я просто писав код в своє задоволення, час від часу повністю переписуючи той чи інший шматок, який на підсвідомому рівні переставав мені подобається (звичайно це означало, що він ставав абсолютно нечитабельним).

Потім на моїй першій роботі-код став ретельно перевірятися (Тимофій, якщо ти це читаєш, великий тобі привіт!). Тоді ж під впливом колег у мене з'явилися якісь зачатки розуміння того, що ж таке хороший код, і бажання цього розуміння відповідати.

Згодом я потрапив у компанію з командою розробки, в якій був більш-менш налагоджений процес code review на основі змін з допомогою pull-реквестов на GitHub (change-based code review).

З часом я став виконувати роль не тільки автора коду, але і ревьюера на постійній основі, що змусило мене досить серйозно підійти до вироблення для себе стандартів доброго коду на основі загальноприйнятих (SOLID, KISS, рекомендації по коду від Microsoft).

Людський фактор при перевірці коду

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

Також, на жаль, не рідкістю стали і pull-реквесты в кілька тисяч рядків коду. При такому обсязі коду навіть кілька ревьюеров неминуче можуть пропустити, наприклад, відсутню перевірку на null, яка згодом може призвести до неприємних NullReferenceException. Коли я приступаю до таких pull-реквестам, я завжди переживаю, що можу щось пропустити, і навіть той факт, що рев'ю таких pull-реквестов зазвичай проходить у кілька етапів, все одно не додає впевненості. Тому що всі ми люди і можемо робити помилки.

Статичний аналіз коду?

Розуміючи, що наш процес код-рев'ю, м'яко кажучи, не ідеальний, ми з командою провели кілька мітингів, на яких кожен озвучував актуальні для себе проблеми і ми разом думали і пропонували можливі рішення. Мій колега запропонував вирішення введення статичних аналізаторів коду, щоб виявляти помилки ще до створення pull-реквеста. Ідея всім сподобалася, і при поверхневому аналізі можливих рішень було очевидно, що тут прихований величезний потенціал щодо поліпшення не тільки процесу код-рев'ю, але і по значному підвищенню загальної якості коду.

Підготовчий етап

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

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

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

  1. Пошук помилок в програмі без необхідності її виконання.
  2. Поліпшення якості коду в широкому сенсі цього слова. Якість вихідного коду — це комплексне поняття, яке включає в себе, крім іншого, кількість помилок на умовні 100 рядків коду. Але також у це поняття входять читаність, поддерживаемость, складність коду (cyclomatic complexity), рівень зв'язності та інші аспекти, які прямо або побічно впливають на кількість помилок, а також на загальний час розробки.
  3. Збір метрик проекту, збір статистики, побудова графіків і діаграм, що відображають загальний стан здоров'я» проекту. Приклад аналізаторів такого типу — NDepend. Також в Visual Studio присутній вбудований інструмент збору метрик коду.
  4. Аналіз коду як частина механізму quality gate в CI/CD. Аналізатори коду можуть не тільки повідомляти про наявність можливих помилок в коді, але і служити захисним механізмом, що запобігає доставку коду, якщо рівень його якості не відповідає заданим вимогам. Таку роль можуть виконувати аналізатори коду, які розширюють поведінка компілятора і блокуючі збірку, якщо були виявлені помилки або невідповідності коду прийнятим стандартам. Це, наприклад, аналізатори на базі Roslyn, які ми і будемо розглядати в цій статті.
  5. Засіб документації прийнятих в команді стандартів і code style. Конфігурація статичних аналізаторів, яка зазвичай зберігається в системі контролю версій разом з основним кодом, обладнана ємними коментарями, може допомогти швидше вводити у проект нових розробників. Вивчаючи файл конфігурації, вони зможуть досить швидко зрозуміти основні вимоги до якості коду в команді, а також специфічні вимоги для конкретного проекту.

Постановка задачі

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

Для нашого проекту було виділено 2 основні завдання:

  1. Додати перевірку коду за допомогою статичних аналізаторів.
  2. Включити введений механізм аналізу в якості quality gate перед створенням pull-реквеста.

Вирішення цих завдань дозволить досягти 3 основних цілей:

  1. Зменшення кількості однотипних повторюваних раз за разом коментарів.
  2. Зменшення кількості часу, необхідного на рев'ю pull-реквеста, за рахунок більш високої якості коду, що потрапляє на рев'ю.
  3. Загальне підвищення якості коду на проекті.

В якості додаткової мети мені було цікаво дізнатися про загальний стан проекту. Так як повноцінних інспекцій коду у нас ще не було, загальний стан здоров'я» проекту було для мене невідомим.

Вибір основного інструменту

Кілька слів про Resharper

Безумовно, Resharper гідний згадки в контексті вибору інструментів статичного аналізу. Resharper — це потужний інструмент, який включає в себе добре конфігуровані засоби статичного аналізу коду, рефакторінгу, а також безліч додаткових інструментів, що розширюють функціонал Visual Studio. Але Resharper не позбавлений недоліків.

Перший недолік, про який хотілося б згадати, — це його вплив на продуктивність. Resharper, будучи надбудовою над Visual Studio, споживає досить багато додаткових ресурсів, а при запуску великого рішення може відчутно гальмувати систему, роблячи процес розробки набагато менш комфортним. Другий недолік, який має безпосереднє відношення до теми цієї статті, — це те, що налаштування Resharper не впливають на білд, а отже, він непридатний для використання в якості quality gate в CI/CD pipeline. Варто зробити застереження, що у Resharper є CLI-версія, яка може використовуватися на CI, але її треба налаштовувати окремо. Також Resharper має фіксований набір правил аналізу, які можна розширювати.

Слід окремо згадати, що Resharper — це платний продукт, який встановлюється і конфігурується на кожній робочій машині окремо, а це додаткові витрати. Цих мінусів, на мій погляд, цілком достатньо, щоб розглянути альтернативу.

Аналізатори на основі Roslyn

Visual Studio, починаючи з 19-ї версії, час від часу рекомендувати установку аналізаторів Roslyn для поліпшення якості коду. Більш детально вивчивши питання, я зрозумів, що це майже ідеальне рішення проблем нашої команди.

Аналізатори на основі Roslyn — це набори аналізаторів, поширюваних через NuGet-пакети. Щоб підключити аналізатори, досить встановити NuGet-пакет на вибраний солюшен. Аналізатори працюють як в live-режимі, тобто при перегляді .cs-файлу в Visual Studio, так і при компіляції. Аналізатори Roslyn застосовуються як розширення пакету стандартних правил компілятора. Попередження Roslyn-аналізаторів також присутні в виводі на консоль при компіляції, а помилки (попередження рівня error) блокують збірку.

Конфігурувати рівні попереджень можна кількома способами. Перший — з використанням .ruleset-файлів, другий — більш сучасний — з використанням .editorconfig. Вибравши будь-який з цих підходів, ви можете зберігати і поширювати конфігурацію ваших аналізаторів (як і самі аналізатори) через систему контролю версій. Тобто програмісту достатньо встановити Visual Studio і завантажити вихідний код, щоб почати роботу, використовуючи аналізатори на базі Roslyn.

Також величезний плюс — це можливість написання власних аналізаторів з використанням Roslyn API на випадок, якщо у вас є необхідність створювати правила, специфічні для вашого проекту або відображають особливі внутрішні угоди кодування у вашій команді. Так як є чудові приклади готових аналізаторів (наприклад, FxCop і Roslynator, які є open-source-проектами), написання власних не повинно викликати істотних труднощів.

Для вирішення завдань нашої команди аналізатори на базі Roslyn здалися привабливішими, ніж Resharper, тому вони і були обрані в якості основного інструменту.

Початок роботи з аналізаторами Roslyn

FxCopAnalysers як рекомендований Visual Studio набір аналізаторів

Visual Studio рекомендує встановити пакет аналізаторів Microsoft.CodeAnalysis.FxCopAnalyzers. Це пакет аналізаторів з відкритим вихідним кодом. Він включає в себе близько 200 правил, яких, на мій погляд, цілком достатньо, щоб покрити більшість запитів на початковому етапі. Існують також і інші набори аналізаторів, наприклад Roslynator, який включає в себе близько 500 правил, але для початку пропоную обмежитися тим, що рекомендує Visual Studio.

Установка і конфігурація

Для можливості конфігурації аналізаторів вам потрібно створити .editorconfig-файл і помістити його в кореневу папку вашого солюшена. У Visual Studio це можна зробити однією кнопкою в розділі налаштувань. Він знадобиться пізніше.

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

Ось, в принципі, і все, що потрібно, щоб почати роботу з аналізаторами Roslyn. It just works!

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

Додаткова конфігурація аналізаторів

Аналізатори FxCopAnalysers мають оптимальну початкову конфігурацію, але за замовчуванням жодне з правил не має рівня попередження error, то є всі додані правила ніяк не впливають на білд. Для того щоб аналізатори діяли як quality gate, потрібно налаштувати рівні попереджень. Це можна зробити через контекстне меню, після чого налаштування автоматично будуть збережені в раніше створений .editorconfig-файл.

З описом всіх правил, включених в набір FxCopAnalysers, можна ознайомитися на сторінці цього пакету аналізаторів у GitHub. Всі правила мають хорошу документацію, а багато з них містять опис з фрагментами коду, які показують, у яких випадках правило буде спрацьовувати.

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

Додаткові пакети аналізаторів

Пакет FxCopAnalysers, на мій погляд, містить досить правил, дотримання яких підвищить загальну якість коду, але цього може виявитися недостатньо. Як вже згадувалося раніше, існує досить велика кількість аналізаторів з відкритим вихідним кодом (наприклад, ось хороший список ). На додаток до FxCopAnalysers я також вказав Roslynator, який містить понад 500 правил, а також Async Fixer, що містить деякі правила для роботи з async/await.

Як і для FxCopAnalysers, зі всіма правилами цих аналізаторів можна ознайомитися на їхніх сторінках GitHub, щоб налаштувати правила оптимально для вашого проекту.
Процес ознайомлення з усіма правилами FxCopAnalysers і Roslynator, а також їх конфігурації зайняв у мене близько 2 годин.

Проміжні підсумки

На цьому етапі можна з упевненістю сказати, що всі поставлені завдання були виконані. Статичні аналізатори були включені в проект і почали працювати як quality gate, що дозволило уникнути зайвих витрат на рев'ю коду, який не пройшов таку перевірку. Також завдяки комбінації FxCopAnalysers і Roslynator вдалося виділити більшість дійсних і потенційних причин для повторюваних коментарів pull-реквестах, і тепер солюшен просто не буде збиратися, поки ці причини не будуть виправлені.

А ще в процесі виправлення помилок білду я знову повернувся до написаного більше двох років тому кодом, що змусила мене по-новому поглянути на те, що раніше «просто працювало».

Підводні камені

Що може чекати на вас, якщо ви вперше включили аналізатори на проект, якому вже більше 3 років

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

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

Чи означає це, що якість вашого коду, м'яко кажучи, залишає бажати кращого? Можливо :) Ще це може означати, що ви виставили дуже суворі рівні попереджень і вам варто переглянути. На мій погляд, процес конфігурації має бути ітеративним. Після аналізу результатів першого білду з включеними аналізаторами ви, швидше за все, перегляньте рівні попередження для деяких правил, знаходячи їх не настільки критичними, як ви думали спочатку. У будь-якому випадку всі попередження рівня warning у рамках завдання по введенню статичних аналізаторів ви просто не встигнете виправити, тому я б порадив для початку обмежитися виправленням правил, для яких застосовні дії швидкого рефакторінгу, а також критичними попередженнями, які блокують білд.

«Зараз збірка солюшена вже займає у мене близько хвилини; з включеними аналізаторами я буду чекати близько 5 хвилин, щоб просто запустити програму», — колега, який побажав залишитися анонімним.

Будь-які інструменти завжди споживають додаткові ресурси системи, особливо статичні аналізатори коду. Статичні аналізатори, розширюють поведінка компілятора численними додатковими перевірками, очікувано можуть збільшити час білду. Однак це не є проблемою, коли у вас невеликий проект і час білду з аналізаторами у вас займає менше хвилини. Якщо ж у вас досить великий проект, як, наприклад, розглянутий у цій статті, то час складання солюшена може зрости до некомфортних величин. На нашому проекті, залежно від потужності машини, час повного білду в середньому зросла до 5 хвилин, що стало критичним для більшості розробників. Які є способи збільшення швидкості білду для C#-солюшена?

Перший спосіб — це оновити конфігурацію заліза на вашій робочій машині. Замінити процесор на модель з великою кількістю ядер і жорсткий диск на SSD, також не завадить додатковий обсяг оперативної пам'яті. Рішення виглядає очевидним, але насправді це лише тимчасова міра, так як розмір вашого солюшена буде з часом тільки зростатиме приріст, який ви отримали при відновленні заліза, може нівелюватися. Та й радикальне оновлення заліза не завжди доступно.

Якщо приріст потужності не дає потрібного ефекту або з деяких причин недоступний, потрібно оптимізувати процес складання. Мені вдалося знайти небагато матеріалів на тему оптимізації білду C#-солюшена, але з того, що я знайшов, можна виділити деякі загальні поради.

В першу чергу, відповідно до рекомендацій, вам необхідно з'ясувати, чи можна розбити ваш солюшен на менші частини. Наприклад, якщо у вас є проект, в якому зосереджена велика частина ваших джерел, вам потрібно спробувати розбити його на незалежні проекти меншого розміру, які можуть збиратися паралельно (у Visual Studio є така опція). Доповнити це рішення можна винесенням частини коду, який дуже рідко змінюється, в NuGet-пакети, що дозволить ще трохи заощадити час білду (але створить додаткові витрати на підтримку посилань на актуальну версію пакету для інших проектів).

Ще доповнити це рішення можна використанням інструментів для прискорення білду. Таким інструментом є, наприклад, IncrediBuild. Він дозволяє розпаралелювати завдання по зборці незалежних одна від одної частин солюшена на кілька машин в мережі. Варто окремо відзначити, що таке рішення працює, тільки якщо ваші проекти незалежні один від одного. Якщо схема залежностей на вашому проекті представляє з себе ланцюжок, рішення працювати не буде. Але якщо вам вдалося розбити солюшен на менші незалежні частини, таке рішення відмінно доповнить попередні і ви зможете отримати значний приріст у швидкості збирання.

Після виконання частині рекомендацій вдалося отримати наступні результати:

Підсумки

Впровадження інструментів статичного аналізу коду на ваш проект може значно поліпшити процес розробки, підвищивши загальну якість коду, збільшити швидкість декількох етапів процесу розробки, особливо code review, а також допомогти вашим колегам рости як розробникам, оскільки вони будуть бачити та застосовувати кращі практики, закладені в аналізаторі. Введення статичних аналізаторів коду може супроводжуватися чималими одноразовими витратами ресурсів (в основному це час досвідчених розробників), але я вважаю, що результат виправдає ваші зусилля.

Спасибі за увагу!

Опубліковано: 06/03/20 @ 11:00
Розділ Різне

Рекомендуємо:

Перші кроки в NLP: розглядаємо Python-бібліотеку NLTK в реальному завданні
Коли варто переписувати код проекту і як це донести до замовника
Ви підприємець чи науковець? Як продакт-менеджера обирати компанію відповідно до схильностей
Arti, Langkah serta Taktik Tepat dalam Main Koa/Ceki/Pei
DevOps дайджест #30: гонка клаудов за перевагу в Kubernetes, Thanos operator, стрімке зростання Sentry