Качество внешнего кода — принцип цикломатической сложности и практика

внешний интерфейс спецификация кода

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

1. Введение

Были ли у вас также следующие мысли?

  • Рефакторинг проекта лучше, чем разработка нового проекта...
  • Кто написал этот код, я действительно хочу...

У вас также есть следующие проблемы в вашем проекте?

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

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

2. Цикломатическая сложность

2.1 Определения

Цикломатическая сложность(Цикломатическая сложность) — это мера сложности кода, также известная как условная сложность или циклическая сложность. Ее можно использовать для измерения сложности структуры принятия решений модуля. Количество выражается как количество независимых текущих путей или может быть Поймите минимальное количество тестовых случаев, используемых для охвата всех возможных случаев. Обозначается как СС. Его символ VG или M .

Цикломатическая сложностьПредставлен в 1976 году Томасом Дж. Маккейбом-старшим.

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

2.2 Метрики

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

Цикломатическая сложность статус кода Тестируемость стоимость технического обслуживания
1 - 10 ясно и структурировано высоко Низкий
10 - 20 сложный середина середина
20 - 30 очень сложно Низкий высоко
>30 нечитаемый Непредсказуемый очень высоко

3. Метод расчета

3.1 Блок-схема управления

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

Вот некоторые распространенные потоки управления:

3.2 Метод оценки узла

Существует простой метод расчета, цикломатическая сложность фактически равна количеству узлов решения плюс 1. К выше упомянутому:if else,switch case,forЦиклы, тернарные операторы и т. д. принадлежат узлу принятия решения, такому как следующий код:

function testComplexity(*param*) {
    let result = 1;
    if (param > 0) {
        result--;
    }
    for (let i = 0; i < 10; i++) {
        result += Math.random();
    }
    switch (parseInt(result)) {
        case 1:
            result += 20;
            break;
        case 2:
            result += 30;
            break;
        default:
            result += 10;
            break;
    }
    return result > 20 ? result : result;
}

Приведенный выше код имеет в общей сложности1индивидуальныйifприговор, аforпетля, дваcaseоператор, тернарный оператор, поэтому сложность кода1+2+1+1+1=6. Кроме того, следует отметить, что|| 和 &&Операторы также считаются узлом принятия решения.Например, сложность следующего кода равна3:

function testComplexity(*param*) {
    let result = 1;
    if (param > 0 && param < 10) {
        result--;
    }
    return result;
}

3.3 Метод расчета остроконечной кромки

M = E − N + 2P
  • E: количество ребер в графе потока управления.
  • N: количество узлов в графе потока управления
  • P: количество независимых компонентов

Первые два, ребра и узлы, являются самыми основными понятиями в диаграммах структур данных:

P представляет количество независимых компонентов на графике Что означают независимые компоненты? Взгляните на следующие два графика: связный граф слева и несвязанный граф справа:

  • Связный граф: любые две вершины графа связаны.

Связный граф — это независимая компонента в графе, поэтому количество независимых компонент в левом графе равно 1, а в правом — две независимых компоненты.

Для графа потока управления, преобразованного из нашего кода, все узлы должны быть подключены при нормальных обстоятельствах, если вы не выполняете перед некоторыми узламиreturn, очевидно такой код неверен. Таким образом, количество независимых компонентов каждой блок-схемы программы равно 1, поэтому приведенную выше формулу также можно упростить доM = E − N + 2.

4. Уменьшить цикломатическую сложность кода

Мы можем уменьшить цикломатическую сложность кода с помощью некоторых методов рефакторинга кода.

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

4.1 абстрактная конфигурация

通过抽象配置将复杂的逻辑判断进行简化。 For example, the following code performs corresponding operations according to the user's selection. After refactoring, the code complexity is reduced, and if there are new options later, the configuration can be added directly, without the need to go deeper into the code logic to вносить изменения:

4.2 Единая ответственность — функция уточнения

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

существуетJavaScriptВ нем не так много сценариев, в которых нужны классы, и принцип единой ответственности больше применяется на уровне объекта или метода.

Функции должны делать что-то одно, делать это хорошо и делать только это. — Способ почистить код

Ключ в том, как определить эту «одну вещь», как абстрагировать логику в коде и как эффективно извлекать функции, что способствует снижению сложности кода и затрат на обслуживание.

4.3 Используйте break и return вместо управляющих тегов

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

4.4 Замена параметров функциями

