Почему это так плохо или не так уж и плохо? — интересуется Сергей.

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

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

Array.prototype
  .shuffle = function(){
    while (i--) {
      …
    }
  };
[1, 2, 3].shuffle();

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

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

window.setTimeout = function( fn, timeout, ctx ) { initialTimeout( function() { fn.call(ctx); }, timeout); }

Конечно, в вашем коде setTimeout будет работать так, как вам нужно, но вы не можете гарантировать, что в других местах, где он используется все будет работать так же гладко. Есть и более экстремальные примеры, например, раньше в JS можно было переопределить даже undefined и window.

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

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

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

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

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

Кажется, что полифилы — это хорошая идея: подключаешь полифил и вне зависимости от того, какой у пользователя браузер, в коде можно использовать новые штуки, например Fetch для HTTP-запросов, Promise или Object.entries для итерирования по объектам

<script src=promise.js>
<script src=fetch.js>

fetch('/api/request')
  .then(resp =>
    resp.json())
  .then(json =>
    render);

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

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

В таких ситуациях лучше использовать функции-обертки, которые иногда называются понифилами. В принципе, они позволяют делать то же самое что и полифилы, но явно. А еще у них нет проблем со стандартами: например, популярная библиотека Lodash выросла как раз из обертки над неработающими итераторами в массивах. Ребята добавили в библиотеку forEach, map и прочие some, every, но не привязывались к стандарту и поэтому у них были изначально развязаны руки. Через какое-то время они добавили другие удобные методы для массивов и коллекций, такие как shuffle, unique, flatten и другие. Несмотря на то, что итераторы в массивах поддерживаются уже давно, Lodash успешно развивается.

// Полифил
[1, 2, 3].map(
  it => it / 2
);
// Понифил
arrayMap(
  [1, 2, 3],
  it => it / 2
);

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

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