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

Функция должна быть маленькой

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

public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite)throws Exception{
   if(isTestPage(pageData)) includeSetupAndTeardownPages(pageData, isSuite);
   return pageData.getHtml();
}

Блоки и отступы

Это означает, что блоки в операторах if, else, while и т. д. должны быть длиной в одну строку. Возможно, эта строка должна быть вызовом функции. Это не только сохраняет размер объемлющей функции, но и добавляет документальную ценность, поскольку функция, вызываемая внутри блока, может иметь хорошо описательное имя.

Сделайте одну вещь

Функции должны делать одну вещь. Они должны делать это хорошо. Это должны делать только они.

Проблема с этим утверждением в том, что трудно понять, что такое «одна вещь». Легко доказать, что он делает три вещи:

  1. Определение того, является ли страница тестовой.
  2. Если да, то включая установки и разборки.
  3. Отрисовка страницы в HTML.

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

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

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

Один уровень абстракции на функцию

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

Операторы Switch

Также трудно сделать оператор switch, который делает что-то одно. По своей природе операторы switch всегда выполняют N действий. К сожалению, мы не всегда можем избежать операторов switch, но мы можем убедиться, что каждый оператор switch скрыт в низкоуровневом классе и никогда не повторяется. Делаем мы это, разумеется, с помощью полиморфизма.

public Money calculatePay(Employee e) throws InvalidEmployeeType{
   switch(e.type){
      case COMMISSIONED:
         return calculateCommissionedPay(e); 
      case HOURLY:
         return calculateHourlyPay(e);
      case SALARIED:
         return calculateSalariedPay(e);
      default:
         throw new InvalidEmployeeType(e.type);
   }
}

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

isPayday(Employee e, Date date);

or

deliverPay(Employee e, Money pay);

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

public abstract class Employee{
   public abstract boolean isPayday();
   public abstract Money calculatePay();
   public abstract void deliverPay(Money pay);
}

--------------
public interface EmployeeFactory{
   public Employee makeEmployee(EmployeeRecord r) throws invalidEmployeeType;
}

--------------
public class EmployeeFactoryImpl implements EmployeeFactory{

   @Override
   public Employee makeEmployee(EmployeeRecord e) throws invalidEmployeeType {
      switch (e.type){
         case COMMISSIONED:
            return new CommissionedEmployee(e);
         case HOURLY:
            return new HourlyEmployee(e);
         case SALARIED:
            return SalariedEmployee(e);
         default:
            throw new InvalidEmployeeType(e.type);
      }
   }
}

Выводы

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