Una guía de codificación basada en datos para medir, visualizar y hacer cumplir la complejidad cognitiva en proyectos de Python utilizando complexipy

En este tutorial, creamos un flujo de trabajo de análisis de complejidad cognitiva de un extremo a otro utilizando complexipy. Comenzamos midiendo la complejidad directamente a partir de cadenas de código sin formato, luego escalamos el mismo análisis a archivos individuales y a un directorio de proyecto completo. A lo largo del camino, generamos informes legibles por máquina, los normalizamos en marcos de datos estructurados y visualizamos distribuciones de complejidad para comprender cómo se acumula la profundidad de las decisiones en las funciones. Al tratar la complejidad cognitiva como una señal de ingeniería mensurable, mostramos cómo se puede integrar de forma natural en el desarrollo cotidiano de Python y en los controles de calidad. Consulta los CÓDIGOS COMPLETOS aquí.

!pip -q instalar complexipy pandas matplotlib importar os importar json importar textwrap importar subproceso desde pathlib importar ruta importar pandas como pd importar matplotlib.pyplot como plt desde complexipy importar code_complexity, file_complexity print(“✅ Complejidad instalada y dependencias”)

Configuramos el entorno instalando las bibliotecas necesarias e importando todas las dependencias necesarias para el análisis y la visualización. Nos aseguramos de que la computadora portátil sea completamente autónoma y esté lista para ejecutarse en Google Colab sin configuración externa. Constituye la columna vertebral de la ejecución de todo lo que sigue.

fragmento = “”” def score_orders(orders): total = 0 para o en pedidos: if o.get(“valid”): if o.get(“prioridad”): if o.get(“cantidad”, 0) > 100: total += 3 else: total += 2 else: if o.get(“cantidad”, 0) > 100: total += 2 else: total += 1 else: total -= 1 return total “”” res = code_complexity(snippet) print(“=== Complejidad de la cadena de código ===”) print(“Complejidad general:”, res.complexity) print(“Funciones:”) para f en res.functions: print(f” – {f.name}: {f.complexity} (líneas {f.line_start}-{f.line_end})”)

Comenzamos analizando una cadena de código Python sin procesar para comprender la complejidad cognitiva a nivel de función. Inspeccionamos directamente cómo los condicionales anidados y el flujo de control contribuyen a la complejidad. Nos ayuda a validar el comportamiento central de complexipy antes de escalar a archivos reales.

root = Path(“toy_project”) src = root / “src” tests = root / “tests” src.mkdir(parents=True, exist_ok=True) tests.mkdir(parents=True, exist_ok=True) (src / “__init__.py”).write_text(“”) (tests / “__init__.py”).write_text(“”) (src / “simple.py”).write_text(textwrap.dedent(“”” def add(a, b): devolver a + b def safe_div(a, b): if b == 0: return Ninguno return a / b “””).strip() + “\n”) (src / “legacy_adapter.py”).write_text(textwrap.dedent(“”” def Legacy_adapter(x, y): si x e y: si x > 0: si y > 0: devuelve x + y else: devuelve x – y else: si y > 0: devuelve y – x else: devuelve -(x + y) devuelve 0 “””).strip() + “\n”) (src / “engine.py”).write_text(textwrap.dedent(“”” def route_event(event): kind = event.get(“kind”) payload = event.get(“payload”, {}) if kind == “A”: if payload.get(“x”) y payload.get(“y”): return _handle_a(payload) return Ninguno elif kind == “B”: if payload.get(“flags”): return _handle_b(payload) else: return Ninguno elif kind == “C”: para el elemento en payload.get(“items”, []): if item.get(“enabled”): if item.get(“mode”) == “fast”: _do_fast(item) else: _do_safe(item) return Verdadero else: return Ninguno def _handle_a(p): total = 0 for v in p.get(“vals”, []): si v > 10: total += 2 else: total += 1 devuelve total def _handle_b(p): puntuación = 0 para f en p.get(“flags”, []): if f == “x”: puntuación += 1 elif f == “y”: puntuación += 2 else: puntuación -= 1 devolver puntuación def _do_fast(item): devolver item.get(“id”) def _do_safe(item): si item.get(“id”) es Ninguno: devolver Ninguno return item.get(“id”) “””).strip() + “\n”) (pruebas / “test_engine.py”).write_text(textwrap.dedent(“”” de src.engine import route_event def test_route_event_smoke(): afirmar route_event({“kind”: “A”, “payload”: {“x”: 1, “y”: 2, “vals”: [1, 20]}}) == 3 “””).strip() + “\n”) print(f”✅ Proyecto creado en: {root.resolve()}”)

Construimos mediante programación un proyecto Python pequeño pero realista con múltiples módulos y archivos de prueba. Incluimos intencionalmente patrones variados de flujo de control para crear diferencias significativas en complejidad. Consulta los CÓDIGOS COMPLETOS aquí.

ruta_motor = src / “motor.py” file_res = file_complexity(str(engine_path)) print(“\n=== Complejidad del archivo (API de Python) ===”) print(“Ruta:”, file_res.path) print(“Complejidad del archivo:”, file_res.complexity) para f en file_res.functions: print(f” – {f.name}: {f.complexity} (líneas {f.line_start}-{f.line_end})”) MAX_ALLOWED = 8 def run_complexipy_cli(project_dir: Ruta, max_allowed: int = 8): cmd = [
“complexipy”,
“.”,
“–max-complexity-allowed”, str(max_allowed),
“–output-json”,
“–output-csv”,
]
proc = subprocess.run(cmd, cwd=str(project_dir), capture_output=True, text=True) preferido_csv = dir_proyecto / “complexipy.csv” json_preferido = dir_proyecto / “complexipy.json” csv_candidates = []
json_candidatos = []

