Статический анализ кода (Static Code Analysis)

6-12-2019

Что такое статический анализ кода?

Люди всегда делают ошибки. И есть мнение, что на каждые 1000 строк кода приходится от 15 до 50 дефектов :-) .Статический анализ может идентифицировать эти дефекты, уязвимости и проблемы соответствия стандартам при кодировании. Это означает, что Вы можете сразу найти уязвимый код и недостатки кода. И Вы можете исправить их быстрее, для снижения общих затрат.

Кроме этого, статический анализ кода позволит:

  1. Улучшить качество программного обеспечения
  2. Соблюдать стандарты кодирования
  3. Больше доверять написанному коду

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

На какой стадии мы должны делать статический анализ кода?

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

Статический или динамический анализ, что лучше?

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

Есть ли ограничения в использовании и интерпретации результатов статического анализа кода?

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

  1. Инструмент статического анализа может обнаружить возможные проблемы в расчётах, выполняемых в например, в функции. Но он не может определить, что эта функция принципиально не выполняет то, что ожидается!
  2. Часто инструмент статического анализа может обнаружить несоответсвие правилам, которые не являются статически обязательными
  3. Обнаруженные статическим анализом возможные дефекты приводят к ложным срабатываниям. Это означает, что инструменты могут сообщать о дефектах, которые на самом деле не существуют (ложные срабатывания-false positives). Или они могут не сообщить о реальных дефектах (false negatives).

В чём преимущество при использовании статического анализа кода?

А даёт он нам вот что:

  1. Скорость проверки (По сравнению с ручной проверкой)
  2. Глубину проверки (Тестирование не может охватить все возможные пути выполнения кода. Но статический анализатор кода может.)
  3. Точность (Ручные проверки кода подвержены человеческим ошибкам, а автоматизированные проверки - нет. Они сканируют каждую строку кода для выявления потенциальных проблем. Так что, до начала тестирования Вы уже соблюдаете стандарт кодирования, а качество кода имеет решающее значение.)

Выбор инструмента статического анализа кода.

Разумеется Вы будете выбирать интрумент под ваш конкретный язык программирования. В этой статье мы не будем углубляться в существующее разнообразие инструментов, а остановимся только на одном инструменте для анализа кода TypeScript. А именно, мы рассмотрим TSLint, который скоро переедит в ESlint, но правила использования, описанные тут, будут актуальны и позднее.

Установка TSLint

$ npm install tslint -g

Теперь мы готовы использовать команды tslint, с которыми можно ознакомиться набрав:

$ tslint --help

Вот они все:

$ tslint --help
Usage:  [options]

Options:
  -v, --version                          output the version number
  -c, --config [config]                  configuration file
  -e, --exclude <exclude>                exclude globs from path expansion (default: [])
  --fix                                  fixes linting errors for select rules (this may overwrite linted files)
  --force                                return status code 0 even if there are lint errors
  -i, --init                             generate a tslint.json config file in the current working directory
  -o, --out [out]                        output file
  --outputAbsolutePaths                  whether or not outputted file paths are absolute
  --print-config                         print resolved configuration for a file
  -r, --rules-dir <rules-dir>            rules directory (default: [])
  -s, --formatters-dir [formatters-dir]  formatters directory
  -t, --format [format]                  output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle,
                                         vso,fileslist, codeFrame)
  --test                                 test that tslint produces the correct output for the specified directory
  -p, --project [project]                tsconfig.json file
  -q, --quiet                            hide warnings on lint
  --type-check                           (deprecated) check for type errors before linting the project
  -h, --help                             output usage information
