Информатика - это не больше компьютеров, чем астрономия - телескопы.

Около года назад я впервые познакомился с языком программирования Ruby. Пока я читал о некоторых причинах, по которым людям нравится его использовать, одно прилагательное повторялось снова и снова: « выразительный».

В то время мне больше всего нравился язык программирования Python, и если вы хоть немного знакомы с Python, то знаете, что его синтаксис важнее всего ясности. Как говорится в Дзен Python: Явное лучше, чем неявное.

По этой причине меня несколько смутило, что люди имели в виду, когда описывали синтаксис Ruby как «выразительный». По сравнению с Python, у Ruby есть миллион способов выполнить любую задачу, что (я чувствовал) увеличивает когнитивную нагрузку при чтении кода Ruby и, следовательно, снижает его «выразительность» (что бы это ни значило).

Однако после того, как в течение последнего года или около того я вертел в голове эту концепцию «выразительного кода», я пришел к тому моменту, когда мне казалось, что я понимаю, что люди имеют в виду, когда они используют этот термин: грубо говоря, это должно делать насколько хорошо код выражает намерения программиста другому человеку, который его читает.

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

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

Я думаю, что это одна из основных идей, лежащих в основе «выразительного» кода. Когда мы пишем код, мы можем писать его таким образом, чтобы не только давать правильные инструкции компьютеру, но и ясно сообщать о наших намерениях следующему человеку, который читает наш код.

Чтобы проиллюстрировать эту идею, предположим, что мне нужно написать функцию, которая будет использовать все гласные в данной строке с заглавной буквы (я не уверен, зачем нам это нужно, но давайте продолжим). В JavaScript я мог бы написать что-то вроде этого:

function capitalizeVowels(string) {
  const VOWELS = ['a', 'e', 'i', 'o', 'u'];
  let result = '';
  for (let i = 0; i < string.length; i++) {
    let nextChar = string[i];
    if (VOWELS.includes(nextChar)) {
      nextChar = nextChar.toUpperCase();
    }
    
    result += nextChar;
  }
  return result;
}

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

Чтобы решить эту проблему, обычно помещают в функцию оператор if, чтобы убедиться, что переданный аргумент действительно является строкой. Наивно, я мог бы сделать что-то вроде этого:

function capitalizeVowels(string) {
  if (typeof string === 'string') {
    const VOWELS = ['a', 'e', 'i', 'o', 'u'];
    let result = '';
    for (let i = 0; i < string.length; i++) {
      let nextChar = string[i];
      if (VOWELS.includes(nextChar)) {
        nextChar = nextChar.toUpperCase();
      }
    
      result += nextChar;
    }
    return result;
  } else {
    return undefined;
  }
}

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

Как мы можем изменить этот код, чтобы лучше выразить намерение функции, сосредоточив внимание на том случае, когда ввод является строкой, какой она должна быть? Один из способов добиться этого - использовать защитное условие, которое позволяет функции «выйти из строя раньше», если произойдет что-то нежелательное. Вот как бы выглядела функция, если бы мы реорганизовали ее для использования защитного условия:

function capitalizeVowels(string) {
  if (typeof string !== 'string') return undefined;
  const VOWELS = ['a', 'e', 'i', 'o', 'u'];
  let result = '';
  for (let i = 0; i < string.length; i++) {
    let nextChar = string[i];
    if (VOWELS.includes(nextChar)) {
      nextChar = nextChar.toUpperCase();
    }
    
    result += nextChar;
  }
  return result;
}

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

Это может показаться не очень большим улучшением, но это может иметь огромное значение для ясности, если есть более сложные условия, которые нужно проверить. Например, предположим, что мы пытаемся написать функцию, которая принимает массив чисел и отфильтровывает все числа, не кратные 3. В версии JavaScript ES2015 это можно очень кратко записать следующим образом:

function getMultiplesOf3(array) {
  return array.filter(number => number % 3 === 0);
}

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

function getMultiplesOf3(array) {
  if (!Array.isArray(array)) {
    return undefined
  } else {
    if (!array.every(item => typeof item === 'number')) {
      throw new Error('Array has a non-number value');
    } else {
      return array.filter(number => number % 3 === 0);
    }
  }
}

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

function getMultiplesOf3(array) {
  if (!Array.isArray(array)) return undefined;
  if (!array.every(item => typeof item === 'number') {
    throw new Error('Array has a non-number value');
  }
  return array.filter(number => number % 3 === 0);
}

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

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

Удачного кодирования!

Если вам понравилась эта статья, возможно, вам также понравится узнать о:

Дэниел Кинг - профессиональный инженер-программист и преподаватель из Лос-Анджелеса, который занимается разработкой программного обеспечения и проводит инструктаж по контракту.

[email protected]