Статические анализаторы JavaScript и ошибки, от которых они помогут отучиться (Часть 2)

в 11:57, , рубрики: javascript, анализаторы, Блог компании Paysto

Продолжаем перевод статьи о статических анализаторах: в прошлой части автор затронул такие нюансы, как использование операторов == и ===, а также неопределенные переменные и поздние определения, кроме того, автор указывает на замечания, которые выдают анализаторы (на примере JSHint) при обнаружении подобных ошибок. В этой части речь пойдет о повторном объявлении переменной, а также о контроле над цикломатической сложностью кода.

Повторное объявление (использование) переменной
В JavaScript можно повторно объявлять (использовать) переменные, но это практически всегда получается случайно. Смотрите:

function incrementCount(counter) {
 if (counter.count) {
  counter.count++;
 } else {
  var counter = 1;
  counter.count = counter;
 }
}

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

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

Этот простой тип позволит запустить код без ошибок, но приведет к довольно странному результату.

JSHint покажет следующее:

test.js: line 21, col 21, 'counter' is already defined.

Фигурные скобки в блоках, циклах и условных конструкциях

if (false)
 doSomethingElse();
 doSomething();

Что сделает этот код — doSomething или doSomethingElse? На первый взгляд мне всегда кажется, что он не выполнит doSomething или doSomethingElse. Именно так это работает на Python, но не в JavaScript. JavaScript, в первую очередь, будет выполнять строку, которая расположена под выражением if, в качестве части блока. Отступ не имеет значения.

Вся проблема заключается в читабельности кода. Если вы не понимаете, что сделает код, вы будете писать баги.
Python и CoffeeScript любят пропускать фигурные скобки. Это хорошо работает в языках, которые используют форматирование свободного пространства, но JavaScript ведет себя иначе. JavaScript позволяет создавать большое количество странного синтаксиса, при этом фигурные скобки помогают избежать неприятностей.

if (false) {
 doSomethingElse();
 doSomething();
}

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

test.js: line 27, col 5, Expected '{' and instead saw 'doSomething'.

Одинарные и двойные кавычки

console.log("This is a string. It's OK.");
console.log('This string is OK too.');
console.log("This string " + 'is legal, but' + "really not OK.");

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

У Google есть руководство по стилю кода, в котором всегда для строк используются одинарные кавычки, а потому им не нужно убирать двойные кавычки в HTML. Я не могу спорить, что одинарные кавычки лучше двойных, но я могу поспорить, что здесь важна согласованность. Соблюдение согласованности делает код более читабельным.

JSHint предупредит вас о смешении кавычек подобным образом:

test.js: line 31, col 27, Mixed double and single quotes.

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

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

Цикломатическая сложность – это мера сложности приведенного блока кода. Посмотрите на код и посчитайте количество ветвей, по которым он может сработать – это число и есть цикломатическая сложность.
Например, цикломатическая сложность этого кода равна 1:

function main() {
 return 'Hello, World!';
}

Вы можете проследить только одну ветвь выполнения этого кода.
Давайте добавим условную логику:

function main() {
 if (true) {
  return 'Hello, World!';
 } else {
  return 'Hello, unWorld!';
 }
}

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

Идеальный код легко читается и понимается. Чем выше цикломатическая сложность, тем труднее будет понять код. Все согласны, что высокая цикломатическая сложность – это плохо, но никто не может прийти к определенному лимиту; 5 – это хорошо, а 100 – слишком много. А посередине слишком большая область неопределенности.

Если цикломатическая сложность достигнет предварительно заданного лимита, JSHint сообщит вам об этом.

test.js: line 35, col 24, This function's cyclomatic complexity is too high. (17)

JSHint – это единственная из трех программ для проверки, которая учитывает цикломатическую сложность. Она также позволяет задавать лимит. Превысьте заданное вами число maxcomplexity, и JSHint предупредит вас. Мне нравится устанавливать лимит на 14, но я могу сделать его немного выше в проектах, где мне нужно много парсинга.

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

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

function getFirstDay(country) {
 if (country === 'USA') {
  return 'Sunday';
 } else if (country === 'France') {
  return 'Monday';
 } else if…
}

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

Проверяйте все, что редактируете, более одного раза

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

Я начал эту статью с утверждения, что я всегда пользуюсь анализатором кода, но я не делаю этого в одном случае: когда пишу одноразовый код. Мне нравится использовать быстрые прототипы, чтобы демонстрировать интерактивные идеи и помогать своей команде понять, как что-то должно работать. Такие прототипы – это одноразовые коды; здесь мне не нужно исправлять баги, потому что я выброшу этот код через несколько недель. Такой одноразовый код существует исключительно для кратких демонстраций, и мне не важно, есть ли в нем малозаметные баги. А все, что мне важно, анализируется.

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

Автор: Irina_Ua

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js