Раздувание кода с неприятным запахом кода

задняя часть

:notebook: Эта статья была заархивирована в: "blog"

Переведено с:источник Making.com/refactoring...

Раздутый код (Bloated) Эта группа неприятных запахов означает: классы, функции и поля в коде неправильно организованы, а просто нагромождены. Проблемы этого типа обычно незаметны в первые дни кода, но накапливаются по мере его роста (особенно, когда не предпринимается никаких усилий для их устранения).

паранойя базового типа

Первобытная одержимость

  • Используйте примитивные типы вместо небольших объектов для простых задач (таких как валюты, диапазоны, строки телефонных номеров и т. д.).
  • Используйте константы для кодирования информации (например, константа, используемая для ссылки на привилегии администратора).USER_ADMIN_ROLE = 1).
  • Используйте строковые константы в качестве имен полей для использования в массивах.



проблема вызывает

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

Примитивные типы часто используются для представления типов моделей. У вас есть набор чисел или строк, представляющих сущность.

Еще один сценарий: в сценарии симуляции для индексации массива используется множество строковых констант.

Решение



Большинство языков программирования поддерживают примитивные типы данных и типы структур (классы, структуры и т. д.). Структурные типы позволяют программистам организовывать базовые типы данных для представления модели чего-либо.

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

  • Если у вас есть много базового поля типа данных, могут быть некоторые поля, которые есть логическая ссылка, организованная для формирования класса. Кроме того, далее являются данные, связанные с этими методами вместе в класс. Для достижения этой цели вы можете попробовать以类取代类型码(Replace Type Code with Class).
  • Если значение поля примитивного типа данных используется для параметра метода, вы можете использовать引入参数对象(Introduce Parameter Object)или保持对象完整(Preserve Whole Object).
  • Если значение данных, которое вы хотите заменить, является кодом типа и не влияет на поведение, вы можете использовать以类取代类型码(Replace Type Code with Class)замени это. Если у вас есть условные выражения, связанные с кодами типов, используйте以子类取代类型码(Replace Type Code with Subclass)или以状态/策略模式取代类型码(Replace Type Code with State/Strategy)Быть адресованным.
  • Если вы обнаружите, что выбираете данные из массива, используйте以对象取代数组(Replace Array with Object).

доход

  • Благодаря использованию объектов вместо примитивных типов данных код становится более гибким.
  • Код становится более читаемым и организованным. Специальные данные можно манипулировать централизованно, а не разрозненно, как раньше. Больше не нужно гадать, что означают эти незнакомые константы и почему они в массиве.
  • Легче обнаружить повторяющийся код.



Описание метода рефакторинга

Замените код типа на класс

проблема

В классе есть числовой код типа, но он не влияет на поведение класса.



решать

Замените числовой код типа новым классом.



Ввести объект параметра

проблема

Определенные параметры всегда отображаются вместе естественным образом.



решать

Замените эти параметры объектом.


Сохранить весь объект

проблема

Вы берете несколько значений из объекта и используете их в качестве аргументов вызова функции.

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

решать

Вместо этого передайте весь объект.

boolean withinPlan = plan.withinRange(daysTempRange);

Заменить код типа подклассом

проблема

У вас есть неизменяемый код типа, который влияет на поведение класса.



решать

Замените этот код типа подклассом.



Замените код типа на состояние/стратегию

проблема

У вас есть код типа, который влияет на поведение класса, но вы не можете избавиться от него с помощью наследования.



решать

Замените код типа на объект состояния.



Заменить массив объектом

проблема

У вас есть массив, в котором каждый элемент представляет разные вещи.

String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";

решать

Замените массивы объектами. Для каждого элемента массива, представленного полем.

Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");

грязь данных

Сгустки данных

Иногда разные части кода содержат одинаковый набор переменных (например, параметры для подключения к базе данных). Эти объединенные данные должны иметь свои собственные объекты.



проблема вызывает

Часто сгустки данных возникают из-за плохой структуры программирования или «программирования с копированием и вставкой».

Есть хороший способ определить, является ли это скоплением данных: удалить одно из множества данных. При этом теряют ли смысл другие данные? Если они больше не имеют смысла, это четкий сигнал: вы должны сгенерировать для них новый объект.

Решение

  • Во-первых, выясните, где эти данные появляются в виде полей, используйте提炼类(Extract Class)Выделите их в отдельный объект.
  • Если в столбце параметров функции появляются сгустки данных, используйте引入参数对象(Introduce Parameter Object)Организуйте их в класс.
  • Если часть блока данных появляется в других функциях, рассмотрите возможность использования保持对象完整(Preserve Whole Object)Передайте весь объект данных в функцию.
  • Взгляните на код, который использует эти поля, возможно, будет хорошей идеей переместить их в класс данных.

