Главные принципы и правила рефакторинга

В среде программистов рефакторингом называют внесение изменений в уже написанный и рабочий код, при котором его поведение остается прежним. По сути, меняется только структура написанного кода для достижения самых разных целей. После рефакторинга мы можем получить структуру кода, более пригодную к расширению и дальнейшему развитию, также можем решить задачи оптимизации кода (сократить время выполнения процесса, использовать память более рационально… ), в некоторых случаях решается задача “украшения кода” для повышения его читабельности разработчиками. Важно отметить, что нередко происходят случаи, когда достижение производительности негативно сказываются на улучшении читаемости кода. Такое систематическое внесение корректировок минимизирует возможность появления ошибок. Это очень важная стадия разработки ПО, специалисты, которые могут проводить качественный рефакторинг, ценятся и получают хорошие зарплаты. Научиться рефакторингу можно на курсах в лучшем Международном IT-колледже DevEducation.

Зачем и когда нужен рефакторинг

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

Зачем проводят рефакторинг? Причин несколько:

  • благодаря рефакторингу находятся малозаметные ошибки;
  • композиция ПО становится намного лучше;
  • программист лучше понимает ПО;
  • программа пишется интенсивнее.

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

Как определить, что нужен рефакторинг

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

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

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

Повторы

Предположим, у нас такой элемент кода:

$dto
->setId($data['id'])
->setTitle($data['title'])

->setCreatedAt($data['createdAt']);

Взвесив все, мы используем гидратор:

$hydrator->hydrate($data, $dto);

Вот его метод:
public function hydrate(array $data, $object) : void{
  foreach($data as $property => $value) {
    $setterName = 'set' . $property;
    if (method_exists($object, $setterName)) {
      $object->{$setterName}($value);
    }
  }
}

Большие методы, классы

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

Комментарии

Всем программистам знакома дилемма, которая возникает, если код получается сложный – просто написать комментарий. Это не стоит делать, даже если комментарий объясняет причину, почему код такой, но не делает его более качественным. Смотрите:
$data = $this->getData($cursor);
// put data to csv
foreach ($data as $row) {
  fputcsv($file, $row);
}

Единственно правильное решение – взять и переписать код. А вместо комментариев вынести код в методы. Короткий код в несколько строк будет правильнее вынести в метод, чем писать комментарий. Старайтесь использовать как можно меньше комментариев, которые объясняют работу метода. Идеальный код не требует комментариев. Правильно принятые названия метода и названия параметров заменяют сложные комментарии.
$data = $this->getData($cursor);
$this->putDataToCsv($file, $data);

Цепочки сообщений – что будет, если не чтить закон Деметры

Программные модули должны обращаться только к модулями – “друзьями”. Они не взаимодействуют с незнакомцами. Так прописано в своде правил, который называют законом Деметры. Если провести аналогию из повседневной, не связанной с программированием, жизни, то нам целесообразнее не приказывать копытам лошади начать движение, а приказать самой лошади, а она уж сама лучше разберется, как привести свои копыта в действие.

Нам в данном контексте важнее, что, согласно этому закону, метод объекта может вызвать методы лишь из:

  • объекта, которого он сам создал;
  • отдельных объектов, с которыми у него есть прямое сообщение;
  • своего объекта;
  • параметров, которые ему были переданы.

По вышеперечисленным причинам, у нас и получается при взаимодействии с “незнакомцами” малая связность кодов. Смотрите:

$postService->getTemplateResolver()->getName();

А вот так должно быть:
$postService->getTemplateName();

Условные выражения

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

Делайте это, как в примере:

class ErrorResponsePart implements ResponsePartInterface{
  /**
  * Error response part data.*
  * @var ResponseDtoInterface $partData*/
  private $partData;
  /**
  * {@inheritdoc}*/
  public function addData(ResponseDtoInterface $data) : void{
    if ($data instanceof ErrorDtoInterface) {
      $this->partData = $this->format($data);
    }
}
  /**
  * Prepares response data.*
  * @param ResponseDtoInterface $data*/
   public function format(ResponseDtoInterface $data) : void{
    if ($data instanceof ListDataDtoInterface) {
      $formatted = $data->getListData();
    } else if (/* some expressions */) {
      // some logic
    } elseif (/* some expressions */) {
      // some logic
    }
    return $formatted ?? $data;
   }
}

