16

我有这个函数负责将文件名和 mime 类型转换为更“人性化”的东西(例如 file.png、image/png 到 [Image, PNG])。我发现有趣的是语句组比if() elseif()语句具有更高的 NPath 复杂性switch(true)

使用以下代码,PHP Mess Detector 输出 NPath 为 4410:

public function humanKind()
{
    $typeRA = explode("/", strtolower($this->type));
    $fileRA = explode(".", $this->name);
    $fileType = strtoupper($fileRA[count($fileRA) - 1]);

    switch($typeRA[0]) {
        case "image":
            $humanType = "Image";
            break;
        case "video":
            $humanType = "Video";
            break;
        case "audio":
            $humanType = "Sound";
            break;
        case "font":
            $humanType = "Font";
            break;
        default:
            $humanType = "File";
    }

    switch ($this->type) {
        case "application/msword":
        case "application/pdf":
        case "applicaiton/wordperfect":
        case "text/plain":
        case "text/rtf":
        case "image/vnd.photoshop":
        case "image/psd":
        case "image/vnd.adobe.photoshop":
        case "image/x-photoshop":
        case "application/xml":
        case "application/x-mspublisher":
        case "text/html":
        case "application/xhtml+xml":
        case "text/richtext":
        case "application/rtf":
        case "application/x-iwork-pages-sffpages":
        case "application/vnd.apple.pages":
            $humanType = "Document";
            break;
        case "application/vnd.ms-excel":
        case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
        case "application/x-iwork-numbers-sffnumbers":
        case "application/vnd.apple.numbers":
            $humanType = "Spreadsheet";
            break;
        case "application/vnd.ms-powerpoint":
        case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
        case "application/vnd.openxmlformats-officedocument.presentationml.slideshow":
        case "application/x-iwork-keynote-sffkey":
        case "application/vnd.apple.keynote":
            $humanType = "Slideshow";
            break;
        case "application/zip":
        case "application/x-zip-compressed":
        case "application/x-compressed":
        case "application/x-compress":
        case "application/x-rar-compressed":
        case "applicaiton/x-7z-compressed":
        case "application/x-ace-compressed":
            $humanType = "Archive";
            break;
        case "text/x-vcard":
        case "text/x-ms-contact":
            $humanType = "Contact";
            break;
        case "text/x-php":
        case "application/x-dosexec":
        case "application/x-xpinstall":
        case "application/x-opera-extension":
        case "application/x-chrome-extension":
        case "application/x-perl":
        case "application/x-shockwave-flash":
        case "application/java-archive":
            $humanType = "Program";
            break;
        case "application/vnd.ms-fontobject":
        case "application/font-woff":
        case "application/x-font-truetype":
        case "application/x-font-opentype":
        case "application/x-font-ttf":
        case "application/font-sfnt":
            $humanType = "Font";
            break;
    }

    // Special Cases
    if ($humanType == "Archive" && $fileType == "APK") { // Android App
        $humanType = "App";
    } elseif ($humanType == "Archive" && $fileType == "XPS") {
        $humanType = "Document";
    } elseif ($this->type == "application/xml" && $fileType == "CONTACT") {
        $humanType = "Contact";
    } elseif ($this->type == "application/octet-stream" && $fileType == "JNT") {
        $humanType = "Document";
    }

    if (strlen($fileType) > 4) {
        $fileType = "";
    }

    return array($humanType, $fileType);

如果我们然后用if elseif以下替换特殊情况:

    // Special Cases
    switch(true) {
        case ($humanType == "Archive" && $fileType == "APK"): // Android App
            $humanType = "App";
            break;
        case ($humanType == "Archive" && $fileType == "XPS"):
            $humanType = "Document";
            break;
        case ($this->type == "application/xml" && $fileType == "CONTACT"):
            $humanType = "Contact";
            break;
        case ($this->type == "application/octet-stream" && $fileType == "JNT"):
            $humanType = "Document";
            break;
    }

PHP Mess Detector 报告了 1960 年的 NPath 复杂性。

为什么是这样?是什么让 switch(true) 不像在我看来几乎相同的控制结构那么复杂?

4

2 回答 2

9

由于 NPath 复杂性衡量的是完全覆盖您的代码所需的单元测试数量,因此您的 2 个“特殊情况”实现之间应该没有区别。

在计算上有一些差异。让我们逐步完成 2 个“特殊情况”实现并手动计算 NPath 复杂度:

NPath 复杂性与if .. elseif ..

if ($humanType == "Archive" && $fileType == "APK") { // Android App
    $humanType = "App";
} 
elseif ($humanType == "Archive" && $fileType == "XPS") {
    $humanType = "Document";
} 
elseif ($this->type == "application/xml" && $fileType == "CONTACT") {
    $humanType = "Contact";
} 
elseif ($this->type == "application/octet-stream" && $fileType == "JNT") {
    $humanType = "Document";
}

此语句导致9的 NPath 复杂性: 1 分if .. else,每个运算符if(expr) 1 分,每个&&运算符 1 分。(1 + 4 + 4 = 9)

NPath 复杂性与switch(true)

switch(true) {
    case ($humanType == "Archive" && $fileType == "APK"): // Android App
        $humanType = "App";
        break;
    case ($humanType == "Archive" && $fileType == "XPS"):
        $humanType = "Document";
        break;
    case ($this->type == "application/xml" && $fileType == "CONTACT"):
        $humanType = "Contact";
        break;
    case ($this->type == "application/octet-stream" && $fileType == "JNT"):
        $humanType = "Document";
        break;
}

并且此语句导致 NPath 复杂性仅为4 : 0 点,switch(true)因为它不包含&&||运算符,每个case标签都包含 1 点。(0 + 4 = 4)

humanKind函数的 NPath 复杂性

为每个语句计算 NPath 值,然后将这些值相乘。没有“特殊情况”语句的函数的 NPath 复杂度为 490。乘以if .. else if ..语句 9 的 NPath 值,得到 NPath 复杂度 4410。乘以switch(true)语句 4 的 NPath 值,得到复杂度只有 1960 年。仅此而已!

现在我们知道:NPath Complexity 并不能衡量语句中case标签的表达式复杂度switch

于 2014-01-18T10:53:45.447 回答
1

通常,switch 可以比 if/elseif 更快,因为 switch 语句评估条件一次,然后与每个案例进行比较。

我的理解是 switch 语句中的案例是内部索引的,因此您可能会因此获得更好的性能(尽管我找不到原始文章谈论这个,因此我无法证明这一点)。

我还认为 switch 语句的 AST 比等效的 if/elseif 语句要简单得多。

编辑:

在基于 C 的语言(以及最有可能的其他语言)中,当 switch 语句的长度超过 4-5 例时,它们将被实现为列表/哈希表。这意味着每个项目的访问时间变得相同。而在 if/elseif 块中,没有这样的优化。

编译器可以更轻松地处理这些类型的 switch 语句,因为它可以对不同的条件做出更多假设。因此,复杂性较低。任意情况的查找是 O(1)。这再次链接到我之前关于 switch 的 AST 如何更简单的陈述。

编辑#2:

在更多的 CS 术语中,编译器可以使用分支表(或跳转)来减少 switch 语句的 CPU 时间:http ://en.wikipedia.org/wiki/Branch_table

于 2014-01-17T16:29:41.387 回答