Вы слышали, как многие программисты шутят о том, что JavaScript не является «настоящим» языком программирования.

Помимо отсутствия системы наследования на основе классов, JS также имеет динамическую типизацию, в отличие от C#, Java, Scala… вы знаете, всех этих реальных языков программирования. Но именно скорость, гибкость и простота использования привели нас к написанию JS! Будь прокляты типы!

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

Использовать параметры по умолчанию

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

const addNameToNameArray = (name, nameArray) => {
  if(name.length){
   nameArray.push(name)
  }
  return nameArray
}

Достаточно просто, верно? Мы берем имя и массив имен, а затем просто добавляем имя в массив, если оно присутствует. Однако здесь мы делаем некоторые предположения, что имя будет иметь тип со свойством length и что наше nameArray будет, ну, в общем, массивом. Если хотя бы одно из этих условий не будет выполнено, мы потерпим неудачу.

const addNameToNameArray = (name = '', nameArray = []) => {
  if(name.length){
   nameArray.push(name)
  }
  return nameArray
}

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

Использование объекта в качестве аргумента

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

const createUser = (name, date, profileId, dataId, dataType, apiKey) => {
  const user = {
   name,
   profileId
  };
  const formattedDate = moment(date).unix();
  thirdPartyAPI.fetch(user, formattedDate, dataId, dataType, apiKey)
}
createUser('Bob', '04/20/20', 1, 123, 1234, 'story', 'abc123')

Опасность таких функций заключается в том, что порядок аргументов имеет значение, и если какой-либо из них неверен, мы рискуем испортить наш вызов API. Чем больше аргументов, тем больше площадь поверхности для ошибок. Почти неизбежно, что какой-нибудь разработчик вызовет эту функцию, и profileId будет вместо dataId. Теперь это не взорвет вашу программу, поскольку они оба могут быть числами или строками, но их неправильный порядок даст вам неправильные данные из API или, что еще хуже, просто не сработает.

const createUser = ({name, date, profileId, dataId, dataType, apiKey}) => {
  const user = {
   name,
   profileId
  };
const formattedDate = moment(date).unix();
  thirdPartyAPI.fetch(user, formattedDate, dataId, dataType, apiKey)
}
createUser({
  name: 'Bob',
  date: '04/20/20',
  profileId: 1,
  dataId: 123,
  dataType: 'story'
  apiKey: '123abc'
})

Бум. Используя объект, мы на самом деле возлагаем меньшую когнитивную нагрузку на наших коллег-разработчиков и следим за тем, чтобы наши аргументы были заданы явно. Я чувствую, что для функций с более чем 4 аргументами использование объекта является обязательным.

Необязательная цепочка

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

const hat = user.outfit.hat;

Безвредно, верно? Что ж, если у нашего пользовательского объекта нет свойства outfit или это свойство undefined, тогда у нас возникнет проблема.

TypeError: Cannot read property 'hat' of undefined

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

const hat = user && user.outfit && user.outfit.hat || 'hat';

Нам немного грустно писать это, и чертовски менее сексуально. К счастью, с мощью Babel разработчики JS могут воспользоваться тем, что разработчики Ruby и Coffeescript имели в течение многих лет: необязательную цепочку

const hat = user?.outfit?.hat || 'hat'

Ммммм, доволен. Теперь, если наш обход в какой-то момент завершится ошибкой, наша операция замкнется и вернет undefined. Код и безопаснее, и читабельнее.

Лучшее нападение — хорошая защита

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