tslint accepts the following commandline options:

    -c, --config:
        The location of the configuration file that tslint will use to
        determine which rules are activated and what options to provide
        to the rules. If no option is specified, the config file named
        tslint.json is used, so long as it exists in the path.
        The format of the file is { rules: { /* rules list */ } },
        where /* rules list */ is a key: value comma-separated list of
        rulename: rule-options pairs. Rule-options can be either a
        boolean true/false value denoting whether the rule is used or not,
        or a list [boolean, ...] where the boolean provides the same role
        as in the non-list case, and the rest of the list are options passed
        to the rule that will determine what it checks for (such as number
        of characters for the max-line-length rule, or what functions to ban
        for the ban rule).

    -e, --exclude:
        A filename or glob which indicates files to exclude from linting.
        This option can be supplied multiple times if you need multiple
        globs to indicate which files to exclude.

    --fix:
        Fixes linting errors for select rules. This may overwrite linted files.

    --force:
        Return status code 0 even if there are any lint errors.
        Useful while running as npm script.

    -i, --init:
        Generates a tslint.json config file in the current working directory.

    -o, --out:
        A filename to output the results to. By default, tslint outputs to
        stdout, which is usually the console where you're running it from.

    --outputAbsolutePaths:
        If true, all paths in the output will be absolute.

    --print-config:
        When passed a single file name, prints the configuration that would
        be used to lint that file.
        No linting is performed and only config-related options are valid.

    -r, --rules-dir:
        An additional rules directory, for user-created rules.
        tslint will always check its default rules directory, in
        node_modules/tslint/lib/rules, before checking the user-provided
        rules directory, so rules in the user-provided rules directory
        with the same name as the base rules will not be loaded.

    -s, --formatters-dir:
        An additional formatters directory, for user-created formatters.
        Formatters are files that will format the tslint output, before
        writing it to stdout or the file passed in --out. The default
        directory, node_modules/tslint/build/formatters, will always be
        checked first, so user-created formatters with the same names
        as the base formatters will not be loaded.

    -t, --format:
        The formatter to use to format the results of the linter before
        outputting it to stdout or the file passed in --out. The core
        formatters are prose (human readable), json (machine readable)
        and verbose. prose is the default if this option is not used.
        Other built-in options include pmd, msbuild, checkstyle, and vso.
        Additional formatters can be added and used if the --formatters-dir
        option is set.

    --test:
        Runs tslint on matched directories and checks if tslint outputs
        match the expected output in .lint files. Automatically loads the
        tslint.json files in the directories as the configuration file for
        the tests. See the full tslint documentation for more details on how
        this can be used to test custom rules.

    -p, --project:
        The path to the tsconfig.json file or to the directory containing
        the tsconfig.json file. The file will be used to determine which
        files will be linted. This flag also enables rules that require the
        type checker.

    -q, --quiet:
        If true, hides warnings from linting output.

    --type-check:
        (deprecated) Checks for type errors before linting a project.
        --project must be specified in order to enable type checking.

    -v, --version:
        The current version of tslint.

    -h, --help:
        Prints this help message.

Итак, берем подходящую нам команду и используем её для анализа кода с нужными нам параметрами.

Например:

Настройка правил TSLint

Для настройки правил, по которым будет работать TSLint, нужно создать tslint.json файл с помощью команды

$ tslint --init

Вот его содержимое по умолчанию:

{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {},
    "rulesDirectory": []
}

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

Выбор формата файла с результатами анализа.

Если, к примеру, мы хотим проанализировать все файлы с расширением ts и при этом результаты сохранить в файл results в формате json, то нужно набрать команду с параметрами:

tslint './*.ts' --format 'json' --out results.json

При этом результаты будут представлены в виде:

[{"endPosition":{"character":58,"line":18,"position":1039},
"failure":"Parentheses are required around the parameters of an arrow function definition",
"fix":[{"innerStart":1033,"innerLength":0,"innerText":"("},
{"innerStart":1039,"innerLength":0,"innerText":")"}],
"name":"composition-parser.ts","ruleName":"arrow-parens","ruleSeverity":"ERROR",
"startPosition":{"character":52,"line":18,"position":1033}},
{"endPosition":{"character":37,"line":20,"position":1114},
"failure":"Parentheses are required around the parameters of an arrow function definition",
"fix":[{"innerStart":1108,"innerLength":0,"innerText":"("},
{"innerStart":1114,"innerLength":0,"innerText":")"}],
...

Если угодно $ tslint './*.ts' --out results.txt получите отчёт в "человеческом" формате:

ERROR: steps.ts:19:42 - Missing semicolon
ERROR: steps.ts:20:3 - unused expression, expected an assignment or function call
ERROR: steps.ts:20:39 - Missing semicolon
ERROR: steps.ts:21:43 - Missing semicolon
ERROR: steps.ts:22:54 - Missing semicolon
ERROR: steps.ts:23:53 - Missing semicolon
ERROR: steps.ts:24:4 - Missing semicolon
ERROR: steps.ts:26:6 - ' should be "
ERROR: steps.ts:27:86 - ' should be "
ERROR: steps.ts:27:119 - Missing semicolon
ERROR: steps.ts:28:40 - Missing semicolon
ERROR: steps.ts:29:68 - ' should be "
ERROR: steps.ts:29:80 - Missing semicolon
ERROR: steps.ts:30:4 - Missing semicolon
ERROR: steps.ts:32:1 - Exceeds maximum line length of 120

Подробнее о форматах смотрите тут.

Вот пожалуй и всё вкратце.

Итог - TSLint прекрасный-лёгкий-гибкий инструмент анализа кода TypeScript, который Вы легко адаптируете под свои нужды.

Да, и не забудьте проанализировать не только код разработчиков, но и код АВТОТЕСТОВ :-)