7. Метрики складності коду. Цикломатична та когнітивна складність коду. Що для вас чистий код?
Відео версія
(YouTube - для зворотнього зв'язку)
Hard skill відповідь на питання "Що для вас чистий код?"
Код має чіткі метрики які можна вирахувати.
Цикломатична складність
Почну з класичної - цикломатична складність. Це метрика розроблена ще у 80-х роках для визначення складності проєкту, конкретного класу, або функції.
Я спробую пояснити ну дуже простими словами, якщо вам не достатньо, то загугліть.
Алгоритм цикломатичної складності будує граф, де кожне розгалуження програми оператори if
, else
, for
, do-while
,
кожен switch-case
варіант, try-catch
і так далі це вершина.
І рахує скільки ж шляхів можливо пройти від початку до кінця алгоритму.
Кількість можливих шляхів і є показник складності. Чим він більший, тим складніша ваша програма.
Але мені ментально подобається інше визначення. Воно має більше практичний сенс. Цикломатична складність показує скільки тестів треба зробити, щоб на 100% покрити весь код. Дуже простий приклад, щоб покрити функцію ділення треба 2 тести перший передасть 2 звичайних числа. Другий передасть дільник рівний нулю і буде очікувати помилку.
function division($numerator, $denominator) {
if ($denominator === 0) {
throw new Exception("Error: Division by zero is not allowed.");
}
return $numerator / $denominator;
}
Саме цей алгоритм і показує тест коверейдж вашої програми. І це дуже класний приклад який показує що покриття тестами на 100% не тестує код на 100%, а лише показує що в тестах брати участь усі рядки коду.
І саме цикломатична складність налаштувалася автоматично у минулому відео про PHP Insights. Там звісно метрика трохи прихована і показує який відсоток класів має складність вищу за 5 за замовчуванням. Тут важливо розглядається кожен клас окремо, не вся програма разом.
Я звісно раджу його використовувати, і використовую сам. Дуже допомагає побачити що щось ти робиш не так... занадто складно.
Але у цикломатичної складності є проблема. Спробую її зобразити.
for ($i = 0; $i < count($array); $i++) { // +1
for ($j = 0; $j < count($array[$i]); $j++) { // +1
for ($k = 0; $k < count($array[$i][$j]); $k++) { // +1
for ($p = 0; $p < count($array[$i][$j][$k]); $p++) { // +1
if ($array[$i][$j][$k][$p] === 1) { // +1
$result = 2;
}
}
}
}
}
Це приклад коду з цикломатичною складність 5. Не велика складність. А ми тут розбираємо 4 мірний масив і робимо це в лоб. Будь-який розробник скаже що це капець складний код і мати рацію. А на противагу гляньте на цей код
switch($number) {
case 1: return 'One'; // +1
case 2: return 'Two'; // +1
case 3: return 'Tree'; // +1
case 4: return 'Four'; // +1
default: return 'More'; // +1
}
І це також складність 5. Не дуже складно погодьтесь.
Тож робимо висновок. Цикломатична складність корисна, але не завжди показує реальну складність.
Когнітивна складність
І знаючи цю проблему у 2017 році компанія sonar (sonarqube) представила свою метрику.
Когнітивна складність. В основі це та ж цикломатична складність, але, по-перше, кожен вложений оператор додає на одиницю складності більше. Тобто приклад з 4-х мірним масивом має складність 15.
for ($i = 0; $i < count($array); $i++) { // +1
for ($j = 0; $j < count($array[$i]); $j++) { // +2
for ($k = 0; $k < count($array[$i][$j]); $k++) { // +3
for ($p = 0; $p < count($array[$i][$j][$k]); $p++) { // +4
if ($array[$i][$j][$k][$p] === 1) { // +5
$result = 2;
}
}
}
}
}
По друге когнітивна складність враховує конструкції мови. Тернарні оператори, match
, switch-case
й так далі. Тому складність прикладу зі switch-case
буде рівнятися 1.
Почитати офіційну документацію про когнітивну складність можна тут.
Звісно є інші метрики, наприклад глибина наслідування, або рівень зв'язаності класів.
Але я тут більше по практичній частині. Тож цикломатична складність уже налаштована, і я маю з нею досвід, дуже раджу.
Cognitive complexity setup
З когнітивною складністю ще не працював, але дуже хочу. Тому ставлю пакет Він вимагає phpstan, але це того варте.
І власне конфіг
parameters:
level: 0
paths:
- app/
- tests/
cognitive_complexity:
class: 50
function: 8
level: 0
- рівень перевірки PHPStan. Ставлю 0 (найслабший) оскільки саме код правки та аналіз робить PHP Insights. Дублювати перевірку не потрібно.paths
- директорії які потрібно сканувати, сюди також додам модуліcognitive_complexity
- максимально допустима когнітивна складність для класу та конкретної функції.
І додаємо команду до .husky/pre-commit
та gitlab-ci.yml
docker exec -t bh-app php artisan insights --fix --no-interaction
docker exec -t bh-app vendor/bin/phpstan
git add $(git diff --cached --name-only --diff-filter=ACM)
script:
- docker compose exec app php artisan insights --no-interaction
- docker compose exec app vendor/bin/phpstan