доход

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



когда игнорировать

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

Описание метода рефакторинга

Извлечь класс

проблема

Класс делает больше, чем одну вещь.



решать

Создайте новый класс и переместите соответствующие поля и функции из старого класса в новый класс.



Ввести объект параметра

проблема

Определенные параметры всегда отображаются вместе естественным образом.



решать

Замените эти параметры объектом.



Сохранить весь объект

проблема

Вы берете несколько значений из объекта и используете их в качестве аргументов вызова функции.

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

решать

Вместо этого передайте весь объект.

boolean withinPlan = plan.withinRange(daysTempRange);

негабаритный класс

Большой класс

Класс содержит слишком много полей, функций, строк кода.



проблема вызывает

Классы обычно начинаются с малого, но расширяются по мере роста программы.

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

Решение

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



  • Если какое-то поведение в слишком большом классе можно выделить в отдельный компонент, вы можете использовать提炼类(Extract Class).
  • Если некоторые из поведения в слишком больших категориях могут быть реализованы по-разному или для специальных сцен, вы можете использовать提炼子类(Extract Subclass).
  • Если необходимо предоставить клиенту набор операций и поведений, можно использовать提炼接口(Extract Interface).
  • Если ваш негабаритный класс является классом графического интерфейса, вам может потребоваться переместить данные и поведение в отдельный объект домена. Возможно, вы захотите сохранить дубликаты данных на каждой стороне и синхронизировать обе стороны.复制被监视数据(Duplicate Observed Data)могу подсказать как сделать.

доход

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



Описание метода рефакторинга

Извлечь класс

проблема

Класс делает больше, чем одну вещь.



решать

Создайте новый класс и переместите соответствующие поля и функции из старого класса в новый класс.



Извлечь подкласс

проблема

Некоторые свойства в классе используются только в определенных сценариях.



решать

Создайте подкласс и поместите в него свойства для конкретного сценария.



Извлечь интерфейс

проблема

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



решать

Переместите ту же частичную функцию в интерфейс.



Дублирование наблюдаемых данных

проблема

Если данные, хранящиеся в классе, отвечают за графический интерфейс.



решать

Лучшим подходом является помещение данных, отвечающих за GUI, в отдельный класс, чтобы обеспечить связь и синхронизацию между данными GUI и классами предметной области.



слишком долгая функция

Длинный метод

Функция имеет слишком много строк кода. В общем, когда какая-либо функция превышает 10 строк, вы можете подумать, не слишком ли она длинная. В принципе количество строк кода в функции не должно превышать 100 строк.



причина проблемы

Часто создать новую функцию сложнее, чем добавить функциональность к существующей функции. Большинство людей думают: «Я просто добавляю эти две строки кода, а создание новой функции для этого — действительно большое дело.» Поэтому Чжан Сан добавил две строки, Ли Си добавил две строки, а Ван Ву добавил две строки. . . Функции становятся все больше и больше, и в конечном итоге они гниют, как котелок с месивом, который никто больше не может полностью понять. Таким образом, все не решаются легко перемещать эту функцию и могут только добавлять к ней код по замкнутому кругу. Таким образом, если вы видите функцию длиной более 200 строк, она обычно слеплена несколькими программистами.

решить функцию

Хороший навык это:искать заметки. Обычно причин для добавления комментариев несколько: логика кода относительно неясна или сложна; функция этого кода относительно независима; особая обработка. Если перед кодом есть строка комментария, это напоминание: вы можете заменить этот код функцией, и вы можете назвать функцию на основе комментария. Если функция имеет описательное имя, нет необходимости видеть, как на самом деле реализован внутренний код. Даже если это всего лишь одна строка кода, если ее нужно объяснить в комментарии, ее стоит выделить в отдельную функцию.



  • Чтобы уменьшить функцию, вы можете использовать提炼函数(Extract Method).
  • Если локальные переменные и параметры мешают уточненной функции, вы можете использовать以查询取代临时变量(Replace Temp with Query),引入参数对象(Introduce Parameter Object)или保持对象完整(Preserve Whole Object).
  • Если впереди нет помощи, можно пройти以函数对象取代函数(Replace Method with Method Object)Попробуйте переместить всю функцию в отдельный объект.
  • Условные выражения и циклы часто также являются уточненными сигналами. Для условных выражений вы можете использовать分解条件表达式(Decompose Conditional). Что касается цикла, вы должны использовать提炼函数(Extract Method)Выделите цикл и код внутри него в отдельную функцию.