setFieldа такжеgetFieldФункция — это типичная функция для замены параметра, если таковой имеется.setField、getFieldфункция, нам может понадобиться очень сложнаяsetValue、getValueЧтобы завершить операцию присвоения свойства:

4.5 Упрощенное суждение о состоянии — обратное состояние

Некоторые сложные условные суждения могут стать проще после обратного мышления.

4.6 Упрощенное суждение о состоянии — условие слияния

Сочетайте сложные и избыточные условные суждения.

4.7 Упрощенная оценка состояния — условие извлечения

Семантическое извлечение сложных и трудных состояний.

5. Метод определения цикломатической сложности

5.1 правила эслинта

eslintПредоставляет метод определения цикломатической сложности кода.rules:

мы откроемrulesсерединаcomplexityправило, и установите цикломатическую сложность больше, чем0кодексаrule severityУстановить какwarnилиerror.

    rules: {
        complexity: [
            'warn',
            { max: 0 }
        ]
    }

такeslintОн автоматически определит сложность кода всех функций и выведет результат, аналогичный следующему.message.

Method 'testFunc' has a complexity of 12. Maximum allowed is 0
Async function has a complexity of 6. Maximum allowed is 0.
...

5.2 CLIEngine

мы можем использоватьeslintизCLIEngine, используйте пользовательский локальноeslintПравило сканирует код и получает результат сканирования.

инициализацияCLIEngine:

const eslint = require('eslint');

const { CLIEngine } = eslint;

const cli = new CLIEngine({
    parserOptions: {
        ecmaVersion: 2018,
    },
    rules: {
        complexity: [
            'error',
            { max: 0 }
        ]
    }
});

использоватьexecuteOnFilesСканировать указанный файл, получить результаты и отфильтровать всеcomplexityизmessageИнформация.

const reports = cli.executeOnFiles(['.']).results;

for (let i = 0; i < reports.length; i++) {
    const { messages } = reports[i];
    for (let j = 0; j < messages.length; j++) {
        const { message, ruleId } = messages[j];
        if (ruleId === 'complexity') {
             console.log(message);
        }
    }
}

5.3 Извлечь сообщение

пройти черезeslintРезультаты обнаружения теста извлекут полезную информацию. Сначала протестируйте несколько различных типов функций, чтобы увидетьeslintРезультат теста:

function func1() {
    console.log(1);
}

const func2 = () => {
    console.log(2);
};

class TestClass {
    func3() {
        console.log(3);
    }
}

async function func4() {
    console.log(1);
}

Результаты:

Function 'func1' has a complexity of 1. Maximum allowed is 0.
Arrow function has a complexity of 1. Maximum allowed is 0.
Method 'func3' has a complexity of 1. Maximum allowed is 0.
Async function 'func4' has a complexity of 1. Maximum allowed is 0.

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

тип функции:

  • Function: обычная функция
  • Arrow function: функция стрелки
  • Method: метод класса
  • Async function: асинхронная функция

Тип метода перехвата:

const REG_FUNC_TYPE = /^(Method |Async function |Arrow function |Function )/g;

function getFunctionType(message) {
    let hasFuncType = REG_FUNC_TYPE.test(message);
    return hasFuncType && RegExp.$1;
}

Извлеките полезные части:

const MESSAGE_PREFIX = 'Maximum allowed is 1.';
const MESSAGE_SUFFIX = 'has a complexity of ';

function getMain(message) {
    return message.replace(MESSAGE_PREFIX, '').replace(MESSAGE_SUFFIX, '');
}

Извлечь имя метода:

function getFunctionName(message) {
    const main = getMain(message);
    let test = /'([a-zA-Z0-9_$]+)'/g.test(main);
    return test ? RegExp.$1 : '*';
}

Сложность кода перехвата:

function getComplexity(message) {
    const main = getMain(message);
    (/(\d+)\./g).test(main);
    return +RegExp.$1;
}

Кромеmessage, и другая полезная информация:

  • расположение функции: получитьmessagesсерединаline,columnТо есть положение строки и столбца функции
  • Текущее имя файла:reportsАбсолютный путь текущего сканируемого файла можно получить в результатеfilePath, получите настоящее имя файла, выполнив следующие действия:
filePath.replace(process.cwd(), '').trim()
  • Уровень сложности, который дает рекомендации по рефакторингу в зависимости от уровня сложности функции:
