The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:
Мне недавно задали вопрос в Twitter, каким образом можно провести рефакторинг, если не понимаешь, как работает код. Я ответил, что это “обучение через рефакторинг”. Потом я попытался найти информацию в Google и ничего не нашел. Я был удивлен. Для меня рефакторинг кажется самым эффективным и очевидным способом изучения исходного кода. Вот как я обычно это делаю, используя девять объектно-ориентированных шагов.
Согласно Википедии, рефакторинг кода - это “процесс переструктуризации существующего компьютерного кода - изменения его факторизации - без изменения его внешнего поведения”. Цель рефакторинга состоит в том, чтобы сделать код более читабельным и пригодным для модификаций.
Мартин Фаулер в своей известной книге “Рефакторинг: Улучшение проектирования существующего кода” предложил ряд техник рефакторинга, которые помогают упростить код, сделать его более абстрактным, читабельным и т.д. Некоторые из них вызывают сомнения с точки зрения объектно-ориентированного подхода - например, “Инкапсуляция поля” - но большинство из них являются действительными.
Вот что я обычно делаю, когда не знаю кода, но нужно его изменить. Техники расположены в порядке возрастания сложности, начиная с самой простой.
Когда я открываю исходный код Cactoos в IntelliJ IDEA с использованием моего собственного settings.jar
, я вижу что-то вроде этого:
Когда я открываю исходный код, скажем, Spring Boot, я вижу что-то похожее на это (это o.s.b.ImageBanner
, случайно выбранный из тысяч других классов, которые выглядят очень похоже).
Первое, что я делаю, когда вижу чей-то код, - это очищаю его от “красных пятен” для своей среды разработки. Большинство этих красных пятен легко убираются, хотя некоторые требуют времени для рефакторинга. В процессе я много узнаю о программе, с которой мне приходится работать.
Я написал некоторое время назад, что пустые строки внутри тел методов - плохо. Они очевидные индикаторы избыточной сложности. Программисты склонны добавлять их в свои методы, чтобы упростить вещи.
Это метод из кодовой базы Apache Maven (класс RepositoryUtils
выбран случайным образом, но почти все другие классы отформатированы таким же образом):
Помимо того, что он “весь красный”, их код полон пустых строк. Удаление их сделает код более читаемым и также поможет мне понять, как он работает. Большим методам потребуется рефакторинг, так как без пустых строк они станут почти полностью непонятными. Поэтому я сжимаю, понимаю и уменьшаю их, в основном, разбивая их на более мелкие методы.
Я в основном поддерживаю использование коротких имен существительных для переменных и глаголов для методов. Я считаю, что длинные “сложные” имена являются признаком излишней сложности кода.
Например, я обнаружил этот метод registerServletContainerInitializerToDriveServletContextInitializers
(69 символов!) в классе o.s.b.w.e.u.UndertowServletWebServerFactory
в Spring Boot. Я задаюсь вопросом, почему автор пропустил приставку couldYouPlease
и суффикс otherwiseThrowAnException
.
Шутки в сторону, такие длинные имена методов явно демонстрируют, что код слишком сложен и не может быть объяснен с помощью простого register
или даже registerContainer
. Кажется, что существует множество разных контейнеров, инициализаторов, сервлетов и других сущностей, которые нужно как-то зарегистрировать. Когда я присоединяюсь к проекту и вижу метод с таким именем, я готовлюсь к большим проблемам.
Сокращение имен - обязательный этап рефакторинга, который я делаю при начале работы с чужим или легаси-кодом.
Большинство классов (и методов) поставляются без какой-либо документации, особенно если речь идет о закрытом коммерческом коде. Мы имеем удачу, если классы имеют более или менее описательные названия и являются небольшими и связанными между собой.
Однако, вместо документации я предпочитаю работать с модульными тестами. Они намного лучше объясняют код и доказывают его работоспособность. Когда я не понимаю, как работает класс, я пытаюсь написать для него модульный тест. В большинстве случаев это не возможно по множеству причин. В таких случаях я пытаюсь применить все, что я узнал из книги “Работа с легаси-кодом с помощью эффективных приемов” Майкла Физерса и “Разработка объектно-ориентированного программного обеспечения с помощью тестов” Стива Фримена и Ната Прайса. Обе книги сосредоточены на этой проблеме: что делать, когда вы не знаете, что делать в отношении тестирования.
Ранее я написал, что наличие нескольких операторов return
в одном методе - это не то, что должно поощряться в объектно-ориентированном программировании. Вместо этого метод всегда должен иметь единую точку выхода, как и функции в функциональном программировании.
Взгляните на этот метод из класса o.s.b.c.p.b.Binder
из Spring Boot (там есть много подобных примеров, я выбрал его случайно):
В таком небольшом методе пять операторов return
. Для объектно-ориентированного кода это слишком много. Для процедурного кода это нормально, я также иногда пишу такой код. Например, этот наш Groovy-скрипт тоже имеет пять ключевых слов return
:
Но это Groovy, и это не класс. Это просто процедура, скрипт.
Рефакторинг и удаление нескольких операторов return
помогают сделать код более чистым. В основном потому, что без них приходится использовать более глубокую вложенность операторов if/then/else
, и код начинает выглядеть некрасиво, если его не разбить на более мелкие части.
NULL - это зло, это известный факт. Тем не менее, они все еще повсюду. Например, в Spring Boot v2.0.0.RELEASE есть 4,100 файлов Java и 243K LoC, которые содержат ключевое слово null
7,055 раз. Это означает примерно одно null
на каждые 35 строк.
В противоположность этому, Takes Framework, которую я основал несколько лет назад, имеет 771 файл Java, 154K LoC и 58 ключевых слов null
. Что приблизительно одно null
на 2,700 строк. Видите разницу?
Код становится чище, когда удаляются NULL, но это не так просто сделать. Иногда это даже невозможно. Вот почему у нас все еще есть эти 58 случаев null
в Takes. Мы просто не можем удалить их, потому что они исходят из JDK.
Как я продемонстрировал некоторое время назад, неизменяемость помогает сохранять объекты меньшими. Большинство классов, с которыми я сталкиваюсь в иностранном коде, являются изменяемыми. И большими.
Если вы посмотрите на любой артефакт, проанализированный jpeek, вы увидите, что в большинстве из них примерно 80% классов являются изменяемыми. Переход от изменяемости к неизменяемости - это большой вызов в объектно-ориентированном программировании, который, если решен, приводит к лучшему коду.
Этот этап рефакторинга, сделать вещи неизменяемыми, является чисто прибыльным.
Статические методы и атрибуты удобны, если вы процедурный программист. Если ваш код объектно-ориентированный, они должны быть исключены. В Spring Boot есть 7 482 ключевых слова static
, что означает одно ключевое слово на каждые 32 строки кода. В отличие от этого, в Takes у нас есть 310 static
-ов, что составляет один на каждые 496 строки.
Сравните эти числа с статистикой о NULL и вы увидите, что избавление от static
является более сложной задачей.
Это последний и самый сложный шаг. Он сложный, потому что я настраиваю статические анализаторы на их максимальный потенциал или даже больше. Я использую Qulice, который является агрегатором Checkstyle, PMD и FindBugs. Эти инструменты сами по себе мощные, но Qulice делает их еще сильнее, добавляя несколько десятков настроенных проверок.
Принцип, который я использую для статического анализа, - это 0/100. Это означает, что либо весь код чист и нет жалоб Qulice, либо он “грязный”. Здесь нет ничего посередине. Это не очень типичный способ взгляда на статический анализ. Большинство программистов используют эти инструменты просто для сбора “мнений” о своем коде. Я использую их как руководство для рефакторинга.
Посмотрите это видео, которое демонстрирует количество жалоб, которые дает Qulice для подмодуля spring-boot-project/spring-boot
в Spring Boot (видео не имеет конца, так как я потерял терпение в ожидании):
Когда Qulice говорит, что всё чисто, я считаю, что кодовая база полностью готова к обслуживанию и модификациям. На этом этапе рефакторинг завершен.
Translated by ChatGPT gpt-3.5-turbo/42 on 2024-01-09 at 18:18