php - globally - ¿Por qué switch(true) tiene una complejidad NPath más pequeña que if() elseif()?
install phpcs linux (2)
Tengo esta función que se encarga de convertir el nombre y el tipo mime de un archivo en algo más "humano" (por ejemplo, archivo.png, imagen / png a [Imagen, PNG]). Lo que encontré interesante fue que los grupos de declaraciones if() elseif()
tenían una mayor complejidad NPath que una instrucción switch(true)
.
Con el siguiente código, PHP Mess Detector genera un NPath de 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);
Si luego reemplazamos los casos especiales if elseif
con el siguiente:
// 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 informa de una complejidad NPath de 1960.
¿Por qué es esto? ¿Qué hace que switch (verdadero) sea menos complejo de lo que me parece que es casi la misma estructura de control?
Debido a que la complejidad de NPath mide la cantidad de pruebas unitarias necesarias para obtener una cobertura completa de su código, no debe haber diferencia entre sus 2 implementaciones de "Casos especiales".
Pero hay alguna diferencia en el cálculo. Revisemos las 2 implementaciones de "Casos especiales" y calculemos la complejidad de NPath manualmente:
NPath Complejidad con 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";
}
Esta declaración da como resultado una complejidad NPath de 9 : 1 punto por if .. else
, 1 punto por cada if(expr)
y 1 punto por cada operador de &&
. (1 + 4 + 4 = 9)
Complejidad NPath con 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;
}
Y esta declaración da como resultado una complejidad de NPath de solo 4 : 0 puntos por switch(true)
porque no contiene &&
o ||
Operadores y 1 punto por cada etiqueta de case
. (0 + 4 = 4)
Complejidad NPath de tu función humanKind
Los valores de NPath se calculan para cada declaración y los valores se multiplican. La complejidad de NPath de su función sin la declaración de "Casos especiales" es 490. Multiplica con el valor de NPath para la afirmación if .. else if ..
de 9, obtiene una complejidad de NPath de 4410. Y se multiplica con el valor de NPath para el switch(true)
declaración de 4, obtienes una complejidad de solo 1960. ¡Eso es todo!
Y ahora sabemos: ¡ NPath Complexity no mide la complejidad de expresión de las etiquetas de case
en declaraciones de switch
!
En general, el cambio puede ser más rápido que if / elseif debido al hecho de que las declaraciones de cambio evalúan la condición una vez y luego se comparan con cada caso.
Tengo entendido que los casos en una declaración de cambio están indexados internamente y, por lo tanto, es posible que tenga un mejor rendimiento debido a esto (aunque no puedo encontrar el artículo original hablando de esto, por lo tanto no puedo probarlo).
También me imagino que el AST para una sentencia de conmutación es mucho más simple que el equivalente si / elseif.
Editar:
En los lenguajes basados en C (y más probablemente en otros), las instrucciones de conmutación se implementan como listas / tablas hash cuando llegan a tener más de 4-5 casos. Esto significa que los tiempos de acceso para cada elemento se vuelven iguales. Mientras que en un bloque if / elseif, no hay tal optimización.
Al compilador le resulta más fácil lidiar con este tipo de declaraciones de cambio, ya que puede hacer más suposiciones sobre las diferentes condiciones. Por lo tanto, menos complejidad. Las búsquedas de un caso arbitrario son O (1). Lo que de nuevo enlaza con mi declaración anterior de cómo el AST de un conmutador es probablemente mucho más simple.
Edición # 2:
En más CS Lingo, los compiladores pueden usar tablas de derivación (o saltos) para reducir el tiempo de CPU para las declaraciones de cambio: http://en.wikipedia.org/wiki/Branch_table