si preferido_csv.exists(): csv_candidates.append(preferred_csv) si preferido_json.exists(): json_candidates.append(preferred_json) csv_candidates += lista(project_dir.glob(“*.csv”)) + lista(project_dir.glob(“**/*.csv”)) json_candidates += list(project_dir.glob(“*.json”)) + list(project_dir.glob(“**/*.json”)) def uniq(rutas): visto = set() out = []
para p en rutas: p = p.resolve() si p no está visto y p.is_file(): visto.add(p) out.append(p) devuelve csv_candidates = uniq(csv_candidates) json_candidates = uniq(json_candidates) def pick_best(rutas): si no rutas: devuelve Ninguno rutas = sorted(rutas, clave=lambda p: p.stat().st_mtime, reverse=True) rutas de retorno[0]

devolver proc.returncode, pick_best(csv_candidates), pick_best(json_candidates) rc, csv_report, json_report = run_complexipy_cli(root, MAX_ALLOWED)

Analizamos un archivo fuente real utilizando la API de Python y luego ejecutamos la CLI compleja en todo el proyecto. Ejecutamos la CLI desde el directorio de trabajo correcto para generar informes de manera confiable. Este paso une el uso de API local con flujos de trabajo de análisis estático de estilo de producción.

df = Ninguno si csv_report y csv_report.exists(): df = pd.read_csv(csv_report) elif json_report y json_report.exists(): data = json.loads(json_report.read_text()) si isinstance(data, list): df = pd.DataFrame(data) elif isinstance(data, dict): if “archivos” en datos y isinstance(datos[“files”]lista): df = pd.DataFrame(datos[“files”]) elif “resulta” en datos e isinstance(datos[“results”]lista): df = pd.DataFrame(datos[“results”]) else: df = pd.json_normalize(data) si df es Ninguno: elevar RuntimeError(“No se produjo ningún informe”) def explode_functions_table(df_in): si “funciones” en df_in.columns: tmp = df_in.explode(“funciones”, ignore_index=True) if tmp[“functions”].notna().any() y isinstance(tmp[“functions”].dropna().iloc[0]dict): fn = pd.json_normalize(tmp[“functions”]) base = tmp.drop(columnas=[“functions”]) devolver pd.concat([base.reset_index(drop=True), fn.reset_index(drop=True)]eje=1) return tmp return df_in fn_df = explode_functions_table(df) col_map = {} for c in fn_df.columns: lc = c.lower() if lc in (“ruta”, “archivo”, “nombre de archivo”, “módulo”): col_map[c] = “ruta” if (“función” en lc y “nombre” en lc) o lc en (“función”, “func”, “nombre_función”): col_map[c] = “función” si lc == “nombre” y “función” no están en fn_df.columns: col_map[c] = “función” si “complejidad” en lc y “permitido” no en lc y “max” no en lc: col_map[c] = “complejidad” si lc in (“line_start”, “linestart”, “start_line”, “startline”): col_map[c] = “line_start” if lc in (“line_end”, “lineend”, “end_line”, “endline”): col_map[c] = “fin_línea” fn_df = fn_df.rename(columnas=col_map)

Cargamos los informes de complejidad generados en pandas y los normalizamos en una tabla de nivel de función. Manejamos múltiples esquemas de informes posibles para mantener el flujo de trabajo sólido. Esta representación estructurada nos permite razonar sobre la complejidad utilizando herramientas estándar de análisis de datos.

si “complejidad” en fn_df.columns: fn_df[“complexity”] = pd.to_numeric(fn_df[“complexity”]errores=”coercer”) plt.figure() fn_df[“complexity”].dropna().plot(kind=”hist”, bins=20) plt.title(“Distribución de complejidad cognitiva (funciones)”) plt.xlabel(“complejidad”) plt.ylabel(“count”) plt.show() def refactor_hints(complejidad): si complejidad >= 20: devolver [
“Split into smaller pure functions”,
“Replace deep nesting with guard clauses”,
“Extract complex boolean predicates”
]
si complejidad >= 12: regresar [
“Extract inner logic into helpers”,
“Flatten conditionals”,
“Use dispatch tables”
]
si complejidad >= 8: regresar [
“Reduce nesting”,
“Early returns”
]
devolver [“Acceptable complexity”]

si “complejidad” en fn_df.columns y “función” en fn_df.columns: para _, r en fn_df.sort_values(“complejidad”, ascendente=False).head(8).iterrows(): cx = float(r[“complexity”]) si pd.notna(r[“complexity”]) más Ninguno si cx es Ninguno: continuar print(r[“function”]cx, refactor_hints(cx)) print(“✅ Tutorial completo.”)

Visualizamos la distribución de la complejidad cognitiva y derivamos orientación de refactorización a partir de umbrales numéricos. Traducimos puntuaciones de complejidad abstracta en acciones de ingeniería concretas. Cierra el círculo al conectar la medición directamente con las decisiones de mantenibilidad.

En conclusión, presentamos un proceso práctico y reproducible para auditar la complejidad cognitiva en proyectos de Python utilizando complexipy. Demostramos cómo podemos pasar de una inspección ad hoc a un razonamiento basado en datos sobre la estructura del código, identificar funciones de alto riesgo y proporcionar orientación práctica de refactorización basada en umbrales cuantificados. El flujo de trabajo nos permite razonar sobre la mantenibilidad desde el principio, aplicar presupuestos de complejidad de manera consistente y desarrollar bases de código con claridad y confianza, en lugar de depender únicamente de la intuición.

Consulta los CÓDIGOS COMPLETOS aquí. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 100.000 ML y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.