Статьи

Cargo-Culting в JavaScript

Программирование с использованием Cargo-Cult — это то, что программист делает, когда он или она недостаточно хорошо знает конкретный язык или парадигму, и в итоге пишет избыточный и, возможно, вредный код. Он часто поднимает голову в мире JavaScript. В этой статье я исследую концепцию культового программирования и мест, где можно следить за этим в JavaScript.

Догматические правила проявляются и распространяются до тех пор, пока они не станут нормой.

Cargo-culting иногда определяется как « крайняя приверженность форме, а не содержанию ». Форма в программировании — это синтаксис, парадигмы, стили и шаблоны, которые мы используем. Содержание — это абстрактная вещь, которую вы хотите представить через свой код — сама сущность вашей программы. Человек с недостатком понимания в какой-либо области может скопировать форму других людей, не понимая по-настоящему, и, таким образом, его содержание — его программа — может пострадать.

Любопытно, что в JavaScript распространено карго-культирование, вероятно, из-за общего низкого барьера для входа в мир интерфейсных разработок. Вы можете создать HTML-страницу с небольшим количеством JavaScript за считанные секунды. В результате есть много людей, которые становятся достаточно опытными в этих технологиях, чтобы чувствовать себя комфортно, создавая и навязывая правила для себя и других. В конце концов, другие новички копируют эти правила. Догматические правила всплывают и распространяются до тех пор, пока они не станут нормой:

  • Всегда используйте операторы строгого равенства
  • Никогда не используйте eval
  • Всегда используйте одну декларацию var для каждой области
  • Всегда используйте IIFE — он «защищает» вас

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


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

Синтаксис, такой как точка с запятой или пробел, может казаться просто элементом формы, а не содержания. Но многие из этих тонких синтаксических правил могут иметь значительные эффекты в JavaScript. Если вы не понимаете «форму», то вы не можете начать понимать «содержание».

Итак, в этой статье мы определим, какие области формы в JavaScript часто обрабатываются, то есть копируются без понимания.

Как может выглядеть JavaScript
Как может показаться JavaScript … изображение из презентации Ангуса Кролла » Политика JavaScript «

Ангус Кролл в своей недавней презентации под названием « Политика JavaScript » выделил одну из наиболее распространенных частей догмы JS, с которой люди связывают культ:

1
if (typeof myObject.foo === ‘undefined’) {…}

Большую часть времени проводить такую ​​длинную проверку на undefined бессмысленно. Техника стала распространенной, потому что люди копировали других людей, а не из-за ее реальной ценности.

Конечно, бывают случаи, когда:

1
typeof x === ‘undefined’

… предпочтительнее:

1
x === undefined

Но в равной степени бывают случаи, когда последний предпочтительнее. Краткий обзор вариантов:

1
2
3
4
5
6
7
8
9
// Determine if `x` is undefined:
x === undefined
typeof x == ‘undefined’
typeof x === ‘undefined’
x === void 0
 
// Determine if `x` is undefined OR null:
x == null
x == undefined

Люди начали использовать подход typeof потому что они защищали себя от:

  • Потенциально необъявленная переменная ( подходы, не относящиеся к typeof, будут вызывать TypeErrors )
  • Кто-то переписал неопределенное глобально или в родительской области. Некоторые среды позволяют перезаписывать undefined в нечто вроде true . Вы должны спросить себя: « Возможно ли, что кто-то переписал неопределенное, и должен ли мой сценарий потворствовать такой глупости? »

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

Единственный раз, когда вам нужно использовать проверку typeof для undefined это когда вы проверяете переменную, которая, возможно, не была объявлена, например, проверяете jQuery в глобальной области видимости:

1
2
3
if (typeof jQuery != ‘undefined’) {
    // … Use jQuery
}

Дело в том, что если jQuery существует, то мы можем быть уверены, что это объект — « правдивая » вещь. Так что этого будет достаточно:

