Статьи

Практический рефакторинг PHP: декомпозировать условно


В этих статьях об условных обозначениях мы будем использовать следующую терминологию:

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

Это если первый выпуск минисериала по условным выражениям. Во всем этом мы увидим, как упростить и перемещаться по условным выражениям, подобным тому, которое можно найти в ifs.

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

сценарий

В этом первом сценарии у нас есть сложное условие, которое трудно понять. Он может содержать много или и и оператор, некоторые отрицания ( ! ), А также нескольких subconditions. Он также может быть длинным, состоящим из ветвей then, else if и else.

Первое решение, которое мы видим сегодня, состоит в том, чтобы применить метод извлечения к различным атомным частям : условию и элементам then или else. Само условие пока оставлено на месте, но большая часть деталей удалена.

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

Почему улучшение условно?

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

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

Тем не менее, условная логика не скрыта и не трансформирована: вы подчеркиваете тот факт, что в этой точке выполняется ветвь.

меры

  1. Метод выписки по условию . Метод обычно называется как есть … или может …
  2. Метод извлечения на тогдашней части.
  3. Метод извлечения на остальной части, где присутствует.

Конечно, локальные переменные должны передаваться в качестве параметров извлеченных методов, в то время как поля обычно доступны через $ this. Извлеченные методы являются закрытыми по умолчанию.

пример

В исходном состоянии у нас есть презентатор (не объект домена) с именем Group; обратите внимание на уродливый логический флаг. Мы увидим полиморфное решение позже в серии.

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

<?php
class DecomposeConditional extends PHPUnit_Framework_TestCase
{
    public function testAGroupIsInEvidenceWhenManyPostsArePresent()
    {
        $group = new Group('PHP forum', 100);
        $this->assertEquals('<span class="evidence">In evidence: PHP forum</span>', $group->__toString());
    }

    public function testAGroupIsShownAsNormalWhenThereAreNotManyPosts()
    {
        $group = new Group('PHP forum', 10);
        $this->assertEquals('<span>PHP forum</span>', $group->__toString());
    }

    public function testAGroupIsAlsoInEvidenceWhenItHasBeenRecentlyCreated()
    {
        $group = new Group('PHP forum', 0, true);
        $this->assertEquals('<span class="evidence">In evidence: PHP forum</span>', $group->__toString());
    }
}

class Group
{
    private $name;
    private $posts;
    private $recentlyCreated;

    public function __construct($name, $posts, $recentlyCreated = false)
    {
        $this->name = $name;
        $this->posts = $posts;
        $this->recentlyCreated = $recentlyCreated;
    }

    public function __toString()
    {
        if ($this->posts > 50 || $this->recentlyCreated) {
            return "<span class=\"evidence\">In evidence: $this->name</span>";
        } else {
            return "<span>$this->name</span>";
        }
    }
}

Шаг за шагом мы извлекаем условие:

class Group
{
    private $name;
    private $posts;
    private $recentlyCreated;

    public function __construct($name, $posts, $recentlyCreated = false)
    {
        $this->name = $name;
        $this->posts = $posts;
        $this->recentlyCreated = $recentlyCreated;
    }

    public function __toString()
    {
        if ($this->shouldBeInEvidence()) {
            return "<span class=\"evidence\">In evidence: $this->name</span>";
        } else {
            return "<span>$this->name</span>";
        }
    }

    private function shouldBeInEvidence()
    {
        return $this->posts > 50
            || $this->recentlyCreated;
    }
}

Извлекаем тогда ветку:

class Group
{
    private $name;
    private $posts;
    private $recentlyCreated;

    public function __construct($name, $posts, $recentlyCreated = false)
    {
        $this->name = $name;
        $this->posts = $posts;
        $this->recentlyCreated = $recentlyCreated;
    }

    public function __toString()
    {
        if ($this->shouldBeInEvidence()) {
            return $this->inEvidenceLabel();
        } else {
            return "<span>$this->name</span>";
        }
    }

    private function shouldBeInEvidence()
    {
        return $this->posts > 50
            || $this->recentlyCreated;
    }

    private function inEvidenceLabel()
    {
        return "<span class=\"evidence\">In evidence: $this->name</span>";
    }
}

Наконец, мы извлекаем также ветку else:

class Group
{
    private $name;
    private $posts;
    private $recentlyCreated;

    public function __construct($name, $posts, $recentlyCreated = false)
    {
        $this->name = $name;
        $this->posts = $posts;
        $this->recentlyCreated = $recentlyCreated;
    }

    public function __toString()
    {
        if ($this->shouldBeInEvidence()) {
            return $this->inEvidenceLabel();
        } else {
            return $this->ordinaryLabel();
        }
    }

    private function shouldBeInEvidence()
    {
        return $this->posts > 50
            || $this->recentlyCreated;
    }

    private function inEvidenceLabel()
    {
        return "<span class=\"evidence\">In evidence: $this->name</span>";
    }
    
    private function ordinaryLabel()
    {
        return "<span>$this->name</span>";
    }
}

Теперь у нас есть читаемый маленький ведущий. В следующих выпусках мы начнем все более и более агрессивно думать о полиморфном решении.