Миф о идеальном количестве строк в методе

в 8:11, , рубрики: Программирование, Проектирование и рефакторинг, проектирование по

Существует миф, что если в функции больше чем n или меньше чем m строк кода, то с функцией есть проблемы в плане проектирования. К примеру, автор публикации «Размышления о принципах проектирования» говорит, что «число строк метода… является необходимым, но недостаточным условием хорошего дизайна». В этой статье я изложу причины, по которым считаю необходимость функции быть определенного размера мифом и приведу примеры, доказывающие это, но сначала давайте рассмотрим причины популярности этого мифа.

Причины популярности мифа

Если взять практически любой алгоритм из реальной жизни, то его легко можно разбить на небольшое количество операций, обычно от 3 до 10, причём эти операции служат своим уникальным микро-целям. Например, чтобы сесть за стул, стоящий возле стола, нужно 1) увидеть стул; 2) подойти к стулу; 3) отодвинуть стул; 4) сесть за стул. Такое описание действий довольно понятно и, взглянув на каждое действие, можно понять, что же за ним скрывается и какие примерно шаги нужно выполнить для его реализации. Это пример хорошего дизайна. Если бы вместо шага «увидеть стул» было бы несколько операций напрягания глазных мышц, а вместо «подойти к стулу» — цикл постепенного продвижения с постоянным редактированием маршрута, то в таком алгоритме было бы сложно разбираться; даже запомнить все детали алгоритма уже проблематично. Это пример плохого дизайна без должного контроля сложности. В таком случае лучше выделить операции, служащие одной цели – сокращению расстояние между стулом и человеком, в одну функцию.

Если взять другие алгоритмы, например, жарки яичницы или похода в кино – опять таки можно выделить до 10 более мелких операций, служащих РАЗНЫМ микро-целям. Согласитесь, что сходу придумать пример, когда операций гораздо больше 10, довольно сложно, не так ли? Если всё-таки получается довольно много операций, то наверняка можно найти для некоторых общую цель, или может быть вы слишком зациклены на обработке ошибок, которая на самом деле не является ОСНОВНОЙ частью алгоритма.

Опровержение мифа

Придумать алгоритм из жизни, в котором на верхнем уровне абстракции существует большое количество операций, которые не удастся скомпоновать в более крупные (в результате чего ваша главная функция алгоритма раздуется на много строк кода) довольно сложно, но возможно. Например, ритуальный танец какого-нибудь аборигенского племени, который представляет из себя следующие действия: 1) присесть; 2) покудахтать; 3) порычать; 4 )помахать руками; 4) привстать; 5) попрыгать… и ещё 100 или 500 хаотичных безсистемных действий. Всё ещё хотите, чтобы ваша функция была меньше n строк кода? Тогда придётся разбить метод на 1) выполнить первую часть ритуального танца; 2) выполнить вторую часть – и так далее, а это пример плохого дизайна, ведь непонятно, что скрывает за собой та или иная функция. Приведу пример аналогичной ситуации, но уже из области программирования. Допустим, есть модуль, скачивающий страницы с некоторого сайта и распарсивающий их. Главный метод модуля выглядит примерно так:

1) Узнать информацию о поставщике;
2) Узнать информацию о лотах закупки;
3) Узнать информацию о аукционе закупки;
4) Узнать информацию о протоколах закупки;
5) Узнать информацию о последней дате изменения состояния закупки.
… и ещё подобные операции. При этом каждая операция скачивает разные страницы и применяет различные алгоритмы парсинга. Каждая операция достойна быть выделена в отдельный метод и никак не получится объединить некоторые из них в отдельную функцию с понятным именем (вариант «Узнать информацию о поставщике и лотах и аукционе» не предлагать). Предметную модель и количество страниц на сайте-источнике можно увеличивать до бесконечности (равно как и количество фронт-энд разработчиков сайта, вносящих всё больше специфики в разные страницы сайта). Другой пример, опровергающий миф – это семейство криптографических алгоритмов. Чем большее число n-максимум допустимого размера метода вы бы не назвали, тем более длинный криптографический алгоритм можно придумать.

Обратная сторона мифа

