Большая часть кода, с которым я сталкиваюсь, состоит из относительно немногих, но длинных методов. Код делает то, что должен делать. Тем не менее, это может быть значительно улучшено. Рефакторинг для использования большего количества методов может привести к созданию более структурированных программ, которые легче понять, легче модифицировать, легче тестировать и легче отлаживать. Ниже приведены 7 причин, по которым использование большего количества методов может быть хорошей идеей.
1. СКРЫТЬ ДЕТАЛИ
На работе я недавно натолкнулся на этот раздел кода Java в методе, выводящем записи сведений о вызовах (CDR):
// Check if CDR should be excluded because of sink filter if (profile.isEnableSinkFilter() && profile.getSinkFilter() != null) { String[] sinkFilters = profile.getSinkFilter().split(";"); for (String str : sinkFilters) { if (str.equals(sinkName)) { sinkNameMatch = true; break; } } if (sinkNameMatch && profile.isInvertSinkFilter()) { return result; // Do not output anything } if (!sinkNameMatch && !profile.isInvertSinkFilter()) { return result; // Do not output anything } }
Он выполняет фильтрацию по имени приемника. В зависимости от того, есть ли совпадение, и если фильтр инвертирован или нет, он может выйти (вернув результат) или продолжить выполнение дополнительной обработки. В коде нет ничего плохого. Он делает именно то, что должен. Проблема, однако, в том, что каждый раз, когда я читал метод включения, мне приходилось видеть все детали того, как была выполнена фильтрация. Но в большинстве случаев достаточно знать, что фильтрация выполнена. Детали только в пути. Поэтому я поместил детали в новый метод с именем excludeBasedOnSinkName () и в итоге получил следующий код:
if (excludeBasedOnSinkName(profile, sinkName)) { return result; // Do not output anything }
Название метода говорит мне, что он делает, и метод включения теперь легче читать, так как детали фильтрации скрыты в новом методе. Если я хочу узнать подробности, я просто прыгну в метод. Однако в большинстве случаев достаточно просто увидеть название, чтобы узнать, что там происходит.
2. ИЗБЕГАЙТЕ ДУБЛИКАЦИИ
Если одни и те же строки кода появляются более одного раза, их следует поместить в метод. Есть несколько веских причин для этого. Это делает программу короче. Это гарантирует, что все операторы выполняются в том же порядке, каждый раз, когда они сделаны. И моя любимая причина: есть только одно место, чтобы внести изменения, если что-то нужно изменить в этой области. Если у вас одинаковые строки кода в 15 разных местах, легко пропустить одну из них при внесении изменений.
Дублированный код, к сожалению, довольно распространен. Часто это результат копирования-вставки. Скопируйте некоторый код, возможно, немного измените некоторые части, и вставьте его в другое место, где требуется то же действие. Копирование-вставка часто (немного) быстрее, чем создание метода с общей частью и вызов его из обоих мест. Также стоит отметить, что эти два случая не должны быть идентичными. Там могут быть различия. Но в этом случае поместите общую часть в метод и передайте части, которые зависят от параметров.
Мартин Фаулер написал в IEEE Software действительно хорошую статью на тему « Избегание повторения» .
3. Поведение, связанное с группой
Иногда необходимо сделать несколько вещей в определенном порядке. Создав метод и вызвав его, вы гарантируете, что ни один шаг не будет забыт. Например, если вы хотите остановить таймер, если он работает, а затем очистить его, используйте метод stopTimer () :
private void stopTimer() { if (this.timer != null) { this.timer.stop(); this.timer = null; } }
Если вы используете метод вместо отдельных операторов, вы знаете, что таймер остановлен и что переменная имеет значение null после его вызова. И это работает на концептуальном уровне. То, что вы хотите сделать, это остановить таймер, а не просто выполнять отдельные операторы.
4. СОЗДАТЬ «ФУННЕЛЬС»
Когда выполняется много сложной обработки, полезно иметь структуру, в которой определенные точки в коде всегда передаются, независимо от того, что еще сделано. Например, при настройке звонка с мобильного телефона одним из первых шагов является проверка того, что у абонента достаточно денег на счете, чтобы выполнить звонок. Могут быть установлены различные типы вызовов, и проверка тарификации может выполняться в каждом из этих мест отдельно. Однако, заставляя все случаи выполнять проверку из одного метода, все различные вызовы «направляются» через этот один метод.
Методы воронки облегчают понимание программы. Что бы ни случилось раньше, каждая установка вызова будет вызывать этот метод. Они также облегчают устранение неполадок и устранение неполадок. Вы можете иметь код регистрации в методах последовательности, и они дают вам определенные контрольные точки. Тогда легко следить за ходом процесса установки вызова.
5. ПРОСТОЙ ТЕСТИРОВАНИЕ
Длинные методы делают много вещей, которые могут усложнить тестирование. Например, в нашей системе на работе есть лицензия, ограничивающая количество одновременных вызовов, которые разрешены. Кроме того, вы можете настроить подачу сигнала тревоги при достижении заданного процента емкости. Простой расчет необходим, чтобы найти количество вызовов, которые должны поднять тревогу, учитывая стоимость лицензии и процент. Поместив вычисление в его собственный метод, легко настроить некоторые модульные тесты для него. Дает ли оно правильное значение, когда предел равен 0%? Когда это 100%? Когда деление дает остаток? Если расчет является частью более длительного метода, его можно проверить только в сочетании с другими действиями, что усложняет тестирование.
Иногда (из-за устаревшего кода, написанного до того, как было распространено модульное тестирование), может быть сложно создать объекты, необходимые для выполнения тестов. Простым решением может быть создание статического метода, который получает все необходимые входные данные в качестве аргументов. Статический метод затем может быть вызван (и протестирован) без какого-либо сложного создания объекта.
6. Упрощенная регистрация и отладка
Является ли метод, содержащий только одну строку, слишком коротким? Нет. Такой метод может быть полезен:
private void setState(State newState) { this.state = newState; }
На работе на днях я решал проблему. В рамках этих усилий я хотел проверить, все ли было правильно закрыто, когда я возвращаюсь в состояние ожидания. К сожалению, штат был настроен на бездействие примерно в 25 местах, поэтому потребовалась небольшая работа, чтобы охватить все случаи. Если бы использовался метод setState, я мог бы просто добавить:
private void setState(State newState) { if (newState == State.IDLE && !everythingClosed()) { logDebug("Going IDLE, but everything not closed"); } this.state = newState; }
Метод setState также хорош для регистрации всех переходов состояний:
private void setState(State newState) { if (logDebugEnabled()) { logDebug("Changing state from " + this.state + " to " + newState); } this.state = newState; }
Просмотр всех переходов состояний в журнале обычно очень помогает при попытке выяснить, что происходит в коде.
7. САМОДОКУМЕНТАЦИОННЫЙ КОД
Код читается гораздо чаще, чем написан. Поэтому важно, чтобы было легко понять, что делает код. Хорошо названные методы значительно облегчают процесс понимания программы. Кроме того, вам не нужно писать много комментариев, потому что имена методов (и переменных) рассказывают историю. Придумать хорошие имена сложнее, чем кажется, но, на мой взгляд, это одна из важных частей программирования. Лучший тест с именами, которые я придумаю, — это когда я возвращаюсь к коду через несколько месяцев (когда я забыл все детали): могу ли я понять, что метод делает только из его имени, или мне нужно перейти в это и читать код?
Я почти никогда не пишу Javadoc для внутренних методов, которые я пишу. Название метода достаточно. Преимущество заключается в более коротком и компактном коде — я вижу больше программ на каждом экране. А если имени недостаточно, можно легко перейти в метод и проверить, что он делает. Единственное исключение — для API. Здесь важен Javadoc, поскольку у вас может не быть возможности исследовать код на другой стороне API.
ПОЧЕМУ БЫ И НЕТ?
Так почему же так много кода, который не использует преимущества использования большего количества методов? Отчасти я думаю, что это связано с тем, что многие люди добавляли много кода в течение длительного периода времени. Это может показаться не стоящим усилий при каждом отдельном обновлении, поэтому никто не проводит рефакторинг.
Я также думаю, что инструменты, которые вы используете, влияют на вас в этом отношении. Последние несколько лет я программировал на Java, используя IntelliJ IDEA. В среде IDE ввод в классы и методы и переходы от них происходят одним нажатием клавиши. Поэтому использовать множество методов легко. Когда я использовал Emacs и программировал на C ++, мои методы были длиннее, а классы больше. Я думаю, причина была в том, что для перехода между классами и методами потребовалось больше усилий — переключение файлов или буферов и использование поиска строки по имени метода для его поиска.
Еще одна причина в том, что некоторые люди просто предпочитают видеть длинные фрагменты кода вместо нескольких методов. Когда я впервые читаю код, я обычно прыгаю во все методы и читаю, что они делают. Но как только я это сделаю, я доверяю имени метода (если это хорошее имя), и мне не нужно читать код в нем каждый раз, когда я вижу этот метод. Название метода достаточно. Я предпочитаю видеть имена методов, а не весь код — это ускоряет чтение.
ВЫВОД
Некоторые из причин для создания методов, упомянутых выше, обычно применяются одновременно. Например, групповое поведение одновременно также скрывает детали, избегает дублирования и может быть самодокументированным. Тем лучше — много веских причин для введения метода в код. Это может быть чрезмерно сделано? Вероятно. Но большая часть кода, который я вижу, ошибается из-за слишком малого числа методов, а не слишком большого количества. Поэтому в следующий раз, когда вы модифицируете некоторый код, посмотрите, получит ли он пользу от нескольких новых методов.