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

Это прямое применение многих устоявшихся принципов программирования. Это включает в себя KISS, разделение задач, принцип единой ответственности и многое другое.

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

Эта статья покажет вам, как его применять. Также будет рассмотрено, почему этот принцип важен и как он делает ваш код лучше.

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

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

Пример 1, плохой вариант:

function main() {
  const data = getData();
  makeRequest(data);
}

function makeRequest(data) {
  if (isValid(data)) {
    fetch('https://myfakeapi.com/', {
      method: 'POST', body: JSON.stringify(data)
    });
  } else {
    fetch('https://errormonitoringservice.com/', {
      method: 'POST', body: JSON.stringify(data)
    });
  }
}

Пример 2, хорошая версия:

function main() {
  const data = getData();
  if (isValid(data)) {
    makeRequest(data);
  } else {
    reportError(data);
  }
}

function makeRequest(data) {
  fetch('https://myfakeapi.com/', {method: 'POST', body: JSON.stringify(data)});
}
function reportError(data) {
  fetch('https://errormonitoringservice.com/', {method: 'POST', body: JSON.stringify(data)});
} 

Давайте посмотрим, почему пример 1 хуже.

Примечание. В этой статье под единицей понимается функция/метод/модуль/класс. Мы будем использовать функции, но можно использовать любую из них.

Небольшие независимые подразделения

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

Это безопасный код, который вы вызываете, чтобы помочь вам что-то сделать.

Когда вы вызываете его, вы знаете, что он будет делать, и вы знаете, что он ничего не сломает.

Это должно быть похоже на хорошо протестированную библиотеку, которую вы импортировали в свой проект. Он делает что-то маленькое и конкретное, и вы ожидаете, что он будет работать в 100% случаев.

Для этого используются такие виды юнитов:

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

Примеры хороших единиц кода

Вот несколько примеров таких хороших юнитов:

function add(a, b) {
  return a + b;
}

function getProperty(object, propertyName) {
  return object[propertyName];
}

function appendElementToBody(element) {
  document.body.append(element);
}

function doubleSpeed(gameObject) {
  gameObject.speed = gameObject.speed * 2;
}

function incrementSpeedDamaged(gameObject) {
  gameObject.speed = gameObject.speed + 0.5;
}
function incrementSpeed(gameObject) {
  gameObject.speed = gameObject.speed + 1;
}

Обратите внимание, что эти единицы:

  • не иметь условных выражений (операторы if / else)
  • делать очень мало
  • не читать/писать ни во что, кроме своих параметров (кроме appendElementToBody, т.к. объект document — глобальный синглтон)
  • имеют побочные эффекты только в том случае, если они не делают ничего другого

Для сравнения, вот некоторые устройства, которые не следуют этим рекомендациям:

const valueToAdd = 5;
function add(x) {
  return valueToAdd + x;
}

const object = {/* has some properties here*/};
function getProperty(propertyName) {
  return object[propertyName]
}

function appendElementToBody(element) {
  if (element.id === 'foo') {
    return; // do nothing
  }
  document.body.append(element);
}

let shouldDouble = true;
function doubleSpeed(gameObject) {
  if (shouldDouble) {
    gameObject.speed *= 2;
  })
}

function incrementSpeed(gameObject, isDamaged) {
  if (isDamaged) {
    gameObject.speed += 0.5;
  } else {
    gameObject.speed += 1;
  }
}

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

Но сначала давайте рассмотрим преимущества и недостатки рекомендаций в целом. Какие преимущества вы получаете от хороших примеров кода, а не от плохих?

Преимущества хороших единиц кода

Если вы будете следовать рекомендациям, вы получите преимущества хорошего кода. Такие вещи, как:

  • код, который легко понять
  • код, который работает правильно, предсказуемо, без непредвиденных последствий
  • код, который легко использовать повторно
  • код, который легко изменить
  • код, который легко тестировать

Если вы используете плохие версии, вы получите обратное. Такие вещи, как:

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

Далее посмотрим, как приведенные примеры влияют на эти преимущества/недостатки.

Изучение примеров единиц кода и их преимуществ

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

Пример: добавить

Функция add тривиально проста.

function add(a, b) {
  return a + b;
}

Тем не менее, он хорошо демонстрирует суть хороших юнитов. Эта функция:

  • чрезвычайно прост для понимания
  • многоразовый каждый раз, когда вам это нужно
  • чрезвычайно легко проверить

Одна вещь, которую вы можете задать, это «ну и что»? Зачем вам функция add, когда вы можете просто добавлять что-то в строку, когда вам нужно?

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

