Пять советов по написанию качественных условных выражений в JavaScript

  • 29 марта, 19:36
  • 2927
  • 0

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

1. Используйте Array.includes для нескольких критериев

// condition
function test(fruit) {
  if (fruit == 'apple' || fruit == 'strawberry') {
    console.log('red');
  }
}

На первый взгляд, код выше не содержит никаких проблем. Но если мы добавим больше красных фруктов (например, cherry и cranberries)? Вы предложите добавить несколько условий с оператором ||?

Лучше воспользоваться Array.includes.

function test(fruit) {
  // extract conditions to array
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  if (redFruits.includes(fruit)) {
    console.log('red');
  }
}

С таким подходом мы выносим red fruits(условие) в отдельный массив. Так код выглядит чище.

2. Применяйте return

Добавим два условия  к предыдущему примеру:

  1. если не встречается фрукт, выбрасывать ошибку;
  2. принимать и выводить название плодов, количество которых превышает 10.

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // condition 1: fruit must has value
  if (fruit) {
    // condition 2: must be red
    if (redFruits.includes(fruit)) {
      console.log('red');

      // condition 3: must be big quantity
      if (quantity > 10) {
        console.log('big quantity');
      }
    }
  } else {
    throw new Error('No fruit!');
  }
}

// test results
test(null); // error: No fruits
test('apple'); // print: red
test('apple', 20); // print: red, big quantity

Посмотрим на код. Мы имеем:

  1. один if/else выражение фильтрующего ошибку;
  2. три уровня вложения if (условие 1, 2 и 3).

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

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  // condition 1: throw error early
  if (!fruit) throw new Error('No fruit!');


  // condition 2: must be red
  if (redFruits.includes(fruit)) {
    console.log('red');

    // condition 3: must be big quantity
    if (quantity > 10) {
      console.log('big quantity');
    }
  }
}

Так мы убираем лишний уровень вложенности. Такой формат особенно удобен, если тело if-выражения громоздкое (представьте, что вам необходимо скролить до самого конца, чтобы узнать есть ли else).

С инверсией мы еще больше уменьшим вложения. Посмотрите на условие 2, чтобы понять как это работает:

/_ return early when invalid conditions found _/

function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];

  if (!fruit) throw new Error('No fruit!'); // condition 1: throw error early
  if (!redFruits.includes(fruit)) return; // condition 2: stop when fruit is not red

  console.log('red');

  // condition 3: must be big quantity
  if (quantity > 10) {
    console.log('big quantity');
  }
}

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

Всегда старайтесь уменьшить количество вложений с return. Но не переусердствуйте.

Если интересно, существует много публикаций и обсуждений на StackOverflow по этой теме:

  1. Сначала return, избегайте else
  2. Дискуссия на Stack Overflow по стилю условных выражений

3. Используйте параметры функций по умолчанию и деструктуризацию

Вы, наверное, уже встречали такой код. При работе с JavaScript необходимо проверять переменные на null / undefined и присваивать значения по умолчанию:

function test(fruit, quantity) {
  if (!fruit) return;
  const q = quantity || 1; // if quantity not provided, default to one
  console.log(`We have ${q} ${fruit}!`);
}

//test results
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!

С параметрами функций по умолчанию можно избежать переменной q.

function test(fruit, quantity = 1) { // if quantity not provided, default to one
  if (!fruit) return;
  console.log(`We have ${quantity} ${fruit}!`);
}

//test results
test('banana'); // We have 1 banana!
test('apple', 2); // We have 2 apple!

Такой код намного проще в понимании, не так ли?

Каждый параметр может иметь особое значение по умолчанию. Например, мы можем присвоить значение по умолчанию и для fruit также: function test(fruit = 'unknown', quantity = 1).

 fruit это объект? Будет ли у него значение по умолчанию?

function test(fruit) { 
  // printing fruit name if value provided
  if (fruit && fruit.name)  {
    console.log (fruit.name);
  } else {
    console.log('unknown');
  }
}


//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

