[Перевод] Топ-10 ошибок из 1000+ JavaScript-проектов и рекомендации по их устранению

В компании Rollbar, которая занимается созданием инструментов для работы с ошибками в программах, решили проанализировать базу из более чем 1000 проектов на JavaScript и найти в них ошибки, которые встречаются чаще всего. В результате они сформировали список из 10 наиболее часто встречающихся ошибок, проанализировали причины их появления и рассказали о том, как их исправлять и избегать. Они полагают, что знакомство с этими ошибками поможет JS-разработчикам писать более качественный код.

image


Сегодня мы публикуем перевод их исследования.

Методика анализа


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

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

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

aced7330c2844069d95dfb92a87a3c82.png


Ошибки, которые встречаются в JS-проектах чаще всего

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

1. Uncaught TypeError: Cannot read property


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

2c381f8bb28ef3b048e83f322b70d051.png


Ошибка Cannot read property

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

class Quiz extends Component {
  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }

  render() {
    return (
      
           {this.state.items.map(item =>          
  • {item.name}
  •        )}      
   );  } }


Тут надо обратить внимание на две важные вещи:

  1. В самом начале состояние компонента (то есть — this.state) представлено значением undefined.
  2. При асинхронной загрузке данных компонент будет выведен как минимум один раз до того, как данные будут загружены, вне зависимости от того, будет ли это выполнено в componentWillMount или в componentDidMount. Когда элемент Quiz выводится в первый раз, в this.state.items записано undefined. Это, в свою очередь, означает, что itemList получает элементы, которые так же представлены значением undefined. Как результат, мы видим в консоли следующую ошибку: "Uncaught TypeError: Cannot read property ‘map’ of undefined".


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

class Quiz extends Component {
  // Добавляем это:
  constructor(props) {
    super(props);

    // Инициализируем состояние и задаём значения элементов по умолчанию
    this.state = {
      items: []
    };
  }

  componentWillMount() {
    axios.get('/thedata').then(res => {
      this.setState({items: res.data});
    });
  }

  render() {
    return (
      
           {this.state.items.map(item =>          
  • {item.name}
  •        )}      
   );  } }


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

2. TypeError: «undefined» is not an object (evaluating…


Эта ошибка возникает в браузере Safari при попытке прочесть свойство или вызвать метод неопределённого объекта. Взглянуть на эту ошибку можно с помощью консоли инструментов разработчика Safari. На самом деле, тут перед нами та же самая проблема, которую мы разбирали выше для Chrome, но в Safari она приводит к другому сообщению об ошибке.

ad1ee0031bb5fd4fcda99562dd868b3f.png


Ошибка «undefined» is not an object

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

3. TypeError: null is not an object (evaluating


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

b1d584588f4117cb4803f21de6ac616c.png


Ошибка TypeError: null is not an object

Напомним, что в JavaScript null и undefined — это не одно и то же, именно поэтому мы видим разные сообщения об ошибках. Смысл значения undefined, записанного в переменную, говорит о том, что переменной не назначено никакого значения, а null указывает на пустое значение. Для того чтобы убедиться в том, что null не равно undefined, можно сравнить их с использованием оператора строгого равенства:

e6c341d4eb00ac4a9aa6a2a71734b7c0.png


Сравнение undefined и null с помощью операторов нестрогого и строгого равенства

Одна из причин возникновения подобной ошибки в реальных приложениях заключается в попытке использования элемента DOM в JavaScript до загрузки элемента. Происходит это из-за того, что DOM API возвращает null для ссылок на пустые объекты.

Любой JS-код, который работает с элементами DOM, должен выполняться после создания элементов DOM. Интерпретация JS-кода производится сверху вниз по мере появления его в HTML-документе. Поэтому если тег

   


4. (unknown): Script error


Эта ошибка возникает в том случае, когда неперехваченная ошибка JavaScript пересекает границы доменов при нарушении политики кросс-доменных ограничений. Например, если ваш JS-код размещён на CDN-ресурсе, в сообщении о любой неперехваченной ошибке (то есть, об ошибке, которая не перехвачена в блоке try-catch и дошла до обработчика window.onerror) будет указано Script error, а не полезная для целей устранения этой ошибки информация. Это — один из браузерных механизмов безопасности, направленный на предотвращение передачи данных между фрагментами кода, источниками которого являются разные домены, и которым в обычных условиях запрещено обмениваться информацией.

Вот последовательность действий, которая поможет увидеть эту ошибку.

1. Отправка заголовка Access-Control-Allow-Origin.

Установка заголовка Access-Control-Allow-Origin в состояние * указывает на то, что к ресурсу можно получить доступ из любого домена.

Знак звёздочки можно, при необходимости, заменить на конкретный домен, например так: Access-Control-Allow-Origin: www.example.com. Однако поддержка нескольких доменов — дело довольно сложное. Такая поддержка может не стоить затраченных на её обеспечение усилий, если вы используете CDN, из-за возможного возникновения проблем с кэшированием. Подробности об этом можно посмотреть здесь.

Вот примеры установки этого заголовка в различных окружениях.

Apache

В папке, из которой будут загружаться ваши JavaScript-файлы, создайте файл .htaccess со следующим содержимым:

Header add Access-Control-Allow-Origin "*"


Nginx

Добавьте директиву add_header к блоку location, который отвечает за обслуживание ваших JS-файлов:

location ~ ^/assets/ {
    add_header Access-Control-Allow-Origin *;
}


HAProxy

Добавьте следующую настройку к параметрам системы, ответственной за поддержку JS-файлов:

rspadd Access-Control-Allow-Origin:\ *


2. Установите crossorigin="anonymous" в теге