Существует и другая интерпретация данного мифа – если в методе меньше, чем m строк кода (например,50), то что-то с ним не так. Как могла зародиться такая точка зрения? Представьте себе код, не имеющий стройной архитектуры, в котором названия классов и методов или не отражают назначения сущностей, или и вовсе вводят в заблуждения. Возможно, изначально код был хорош, но затем кто-то внёс изменения в функцию «Узнать информацию о последней дате изменения состояния закупки» и теперь она также ещё и сохраняет информацию в базу данных и шлёт пользователям уведомления на почту, но вот название функции не изменилось. Или кто-то внёс изменения в сам алгоритм поиска даты изменения, но внёс их не в эту функцию, а в какое-либо другое место, тогда функцию нужно было бы переименовать в «Узнать информацию о последней дате изменения ЧАСТИ состояния закупки» или в «Узнать информацию о журнале событий» (теперь это ЧАСТЬ операции поиска даты изменения, и метод должен называться соответственно), но, увы и ах, функция не была переименована. В результате к именам методов в таком коде нет доверия, и чтобы узнать, ЧТО В ДЕЙСТВИТЕЛЬНО ЗДЕСЬ ПРОИСХОДИТ, нужно провалиться в каждый из них. А если код раздроблен на большое количество методов и глубина вложенности велика, то нужно проваливаться всё глубже и глубже… В результате в коде легко запутаться, как в лабиринте. А вот если бы весь код класса был в одной гигантской функции, то он хотя бы был виден, как на ладони, да и заведомо ложные имена функций не вводили бы в замешательство.

Теперь представим себе вымышленного программиста по имени Маркус. Маркус не особо усердствует в изучении проектирования и каждый день работает с вышеописанным несуразным кодом. Постепено Маркус начинает замечать, что в «большом коде проще разбираться», а «мелкодроблёный» код начинает у него ассоциироваться с головной болью. Затем кто-то мельком говорит ему о принципе «не плодите лишние сущности». Какая сущность лишняя, а какая — нет, Маркус объяснить не может, но берёт принцип на вооружение. Затем Маркус откуда-то узнаёт о принципе KISS, и решает, что раз «чем меньше сущностей, тем проще разобраться», то «чем меньше сущностей, тем больше код соответствует KISS».

Вот пример статьи, персонажа которой тоже зовут Маркус, и который написал класс, который умеет печь любой сорт хлебо-булочной продукции по любому рецепту на любой печке с любым источником топлива, и этот класс имеет всего один метод. Насколько я понял, у него во всей программе два класса – хлеб (который на самом деле может быть и пирожком) и Manager (который при встрече может сказать «Я всё могу!» и не соврать). Наш Маркус (наш-который из этой статьи) согласен и считает, что это BEST PRACTICE и следование принципу KISS и если вы не плодите God object-ы по 1000 строк кода в каждом, то вам есть чему у него поучиться.
Лично я считаю, что нет правила, что метод обязан быть больше m строк, и что очень даже возможно написать аккуратненькие небольшие функции, по внешнему виду которых можно сказать, что происходит внутри них, каков их контракт и какой цели они служат. При этом для поиска нужного функционала не уйдёт много времени и не придётся просматривать весь код.

А как же нужно?

Мы уже знаем, как не нужно делать – слепо доверять количеству строк в методе. Возникает закономерный вопрос – а как же нужно? Как узнать, что нужно что-то добавить в функцию, или что-то из неё убрать? Узнать нам помогут принцип «Low coupling & High cohesion» и два smell-а: «Стрельба дробью» и «Расходящиеся модификации». Если при изменении какого-то типа функционала нужно подправить кусочек этой сущности и кусочек другой сущности, то, значит, что в коде завелась «Стрельба дробью» и «Low cohesion» и неплохо бы эти две сущности слить в одну. Если же при изменении какого то типа функционала у нас всегда меняется вон та часть сущности и никогда не меняется вот эта часть сущности, то код пахнет «Расходящейся модификацией» и возможно стоит разбить сущность на две. Для пояснения используем немного измененный пример из начала статьи: если при смене способа перемещения у робота, приближающегося к стулу, у вас постоянно меняется часть алгоритма, касающаяся выбора маршрута и передвижения (в зависимости от того, перемещается ли робот по земле, под землей или над землей), то нужно выделить отдельную функцию «приблизиться к столу». В итоге там где была одна сущность, появляется две. Также нужно давать понятные названия сущностей, чтобы по одному только названию можно было понять, что эта сущность делает (и чего не делает).

P.S. Все вышесказанное является всего лишь моим личным мнением. Все персонажи вымышленные.

Автор: FiresShadow

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js