Должны ли функции быть маленькими?

Чистый код говорит нам:

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

Практически каждый разработчик знаком с принципом Do One Thing (DOT), также известным как Закон Curly. Это руководство говорит нам, что функции должны делать только одну вещь. Чистый код объясняет это немного подробнее:

«Функции должны что-то делать или отвечать на что-то, но не то и другое одновременно».

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

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

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

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

Все три шага Find-Update-Save находятся на одном уровне и могут содержаться в одной функции, где «одна вещь», которую делает эта функция, — это UpdateUser(). Подумайте об ужасах чтения кода, в котором UpdateUser() начинается с низкоуровневого чтения с диска. Или если UpdateUser() также выполняет UpdateCompany() и UpdateAccount() одновременно.

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

Правило PowerPoint

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

  • Используйте крупные шрифты. Чем выше вы поднимаетесь, тем хуже становится ваше зрение.
  • Скажи меньше. Разговорная часть предназначена для объяснения, того, что на экране должно быть минимум.
  • Три. У вас никогда не должно быть более трех пуль на слайде. Это максимум информации, которую руководитель может удержать в голове.

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

  • Функция должна помещаться на одном экране с разумным размером шрифта. Дядя Боб рекомендует не более 20 строк.
  • Он должен читаться просто. Все, что поясняет, является функцией более низкого уровня с правильным именем. В результате должен получиться код, который читается как история.
  • Три. Сохраняйте функцию примерно тремя блоками кода. Не строки, а около трех «абзацев». Эти абзацы также должны состоять примерно из трех шагов. Заметьте, я ничего не говорил о строках кода.

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

  • Не повторяйся (СУХОЙ). Более длинные функции, как правило, имеют повторяющийся код, который используется немного по-разному. Когда у нас есть более короткие функции, легче удалить повторения в действиях.
  • Отладка с помощью модульных тестов. Да, да, я знаю, мы не тестируем функции, мы тестируем поведение. Но когда мы отлаживаем, почему что-то работает не так, как мы ожидаем, может быть чрезвычайно полезно написать модульные тесты, которые делают утверждения о том, как работают строительные блоки. Чем меньше наши блоки, тем более мелкую зернистость мы можем отлаживать. Конечно, мы также можем двигаться вверх по лестнице абстракций и делать утверждения о том, что происходит, когда мы продвигаемся вверх.
  • Рефакторинг для уменьшения сложности. Меньшие функции облегчают удаление кода.
  • Реорганизация. Меньшие функции облегчают перемещение вещей между модулями/классами. Если функция выполняет несколько действий, нам, возможно, придется разбить ее, прежде чем мы сможем переместить ее часть.

А как насчет другой стороны?

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

  • Исследования, на которые опирается статья в отношении заявлений о более высокой плотности дефектов, относятся к периоду до 2000 года. Знаете, чего не было до 2000 года? Чистый код, TDD и более мощные языки и библиотеки, которые у нас есть сегодня.
  • Возможно, в прежние времена (которые я пережил, убирайся с моей лужайки) более длинные процедуры привлекали больше внимания, поскольку они были ядром системы. Дело не столько в продолжительности подпрограмм, сколько в количестве внимания разработчиков, которое приводит к количеству дефектов.
  • Если вы следуете циклу отладки кода, то более короткие функции действительно могут быть проблемой. Но мы все время проводим рефакторинг, используем TDD и пишем код, чтобы его можно было прочитать. Читаемый код по своей природе легче поддерживать. Тот, кто с этим не согласен, никогда не унаследовал беспорядок.
  • В первые дни читабельность не ценилась так высоко. В Фортране тип вашей переменной определялся начальной буквой имени переменной. Максимальная длина переменной составляла 8 символов, когда я писал Fortran. Более короткие функции, вероятно, увеличили когнитивную нагрузку, теперь они имеют противоположный эффект. Утверждалось, что COBOL является самодокументируемым, что легко опровергается любым, кто работал с ним.
  • Каждый раз, когда один подход утверждает, что у него меньше дефектов, чем у другого, вы должны ответить «по сравнению с чем?» Если вы не нашли дефект, он все еще существует? Вы строили одно и то же дважды и сравнивали количество дефектов? Как вы убрали из головы знания, полученные от первой реализации? Как вы удостоверились, что пулы разработчиков, выполняющих работу, имеют одинаковый уровень талантов? В ходе исследований изучались уже разработанные кодовые базы и измерялась длина функции по сравнению с обнаруженными дефектами. Вполне могло быть, что более короткие функции просто недостаточно созрели, чтобы покрыть крайние случаи, которые проявляются как дефекты.

Это НЕ принцип единственной ответственности

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

К счастью, соблюдение DOT помогает нам с SRP. Возможность легко перемещать методы, потому что у нас есть много атомарных строительных блоков, помогает нам разделить поведение на правильные модули.

Написание коротких функций ломает ход моих мыслей

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

Как говорит дядя Боб в одном из своих видео, он любит вознаграждать себя за более короткий код, когда он может удалить фигурные скобки из своих операторов IF. Я сама люблю брекеты, но каждому свое.

Насколько короткий слишком короткий?

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

Меня часто спрашивают, сколько времени что-то готовить. Ответ всегда «Пока это не будет сделано». Что-то с кодом. Продолжайте чистить код, пока он не станет чистым.