Обратите внимание, что решение для нас в этой задаче – вынести if из метода addData в спецификацию. А что же делать с методом format? Его мы также должны вынести в отдельный класс. При этом лучше всего задействовать стратегию:

class ErrorResponsePart implements ResponsePartInterface {
/**
* Specification.*
* @var SpecificationInterface $specification
*/
private $specification;
/**
* Response part data.*
* @var ResponseDtoInterface $partData*/
private $partData;
/**
* Formatter context.*
* @var FormatterContext $formatterContext*/
private $formatterContext;
/**
* ErrorResponsePart constructor.*
* @param SpecificationInterface $specification
* @param FormatterContext $formatterContext
*/
public function __construct(SpecificationInterface $specification, FormatterContext $formatterContext){
  $this->specification = $specification;
  $this->formatterContext = $formatterContext;
}
/**
* {@inheritdoc}
*/
public function addData(ResponseDtoInterface $data) : void {
  if ($this->specification->isSatisfiedBy($data)) {
    $this->partData = $this->formatterContext->process($data);
  }
}
}

А вот как обстоят дела со спецификацией, которые связаны с бизнес-правилами:
class ErrorSpecification implements SpecificationInterface{
/**
* {@inheritdoc}*/
   public function isSatisfiedBy(ResponseDtoInterface $object): bool{
     return $object instanceof ErrorDtoInterface;
   }
}

Вот еще один способ, как минимизировать количество if. Нужно передать какое-то количество стратегий, используя контекст-стратегию. Прямо, как вот здесь:
class FormatterContext{
/**
* Formatters.*
* @var FormatterInterface[]*/
private $formatters;
/**
* FormatterContext constructor.*
* @param FormatterInterface[] $formatters*/
public function __construct(array $formatters = []){
  $this->formatters = $formatters;
}
/**
* Format response data part.*
* @param ResponseDtoInterface $data
* @return mixed*/
public function process(ResponseDtoInterface $data){
  foreach ($this->formatters as $formatter) {
    $data = $formatter->format($data);
}
  return $data;
}
}

Переходим к конкретике:
class ListDataFormatter implements FormatterInterface{
/**
* Checks object for needed requirements.*
* @var FormatterSpecificationInterface $specification*/
private $specification;
/**
* ListDataFormatter constructor.*
* @param FormatterSpecificationInterface $specification*/
public function __construct(FormatterSpecificationInterface $specification){
  $this->specification = $specification;
}
/**
* {@inheritdoc}*/
public function format(ResponseDtoInterface $data){
  if ($this->specification->isSatisfiedBy($data)) {
    $data = $data->getData();
}
return $data;
}
}

А вот как необходимо действовать со спецификацией для форматера:
class ListDataSpecification implements FormatterSpecificationInterface{
/**
* {@inheritdoc}*/
  public function isSatisfiedBy(ResponseDtoInterface $object) : bool{
    return $object instanceof ListDataDtoInterface;
}
}

Непонятные имена

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

$fl = count($data) >= ExportBag::LIMIT_PER_FILE;

Нам необходимо заменить:
$isExceededLimitPerFile = count($data) >= ExportBag::LIMIT_PER_FILE;

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

Тут все просто – не следует использовать количество аргументов в конструкторе, которое не соответствует установленному в вашей команде.

Статика

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

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

Рефакторинг – важный элемент всего процесса создания и дальнейшей оптимизации программного обеспечения. Научитесь делать качественный рефакторинг на лучших и самых эффективных курсах в Международный IT-колледж DevEducation. Для тех, кто покажет хорошие результаты во время собеседования, работает грантовая система со скидкой 85% от стоимости курса.

Присоединяйся к DevEducation — стань востребованным специалистом и построй карьеру в IT!