Цикломатическая сложность статус кода Тестируемость стоимость технического обслуживания
1 - 10 ясно и структурировано высоко Низкий
10 - 20 сложный середина середина
20 - 30 очень сложно Низкий высоко
>30 нечитаемый Непредсказуемый очень высоко
Цикломатическая сложность статус кода
1 - 10 Рефакторинг не требуется
11 - 15 рекомендуемый рефакторинг
>15 Рефакторинг настоятельно рекомендуется

6. Архитектурный дизайн

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

Выше показано использованиеeslintЧтобы получить представление о сложности кода, мы инкапсулируем его как общий инструмент.Учитывая, что инструмент может использоваться в разных сценариях, таких как отчет об анализе веб-версии и инструмент командной строки версии cli, мы абстрагироваться от общих возможностейnpm包формы для использования другими приложениями.

Прежде чем вычислять сложность кода проекта, мы должны сначала иметь базовую способность, сканирование кода, то есть нам нужно знать, какие файлы в проекте мы хотим проанализировать, в первую очередьeslintспособен на это, мы также можем напрямую использоватьglobдля обхода файла. Но у всех есть недостаток, т.ignoreПравила разные, что имеет определенную стоимость обучения для пользователей, поэтому здесь я сканирую код ручной инкапсуляции и использую общийnpm ignoreправила, чтобы сканирование кода можно было использовать напрямую.gitignoreтакой файл конфигурации. Кроме того, сканирование кода является основной возможностью анализа кода, и другие виды анализа кода также могут использоваться совместно.

  • Базовая способность
    • Возможность сканирования кода
    • Возможность обнаружения сложности
    • ...
  • заявление
    • инструмент командной строки
    • Отчет об анализе кода
    • ...

7. Базовая способность — сканирование кода

эта статья посвященаnpmпакет иcliИсходный код команды можно найти в моем проекте с открытым исходным кодом.awesome-cliПосмотреть в.

awesome-cli — это новый проект с открытым исходным кодом, который я создал: интересный и практичный инструмент командной строки, который будет поддерживаться в будущем, так что следите за обновлениями и добро пожаловать в звезду.

скан кода(c-scan) исходный код:GitHub.com/con AR DL i/Арвен…

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

  • какой тип файла я получу
  • какие файлы мне не нужны

7.1

npm i c-scan --save

const scan = require('c-scan');
scan({
    extensions:'**/*.js',
    rootPath:'src',
    defalutIgnore:'true',
    ignoreRules:[],
    ignoreFileName:'.gitignore'
});

7.2 Возвращаемое значение

Массив путей к файлам, соответствующих правилам:

7.3 Параметры

  • extensions

    • Сканировать расширения файлов
    • По умолчанию:**/*.js
  • rootPath

    • путь к файлу сканирования
    • По умолчанию:.
  • defalutIgnore

    • Включить ли игнорирование по умолчанию (globправило)
    • glob ignoreПравила предназначены для внутреннего пользования, для единообразияignoreправила, использование пользовательских правилgitignoreправило
    • По умолчанию:true
    • включено по умолчаниюglob ignoreправило:
const DEFAULT_IGNORE_PATTERNS = [
    'node_modules/**',
    'build/**',
    'dist/**',
    'output/**',
    'common_build/**'
];
  • ignoreRules

    • Пользовательские правила игнорирования (gitignoreправило)
    • По умолчанию:[]
  • ignoreFileName

    • Путь к файлу конфигурации пользовательского правила игнорирования (gitignoreправило)
    • По умолчанию:.gitignore
    • Назначенnullне включеноignoreконфигурационный файл

7.4 Основная реализация

на основеglob,настроитьignoreПравила инкапсулируются дважды.

/**
 * 获取glob扫描的文件列表
 * @param {*} rootPath 跟路径
 * @param {*} extensions 扩展
 * @param {*} defalutIgnore 是否开启默认忽略
 */
function getGlobScan(rootPath, extensions, defalutIgnore) {
    return new Promise(resolve => {
        glob(`${rootPath}${extensions}`,
            { dot: true, ignore: defalutIgnore ? DEFAULT_IGNORE_PATTERNS : [] },
            (err, files) => {
                if (err) {
                    console.log(err);
                    process.exit(1);
                }
                resolve(files);
            });
    });
}

/**
 * 加载ignore配置文件,并处理成数组
 * @param {*} ignoreFileName 
 */
async function loadIgnorePatterns(ignoreFileName) {
    const ignorePath = path.resolve(process.cwd(), ignoreFileName);
    try {
        const ignores = fs.readFileSync(ignorePath, 'utf8');
        return ignores.split(/[\n\r]|\n\r/).filter(pattern => Boolean(pattern));
    } catch (e) {
        return [];
    }
}

