Недавно я увидел ужасный комикс о ловушке кодирования.

После некоторого смеха я пожалел, что не поделился нашим опытом и практикой в ​​отношении ловушек кодирования. Ученые были бы живы, если бы знали это правило, RIP.

Ладно, приступим к делу.

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

С этой целью я разработал статический анализатор JavaScript (DeepScan), который находит потенциально подверженный ошибкам код без выполнения кода. Вместо того, чтобы обнаруживать простые нарушения соглашений о коде, он пытается найти исключения ECMAScript и распространенные ошибки, которые допускают разработчики.

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

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

1. Неправильное использование оператора typeof

Возможными возвращаемыми значениями оператора typeof являются следующие строки:

  • "неопределенный"
  • "объект"
  • "Логическое"
  • "номер"
  • "нить"
  • "символ"
  • «Функция»

В отличие от правого операнда instanceof в Java, это не произвольно. Когда разработчик сравнивает со значением, не указанным выше, всегда будет false нежелательный результат.

Я обнаружил, что некоторые разработчики, похоже, используют оператор typeof, чтобы проверить, является ли объект типом массива.

var param = typeof pChangeMessageList == "Array" ? pChangeMessageList.join('|') : pChangeMessageList;

Разве это не естественно?

Но «Массив» (или «массив») не является допустимым возвращаемым значением typeof, и pChangeMessageList не будет объединен, даже если это действительно массив. Разработчики должны использовать Array.isArray() или служебные функции в jQuery, lodash,….

Кроме того, поскольку возвращаемое значение является строкой, часто встречается опечатка.

В Apache Ambari можно увидеть опечатку udefined: app / controllers / main / host / details.js

В других проектах я также мог видеть опечатку типа undeifned.

if (typeof Object.prototype[k] != "undeifned" && Object.prototype[k] === this._table[k]) continue;

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

Обратите внимание, что оператор typeof возвращает только предопределенную строку.

2. Использование необъявленных переменных

Давайте сначала посмотрим на пример. (Наш внутренний кодекс)

var rows = [];
for (var i = 0; i < args.length; i++) {
    var obj = {
        patternId: patternId
    };
    rows.push(obj);
}
var patternId = $("#pattern").attr("patternId");
$.post('/delete.do', { data: JSON.stringify(rows) });

Поскольку переменная patternId используется перед ее объявлением и присвоением, obj имеет неправильное значение.

В Java компилятор будет ворчать по поводу этого случая «неразрешенной переменной». Но жизненный цикл переменных JavaScript допускает вышеуказанный шаблон предыдущего использования, потому что переменная patternId поднимается и инициализируется как undefined сразу после выполнения окружающей функции.

Просто используйте let. Он выдаст _16 _ («patternId не определен»), и вы, по крайней мере, не сможете отправить ошибочные данные на сервер.

Я рекомендую вам прочитать эту статью о подъеме вар. Честно говоря, я не знал точно разницы между var и let.

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

3. Неиспользуемые и неинициализированные переменные.

Неиспользуемые переменные могут вызвать проблемы. Ниже внутренний код добавлял параметр userName и обрезал специальные символы.

if ($.browser.msie == true) {
    var target = url + "userName" + userName;
    target = url.replace(/\.|\?|\&|\/|\=|\:|\-|\s/gi,"");
}

Но переменная target, к которой добавлено userName, впоследствии не используется, окончательное значение переменной target не включает параметр.

Готов поспорить, разработчик намеревался написать target = target.replace, но совершил ошибку. Чтобы избежать такой ошибки, дважды проверьте, если вы не используете переменную, которой присвоено значение.

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

В Modernizr вы можете увидеть некорректное использование неинициализированной переменной html5: /src/html5shiv.js

Вы можете увидеть объявление переменной html5 в строке 7. Оно здесь, потому что разработчик хотел «вынуть переменную html5 из области видимости html5shiv, чтобы мы могли ее вернуть».

Но 2 года спустя другой разработчик совершил фиксацию, добавив var в строке 23. В результате переменная html5, возвращенная в строке 38, имеет значение undefined.

Если вы разработчик Java, компилятор будет жаловаться на эту неинициализированную переменную, но разработчик JavaScript не может получить такую ​​помощь. Поэтому я разработал это правило для проверки использования неинициализированной переменной.

4. Использование постоянного условия

Проверьте свой код еще раз, если вы используете постоянные или избыточные проверки в условных операторах.

null / undefined

В Apache Ambari рассмотрим проверку аргумента: app / utils / number_utils.js

Условие str==undefined всегда равно false из-за предыдущего условия str==null. Обратите внимание, что undefined равно null оператором нестрогого равенства ==. Условие совершенно ненужное.

Также, когда я просматриваю код, я часто вижу код проверки аргумента, например:

if (arg == null || arg == undefined || arg == '')

Вы можете делать все просто, потому что оператор == в JavaScript одинаково обрабатывает null, undefined и пустую строку ''.

if (!arg)

Вложенный, если

В Apache Ambari рассмотрим проверку аргумента: app / controllers / main / host / bulk_operations_controller.js

turn_off в строке 12 всегда false, потому что он находится в ветви false условия в строке 5. Следовательно, строка 13 никогда не выполняется и параметр included_hosts никогда не устанавливается.

Похожую проблему можно найти в Adobe Brackets: src / extensions / default / UrlCodeHints / main.js

self.cachedHints в строке 11 равно null, потому что он проверяется условием if (this.cachedHints) в строке 3. Таким образом, строка 12 никогда не выполняется, и это означает, что вам необходимо проверить логику отклонения. Было ли отклонение ненужным или обработано не в том филиале.

