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

Я получил сообщение PHPMD, говорящее мне:

else никогда не требуется, и вы можете упростить код для работы без остального в этой части кода:

if ($settings == null) {
        $settings = new self($arrSettings);
} else {
        $settings->fill($arrSettings);
}
$settings->save();

return $settings;

Мой вопрос: как я должен избегать else(). Единственный способ, которым я вижу, - это дублирование $setting->save() и вернуться.

Любая идея?

1 ответ

Решение

Вероятно, потому что это может быть переписано как

if ($settings === null) {
    $settings = new self; // or new self([]);
}
$settings->fill($arrSettings);
$settings->save();

return $settings;

Но, TBH, все это выглядит как одно серьезное нарушение SRP, потому что экземпляры классов не должны быть способны создавать новые экземпляры самих себя. Это просто не имеет никакого смысла... но опять же, я не "ремесленник".

TL,DR

  • PHPMD имеет смысл
  • Намерения хороши, но PHPMD советует else "никогда не нужно" неправильно
  • Вы всегда можете удалить else - но во многих случаях это приведет к запутыванию кода (что иронично, поскольку вы будете следовать совету "детектора беспорядка").
  • Поэтому вы всегда должны спрашивать себя: следует ли мне этого избегать?else?
  • Даже сам PHPMD использует elseтак кажется, что иногда это необходимо

Длинный ответ

else- довольно стандартная языковая конструкция, используемая во многих языковых и программных пакетах (включая сам PHPMD). Мне, говоря, чтоelse никогда не нужно - это то же самое, что сказать, что do...while никогда не нужно, потому что вы можете комбинировать while (true) а также break, что верно, но бессмысленно. Поэтому я бы воспринял этот совет с долей скепсиса и немного подумал, прежде чем менять какое-либо программное обеспечение, основываясь исключительно на рекомендации статического анализатора.

Все сказанное, я думаю, разработчики PHPMD имели в виду, что во многих случаях вы можете и должны удалитьelse из вашего кода, чтобы сделать его более читабельным.

Самые простые случаи:

    if ($condition) {
        $a = 1;
    } else {
        $a = 2;
    }

Что можно упростить до:

    $a = ($condition ? 1 : 2);

Теперь посмотрим на это выражение:

    // Calculate using different formulas, depending on $condition.
    if ($condition) {
        // Calculate using secret formula.
        $a = power($r * M_PI, 2) / sqrt(exp($k));
    } else {
        // Calculate using standard formula.
        $a = power(($r / $k) * M_PI, 2) / sqrt(1 / $k);
    }

Это можно изменить на:

    $a = ($condition ? power($r * M_PI, 2) / sqrt(exp($k)) : power(($r / $k) * M_PI, 2) / sqrt(1 / $k));

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

ИМХО, это так. И я всегда используюelse в подобных случаях.

Другой простой пример:

    // Check animal first.
    if ($animal === 'dog') {
        if ($breed === 'pinscher') {
            $weight = 'light';
        } else {
            $weight = 'heavy';
        }
    } else {
        // We only deal with dogs.
        $weight = "we cannot say anything about $animal";
    }

Версия без остального:

    $weight = ($animal === 'dog' ? ($breed === 'pinscher' ? 'light' : 'heavy') : "we cannot say anything about $animal");

Обратите внимание, что в этом случае версия без else является прямым нарушением PSR-2, который запрещает вложенные тернарные операторы.

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

    if (sqrt($weight) > 30 && $animal === 'elephant' && $location == 'zoo') {
        $visitors_max = sqrt($guards) / ($ticker_price * M_2_SQRTPI)
    } else {
        $visitors_max = $visitors_max_last_year * exp($ticket_price) * 1.1;
    }

Это становится:

    $visitors_max = (sqrt($weight) > 30 && $animal === 'elephant' && $location == 'zoo') ? sqrt($guards) / ($ticker_price * M_2_SQRTPI) : $visitors_max_last_year * exp($ticket_price) * 1.1);

