【问题标题】:else is never necessary and you can simplify the code to work without else永远不需要 else ,您可以简化代码以在没有 else 的情况下工作
【发布时间】:2017-05-21 18:41:03
【问题描述】:

我收到一条 PHPMD 的消息告诉我:

else 从来都不是必需的,您可以简化代码以在没有 else 的情况下工作 在这部分代码上:

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

return $settings;

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

有什么想法吗?

【问题讨论】:

    标签: php laravel phpmd


    【解决方案1】:

    可能是因为它可以重写为

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

    但是,TBH,整个事情看起来像是对SRP 的一次重大违反,因为类实例不应该能够创建自己的新实例。那没有任何意义..但话又说回来,我不是“工匠”。

    【讨论】:

    • 好的,我正在尝试编写最好的 PHP 代码,所以您的评论对我来说很棒。发生这种情况是因为我称这个方法是静态的,这是 PHPMD 的另一个警告。所以在函数之外创建实例应该可以解决这个问题
    • 在这种情况下,您可能应该创建一个单独的工厂类,它负责创建新的Settings 实例。
    • 是的,应该是因为我不知道怎么用$this改$settings = new self($arrSettings);
    • 是的,没有干净的方法来“替换$this”,这就是为什么实体的逻辑和创建逻辑应该分开的原因之一
    • 问题是我不知道这个小代码是否值得工厂,这对我来说听起来有点过于工程......会考虑一下。但是解决 Tx 的主要问题!!!
    【解决方案2】:

    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 的原始代码要清晰得多,更不用说使用代码 cmets 解释“改进”版本要困难得多,不是吗?

    恕我直言,确实如此。而且我总是在这种情况下使用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),我们可以删除 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));
        }
    
    

    我不确定你,但第一个版本更接近我的计算思路,因此比第二个更容易理解和维护,即使它使用“禁止”@987654346 @。

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

    那么,为了回答你的问题我应该如何避免其他问题?,我会问另一个问题:你为什么要这样做?

    @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 的版本中,编程逻辑有一个细微的变化:您(以及所有未来的开发人员)必须假设使用我的设置调用$object 的“填充”方法 和 create a new $object with my seetings 总是以具有相同内部状态的对象结束。原始版本不需要此假设。

    换句话说,只要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吗?

    【讨论】:

      【解决方案3】:

      在撰写本文时,为了让 phpmd 开心,只需使用elseif 而不是else (当第一个if 的正文很长时,在可读性方面有意义)

      $condition = ... ;
      if ($condition) {
         ...
      } elseif (!$condition) {
         ...
      }
      

      【讨论】:

      • 添加elseif 似乎是一场维护噩梦。如果唯一的目的是让 phpmd 高兴 - 只是annotate the code,例如@SuppressWarnings(PHPMD.ElseExpression).
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-21
      • 1970-01-01
      • 2021-04-30
      相关资源
      最近更新 更多