Одни функции устроены сложнее других. Иногда так происходит в силу объективных причин (необходимая сложность). Иногда — в силу особенностей писавшего ее программиста (случайная сложность). И хотя нельзя однозначно описать эту сложность, существуют способы, позволяющие хотя бы частично ее оценить.
Цикломатическая сложность
Цикломатическая сложность — это структурная или топологическая мера сложности компьютерной программы, разработанная Томасом Дж. Маккейбом в 1976 году.
Цикломатическая сложность части программного кода — количество линейно независимых маршрутов через программный код. Если исходный код не содержит никаких точек ветвления или циклов, то сложность равна единице, поскольку есть только один маршрут через код.
Если код имеет единственный оператор if
, содержащий простое условие, то существует два пути через код: один, если условие оператора if
имеет значение true
, и один — если false
.
Такую оценку можно применять как в целом к программе, так и к отдельным функциям.
// Complexity: 1
const sum = (a, b) => a + b;
sum(1, 3); // 4
// Complexity: 2
const abs = n => (n >= 0 ? n : -n);
abs(10); // 10
abs(-3); // 3
В примере выше у функции sum
цикломатическая сложность равна единице, а у функции abs
— двойке, так как она содержит ветвление, а значит два независимых пути выполнения.
Чем больше возможных путей выполнения, тем сложнее функцию понять, отладить и модифицировать. Очевидно, что, с одной стороны, функции нужно дробить, а с другой — описывать логику программы так, чтобы не появлялись лишние пути. Даже опытные разработчики часто сталкиваются с этой проблемой.
Линтеры многих языков измеряют показатель сложности и сигнализируют, если он, скажем, больше 5 для одной функции.
Guard Expression
Подход, который мы изучим, также называемый «паттерном», помогает лучше структурировать функцию и иногда сократить цикломатическую сложность. Рассмотрим пример:
const f = (age, sex) => {
if (age >= 18) {
if (sex === 'male') {
return 'yes';
}
if (sex === 'female') {
return 'no';
}
}
return null;
}
В этой функции вам все должно быть знакомо. Она принимает на вход возраст и пол. Для людей старше 18 в зависимости от пола возвращает строчку "yes" или "no". Для всех остальных — "null". В целом, с этой функцией все нормально, но кое-что можно улучшить.
Условие "вернуть null, если младше 18 лет" гораздо более простое и очевидное. Оно не подразумевает дальнейшего разветвления и сформулировано просто. Этим можно воспользоваться и произвести рефакторинг (улучшение работающего кода без изменения функциональности) таким образом, чтобы это условие отрабатывало первым.
const f = (age, sex) => {
if (age < 18) {
return null;
}
if (sex === 'male') {
return 'yes';
}
if (sex === 'female') {
return 'no';
}
}
Обратите внимание на то, что уровень вложенности понизился. Основная логика находится вне условных конструкций. В такой реализации функции сложнее ошибиться: все, что пишется ниже guard expression (первая проверка в данном случае), попадает под требование "от 18 и старше", а в первом примере код для этого условия нужно не забывать вставить внутрь соответствующего условия.
Нам уже знаком такой подход. Терминальные условия в рекурсиях выглядят точно так же.
В будущих практиках и в реальной жизни он встречается повсеместно. Используйте его осознанно и понижайте энтропию.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.