Как то, как мы пишем наш код, может настроить будущих разработчиков на успех?

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

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

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

Синтаксис толстой стрелки в JavaScript 🏹

Здесь мы будем использовать Javascript и React, однако общие принципы применимы далеко за их пределами. Чтобы следовать этой статье, вам нужно понять одну часть синтаксиса, специфичного для Javascript, и это функция жирной стрелки. Во-первых, мы рассмотрим его с явным возвратом:

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

Есть более короткий способ написать это. Опуская фигурные скобки и ключевое слово return, мы можем немного сократить жирную стрелку в этом случае.

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

Общий шаблон

Давайте посмотрим на распространенный шаблон в React. Здесь мы будем использовать воображаемый компонент NumberInput, который обновляет некоторое состояние при его обновлении.

Передача функции толстой стрелки в реквизит onChange для NumberInput представляет собой очень распространенный способ передачи логики в React. Он короткий, лаконичный и находится близко к тому месту в коде, где он фактически используется. Некоторые разработчики предпочли бы выделить функцию onChange в отдельную переменную, но сегодня мы сосредоточимся не на этом.

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

Теперь возникает вопрос: скажем, через 6 месяцев мы хотим реорганизовать этот код, чтобы вывести текущее время до и после его обновления. Как мы поступим с возвращаемым значением? Как правильно обновить существующую логику, ничего не сломав?

Без проверки того, как работает NumberInput, есть только одно «правильное» решение, которое, как мы можем быть уверены, не сломается:

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

Итак, почему мы пытаемся сохранить, чтобы мы могли вернуться? Ответ прост: мы не знаем, к каким проблемам может привести отсутствие возврата.

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

Бесстрашный рефакторинг

Кризисные изменения — это не обязательно плохо. Проблема возникает, когда они неожиданны.

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

1. Недвусмысленно сообщите о своей невозвратности

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

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

Но это изолированное изменение; это не решает эту проблему более широко или для всех случаев использования NumberInput.

2. Утверждения и модульные тесты

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

Сначала мы рассмотрим базовую реализацию NumberInput.

А теперь давайте изменим его на форму, которая гарантирует, что реквизит onChange на самом деле ничего не возвращает.

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

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

3. Переместите его во время компиляции с безопасностью типов

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

Мы можем сделать это тривиально, если напишем наше определение NumberInput как таковое:

Обратите внимание на параметры для NumberInput в строке 4. Мы добавили только следующий бит: {onChange}: {onChange: (newNumber: number) => undefined}. Мы фактически взяли весь код, который мы написали в примере № 2, и переместили его из среды выполнения в этап компиляции. Таким образом, любой неправильный код обнаруживается задолго до того, как мы потратим время на выполнение модульных тестов или до того, как мы вручную нажмем кнопки на веб-странице в рамках контроля качества.

Вывод

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

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

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

Усталая, напряженная будущая версия вас будет благодарна — или, что еще лучше, они никогда не устанут и не напрягутся из-за непонятного кода!

Бонус: () =› пустота

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

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

Все, что вы там видите, является действительным TypeScript. Несмотря на то, что functionReturnsVoid объявлен как функция, возвращающая void, определение фактически возвращает number 5. TypeScript рассматривает эту функцию как если бы ничего не возвращала, хотя базовый код JavaScript по-прежнему выполняется.

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