一个用Case代替许多小函数的大函数


A Big Function with Case instead of many small functions

将代码分解成只执行一个简单功能的非常小的组件的好处是显而易见的。但是,没有任何规定要求我们必须使每个函数本身成为一个单独的函数。

考虑以下情况:

  • 有一个大函数,但是函数中包含的每个case都是孤立的,只能自己运行。所有的case块都可以复制/粘贴到不同的代码体中,用它们的函数名包装。所以它是模的。不会有任何多个情况(if)合并,从来没有

  • 所有位于case块中的小函数都使用$vars数组作为它们的主变量。因此,任意数量、任意格式的变量都可以作为数组的一部分传递给父迭代器。

  • 父迭代器可以在任何地方运行,甚至可以通过请求一个特定的动作在自己内部运行。即$ this -> run_actions (close_ticket);

  • 对于需要运行的通用过程,它具有巨大的优势,并且可能需要在所有请求的操作上运行。输出缓冲是一个,任何动作钩子,过滤器或任何其他所有可以想象的系统。

  • 这种格式允许在任何操作之前和之后以及/或任何操作的输入和输出上需要运行的任何未来新程序轻松集成。(对于我手上的这个特殊案例,这种案例在未来的出现是肯定的!!)如果所有这些操作都被划分为小函数,那么您需要更改每个函数上的钩子和过滤器,或者仍然需要一些其他函数来将这些操作分派到这些小函数上。使用这种格式,您只需将它们放在switch/case块之前或之后。

  • 代码阅读很简单:当你看到一个特殊的情况,你知道它做什么-> 'view_tickets'是票证视图操作,它接受$vars作为输入。

显然,一个真正的超大质量函数会有各种各样的缺点。

所以,问题是:

假设函数的大小保持在一个合理的大小,模块化的原则和每个案例一个简单的操作被保留,也考虑到将来使用这段代码的任何人都不需要查看这段代码,也不需要修改这段代码,只需要知道钩子和过滤器,它们将被记录在其他地方,而不是代码本身。(包括他们使用的任何变量)你认为这可能是一种有效的方法来组合需要共同过程运行的任务吗?

public function run_actions($action,$vars=false)
{
    // Global, common filters and procedures which all actions need or may need
    ob_start();
    switch($action)
    {
        case 'view_tickets':
        {
            // Do things
            // Echo things if necessary
            break;
        }
        case 'close_ticket':
        {
            // Do things
            // Echo things if necessary
            break;
        }
        case 'do_even_more_stuff':
        {
            // Do things
            // Echo things if necessary
            break;
        }
        // Many more cases as needed
    }
    // Even more common post-processing actions, filters and other stuff any action may need
    $output=ob_get_clean();
    return $output;
}

可以用多态性替换条件。创建一个抽象的操作类,使用类似"execute"的方法,然后为实现该方法的所有不同操作创建子类。

function run_actions(IAction action) {
    //...
    action->execute();
    //...
}
这样,如果您需要引入额外的行为,您就不需要修改和测试具有许多职责的长run_actions。

各种缺点:

开关箱都使用$vars,所以它们没有特定的签名

  • 向开发人员隐藏签名,从而强制其读取代码。
  • 你不能在$vars上做类型提示(强制参数为数组,某些类的实例,等等)
  • 没有IDE自动完成

更容易出错

  • 忘记break,你就完了。没有可识别的错误。

难以重构

  • 你会怎么做,如果你需要提取代码到一个函数?您需要复制预处理(ob_start等)或更改所有
  • 如果你需要在没有预处理的情况下运行动作,你会怎么做?

我同意它很简单,但它有长期的缺点。这取决于你如何平衡:)

当我看到这种架构时,我认为它开始在现有的编程语言之上构建一种新的编程语言。这并不总是一件坏事,如果你正在构建的功能是一个比你正在构建它们的语言更好的抽象,但值得挑战这些功能是什么。

在这种情况下,您要重新发明的语言部分是函数分派:您有一个命名的操作,它接受任意参数,并运行任意代码。PHP已经做到了这一点,而且相当有效;它还具有您的系统所缺乏的特性,例如内置的参数数量和(在某种程度上)类型检查。此外,通过发明一种非标准的"语法",现有的工具将不能很好地工作——例如,它们将不能将动作识别为自文档结构。

您获得的主要部分是能够在操作周围添加预处理和后处理。如果没有其他方法来实现这一点,权衡可能是值得的,但幸运的是,你有更好的选择,例如,将每个动作放入一个函数中,并将其作为回调传递给包装函数;或者使每个动作都成为对象,并使用继承或组合来附加预处理和后处理,而不重复它。

通过将参数包装在数组中,还可以模拟PHP所缺乏的命名参数。如果一个函数有许多参数,其中一些可能是可选的,这可能是一个好主意,但它确实有一些缺点,即重新定义语言通常为您做的处理,例如应用正确的默认值,抱怨缺少强制项等

有一个简单的原则,即不要使用超过2个制表符缩进。

public function applyRules($rules){
    if($rules){
        foreach($rules as $rule){
            //apply ryle
        }
    }
}

在重构后变得更好:

public function applyRules($rules){
    if($rules){
        $this->executeRules($rules);
    }
}
private function executeRules($rules){
    foreach($rules as $rule){
        $rule->execute();
    }
}

这样你的代码将被更好地重构,你可以应用更多的单元测试。

另一个规则是不要使用else,而是破坏代码,例如:

public function usernameExists($username){
    if($username){
        return true;
    }else{
        return false;   
    }
}

你应该这样做:

public function usernameExists($username){
    if($username){
        return true;
    }
    return false;
}

只有这样你才能确保你的代码100%流畅