Вы когда-нибудь обнаруживали, что читаете строки кода и внезапно нажимаете закрывающую скобку и совершенно не знаете, где начинается блок кода? Это был for цикл? Было ли это if заявление? Функция закончилась? Или, что еще хуже, часть else лестницы if-else внутри цикла for какой-то функции закончилась?

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

Если название вам кажется знакомым, возможно, вы читали книгу Роберта Мартина Чистый код. Если нет, я настоятельно рекомендую прочитать эту книгу. Это сильно повлияет на способ написания кода. Двигаясь дальше, я собираюсь обсудить некоторые моменты из главы Функции из Чистого кода и то, как правила, применимые к функциям, могут быть распространены на любой блок кода в целом.

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

«Первое правило функций - они должны быть небольшими. Второе правило функций - они должны быть меньше ».

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

Самый простой способ добиться небольшой функции - сохранить все конструкции внутри функции небольшими. Это означает:

  • Блок кода для конструкции if-elseif-else должен быть небольшим.
  • Блок кода для цикла for должен быть небольшим.
  • Блоки кода для конструкции try-catch-finally должны быть небольшими и так далее…

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

Мы начнем с одного плохого примера функции и продолжим рефакторинг, пока не получим одну идеальную реализацию.

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

Приступим к рефакторингу!

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

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

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

«Функции должны делать одно. Они должны делать это хорошо. Только они должны это делать ».

Функция, которая делает только одно, по своей сути меньше. Давайте отделим две функции от нашей changePassword функции.

Мы начали с одной большой функции, а теперь у нас есть три функции поменьше. Как видите, мы значительно уменьшили размер каждой функции, чтобы одна функция выполняла одну работу. Но наша changeAdminPassword функция точно не выполняет одну задачу. Он изменяет пароль и сообщает администратору, что пароль был изменен, и предоставляет возможность сбросить пароль. Это две разные задачи, и мы должны создавать для них разные функции.

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

«Можем ли мы провести еще рефакторинг?»
«Определенно. ”

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

Ах! Код, который у нас сейчас есть, настолько идеален, насколько это возможно, за исключением одного момента. Обратите внимание на вызов функции clearSession() после обновления пароля и сохранения изменений. Требование гласит, что всякий раз, когда пользователь меняет пароль, он / она должен повторно войти в систему, и код делает это, очищая сеанс. Но код неверен. Это подводит меня к еще одному свойству функции:

«Функция не должна иметь побочных эффектов»

Обе наши функции changeUserPassword() и changeAdminPassword() имеют побочный эффект - они также очищают сеанс! В команде, когда какой-либо другой разработчик пытается использовать эту функцию, он не подозревает, что вызов changePassword также очистит пользовательский объект из сеанса. Он может закончить тем, что позвонит clearSession после changePassword, что приведет к дополнительным вычислительным ресурсам без какого-либо влияния на систему.

Каково решение?

Должны ли мы переименовать changePassword в changePasswordAndClearSession?
Из кода можно сказать, что changePasswordAndClearSession делает две вещи!
Идеальным решением было бы полностью опустить функцию clearSession. Его следует вызывать в контроллере (или в службе) сразу после вызова changePassword.

Примечание ноги:

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

Чтобы повторить, вот список советов:

  1. Удалите ненужные блоки кода.
  2. Ограничьте функцию только чем-то одним.
  3. Отделяйте проверку аргументов от реальной логики.
  4. Попробуйте разбить функции на несколько уровней абстракции.
  5. Устранение побочных эффектов функции.
  6. Попробуйте включить «закрывающие комментарии».