BookinglyTech News
Software

assay-checks caza funciones duplicadas ejecutándolas en vez de leyéndolas

La herramienta sondea cada función comparable con una escalera fija de entradas y compara vectores de resultados: 'differs' es una prueba con testigo, 'same' solo la ausencia de uno.

4 min de lecturaDev.to0 vistas

assay-checks busca funciones duplicadas sin leer una sola línea de código: las ejecuta. Se instala con pip install assay-checks o npm install -g assay-checks, y se lanza con assay scan src/. En lugar de comparar texto o nombres, pasa cada función comparable por una misma escalera determinista de entradas y anota qué devuelve o qué excepción levanta en cada peldaño. Dos funciones son candidatas a ser la misma exactamente cuando sus vectores de resultados coinciden.

Eso convierte la búsqueda en un cubo de hash en vez de un barrido cuadrático. El criterio es la ejecución, no el texto: los nombres no se leen nunca. En el árbol donde nació la herramienta emparejó is_wordy con _word, una pareja que ningún detector textual o basado en nombres pondría junta.

El valor del veredicto depende, eso sí, de la escalera. Esas dos funciones se diferencian en un solo predicado: isalpha más una comprobación de dígito frente a isalnum. Todos los caracteres de la escalera las hacían coincidir. Bastaron tres (½, é, tab más salto de línea) para que el same pasara a differs con testigo: ('½',) -> V:False vs V:True, porque ½ es alfanumérico para isalnum y nada para la otra.

De ahí que los veredictos estén redactados de forma asimétrica. Un differs es una prueba: hay una entrada testigo que cualquiera puede replicar. Un same es solo la ausencia de testigo en una escalera finita. Y es same lo que hace fallar la ejecución, precisamente porque es la afirmación más débil y alguien tiene que revisar la pareja a mano.

Guardas contra falsos positivos

Debajo de la mitad de igualdad hay una guarda, y cada cláusula existe porque el autor cometió el error que bloquea. Dos funciones que levantan TypeError con cualquier entrada coinciden perfectamente; también dos que devuelven la misma constante. Sin guarda, un escaneo reportaría cualquier función de un argumento como gemela de todas las demás.

Contar resultados distintos no basta: un valor devuelto más una excepción son dos resultados, lo que premia a un sondeo que solo encontró errores de tipo. Comparar vectores enteros contra la identidad tampoco: una transformación cuyo vocabulario falta en la escalera es la identidad donde responde y levanta excepción en el resto. Y devolver un argumento no es la única forma de no hacer nada con él. Dos transformaciones de parámetros de consulta sin relación coincidieron en todos los peldaños porque la escalera no tenía ninguna clave que ambas reconocieran y las dos acababan copiando el objeto.

Hay además falsos pares por una función con dos nombres: un módulo CommonJS cuyo export es una función llega bajo dos claves, y un barrel module devuelve los mismos objetos que definieron sus dependencias. Eso se rechaza por identidad de objeto, no comparando nombres ni código.

Comparable es una palabra estrecha a propósito. Solo se sondea una función si es de nivel de módulo, sin decoradores, que no sea método ni generador, con uno a tres argumentos y que no toque nada fuera de ellos: ni ficheros, ni red, ni reloj, ni aleatoriedad. La cobertura ronda una décima parte de las funciones, y el censo lo imprime.

Cada escaneo termina con recuentos de lo que rechazó y por qué, con las funciones sondeadas más las no sondeadas igual al total. Un informe que dice que no hay differs y calla lo que nunca comparó está reportando «no miramos» como «no encontramos».

Las versiones recientes aplican la misma maquinaria a varios lenguajes: sondean un árbol de Python y otro de JavaScript sobre una escalera compartida cuyos peldaños viajan como un único JSON con su digest en la clave. Cada excepción se escribe como un token desnudo, porque las taxonomías de error de ambos lenguajes divergen de verdad. Un peldaño donde los dos levantaron no puede ser testigo; uno donde uno levantó y el otro respondió es el caso más interesante.

La suite son 319 tests de Python, 302 de JavaScript y un runner de mutaciones que aplica 193 roturas deliberadas. Varias son defectos reales que la herramienta llegó a enviar, y se mantienen como mutaciones para que un arreglo no se rompa en silencio. Uno merece mención: un byte NUL cayó donde iba un espacio dentro de un template literal. El fichero se veía bien, el parser lo aceptó y al imprimir la función salía un espacio. Pero la clave construida en tiempo de ejecución nunca podía casar con la de la tabla, así que la auditoría seguía reportando un hallazgo que una exención ya había silenciado.