Сделайте ваш код приятным для чтения.
Было опубликовано бесчисленное количество статей о передовых методах программирования, в которых обычно говорится следующее:
- Организуйте свои файлы.
- Разбивайте большие функции.
- Назовите вещи хорошо.
Но они, похоже, упускают более крупную объединяющую концепцию.
Код читается людьми.
Если мы будем думать о нашем коде как о чем-то, что нужно читать как историю, а не просто интерпретировать как инструкции, мы можем значительно упростить возвращение к нему, чтобы его можно было понять позже.
Что делает «хорошую функцию»?
Одна из самых сложных частей программирования - найти правильный баланс между грубыми и мелкими функциями и методами. Когда имеет смысл использовать двухстрочную функцию? 100-строчная функция?
По моему опыту, все сводится к следующему:
Функция считается «хорошей функцией», если кто-то может понять, что она делает, в одном коротком предложении.
Если мне нужно сделать паузу, чтобы объяснить это, или если предложение, которое я использую, слишком расплывчато, мне нужно его переписать. Некоторые примеры того, что я считаю «хорошими функциями» в JavaScript:
Их, вероятно, можно было бы написать лучше и использовать разные алгоритмы, но идея состоит в том, что каждый из них имеет единую простую концепцию. arraysEquals
сообщает мне, равны ли два массива. getUserProfile
получает профиль пользователя. createCommentThreadedPosts
… вы поняли.
Если функция пытается делать слишком много вещей или имеет слишком много движущихся частей, вероятно, имеет смысл сломать ее. Даже если функция используется только один раз, если втягивание кода в эту функцию упрощает понимание исходной функции, если весь этот код объединен как идея, имеет смысл создать эту новую функцию.
В то же время, если функция буквально переименовывает простую операцию, например
function isFunction(fn) { return !!fn && (typeof fn === 'function' || fn instancof Function); }
вероятно, это не обязательно должно быть функцией само по себе. Может быть слишком легко создать совершенно новый язык программирования, скрывая общие операции внутри функций-оболочек, и новым разработчикам необходимо изучать этот метаязык в дополнение к традиционному языку, на котором он написан.
Разбиение кода на абзацы
Когда у нас слишком много кода в одном месте, мы можем разбить его на более мелкие функции или методы, чтобы у нас было меньше возможностей для просмотра в одном месте. Но что делать, если нет смысла разбирать функцию дальше? (Есть такая вещь, как слишком много хорошего.)
Используйте пробелы, чтобы сделать «абзацы кода».
Можете ли вы изменить свой код так, чтобы все операторы для данного процесса объединились? Есть ли у них общая тема? Если да, обведите их пустым пространством.
Как набор строк кода, каждый «абзац кода» имеет тему. Это может быть так просто, как «Это часть инициализации функции». «Этот цикл преобразует мои данные». «Здесь я собираю данные из базы данных».
Каждый из них может быть собственным методом или функцией, но это может затруднить чтение, переключаясь между функциями. Помещая пустую строку между каждым абзацем, вы даете читателю возможность сделать паузу. Вы говорите им, что пора перевести дух и подготовиться к новому образу мышления. Это позволяет им усвоить эту предыдущую идею, прежде чем переходить к новой.
Вы можете увидеть это в методе arraysEqual
выше. После каждого теста раннего возврата добавляется линия пробела. Это позволяет читателям сделать паузу и сказать: «Хорошо, это был выход. Дайте мне время вернуться к фактическому назначению функции ».
В этом случае большая часть функции является проверкой раннего возврата, поэтому здесь много пробелов, но это, в частности, из-за характера этой функции. Надеюсь, это иллюстрирует, как на поток чтения функции влияет наличие пробелов.
Следите за сложными предложениями.
Создавать действительно сложные выражения может быть до смешного просто. Чтение сложного выражения может быть таким же утомительным, как и чтение сложного предложения. Взгляните на следующую проверку пароля:
Здесь много чего происходит, и хотя ни один элемент не является сложным, чтобы написать его таким образом, нужно держать все в уме, прежде чем переходить к тому, что делать, если условие пройдет.
В таком случае можно разделить все на собственное выражение, а затем объединить их в конце:
Здесь гораздо больше утверждений, но каждое из них описывает себя. И, если вас просто волнует, что делает условие, вы сразу увидите, что отправка формы происходит после проверки, действителен ли пароль. Никаких умственных способностей не требуется, если только вы не хотите разбираться в том, что именно делает пароль действительным.
Точно так же довольно легко объединить функции вместе, чтобы сделать волшебство из длинного списка операций:
Это функция, которая разбивает предложение на слова, отслеживает версии слов в верхнем и нижнем регистре, отфильтровывает статьи на основе версий в нижнем регистре, а затем снова объединяет предложения.
В одном заявлении.
На самом деле это не это безумное заявление. Мы можем читать его, строка за строкой, и видеть, что делает каждая строка. Данные, над которыми он работает, довольно просты.
Но при выполнении того же вида цепочки для сложных объектов, поступающих с сервера, фильтрации и преобразования их для представления пользователю, такая цепочка может вызвать сильную путаницу.
Когда цепи становятся слишком длинными или когда преобразования становятся слишком запутанными, это может помочь разбить цепочку на более мелкие части или определить трансформаторы независимо:
Оно длиннее, но каждое предложение представляет собой законченный рассказ. Это может быть слишком сложно для данного примера, но подумайте, например, если бы buildWordCaseObject
была 15-строчной transformServerData
функцией.
Стрелочные функции или традиционные функции?
Это больше связано с JavaScript, но я обычно использую function
для основных функций модуля JavaScript. Мне легче определить «Это функция» без необходимости искать стрелку после исходных имен переменных и параметров.
Использование function
также позволяет мне определять вспомогательные функции ниже основной благодаря функции подъема. Я могу дать читателям основную функцию в верхней части модуля, не заставляя их прокручивать вниз и продвигаться вверх. Каждая функция, объявленная с использованием ключевого слова верхнего уровня function
, доступна в любом месте модуля, поэтому я могу писать их везде, где это имеет смысл.
Так я говорю, что стрелочные функции бесполезны? Неа! Я использую стрелочные функции для коротких, чистых служебных функций, а также для функций, встроенных в качестве аргументов таких функций, как функции массива map()
, sort()
и filter()
. В этих ситуациях важно, чтобы определения функций проходили в более широком контексте кода, а не создавали четкие границы между внешней функцией и определением встроенной функции.
Прочтите вслух имена переменных.
Несмотря на то, что нам велят писать описательные имена переменных, нам не говорят, как придумать лучшее описательное имя. objectCount
? objectTotal
? numObjects
?
Ключ в том, чтобы посмотреть, где используется ваша переменная, и прочитать их вслух.
Имя скатывается с вашего языка? Между objectCount
и numObjects
, например, m
в numObjects
образует мягкий разделитель между словами, что затрудняет их произнесение в разговоре. Это может быть неважно, когда вы пишете код, но становится важным, когда вы пытаетесь объяснить это вслух своим коллегам. Это также может облегчить чтение, если вы обнаружите, что читаете код вслух в своей голове. Разница небольшая, но помогает каждый бит!
Это также может иметь значение, как используется переменная. Если в условии используется переменная, подумайте, как условие читается с этим именем переменной. Хотя логические значения традиционно имеют префикс is
, возможно, имеет смысл написать if (passwordHasNumbers)
вместо if (isPasswordWithNumbers)
.
Для отслеживания переменных рекомендуется множество соглашений об именах, особенно для языков со слабой типизацией, таких как Javascript. Однако должно быть место для гибкости, иначе код станет неестественным и трудным для обсуждения.
Напишите, чтобы улучшить поток истории вашего кода.
Пишете ли вы более длинные или короткие инструкции, используете ли вы пробелы, разбиваете функции или выбираете имена переменных, помните об этом в первую очередь:
Пишите, чтобы улучшить поток истории вашего кода.
Определенная функция слишком неуклюжая? Разделите его на более мелкие функции с именами, объясняющими, что происходит. Используйте пустое пространство, чтобы показать, где история вашего кода меняет сцены и темп. Используйте имена функций и переменных, которые позволят вам рассказать историю устно. Расскажите основную историю заранее (поместив основную функцию вверху модуля), а вспомогательные функции - внизу.
Всегда помните, что вы пишете для аудитории. Ваше будущее и ваши коллеги будут вам благодарны за это. И, в конце концов, вы можете взглянуть на свой код и сказать:
"Проклятие. Хорошая история.
Больше контента на plainenglish.io