Кроме того, add просто демонстрирует принцип. Вместо add у вас может быть какая-то реальная функциональность, которая внутри работает точно так же, как add. Например, у вас может быть функция formUserGreeting(username, userFlair), которая может объединять (добавлять) username и userFlair вместе.

Вот плохая версия кода add:

const valueToAdd = 5;
function add(x) {
  return valueToAdd + x;
}

Эта версия намного хуже.

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

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

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

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

Так что в целом все намного хуже.

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

Наконец, не требуется никаких усилий, чтобы получить лучшую версию, так что она вполне может быть у вас.

Пример: получить свойство

Далее идет пример getProperty.

Вот код хорошей версии:

function getProperty(object, propertyName) {
  return object[propertyName];
}

Вот код плохой версии:

const object = {/* has some properties here*/};
function getProperty(propertyName) {
  return object[propertyName]
}

Преимущества/недостатки такие же, как у примера add.

Хорошая версия:

  • 100% предсказуемый
  • Легко понять
  • легко повторно использовать
  • легко проверить

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

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

Пример: appendElementToDom

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

Вот хорошая версия:

function appendElementToBody(element) {
  document.body.append(element);
}

Вот плохая версия:

function appendElementToBody(element) {
  if (element.id === 'foo') {
    return; // do nothing
  }
  document.body.append(element);
}

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

Подумайте, если вы используете функцию с именем appendElementToBody, что вы ожидаете от нее?

Вы, вероятно, ожидаете, что он будет добавлять элемент HTML к элементу body в 100% случаев, а не только в некоторых случаях.

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

Проблема с этим кодом заключается в следующем сценарии:

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

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

Скорее всего, вы будете отслеживать код с самого начала, когда пользователь нажимает «отправить» для новой задачи. appendElementToBody — это последняя функция, которая запускается, поэтому вы можете не проверять ее в течение длительного времени.

Этот пример очень маленький и тривиальный. Маловероятно, что у вас возникнут проблемы с проверкой того, имеет ли элемент идентификатор foo.

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

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

В любом случае, хватит болтовни. Дело в том, что не делайте этого.

Возможные улучшения

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

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

Явные условные операторы, по крайней мере, предсказуемы. Что-то вроде этого было бы лучше:

function appendElementToBody(element, excludedSelectors) {
  for (let i = 0; i < excludedSelectors.length; i++) {
    const selector = excludedSelectors[i];
    if (document.querySelector(selector)) {
      return; // exit the function and do nothing
    }
  }
  document.body.append(element);
}

Лучшим вариантом может быть изменение имени функции, чтобы ее функциональность была очевидна:

function maybeAppendElementToBody(element, excludedSelectors) {
  for (let i = 0; i < excludedSelectors.length; i++) {
    const selector = excludedSelectors[i];
    if (document.querySelector(selector)) {
      return; // exit the function and do nothing
    }
  }
  document.body.append(element);
}

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

Но для лучших улучшений учтите:

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

Например, у вас может быть что-то вроде этого:

// Extremely simple TODO creator with very basic code

const todos = [];

function handleNewTodoSubmit(event) {
  event.preventDefault();

  // read the DOM to see what the user has typed as the TODO title
  const title = document.querySelector('#todo-input').value;

  // condition is checked here (albeit slightly altered to the original)
  if (!doesTodoTitleAlreadyExist(todos, title)) {
    const todo = createTodoObject(title);
    todos.push(todo);
    displayTodo(todo);
  }
}

function doesTodoTitleAlreadyExist(todos, title) {
  function hasTargetTitle(todo) {
    return todo.title === title;
  }
  return todos.some(hasTargetTitle); // returns true if any of a todo in the array has the same title
}

function createTodoObject(title) {
  return { title };
}

function displayTodo(todo) {
  const todoElement = createTodoElement(todo);
  appendElementToBody(todoElement);
}

function createTodoElement(todo) {
  const todoElement = document.createElement('div');
  todoElement.id = todo.title;
  todoElement.textContent = todo.title;
  return todoElement;
}

function appendElementToBody(element) {
  document.body.append(element);
}

const todoForm = document.querySelector('#todo-form')
todoForm.addEventListener('submit', handleNewTodoSubmit);

В этом примере кода каждая функция, включая appendElementToBody, делает то, что вы ожидаете, в 100% случаев.

Проверка задачи была перемещена с appendElementToBody на handleNewTodoSubmit. Это гораздо более подходящее место для этого.

Правильный способ думать об этом состоит в том, что задачу не следует создавать, если она уже существует. Это домен функции handleNewTodoSubmit, а не функции appendElementToBody.

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

