[ ПРИМЕЧАНИЕ: В этой статье я ссылаюсь на библиотеку проверки, которую я написал, под названием разрешить Анкет Теперь это в пакете NPM, который можно найти здесь: https://www.npmjs.com/package/@toolz/allow ]
Мои постоянные читатели (оба) знают, что я много написал о целостности ценностей, которые передаются между разными частями приложения. Иногда мы добавляем ручные проверки. Иногда эти значения вообще не проверены. Иногда мы проверяем их во время компиляции, но мы Предположим Они будут правы во время выполнения (я ищу на вас, TypeScript).
Каким бы ни был подход, я только недавно осознал, что термин «оборонительное программирование» обычно используется многими программистами в качестве уничижительного. У меня сложилось впечатление, что «оборонительное программирование» часто интерпретируется как «прыгающий через нелепое количество обручей для проверки данных – данных, которые, вероятно, на самом деле не нужно вообще подтверждать». И я не полностью не согласен с этой оценкой. Но я боюсь, что некоторые, возможно, стали настолько склонны к идее Оборонительное программирование что они не распознают другие лазейки, которые они включают в свой собственный код.
Основные предположения
Давайте убедитесь, что мы все на «той же странице» здесь. Я уверен, что есть несколько определений для Оборонительное программирование Анкет Итак, ради это Статья, это определение, которое я буду использовать:
Защитное программирование: Практика лечения всех Вход к программе как «неизвестно» – враждебно , даже. Эта практика охраняет такие входные данные из основного потока приложения, пока они не будут подтверждены как соответствующие «ожидаемому» типу/значению/формату.
Я сосредотачиваюсь на Вход Анкет Было бы возможно проверить данные в том же кодовом блоке, где они были определены. И такая практика, безусловно, была бы Оборона Анкет Но это также было бы экстремальным. И глупо.
Но входные данные представляют собой самый сильный случай для защитного программирования. Потому что входные данные поступают … где -то иначе Анкет И ты не хочешь это Программа, чтобы знать о внутренней работе другой Программа, чтобы он занялся своим бизнесом. Вы хотите это Программа, чтобы быть отдельным подразделением. Но если эта программа стоит одна, то она также должна предположить, что любой вклад в программу потенциально враждебно Анкет
Проверка ад
Здесь «защитное программирование» становится грязным словом. Когда мы говорим об проверке все Из наших вкладов мы боимся, что это приведет к чему -то подобному:
const calculatePassAttemptsPerGame = (passAttempts = 0, gamesPlayed = 0) => {
if (isNaN(passAttempts)) {
console.log('passAttempts must be a number.');
return;
}
if (isNaN(gamesPlayed)) {
console.log('gamesPlayed must be a number.');
return;
}
if (gamesPlayed === 0) {
console.log('Cannot calculate attempts-per-game before a single game has been played.');
return;
}
return passAttempts / gamesPlayed;
}
Функция имеет Вход Анкет И функция не должна знать, где возникли эти входные данные. Поэтому с точки зрения функции Входные данные потенциально опасны.
Вот почему к этой функции уже есть значительный багаж. Мы не можем обязательно доверять, что Passattempts или Игрные игры числа. Потому что Passattempts и Игрные игры являются Вход к этому программа Анкет И если мы чувствуем необходимость программировать «оборонительно», мы в конечном итоге заполняем дополнительные проверки в нашей программе.
Честно говоря, подтверждения, показанные выше, не являются адекватными, насколько я понимаю. Потому что, пока мы гарантируем, что входные данные Числа Анкет Мы не подтверждаем, что они Правильный вид чисел.
Думать об этом: Если мы регистрируем попытки прохода за игру, имеет ли смысл, что или или отрицательный ? Имеет ли это смысл, если кто -то из них дробный ?? Я не могу вспомнить в последний раз, когда игрок бросил 19,32 проходов за одну игру. Я не могу вспомнить, когда игрок играл в играх -4. И если мы хотим убедиться, что наша функция действительно оснащена, чтобы всегда обеспечить наиболее логичные возвраты, мы также должны убедиться, что она всегда получала наиболее логичные входы. Так что, если бы мы действительно хотели пойти на методы защитного программирования, мы бы добавили даже Подробнее Валидации, чтобы убедиться, что входные данные неотрицательные целые числа Анкет
Но кто действительно хочет сделать все это ?? Все, что мы хотели, это простая функция, которая возвращает результат Passattempts разделен на Игрные игры , и мы закончили раздутым беспорядком кода. Написание всех этих Оборона Валидации кажутся трудоемкими и бессмысленными.
Итак, как мы можем избежать неприятностей защитного программирования? Ну, вот подходы (оправдания), с которыми я чаще всего сталкиваюсь.
Пропавшая в лесу для деревьев
Картина над кучей деревьев? Или это единственный лес? Конечно, в зависимости от вашей системы отсчета, это может быть либо (или оба). Но может быть опасно предположить, что на картинке выше не показано «деревья» и показывает только один «лес».
Точно так же, что вы видите, когда смотрите на такой код?
const calculatePassAttemptsPerGame = (passAttempts = 0, gamesPlayed = 0) => {
//...
}
const calculateYardsPerAttempt = (totalYards = 0, passAttempts = 0) => {
//...
}
const getPlayerName = (playerId = '') => {
//...
}
const getTeamName = (teamId = '') => {
//...
}
Это один Программа («лес»)? Или это куча отдельных программ («деревья») ??
С одной стороны, они представлены в одном примере кода. И все они, кажется, связаны с каким -то центральным игроком/командой/спортивным приложением. И вполне возможно, что эти функции будут только когда -либо призван в одном время выполнения. Итак … они все часть одной программы («лес»), Правильно ??
Что ж, если мы думаем за пределами нашего чрезмерного примера, простой факт заключается в том, что мы всегда должны пытаться написать наши функции как можно более «универсально».
Это означает, что функция может использоваться только в контексте этого конкретного примера. Но на функцию также можно ссылаться десятки разных времен в приложении. На самом деле, некоторые функции оказываются настолько утилитарными, что мы в конечном итоге используем их в нескольких приложениях.
Вот почему лучшие функции работают как автономные, атомно единицы измерения. Это их собственное «вещь». И как таковые, они должны иметь возможность работать независимо от более широкого приложения, из которого они называются. По этой причине, я верю, религиозно, это:
Каждая функция: Программа Анкет
Конечно, не все согласны со мной на этом фронте. Они утверждают, что каждая функция – это дерево. И им нужно только беспокоиться о Вход которые предоставляются их общей программе (лес).
Это дает разработчикам удобный способ избежать головных болей кислой тестирования их кода. Они смотрят на пример выше, и они говорят что -то вроде: «Никто никогда не пропустит логическое в getPlayerName () Потому что getPlayerName () только когда -либо из -за в моей программе И я знаю, что я никогда не передам в это что -то глупое – как логический ». Или они говорят: «Никто никогда не пропустит отрицательное число в cangulateyardsperatempt () Потому что Cangulateyardsperattempempemp () только когда -либо из -за в моей программе И я знаю, что я никогда не передам в это что -то глупое – как негативное число ».
Если вы знакомы с Логические ошибки , эти контраргументы в основном попадают под Апелляция к полномочиям Анкет Эти разработчики относятся Программа как «авторитет». И они просто предполагают, что, пока вход предоставляется где -то еще В рамках той же программы , никогда не будет никаких проблем. Другими словами, они говорят: «Входные данные для этой функции будут хорошими Потому что« программа »говорит, что они в порядке ».
И это это Хорошо – пока ваше приложение не заслуживает. Но как только ваше приложение растет до такой степени, что это «реальное», надежное приложение, эта апелляция не хватает. Я не знаю, сколько раз мне приходилось устранение кода (часто … мой Код), когда я понял, что что -то терпит неудачу, потому что неправильный «вид» данных был передан в функцию – даже если данные пришли откуда -то еще Внутри той же программы Анкет
Если в проекте есть (или когда-либо) два или более разработчики, эта «логика» крайне недостаточна. Потому что это опирается на глупую идею, что кто -либо иначе Кто работает над проектом, никогда не будет называть функцию «неправильным».
Если проект (или когда -либо будет) достаточно большим, чтобы ожидать, что один разработчик будет иметь всю программу в их голове Эта «логика», опять же, крайне недостаточна. Если конечный пользователь может поместить нелепые значения в поле формы, то в равной степени верно, что другой программист может попытаться вызвать вашу функцию смешным образом. И Если логика Внутри Ваша функция настолько хрупкая, что она взрывается всякий раз, когда получает плохие данные – затем ваша функция отстой Анкет
Поэтому, прежде чем мы пойдем дальше, я хочу прояснить этот кристалл: Если ваше оправдание не подтверждает входы вашей функции, просто чтобы опираться на факт это ты Знайте все способы, которыми функция будет называться вам в Ваш Приложение, тогда нам действительно никогда не нужно быть в одной команде разработчиков. Потому что вы не кодируете таким образом, что это способствует развитию команды.
Игра в тестирование оболочки
Я обнаружил, что многие разработчики не пытаются решить проблему хрупких входов, написав кучу защитного кода. Они «решают» это, написав метрическую дерьмовую тону (технический термин) тестов.
Они напишут что -то вроде этого:
const calculatePassAttemptsPerGame = (passAttempts = 0, gamesPlayed = 0) => {
return passAttempts / gamesPlayed;
}
И затем они отмахнулись от хрупкой природы этой функции, указывая на невероятную кучу интеграционных тестов, которые они написали, чтобы гарантировать, что эта функция когда -либо вызвана только «правильным».
Чтобы быть ясным, этот подход не обязательно неправильно Анкет Но это только шунтирует реальную работу по обеспечению правильной функции применения в наборе тестов этого не существует во время выполнения Анкет
Например, может быть Рассчитывать ppassattemptspergame () это только когда -либо назывался из PlayerProfile составная часть. Следовательно, мы могли бы попытаться создать целую серию интеграционных тестов, которые гарантируют, что эта функция никогда не вызывается чем -то другим, кроме «правильных» данных.
Но этот подход трагически ограничен.
Во -первых, как я уже отметил, тестов не существует во время выполнения. Обычно они запускаются/проверяются только до развертывания. Таким образом, они по -прежнему подлежат надзору за разработчиком.
И если говорить о надзоре за разработчиками … Пытание провести кислотую тестирование этой функции с помощью интеграционных тестов подразумевает, что мы можем подумать обо всех возможных способах/местах, где можно назвать функцию. Это склонно к близорукости.
Гораздо проще (в коде) включить проверки В тот момент, когда данные должны быть подтверждены Анкет Это означает, что обычно меньше контролей, когда мы включаем подтверждения непосредственно в или после подписи функции. Итак, позвольте мне произнести это просто:
Тесты великолепны. Но они никогда не являются заменой одного на один для проверки данных.
Очевидно, я не говорю вам, чтобы отказаться от тестов на единицу/интеграции. Но если вы пишете кучу тестов, просто чтобы обеспечить правильную функциональность Когда входы функции «плохие» , тогда вы просто делаете раковину с логикой проверки. Вы пытаетесь сохранить свое приложение «чистым» – выбросив всю проверку в тестах. И по мере того, как ваше приложение растет в сложности (это означает, что: существуют более широкие способы для каждой функции), ваши тесты должны идти в ногу – или вы в конечном итоге получаете вопиющие слепые пятна в своей стратегии тестирования.
Заблуждение TypeScript
Существует большая подмножество читателей, которые читали это с дерзкой ухмылкой и подумают: «Ну, очевидно – Это это почему вы используете TypeScript!» И для тех дерзких разработчиков я бы сказал: «Да, ммм … вроде как».
Мои постоянные читатели (оба) знают, что у меня были настоящие «приключения» за последние полгода или не с TS. И я не против Т.С. Но я также настороженно относился к чрезмерным обещаниям, сделанным TS Acolytes. Прежде чем маркировать меня как Haterrr Grade-A Haterrr, ясно, где TS сияет Анкет
Когда вы передаете данные В вашем собственном приложении , TS невероятно полезен. Так, например, когда у вас есть вспомогательная функция, которая только когда -либо использовалась В данном приложении , и вы знаете, что данные (их аргументы ) только когда -либо исходят из В приложении , TS невероятно. Ты в значительной степени поймаешь все из критических ошибок, которые могут возникнуть по всему приложению всякий раз, когда называется эта вспомогательная функция.
Утилита этого довольно очевидно. Если вспомогательная функция требует ввода типа номер и в любой точке остальной части приложения вы пытаетесь назвать эту функцию с аргументом типа строка , TS сразу же будет жаловаться. Если вы используете какой -либо современный IDE, это также означает, что ваша среда кодирования сразу же будет жаловаться. Таким образом, вы, вероятно, знаете, сразу, когда вы пытаетесь написать что -то, что просто не «работает».
Довольно круто, Правильно ???
За исключением … когда эти данные исходят из снаружи приложение. Если вы имеете дело с данными API, вы можете написать все удобные определения типа TS, которые вы хотите – Но это все еще может взорваться в Runtime Если неверные данные получены. То же самое, если вы имеете дело с пользовательским вводом. То же самое, если вы имеете дело с некоторыми типами входов базы данных. В этих случаях вы по -прежнему смирились с а) написанием хрупких функций, либо б) добавляя дополнительные проверки времени выполнения в вашу функцию.
Это не какая -то стука по TS. Даже сильно используемые языки OO, такие как Java или C#, подвержены сбоям времени выполнения, если они не включают в себя правильную обработку ошибок.
Проблема, которую я замечаю, состоит в том, что Do Soumy Devs TS пишут свои данные «определения» внутри подписи функции – или внутри их интерфейсов – а затем … они закончили. Вот и все. Они чувствуют, что «сделали работу» – хотя эти великолепные определения типа даже не существует во время выполнения.
Определения TS также (сильно) ограничены основными типами данных, доступными в самом JS. Например, в коде, показанном выше, нет нативного типа данных TS, который говорит Passattempts должен быть неотрицательное целое число Анкет Вы можете обозначить Passattempts как номер , но это слабая проверка, которая все еще уязвима для функции, называемой «неправильным». Так что, если вы действительно хотите убедиться, что Passattempts Это «правильные» данные, вы все равно в конечном итоге напишите дополнительные ручные проверки.
Трипча
Есть один Больше проспекта мы могли бы исследовать, чтобы избежать защитного программирования: попытки.
Try-Catch, очевидно, имеет свое место в программировании JS/TS. Но он весьма ограничен в качестве инструмента для защитного программирования, когда речь идет о проверке входов. Это происходит потому, что Try-Catch действительно имеет смысл только тогда, когда сам JS бросает ошибку Анкет Но когда мы имеем дело с аберрантными входными данными, часто возникают варианты использования, где «плохие» данные не приводят к прямому Ошибка Анкет Это просто обеспечивает какой -то неожиданный/нежелательный выход.
Рассмотрим следующий пример:
const calculatePassAttemptsPerGame = (passAttempts = 0, gamesPlayed = 0) => {
try {
return passAttempts / gamesPlayed;
} catch (error) {
console.log('something went wrong:', error);
}
}
const attemptsPerGame = calculatePassAttemptsPerGame(true, 48);
console.log(attemptsPerGame); // 0.0208333333
Трип-карт никогда не запускается, потому что True/48 не бросает ошибку. JS “Полезно” интерпретирует истинный как 1 и функция возвращает результат 1/48 Анкет
Это не так сложно
На данный момент, для тех, кто все еще читает, вы, вероятно, думаете: «Ну, тогда … нет хорошего ответа на это. Оборонительное программирование громоздкий и медленный. Другие методы подвержены надзору и неудачам. Так… Что делать ??? “
Мой ответ в том, что защитное программирование не нужно быть так трудно. Некоторые люди читают «оборонительное программирование» как «подтвердить все входы» – и они приходят к выводу, что проверка Все Входные данные должны, по определению, быть кошмаром. Но это не так.
Я писал ранее о том, как Я сделать проверку времени выполнения на Все моих функций, которые принимают входные данные. И для меня, это легко Анкет (Если вы хотите прочитать об этом, статья здесь: https://dev.to/bytebodger/better-typescript-with-javascript-4ke5 )
Ключ должен сделать встроенные проверки быстрыми, простыми, и кратко Анкет Никто не хочет загромождать каждую из своих функций с помощью 30 дополнительных локаций проверки. Но – тебе не нужно.
Чтобы дать вам осязаемый пример моего подхода, рассмотрите следующее:
import allow from 'allow';
const calculatePassAttemptsPerGame = (passAttempts = 0, gamesPlayed = 0) => {
allow.anInteger(passAttempts, 0).anInteger(gamesPlayed, 1);
return passAttempts / gamesPlayed;
}
Все время выполнения Валидация для этой функции обрабатывается в одной строке:
Passattemptsдолжно быть целое число, с минимальным значением0АнкетИгрные игрыТакже должно быть целое число, с минимальным значением1Анкет
Вот и все. Не нужно. Нет причудливых библиотек. Ни один код спагетти не втиснулся на каждую функцию, чтобы вручную проверить все аргументы. Просто один звонок разрешить , это может быть приковано, если в функции ожидаются аргументы с двумя или местами.
Чтобы быть абсолютно ясным, это не Какая-то (многословная) реклама для моей глупой, маленькой, доморощенной библиотеки проверки. Мне все равно который Библиотека, которую вы используете – или вы закатываете свой собственный. Дело в том, что время выполнения Валидация не должна быть такой тяжелой. Это не должно быть многословным. И это может обеспечить гораздо большую общую безопасность вашего приложения, чем любой инструмент только для компиляции.
Высокомерие укоренившихся
Так должен ты пересмотреть любые отвращения, которые вы должны «для оборонительного программирования» ?? Хорошо , ммм … вероятно, нет.
Я понимаю, что у вас, вероятно, уже есть работа, где вам платят за программу. И в этой работе вы, вероятно, уже работаете с другими программистами, которые устанавливают все свои идеи кодирования в камне лет назад Анкет Они уже позволили этим программированию бромидов глубоко погрузиться в свою душу. И если вы поставите под сомнение что -нибудь из этого, вы Наверное быть сбитым – и тихо презирается.
Не верите мне? Просто посмотрите на статью, с которой я связал выше. В комментариях было несколько хороших отзывов. Но один, ммм … “Джентльмен” решил ответить ничего, кроме: “yuck …”
Вот и все. Нет конструктивной обратной связи. Нет рациональной логики. Просто: “ты …”
И это В основном это то, что так много программирования сводится к этим дням. Вы можете разработать способ сделать ядерный слияние просто путем написания кода JavaScript. Но кто -то придет, без каких -либо дополнительных объяснений, и просто скажет: «Юк …»
Так… Я получить Это. Я действительно. Продолжайте писать свой TS. И ваши обильные тесты. И продолжайте отказываться проверять входы вашей функции. Потому что это было бы «защитное программирование». И защитное программирование – это плохой , mmmmkay ????
И я буду продолжать писать приложения, которые Подробнее Ошибочный, с меньше строки кода.
Оригинал: “https://dev.to/bytebodger/in-defense-of-defensive-programming-k45”