Почему коммутатор (true) имеет меньшую сложность NPath, чем if() elseif()?

У меня есть эта функция, которая отвечает за преобразование имени файла и 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 сообщает о сложности NPath 1960 года.

Почему это? Что делает switch (true) менее сложным, чем то, что кажется мне примерно такой же структурой управления?

Ответы

Ответ 1

Так как сложность 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";
}

Это утверждение приводит к сложности NPath 9: 1 балл для if .. else, 1 балл для каждого if(expr) и 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), потому что в нем нет операторов && или || и 1 балл для каждой метки case. (0 + 4 = 4)

NPath Сложность вашей функции humanKind

Значения NPath вычисляются для каждого оператора, а значения умножаются. NPath Сложность вашей функции без оператора "Особые случаи" равна 490. Умножая значение NPath для оператора if .. else if .. из 9, вы получаете NPath Complexity 4410. И умножается на значение NPath для инструкции switch(true) 4 вы получаете сложность всего в 1960 году. Это все!

И теперь мы знаем: NPath Сложность не измеряет сложность выражения case ярлыков в операторах switch!

Ответ 2

В общем случае коммутатор может быть быстрее, чем if/elseif из-за того, что операторы switch оценивают условие один раз, а затем сравнивают с каждым случаем.

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

Я бы также предположил, что AST для оператора switch намного проще, чем эквивалент if/elseif.

Edit:

В языках на языке C (и, скорее всего, другие), операторы switch реализуются как списки/хеш-таблицы, когда они становятся длиннее 4-5 случаев. Это означает, что время доступа для каждого элемента становится одинаковым. Если в блоке if/elseif такой оптимизации нет.

У компилятора есть более легкое время, связанное с этими типами операторов switch, поскольку оно может делать больше предположений о разных условиях. Таким образом, меньше сложности. Следствием произвольного случая являются O (1). Что снова связано с моим предыдущим утверждением о том, что AST для коммутатора, скорее всего, намного проще.

Изменить # 2:

В более CS lingo компиляторы могут использовать таблицы ветвей (или прыгать), чтобы уменьшить время процессора для операторов switch: http://en.wikipedia.org/wiki/Branch_table