1
2
3
4
// or:
if (window.jQuery) {
 
}

Давайте возьмем что-то очень распространенное и обычно считающееся хорошим советом, используя только строгое равенство:

1
a === b

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

1
2
3
1 == 1 // true —
1 == «1» // true —
1 == [1] // true —

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

Когда вы знаете со 100% уверенностью, что типы обоих операндов одинаковы, вы можете избежать необходимости строгого равенства. Например, я всегда знаю, что оператор typeof возвращает строку, и мой правый операнд также является строкой (например, "number" ):

1
2
3
4
5
// With strict-equals
typeof x === ‘number’
 
// With non-strict-equals:
typeof x == ‘number’

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

Еще один весьма полезный пример — когда вы хотите узнать, является ли значение null или undefined . При строгом равенстве вы можете сделать это:

1
2
3
if (value === undefined || value === null) {
    // …
}

С нестрогим равенством это намного проще:

1
2
3
if (value == null) {
    // …
}

Здесь нет подвоха — он делает именно то, что мы хотим, только, возможно, менее заметно. Но если мы знаем язык, то в чем проблема? Это прямо в спецификации :

Сравнение x == y , где x и y являются значениями, дает true или false . Такое сравнение выполняется следующим образом:

  • Если x равно нулю, а y не определено, верните true.
  • Если x не определено, а y равно нулю, верните true.

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


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

1
2
3
4
5
for (var i in object) {
    if (object.hasOwnProperty(i)) {
        // We can do stuff with `object[i]`
    }
}

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

Проверка hasOwnProperty специально hasOwnProperty вам касаться свойств, которые вы или какой-либо сторонний сценарий определили, т. hasOwnProperty Когда прототип вашего объекта имеет перечисляемые свойства.

Если вы знаете, что прототип вашего объекта ( или прототип его прототипа и т. Д.) Не имеет перечисляемых свойств, вам не нужно беспокоиться об использовании hasOwnProperty в hasOwnProperty for-in . И, если ваш объект инициализируется через Object.create Object.create(null) в Object.create(null) , вы даже не сможете вызывать hasOwnProperty непосредственно для объекта ( ни один прототип не означает никаких унаследованных нативных методов ). Это означает, что использование hasOwnProperty по умолчанию во всех ваших циклах for-in может иногда порваться.

Одним из возможных решений для объектов с null прототипами является использование сохраненной ссылки на hasOwnProperty , например, так:

1
2
3
4
5
6
7
8
var hasOwnProperty = Object.prototype.hasOwnProperty;
 
// Later in your code:
for (var i in someObject) {
    if (hasOwnProperty.call(someObject, i)) {
        // …
    }
}

Это будет работать, даже если у объекта нет прототипа (в случае Object.create(null) ). Но, конечно, мы должны делать это в первую очередь, только если мы знаем, что нам это нужно. Если вы пишете сторонний скрипт для «враждебной» среды, тогда да, обязательно проверьте перечислимые унаследованные свойства. В противном случае, это может быть необязательно все время.

Примечание. IE9 и Safari 2.0 усложняют ситуацию, когда вы пытаетесь идентифицировать перечислимые свойства, которые уже определены как не перечисляемые. Стоит проверить действительно кросс-браузерную реализацию цикла forOwn .

В заключение: ваше использование hasOwnProperty должно зависеть от объекта, который зацикливается. Это зависит от того, какие предположения вы можете сделать безопасно. Слепой защиты с помощью hasOwnProperty будет недостаточно во всех случаях. Остерегайтесь различий между браузерами.


Другая распространенная избыточность, попадающая в код JS, — это круглые скобки. Внутри выражений он используется для принудительной конкретной группировки подвыражений. Без них вы находитесь в зависимости от приоритетов операторов и ассоциативности . Например:

1
2
3
A && B ||
A && (B || C)
(A && B) ||

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

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

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

