else 永远不是必需的,您可以简化代码以在没有 else 的情况下工作

else is never necessary and you can simplify the code to work without else

我收到 PHPMD 的消息告诉我:

else is never necessary and you can simplify the code to work without else on this portion of code:

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

return $settings;

我的问题是:我应该如何避免 else()。我看到的唯一方法是复制 $setting->save() 和 return.

有什么想法吗?

可能是因为可以改写为

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

return $settings;

但是,老实说,整个事情看起来像是对 SRP 的一次重大违反,因为 class 实例不应该创建自己的新实例。那只是没有任何意义..但是话又说回来,我不是 "artisan".

TL,DR

  • PHPMD 有一点
  • 本意是好的,但是 PHPMD else "is never necessary" 的建议是错误的
  • 您始终可以删除 else——但很多时候这会导致代码更加混乱(这很讽刺,因为您将遵循 "mess detector" 的建议)。
  • 所以你应该经常问自己:我应该避免这种情况吗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 的原始代码要清晰得多,更不用说使用代码注释解释 "improved" 版本要困难得多,不是吗?

恕我直言,是的。 我总是在这种情况下使用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;
    }

这个函数使用了曾经教过的关于编程的建议类,即函数应该有一个单一的退出点,因为这(可以说)更容易理解程序流程并找到错误。

恕我直言(和 PHPMD's),我们可以删除 else 并改进代码 clarity/maintainability,而不会丢失任何东西:


    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));
    }

"improved" 版本应该是这样的:


    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));
    }

我不确定你,但是第一个版本更接近我的计算思路,因此比第二个更容易理解和维护,即使它使用 "forbidden" else.

(有人可能会争辩说我们可以使用第二种形式,加上一个辅助变量来保存通用计算。这很公平,但人们总是会反驳说添加一个不必要的变量会使代码更少 清晰且维护成本高。)

所以,为了回答你的问题 我应该如何避免 else?,我会问另一个问题:你为什么要避免?

@tereško 的回答有些道理,确实让代码更简洁了。但是,我个人认为您的第一个版本非常好,意图更明确,因此从可理解性和可维护性 POV 来看更好:


    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" $object 的方法与我的设置 使用我的设置创建一个新的 $object 总是以具有相同内部状态的对象结束。原版不需要这个假设。

换句话说,只要 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) {
   ...
}