Пример: двойная скорость

Код хорошей версии doubleSpeed:

function doubleSpeed(gameObject) {
  gameObject.speed = gameObject.speed * 2;
}

Код плохой версии doubleSpeed:

let shouldDouble = true;
function doubleSpeed(gameObject) {
  if (shouldDouble) {
    const currentSpeed = gameObject.speed;
    gameObject.speed = currentSpeed * 2;
  })
}

Этот пример аналогичен примеру appendElementToBody.

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

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

Преимущества хорошей версии кода заключаются в том, что она:

  • предсказуем, легко отслеживается и с меньшей вероятностью содержит странные ошибки, зависящие от странного состояния и времени.
  • Легко понять
  • многоразовый. Вы можете повторно использовать эту функцию в любом месте кодовой базы. Однако вы не можете повторно использовать плохую версию, если вам не нужны точно такие же условия.
  • легко проверить. Плохую версию практически невозможно протестировать (потому что ваш тестовый файл не может изменить переменную shouldDouble, если только вы не приложите много усилий, чтобы обойти это).

Пример: увеличение скорости

Этот пример демонстрирует, почему вам следует избегать логических параметров.

Вот хорошая версия кода:

function incrementSpeedDamaged(gameObject) {
  gameObject.speed = gameObject.speed + 0.5;
}
function incrementSpeed(gameObject) {
  gameObject.speed = gameObject.speed + 1;
}

Вот плохая версия кода:

function incrementSpeed(gameObject, isDamaged) {
  if (isDamaged) {
    gameObject.speed += 1;
  } else {
    gameObject.speed += 0.5;
  }
}

Булев параметр имеет значение?

Да. В этом примере не так много, но определенно хуже.

Одна проблема с булевыми параметрами заключается в том, что они умножают количество путей кода в функции. Другими словами, там есть оператор if / else.

Например:

function (booleanParameter) {
  if (booleanParameter) {
    doSomething();
  } else {
    doSomethingElse();
  }
}

Каждый дополнительный логический параметр может удвоить количество возможных путей кода.

Например, с двумя булевыми параметрами код может выглядеть так. Обратите особое внимание на функцию sendData:

function sendData(data, isValid, isDataFormatted) {
  if (isDataValid) {
    if (!isDataFormatted) {
      data = formatData(data);
    }
    fetch('https://myfakeapi.com', {method: 'POST', body: JSON.stringify(data)})
  } else {
    if (!isDataFormatted) {
      data = formatInvalidData(data);
    }
    fetch('https://myfakeapi.com/errors', {method: 'POST', body: JSON.stringify(data)})
  }
}

function formatData(data) {
  return data.split('');
}

function formatInvalidData(data) {
  return 'Error: ' + data;
}

function main() {
  const data = '123'; // get data from somewhere
  const isDataValid = validateData(data);
  const isDataFormatted = false;
  sendData(data, isDataValid, isDataFormatted);
}

Функция sendData довольно сложная. Его трудно понять и прочитать. Он имеет вложенные условные операторы, которые усложняют понимание кода и работу с ним.

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

Это также сложно проверить. Вам нужны тесты, охватывающие все возможные пути кода.

Короче говоря, с ним сложно работать, и в будущем это может стать еще сложнее.

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

function sendData(data) {
  fetch('https://myfakeapi.com', {method: 'POST', body: JSON.stringify(data)});
}
function reportDataError(data) {
  fetch('https://myfakeapi.com/errors', {method: 'POST', body: JSON.stringify(data)});
}
function formatData(data) {
  return data.split('');
}
function formatIvalidData(data) {
  return 'Error: ' + data;
}
function main() {
  const data = '123'; // get data from somewhere
  const isDataValid = validateData(data);
  if (isDataValid) {
    const formattedData = formatData(data);
    sendData(formattedData);
  } else {
    const formattedData = formatInvalidData(data);
    reportDataError(formattedData);
  }
}

Обратите внимание, что функция sendData теперь тривиально проста.

Вы можете подумать: «Но ведь эти условия только что переместились в функцию main, разве это не то же самое?» Это справедливый аргумент. Тем не менее, этот код все же имеет некоторые преимущества. В этой версии:

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

Более важная причина заключается в том, как хорошая версия кода может расти завтра по сравнению с плохой версией кода.

Например, если завтра возникнут новые условия, хорошая версия кода может закончиться так:

// We've kept the unit functions like sendData, but they're omitted for brevity

// More simple functions for new use-cases
function validateDataADifferentWay(data) {}
function validateSpecialData(data) {}