/**
 * 根据ignore配置过滤文件列表
 * @param {*} files 
 * @param {*} ignorePatterns 
 * @param {*} cwd 
 */
function filterFilesByIgnore(files, ignorePatterns, ignoreRules, cwd = process.cwd()) {
    const ig = ignore().add([...ignorePatterns, ...ignoreRules]);
    const filtered = files
        .map(raw => (path.isAbsolute(raw) ? raw : path.resolve(cwd, raw)))
        .map(raw => path.relative(cwd, raw))
        .filter(filePath => !ig.ignores(filePath))
        .map(raw => path.resolve(cwd, raw));
    return filtered;
}

8. Базовая способность — определение сложности кода

Обнаружение сложности кода (c-complexity) исходный код:GitHub.com/con AR DL i/Арвен…

Базовый пакет обнаружения кода должен иметь следующие возможности:

  • Настройка папок и типов сканирования
  • Поддержка игнорирования файлов
  • Определить минимальную сложность кода оповещения

8.1

npm i c-complexity --save

const cc = require('c-complexity');
cc({},10);

8.2 Возвращаемое значение

  • fileCount: количество файлов
  • funcCount: количество функций
  • результат: подробный результат
    • funcType: тип функции
    • Funcname; имя функции
    • position: подробная позиция (номер строки и столбца)
    • fileName: относительный путь к файлу
    • сложность: сложность кода
    • совет: совет по рефакторингу

8.3 Параметры

  • scanParam
    • Параметры, унаследованные от сканирования кода выше
  • min
    • Минимальная сложность кода оповещения, по умолчанию 1

9. Приложение — инструмент определения сложности кода

Определение сложности кода (conard cc) исходный код:GitHub.com/con AR DL i/Арвен…

9.1 Укажите минимальную сложность оповещения

Минимальная сложность, при которой может сработать оповещение.

  • По умолчанию10
  • по командеconard cc --min=5настроить

9.2 Задание параметров сканирования

Пользовательские правила сканирования

  • Параметры сканирования наследуются от вышеперечисленныхscan param
  • Например:conard cc --defalutIgnore=false

10. Приложение — отчет о сложности кода

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

Изменение тенденции сложности кода

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

Судить о том, здоров проект или нет, по тенденции изменения [Сложность/Количество строк кода] или [Сложность/Количество функций].

  • Если соотношение продолжает расти, ваш код становится все труднее и труднее понять. Это не только подвергает нас риску непреднамеренных функциональных взаимодействий и дефектов, но также затрудняет повторное использование кода, его модификацию и тестирование из-за чрезмерной когнитивной нагрузки, с которой мы сталкиваемся в модулях с более или менее связанными функциями. (Рисунок 1 ниже)

  • Если соотношение мутирует на каком-то этапе, значит, качество итерации в этот период плохое. (Рисунок 2 ниже)

  • График сложности может быстро помочь вам найти две вышеуказанные проблемы раньше, после их обнаружения может потребоваться рефакторинг кода. Тенденции сложности также полезны для отслеживания рефакторинга вашего кода. Нисходящий тренд сложности — хороший знак. Это либо означает, что ваш код становится проще (например, if-else рефакторинг в полиморфное решение), либо меньше кода (извлечение ненужных частей в другие модули). (Рисунок 3 ниже)

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

Распределение файлов сложности кода

Подсчитайте количество функций для каждого распределения сложности.

Сведения о файле сложности кода

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

В реальной разработке анализировать нужно не весь код, например упакованные продукты, статические файлы ресурсов и т. д. Эти файлы часто вводят в заблуждение результаты нашего анализа. Теперь инструменты анализа по умолчанию будут игнорировать некоторые правила, такие как: файл .gitignore, статический In Фактически, эти правила также необходимо постоянно улучшать в соответствии с реальной ситуацией в проекте, чтобы сделать результаты анализа более точными.

Ссылаться на

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

резюме

Надеюсь, чтение этой статьи поможет вам в следующем:

  • Понимать значение и расчет цикломатической сложности
  • Может реально применять цикломатическую сложность в проекте для улучшения качества проекта

Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь.

эта статья посвященаnpmпакет иcliИсходный код команды можно найти в моем проекте с открытым исходным кодом.awesome-cliПосмотреть в.

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

Рекомендую обратить внимание на мой паблик WeChat [code secret garden], каждый день выкладывать качественные статьи, будем общаться и расти вместе.