Взгляните на этот пример. Мы хотим вывести название фрукта, если это возможно, или же результатом будет unknown. Можно избежать условие fruit && fruit.name. Здесь на помощь приходят параметры функции по умолчанию и деструктуризация.

// destructing - get name property only
// assign default empty object {}
function test({name} = {}) {
  console.log (name || 'unknown');
}

//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

Поскольку нам нужна лишь свойство name объекта fruit, мы можем деструктуризуваты этот параметр так: {name}. Потом можем использовать name как переменную в нашем коде вместо выражения fruit.name.

Затем по умолчанию присваиваем пустой объект {}. Если этого не сделаем, то получим ошибку при исполнении: test(undefined) - Cannot destructure property name of 'undefined' or 'null'., потому что в undefined не существует свойства name.

Если вы не против сторонних библиотек, существуют способы сократить проверку на null:

  1. используйте get функцию по Lodash;
  2. используйте библиотеку с открытым исходным кодом idx от Facebook (с Babel.js).

Ниже пример использования Lodash:

function test(fruit) {
  console.log(__.get(fruit, 'name', 'unknown'); // get property name, if not available, assign default value 'unknown'
}

//test results
test(undefined); // unknown
test({ }); // unknown
test({ name: 'apple', color: 'red' }); // apple

4. Отдавайте предпочтение Map/литералу объекта перед Switch-выражениями

Посмотрим на пример ниже. Здесь мы выводим фрукты в соответствии с цветом.

function test(color) {
  // use switch case to find fruits in color
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];
  }
}

//test results
test(null); // []
test('yellow'); // ['banana', 'pineapple']

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

// use object literal to find fruits in color
  const fruitColor = {
    red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum']
  };

function test(color) {
  return fruitColor[color] || [];
}

В качестве альтернативы попробуйте Map:

// use Map to find fruits in color
  const fruitColor = new Map()
    .set('red', ['apple', 'strawberry'])
    .set('yellow', ['banana', 'pineapple'])
    .set('purple', ['grape', 'plum']);

function test(color) {
  return fruitColor.get(color) || [];
}

Map доступный в JavaScript с ES2015. Позволяет хранить пары «ключ/значение».

Стоит избегать switch?  Желательно использовать литералы объектов всюду, где это возможно. Но это не жесткое правило. Используйте то, что будет более приемлемым для вашего случая.

Рефакторинг

Мы можем сделать рефакторинг нашего кода с Array.filter.

 const fruits = [
    { name: 'apple', color: 'red' }, 
    { name: 'strawberry', color: 'red' }, 
    { name: 'banana', color: 'yellow' }, 
    { name: 'pineapple', color: 'yellow' }, 
    { name: 'grape', color: 'purple' }, 
    { name: 'plum', color: 'purple' }
];

function test(color) {
  // use Array filter to find fruits in color

  return fruits.filter(f => f.color == color);
}

Всегда существует более, чем один способ получить одинаковый результат. У нас было четыре варианта.

5. Используйте Array.every& Array.some для критериев

Последний совет касается использования (относительно) новых функций массивов в JavaScript для уменьшения количества строк кода. Посмотрите на код ниже: мы хотим проверить все фрукты красного цвета.

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

function test() {
  let isAllRed = true;

  // condition: all fruits must be red
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color == 'red');
  }

  console.log(isAllRed); // false
}

Этот код такой длинный! Мы можем уменьшить количество строк с Array.every:

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
  ];

function test() {
  // condition: short way, all fruits must be red
  const isAllRed = fruits.every(f => f.color == 'red');

  console.log(isAllRed); // false
}

Теперь код выглядит чище. Реализуем в одну строку проверку условия с Array.some.

const fruits = [
    { name: 'apple', color: 'red' },
    { name: 'banana', color: 'yellow' },
    { name: 'grape', color: 'purple' }
];

function test() {
  // condition: if any fruit is red
  const isAnyRed = fruits.some(f => f.color == 'red');

  console.log(isAnyRed); // true
}


Теги: код javascript
0 комментариев
Сортировка:
Добавить комментарий

IT Новости