function main1() {
  const data = '123'; // get data from somewhere
  const isDataValid = validateData(data);
  if (isDataValid) {
    const formattedData = formatData(data);
    sendData(formattedData);
  } else {
    const formattedData = formatInvalidData(data);
    reportDataError(formattedData);
  }
}

function main2() {
  const data = '123'; // get data from somewhere, it should always be valid
  const speciallyFormattedData = formatDataADifferentWay(data);
  sendData(speciallyFormattedData);
}

function main3() {
  const data = '123'; // get data from somewhere
  const isDataValid = validateSpecialData(data);
  if (isDataValid) {
    const formattedData = formatData(data);
  } else {
    const formattedData = formatInvalidData(data);
    reportDataError(formattedData);
  }
}

Это очень хорошо.

Функции юнитов, которые у нас были, остались на 100% такими же. Мы обрабатываем новые условия в различных main функциях, которые не слишком сложны. Для новых специфических функций мы создали новые юнит-функции validateSpecialData и formatDataADifferentWay. (Для краткости мы опустили реализации.)

Однако с плохой версией кода дела обстоят не так хорошо. Каждое новое условие будет обрабатываться в sendData. В результате sendData станет намного сложнее.

Рассмотрим этот пример, где мы добавляем логический параметр needsSpecialFormatting. Это флаг, который говорит, что мы должны форматировать данные по-другому:

function sendData(data, isValid, isDataFormatted, needsSpecialFormatting) {
  if (isValid) {
    if (!isDataFormatted) {
      if (needsSpecialFormatting) {
        data = formatDataADifferentWay(data);
      } else {
        data = formatData(data);
      }
    }
    fetch('https://myfakeapi.com', {method: 'POST', body: JSON.stringify(data)})
  } else {
    if (!isDataFormatted) {
      if (needsSpecialFormatting) {
        formattedData = formatDataADifferentWay(data);
      } else {
        formattedData = formatInvalidData(data);
      }
    }
    fetch('https://myfakeapi.com/errors', {method: 'POST', body: JSON.stringify(data)})
  }
}

function main1() {
  const data = '123'; // get data from somewhere
  const isDataValid = validateData(data);
  const isDataFormatted = false;
  sendData(data, isDataValid, isDataFormatted, false);
}

function main2() {
  const data = '123'; // get data from somewhere, it will always be valid
  const speciallyFormattedData = formatDataADifferentWay(data);
  sendData(data, true, false, true);
}

function main3() {
  const data = '123'; // get data from somewhere
  const isDataValid = validateSpecialData(data);
  if (isDataValid) {
    sendData(data, true, false, false);
  } else {
    sendData(data, false, false, false);
  }
}

Как видите, с еще одним логическим параметром sendData становится намного сложнее. Все станет еще хуже, если будет добавлено больше параметров.

Вдобавок ко всему, даже на призыв sendData(data, true, false, false) трудно смотреть. Это умственное упражнение, пытающееся сопоставить каждое логическое значение с параметром, который оно представляет. Это можно улучшить, заставив sendData вместо этого принимать объект, но это требует больше усилий, чем простая версия.

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

Наконец, эта функция нарушает многие принципы программирования, потому что:

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

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

Возвращаясь к принципам программирования

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

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

Кроме того, тот, кто хорошо понимает эти принципы, естественным образом создаст блоки кода, подобные описанным в этой статье.

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

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

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

Применение этих рекомендаций к другим единицам кода

Мы рассмотрели функции на границе приложения, потому что они могут быть простыми. Другие функции могут быть более сложными.

Как показано в примерах, функции более высокого уровня могут иметь условные операторы и могут быть длиннее.

Как бы хорошо ни было вообще избегать условных выражений, это просто невозможно.

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

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

Например:

function handleFormSubmit(event) {
  event.preventDefault(); // necessary to handle form submission with JavaScript, rather than HTML
  const data = getDataFromForm();
  const formattedData = formatData(data);
  sendData(formattedData);
}

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

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

В любой момент времени подумайте, является ли ваш код:

Будьте прагматичны

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

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

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

Заключительные заметки

Это все для этой статьи.

Я надеюсь, что вы нашли это полезным, и я надеюсь, что концепции и причины для них имеют смысл.

Каково ваше мнение о том, какими должны быть блоки кода, особенно блоки на «крае» вашего приложения? Есть ли что-то, с чем вы не согласны? Что-то не прикрытое? Если есть что-то, пожалуйста, оставьте комментарий ниже.

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

Первоначально опубликовано на https://programmingduck.com 12 марта 2021 г.