Иногда это сложно для читателя. Следует учитывать, что любые указанные скобки могут быть добавлены, потому что:

  • Необходимо было переопределить приоритет / ассоциативность по умолчанию
  • Без всякой функциональной причины, просто для «защиты» или «ясности»

Возьмите этот пример:

1
A && B ?

Без знания правил приоритета операторов мы можем увидеть здесь две возможные операции:

1
2
(A && B) ?
A && (B ? doFoo() : doBaz())

В этом случае логическое И имеет более высокий приоритет, то есть эквивалентное выражение в скобках:

1
(A && B) ?

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

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


Нередко встречаются лишние кавычки в объектных литералах:

1
2
3
4
5
6
var data = {
  ‘date’: ‘2011-01-01’,
  ‘id’: 3243,
  ‘action’: ‘UPDATE’,
  ‘related’: { ‘1253’: 2, ‘3411’: 3 }
};

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

1
2
3
4
5
6
var data = {
  date: ‘2011-01-01’,
  id: 3243,
  action: ‘UPDATE’,
  related: { 1253: 2, 3411: 3 }
};

Иногда вы можете предпочесть дополнительную согласованность возможности использования кавычек, особенно если имя поля является зарезервированным словом в JavaScript (например, «класс» или «instanceof»). И это нормально.

Использование кавычек — это не плохо. Но это избыточно. Знание того, что вам не нужно их использовать, — половина выигранной битвы. Теперь ваш выбор — делать то, что вы хотите.


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

Инициализация объекта в традиционно идиоматическом JavaScript выглядит следующим образом:

1
2
3
4
5
var obj = {
    a: 1,
    b: 2,
    c: 3
};

Существует альтернативный подход, который набирает обороты, хотя:

1
2
3
4
5
var obj = {
      a: 1
    , b: 2
    , c: 3
};

Предполагаемое преимущество размещения запятых перед каждой парой ключ-значение (кроме первой) заключается в том, что для удаления свойства нужно коснуться только одной строки. Используя традиционный подход, вам нужно убрать « c: 3 » и затем запятую в строке выше. Но с первым запятым вы можете удалить » , c: 3 «. Сторонники утверждают, что это снижает вероятность запятых в конце, а также убирает различия в управлении исходным кодом.

Противники, однако, говорят, что этот подход позволяет избавиться от «проблемы» с запятой в конце, введя новую проблему с запятой. Попробуйте удалить первую строку, и на следующей строке у вас останется запятая. Сторонники, начинающие использовать запятую, на самом деле считают это хорошей вещью, поскольку начальная запятая немедленно выдаст ошибку SyntaxError. Конечная запятая, однако, ничего не выдает, кроме IE6 и 7. Поэтому, если разработчику не удается протестировать свой JS в этих версиях IE, то запятые часто могут заползти в рабочий код, что никогда не бывает хорошо. Ведущая запятая выбрасывается во всех средах, поэтому вероятность ее пропуска меньше.

Конечно, вы можете возразить, что вся эта вещь является спорным. Вероятно, нам следует использовать такие линтеры, как JSLint или Kinder JSHint . Тогда мы можем свободно использовать знаки препинания и пробелов, которые наиболее важны для нас и наших коллег.

Давайте даже не будем начинать с стиля запятая в объявлениях переменных.

1
2
3
4
var a = 1
  , b = 2
  , c = 3
  ;

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

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

На тему ремонтопригодности нам напоминает эта знаменитая цитата :

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

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

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

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

Если я навсегда потворствую более низкому уровню понимания в своем коде — мягко следую — строго придерживаясь соглашений и руководств по стилю, и вещей, которые, как я вижу, делают «эксперты», то я никогда не смогу продвинуть свое собственное понимание или воспользоваться преимуществами язык во всей своей странности и красоте. Я счастливо и блаженно поселился в этом мире правил и абсолютов, но чтобы двигаться вперед, я должен покинуть этот мир и принять более высокое понимание.