Двигаясь дальше, вот еще один хорошо известный шаблон, который, я полагаю, PHPMD хочет, чтобы вы адресовали:


    function myfunc($arg)
    {
        if ($arg === 'foo') {
            $res = 'foo found';
        } else {
            $len = strlen($arg);
        if ($len > 10) {
            $res = 'arg is too big';
        } else {
            $bar = dosomething($res);
            $res = "$arg results in $bar";
        }
        return $res;
    }

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

IMHO (и PHPMD), мы можем удалить else и улучшить ясность кода / ремонтопригодность в таком коде, ничего не теряя:


    function myfunc($arg)
    {
        if ($arg === 'foo') {
            return 'foo found';
        }

        $len = strlen($arg);
        if ($len > 10) {
        return 'arg is too big';
        }

        $bar = dosomething($res);
        return "$arg results in $bar";
    }

Но это не всегда может быть желательно:


    function mycalc($n)
    {
        if ($n === 0) {
           $multiplier = 0.5;
        } elseif ($n === 1) {
           $multiplier = M_LN2;
        } else {
           $multiplier = power(sqrt($n * M_PI), 2);
        }

        return $multiplier * (M_2_PI * power($n * M_PI, 2));
    }

"Улучшенная" версия должна выглядеть примерно так:


    function mycalc($n)
    {
        if ($n === 0) {
           return 0.5 * (M_2_PI * power($n * M_PI, 2));
        }

        if ($n === 1) {
           return M_LN2 * (M_2_PI * power($n * M_PI, 2));
        }

        return power(sqrt($n * M_PI), 2) * (M_2_PI * power($n * M_PI, 2));
    }

Я не уверен насчет вас, но первая версия гораздо точнее следует моему ходу мыслей о расчетах, поэтому ее гораздо легче понять и поддерживать, чем вторую, даже если в ней используется слово "запрещено" else.

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

Итак, чтобы ответить на ваш вопрос, как мне избежать другого?, Я еще один спрошу: а зачем вам?

Ответ @tereško имеет некоторый смысл и действительно делает код более лаконичным. Тем не менее, я лично считаю, что ваша первая версия вполне хороша, более ясна по своим намерениям, поэтому намного лучше с точки зрения понятности и ремонтопригодности:


    if (I do not have $object)
        create a new $object with my settings
    else
        call the "fill" method of $object with my settings
    endif
    do stuff with $object

Против:


    if (I do not have $object)
        create a new $object
    endif

    call the "fill" method of $object with my settings
    do stuff with $object

Также обратите внимание на небольшое изменение логики программирования в версии без elseвыше: вы (и все будущие разработчики) должны предполагать, что вызов метода "fill" объекта $ с моими настройками и создание нового объекта $ с моими настройками всегда приводит к объекту с таким же внутренним состоянием. В исходной версии это предположение не требуется.

Другими словами, отредактированный код будет работать, пока fill() метод и конструктор объекта делают то же самое с внутренним состоянием объекта, которое может быть или не быть истинным - сейчас или когда-либо.

Чтобы проиллюстрировать этот момент, предположим, что объект определен следующим образом:


    class MyClass
    {
        protected $fillCount = 0;

        protected $settings;

        public function __construct(array $settings)
        {
            $this->settings = $settings;
        }

        public function fill(array $settings)
        {
            $this->fillCount++;
            $this->settings = $settings;
        }
    }

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

Теперь давайте посмотрим на один собственный PHPMD else:


    // File: src/bin/phpmd

    if (file_exists(__DIR__ . '/../../../../autoload.php')) {
        // phpmd is part of a composer installation
        require_once __DIR__ . '/../../../../autoload.php';
    } else {
        require_once __DIR__ . '/../../vendor/autoload.php';

        // PEAR installation workaround
        if (strpos('@package_version@', '@package_version') === 0) {
            set_include_path(
                dirname(__FILE__) . '/../main/php' .
                PATH_SEPARATOR .
                dirname(__FILE__) . '/../../vendor/pdepend/pdepend/src/main/php' .
                PATH_SEPARATOR .
                '.'
            );
        }
    }

    // (...100+ lines of code follows...)

Вопрос в том, стоит ли этого избегать?else?

На момент написания, чтобы сделать phpmd счастливым, просто используйте elseif вместо тогоelse (имеет смысл с точки зрения удобочитаемости, когда тело первого if длинный)

$condition = ... ;
if ($condition) {
   ...
} elseif (!$condition) {
   ...
}
Другие вопросы по тегам