доход

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



представление

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

Описание метода рефакторинга

Метод извлечения

проблема

У вас есть фрагмент кода, который можно сгруппировать.

void printOwing() {
  printBanner();

  //print details
  System.out.println("name: " + name);
  System.out.println("amount: " + getOutstanding());
}

решать

Переместите этот код в новую функцию, заменив старый код вызовом функции.

void printOwing() {
  printBanner();
  printDetails(getOutstanding());
}

void printDetails(double outstanding) {
  System.out.println("name: " + name);
  System.out.println("amount: " + outstanding);
}

Заменить Temp запросом

проблема

Поместите результат выражения в локальную переменную и используйте его в своем коде.

double calculateTotal() {
  double basePrice = quantity * itemPrice;
  if (basePrice > 1000) {
    return basePrice * 0.95;
  }
  else {
    return basePrice * 0.98;
  }
}

решать

Вынести все выражение в отдельную функцию и вернуть результат. Используйте функции запросов вместо использования переменных. При желании новые функции могут быть объединены с другими функциями.

double calculateTotal() {
  if (basePrice() > 1000) {
    return basePrice() * 0.95;
  }
  else {
    return basePrice() * 0.98;
  }
}
double basePrice() {
  return quantity * itemPrice;
}

Ввести объект параметра

проблема

Определенные параметры всегда отображаются вместе естественным образом.



решать

Замените эти параметры объектом.



Сохранить весь объект

проблема

Вы берете несколько значений из объекта и используете их в качестве аргументов вызова функции.

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

решать

Вместо этого передайте весь объект.

boolean withinPlan = plan.withinRange(daysTempRange);

Заменить метод объектом метода

проблема

У вас есть функция, которая настолько длинная, что ее локальные переменные настолько переплетены, что вы не можете применить метод извлечения.

class Order {
  //...
  public double price() {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation.
    //...
  }
}

решать

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

class Order {
  //...
  public double price() {
    return new PriceCalculator(this).compute();
  }
}

class PriceCalculator {
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;

  public PriceCalculator(Order order) {
    // copy relevant information from order object.
    //...
  }

  public double compute() {
    // long computation.
    //...
  }
}

Разложить условное

проблема

У вас есть сложные условные выражения.

if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
  charge = quantity * winterRate + winterServiceCharge;
}
else {
  charge = quantity * summerRate;
}

решать

Разбивает все условное выражение на несколько функций на основе условных ветвей.

if (notSummer(date)) {
  charge = winterCharge(quantity);
}
else {
  charge = summerCharge(quantity);
}

Слишком длинный столбец параметров

Длинный список параметров

Функция имеет более 3 или 4 параметров.



проблема вызывает

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

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

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

решение



  • Если запрос к существующему объекту может заменить параметр, то следует использовать以函数取代参数(Replace Parameter with Methods). Здесь «существующий объект» может быть полем в классе, к которому принадлежит функция, или другим параметром.
  • вы также можете использовать保持对象完整(Preserve Whole Object)Соберите кучу данных из одного и того же объекта и замените их этим объектом.
  • Если в некоторых данных отсутствует разумная атрибуция объекта, используйте引入参数对象(Introduce Parameter Object)Создайте для них «объект параметра».

доход

  • Более читаемый, более короткий код.
  • Рефакторинг может выявить повторяющийся код, который ранее оставался незамеченным.

когда игнорировать

  • Здесь есть важное исключение: иногда вы явно не хотите создавать какую-то зависимость между «вызываемым объектом» и «более крупным объектом». В это время также разумно дизассемблировать данные из объекта как отдельный параметр. Но помните о затратах, которые он несет. Если список параметров слишком длинный или меняется слишком часто, вам нужно переосмыслить свою структуру зависимостей.

Описание метода рефакторинга

Заменить параметр методами

проблема

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

int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);

решать

Пусть получатель параметра удалит параметр и вызовет предыдущую функцию напрямую.

int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);

Сохранить весь объект

проблема

Вы берете несколько значений из объекта и используете их в качестве аргументов вызова функции.

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

решать

Вместо этого передайте весь объект.

boolean withinPlan = plan.withinRange(daysTempRange);

Ввести объект параметра

проблема

Определенные параметры всегда отображаются вместе естественным образом.



решать

Замените эти параметры объектом.



Расширенное чтение

использованная литература