Как правило, проверьте логику еще раз, когда увидите постоянное условие, и удалите его, если оно окажется ненужным.

5. Несогласованные нулевые проверки

Проверку нулевых аргументов следует выполнять последовательно.

Давайте посмотрим на противоречивый пример в detect.js: plugin / markdown / markdown.js

  • В строке 3 проверяется аргумент element, является ли он null.
  • В строке 11 осуществляется доступ без какой-либо проверки.
  • Это может вызвать TypeError, когда передан нулевой аргумент. Здесь также нужна проверка, если аргумент может быть null.

Другой пример в Apache Ambari: app / mixins / common / serverValidator.js

  • Сначала проверяется аргумент data и выводится предупреждение.
  • Но поток не прекращается!
  • Значение ложности сохраняется, как рекомендовано в строке 5.
  • Наконец, TypeError выдается при доступе к массиву resources в строке 6.

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

  • Удалите саму проверку, если аргумент не может быть нулевым.
  • Добавляйте чеки последовательно для каждого использования.

6. Нулевой указатель

Я обнаружил несколько случаев, когда пытались получить доступ к свойству нулевого объекта. Его часто можно найти в ветке else или встроенном блоке инициализации. Кажется, что разработчики израсходовали всю свою энергию на кодирование основного потока выполнения.

Давайте посмотрим на Adobe Brackets: src / language / HTMLInstrumentation.js

В строке 20 (ветвь else) переменная match установлена ​​в null. Но доступ к match.mark осуществляется в конце функции, вызывая TypeError.

Я думаю, что исправление должно быть return match && match.mark;, и я с радостью обнаружил, что проблема исчезла благодаря этой фиксации, в которой говорится: Это исправляет« TypeError: невозможно прочитать свойство «mark, равное нулю »». Молодец!

Другой случай в jQuery Mobile: demos / popup-alignment / popup.alignment.js

См. align.x в строке 11 (встроенный блок инициализации). Поскольку align в этот момент не инициализирован, доступ к align.x выдает TypeError.

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

    var arX = parseFloat( ar[ 0 ] );
    align = {
        x: arX,
        y: ar.length > 1 ? parseFloat( ar[ 1 ] ) : arX
    };

Нулевой указатель - одна из основных ошибок времени выполнения. Поэтому, если вы устанавливаете переменную в null или обращаетесь к объекту в блоке инициализации, обратите внимание на то, как переменная используется впоследствии или объект правильно инициализирован.

7. Неправильное использование возвращаемых значений.

Есть два ошибочных случая возврата значения функции:

  • Вы используете возвращаемое значение из функции, которая ничего не возвращает
  • Вы не используете возвращаемое значение из функции, которая не имеет побочного эффекта.

Давайте посмотрим на первый случай в CodeMirror: mode / vb / vb.js

Функция dedent ничего не возвращает. Таким образом, if (dedent(stream, state)) всегда false, а ERRORCLASS не возвращается.

Когда нужно вернуть ERRORCLASS? Я предполагаю, что это может быть, когда dedent возвращает нулевой или отрицательный индекс. Другими словами, это означает, что в функции отсутствует оператор возврата.

И второй случай часто встречается при использовании встроенного API строки JavaScript.

Ниже приведен наш внутренний код.

var retString = i18n.RESTOREDLG_SUCCESS + ' : ( ' + returnValue.unitServiceName + ' )';
if(returnValue.isRenamed) {
    retString.concat(' , '+i18n.RESTOREDLG_RENAME);
}
Notify.success(retString);

String.prototype.concat() всегда возвращает новую строку без побочного эффекта. Но разработчик упустил из виду возвращаемое значение. Таким образом, пользователи видят одно и то же уведомление независимо от его действия.

Аналогичная путаница наблюдается и с API массива. Array.prototype.concat() возвращает новый массив без побочного эффекта, а Array.prototype.push() добавляет элемент к самому входному массиву.

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

8. Плохие операторы

Теперь давайте посмотрим, как неправильно используются операторы присваивания, побитовые операции и отрицания.

См. Ниже оператор отрицания в нашем внутреннем коде.

if (!data instanceof Object) {

Выражение оценивается как (!data) instanceof Object, при сравнении логического значения с Object. Готов поспорить, разработчик хотел !(data instanceof Object). В противном случае использование круглых скобок поможет указать на редкую ситуацию.

Второй - о побитовом операторе. См. Время работы: lib / pollers / webpagetest / webPageTestPoller.js

config.webPageTest.testOptions | {} определенно является опечаткой config.webPageTest.testOptions || {}, потому что побитовый оператор в первом случае преобразует объект в NaN, в результате чего получается ноль.

Следовательно, вы должны проверить это, когда побитовый оператор используется в шаблоне инициализации, например bar | {}.

Хотя JSHint может запретить использование побитового оператора, это может раздражать, поскольку он буквально определяет все варианты использования. Например, приведенный ниже код мне кажется правильным, а JSHint жалуется.

function foo(option) {
    const OPTION_1 = 1 << 0;
    const OPTION_2 = 1 << 1;
    let isOption1 = option | OPTION_1;

Что касается оператора присваивания, давайте рассмотрим MagicMirror: modules / default / alert / alert.js

В строке 6 вы можете увидеть оператор присваивания в условном выражении. В результате show_notification вызывается, даже если тип полезной нагрузки не является «уведомлением».

Обратите внимание на оператор присваивания, если он находится в условном выражении.

Спасибо за чтение!

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

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

Подскажите, есть ли у вас идея проверить шаблон в JavaScript. Мы реализуем и распространим эту идею!

Вы можете найти меня здесь: