Salta al contenuto principale

07 - Complexity Assessment e Cognitive Load Metrics

Cyclomatic complexity, cognitive complexity, maintainability assessment, AI code tendency per complessità, threshold setting, refactoring recommendations.

Cosa ne pensi di questo articolo?

07 - Complexity Assessment e Cognitive Load Metrics
Quality EngineeringFoto:Christina MorilloPexels

In Breve

Cyclomatic complexity, cognitive complexity, maintainability assessment, AI code tendency per complessità, threshold setting, refactoring recommendations.

Complexity Assessment e Cognitive Load Metrics

La complessità del software è il nemico silenzioso della manutenibilità. Il codice AI-generated tende a essere più complesso del necessario, con funzioni lunghe, nesting profondo e accoppiamento eccessivo tra i componenti. Misurare e controllare questa complessità è essenziale per mantenere un codebase sano nel lungo termine.

In questo articolo esploreremo le metriche avanzate di complessità, dal cognitive load alle Halstead metrics, dalle architecture fitness functions fino agli strumenti pratici per valutare e ridurre la complessità del codice AI-generated.

Cosa Imparerai

  • Cognitive complexity e perchè è più rilevante della complessità ciclomatica per codice AI
  • Le metriche di Halstead: volume, difficolta e effort computazionale
  • Architecture fitness functions per valutare l'impatto architetturale dell'AI
  • Tendenze specifiche dell'AI nella generazione di codice complesso
  • Strategie di refactoring guidate dalle metriche di complessità
  • Come impostare soglie e threshold per la complessità del codice AI

Cognitive Complexity: Misurare la Difficolta di Comprensione

La cognitive complexity misura quanto il codice è difficile da comprendere per un essere umano. A differenza della complessità ciclomatica che conta i percorsi di esecuzione, la cognitive complexity valuta lo sforzo mentale necessario per seguire la logica del codice, penalizzando in modo particolare il nesting profondo e i break nel flusso lineare.

Per il codice AI-generated, la cognitive complexity è una metrica particolarmente rilevante perchè l'AI produce codice che spesso appare logicamente corretto ma è inutilmente complesso da leggere e mantenere, con livelli di nesting che un developer esperto non produrrebbe.


# Calcolo della cognitive complexity
class CognitiveComplexityCalculator:
    """Calcola la complessità cognitiva secondo il modello SonarSource"""

    def __init__(self):
        self.complexity = 0
        self.nesting_level = 0

    def calculate(self, ast_node):
        """Calcola la complessità cognitiva di una funzione"""
        self.complexity = 0
        self.nesting_level = 0
        self._visit(ast_node)
        return self.complexity

    def _visit(self, node):
        """Visita ricorsiva dell'AST con calcolo incrementale"""
        import ast

        # Incrementi strutturali (B1): +1 per break nel flusso lineare
        if isinstance(node, (ast.If, ast.For, ast.While)):
            self.complexity += 1  # incremento base
            self.complexity += self.nesting_level  # incremento nesting
            self.nesting_level += 1
            for child in ast.iter_child_nodes(node):
                self._visit(child)
            self.nesting_level -= 1
            return

        # Incrementi per operatori logici composti
        if isinstance(node, ast.BoolOp):
            # Sequenze di and/or contano +1 per switch di operatore
            self.complexity += 1

        # Incrementi per break nel flusso: else, elif, except
        if isinstance(node, ast.ExceptHandler):
            self.complexity += 1
            self.complexity += self.nesting_level

        # Ricorsione e goto (in linguaggi che li supportano): +1
        # Lambda annidate: +1 + nesting
        if isinstance(node, ast.Lambda):
            self.complexity += 1 + self.nesting_level

        for child in ast.iter_child_nodes(node):
            self._visit(child)

# Esempio di codice AI con alta cognitive complexity
# vs versione refactored

# ALTO: Cognitive Complexity = 21
def process_order_ai(order):
    if order:                                      # +1
        if order.status == "pending":              # +2 (nesting=1)
            for item in order.items:               # +3 (nesting=2)
                if item.quantity > 0:              # +4 (nesting=3)
                    if item.in_stock:              # +5 (nesting=4)
                        try:                       #
                            charge(item)           #
                        except PaymentError:       # +6 (nesting=4)
                            if item.retry_count < 3:  # +7 (nesting=5)
                                retry(item)
                            else:
                                cancel(item)
    return None

# BASSO: Cognitive Complexity = 7
def process_order_refactored(order):
    if not order or order.status != "pending":     # +1
        return None

    for item in order.items:                       # +1
        process_single_item(item)                  # funzione estratta

def process_single_item(item):
    if item.quantity <= 0 or not item.in_stock:   # +1 (+1 operatore)
        return
    try_charge_item(item)                          # funzione estratta

def try_charge_item(item):
    try:
        charge(item)
    except PaymentError:                           # +1
        if item.retry_count < 3:                   # +2 (nesting=1)
            retry(item)
        else:                                      # +1
            cancel(item)
      

Halstead Metrics: Volume, Difficolta e Effort

Le metriche di Halstead, sviluppate da Maurice Halstead nel 1977, forniscono una misura quantitativa della complessità del software basata sul conteggio degli operatori e degli operandi nel codice sorgente. Per il codice AI-generated, queste metriche rivelano pattern interessanti: l'AI tende a produrre codice con un alto numero di operatori unici ma una varieta limitata di operandi.


import math

class HalsteadMetrics:
    """Calcola le metriche di Halstead per il codice sorgente"""

    def __init__(self, operators, operands):
        # n1 = numero di operatori distinti
        # n2 = numero di operandi distinti
        # N1 = numero totale di operatori
        # N2 = numero totale di operandi
        self.n1 = len(set(operators))
        self.n2 = len(set(operands))
        self.N1 = len(operators)
        self.N2 = len(operands)

    def vocabulary(self):
        """n = n1 + n2: vocabolario del programma"""
        return self.n1 + self.n2

    def length(self):
        """N = N1 + N2: lunghezza del programma"""
        return self.N1 + self.N2

    def volume(self):
        """V = N * log2(n): volume del programma"""
        n = self.vocabulary()
        N = self.length()
        return N * math.log2(n) if n > 0 else 0

    def difficulty(self):
        """D = (n1/2) * (N2/n2): difficolta del programma"""
        if self.n2 == 0:
            return 0
        return (self.n1 / 2) * (self.N2 / self.n2)

    def effort(self):
        """E = D * V: effort di implementazione"""
        return self.difficulty() * self.volume()

    def time_to_program(self):
        """T = E / 18: tempo stimato in secondi (Stroud number)"""
        return self.effort() / 18

    def bugs_estimate(self):
        """B = V / 3000: stima dei bug (metrica empirica)"""
        return self.volume() / 3000

    def summary(self):
        """Riepilogo completo delle metriche"""
        return {
            "vocabulary": self.vocabulary(),
            "length": self.length(),
            "volume": round(self.volume(), 2),
            "difficulty": round(self.difficulty(), 2),
            "effort": round(self.effort(), 2),
            "time_seconds": round(self.time_to_program(), 2),
            "estimated_bugs": round(self.bugs_estimate(), 3)
        }
      

Halstead Metrics: Soglie per Codice AI

Metrica Buono Accettabile Critico
Volume (V) <100 100-1000 >1000
Difficolta (D) <10 10-30 >30
Effort (E) <1000 1000-10000 >10000
Estimated Bugs (B) <0.1 0.1-0.5 >0.5

Architecture Fitness Functions

Le architecture fitness functions sono metriche che valutano quanto il codice rispetta i vincoli architetturali definiti dal team. Per il codice AI-generated, queste funzioni sono critiche perchè l'AI non conosce l'architettura del progetto e potrebbe introdurre violazioni strutturali come dipendenze circolari, accoppiamento eccessivo o violazioni dei layer architetturali.


# Architecture Fitness Functions per codice AI
class ArchitectureFitness:
    """Valuta l'aderenza architetturale del codice AI"""

    def __init__(self, project_structure, architecture_rules):
        self.structure = project_structure
        self.rules = architecture_rules

    def evaluate_all(self):
        """Esegue tutte le fitness functions"""
        return {
            "coupling": self._check_coupling(),
            "cohesion": self._check_cohesion(),
            "layer_violations": self._check_layer_violations(),
            "circular_dependencies": self._check_circular_deps(),
            "component_size": self._check_component_size(),
            "overall_fitness": self._calculate_overall_score()
        }

    def _check_coupling(self):
        """Misura l'accoppiamento tra moduli"""
        # Afferent coupling (Ca): chi dipende da me
        # Efferent coupling (Ce): da chi dipendo
        # Instability = Ce / (Ca + Ce)
        results = []
        for module in self.structure.modules:
            ca = len(module.dependents)
            ce = len(module.dependencies)
            instability = ce / (ca + ce) if (ca + ce) > 0 else 0
            results.append({
                "module": module.name,
                "afferent": ca,
                "efferent": ce,
                "instability": round(instability, 2),
                "status": "OK" if instability < 0.7 else "WARNING"
            })
        return results

    def _check_layer_violations(self):
        """Verifica che i layer architetturali siano rispettati"""
        # Es: presentation non deve importare da data layer
        violations = []
        for rule in self.rules.get("layer_rules", []):
            source = rule["from_layer"]
            forbidden = rule["cannot_import"]
            imports = self.structure.get_imports(source)
            for imp in imports:
                if any(f in imp for f in forbidden):
                    violations.append({
                        "from": source,
                        "imports": imp,
                        "forbidden_layer": forbidden,
                        "severity": "HIGH"
                    })
        return violations
      

Tendenze dell'AI nella Generazione di Codice Complesso

L'analisi di migliaia di output AI rivela pattern ricorrenti di complessità non necessaria. Comprendere queste tendenze aiuta i team a configurare i controlli più appropriati e a guidare meglio i prompt per ottenere codice più semplice.

  • Over-engineering: l'AI aggiunge pattern (factory, strategy, observer) dove una soluzione semplice basterebbe
  • Deep nesting: l'AI genera frequentemente 4-5 livelli di nesting dove 1-2 sarebbero sufficienti
  • God functions: funzioni troppo lunghe che fanno troppe cose, con centinaia di righe
  • Premature abstraction: interfacce e classi astratte per codice che non necessità di estensibilita
  • Copy-paste evolution: l'AI duplica e modifica leggermente piuttosto che generalizzare

Soglie di Complessità Raccomandate per Codice AI

  • Cognitive complexity per funzione: max 15 (vs 25 per codice umano)
  • Nesting depth massimo: 3 livelli (vs 4 per codice umano)
  • Righe per funzione: max 40 (vs 60 per codice umano)
  • Parametri per funzione: max 4 (vs 5 per codice umano)
  • Dipendenze per modulo: max 8 import esterni (vs 10 per codice umano)
  • Halstead Difficulty: max 25 (segnale di codice troppo denso)

Strategie di Refactoring Guidate dalle Metriche

Le metriche di complessità non servono solo a bloccare il codice ma anche a guidare il refactoring. Ogni metrica alta punta a una specifica strategia di semplificazione che può essere applicata sistematicamente.

Mappa Metrica-Refactoring

Problema Rilevato Metrica Strategia di Refactoring
Nesting profondo Cognitive complexity alta Early return, guard clauses, extract method
Funzione troppo lunga Halstead volume alto Extract method, single responsibility
Troppi parametri Halstead difficulty alta Parameter object, builder pattern
Dipendenze eccessive Efferent coupling alto Dependency injection, interface segregation
Classe troppo grande LOC + instability Extract class, decompose by responsibility

Monitoraggio della Complessità nel Tempo

La complessità del codebase deve essere monitorata come trend nel tempo, non come singola misura puntuale. Un codebase sano mantiene la complessità stabile o in diminuzione. Un aumento costante della complessità, specialmente correlato all'adozione di AI coding tools, e un segnale di allarme che richiede intervento immediato.

Conclusioni

La complessità è il nemico più insidioso della qualità del software, e il codice AI-generated tende ad amplificarla. La cognitive complexity, le metriche di Halstead e le architecture fitness functions forniscono gli strumenti per misurare, monitorare e controllare questa complessità in modo sistematico.

Nel prossimo articolo affronteremo le productivity metrics: come misurare l'impatto dell'AI sulla velocità di sviluppo, il paradosso della produttività è il delicato equilibrio tra velocità e qualità nel contesto dello sviluppo assistito da AI.

La semplicità è la sofisticazione suprema. E per il codice AI-generated, raggiungere la semplicità richiede metriche, disciplina è refactoring costante.

Domande frequenti

Quali sono le principali cause della complessità del software generato dall'AI?

Le principali cause della complessità del software generato dall'AI includono funzioni lunghe, nesting profondo e accoppiamento eccessivo tra i componenti.

Cosa è la cognitive complexity e perché è importante per il codice AI-generated?

La cognitive complexity misura quanto il codice è difficile da comprendere per un essere umano e è particolarmente rilevante per il codice AI-generated, poiché l'AI produce codice che spesso appare logicamente corretto ma è inutilmente complesso da leggere e mantenere.

Quali sono le metriche di Halstead e cosa rivelano riguardo al codice AI-generated?

Le metriche di Halstead forniscono una misura quantitativa della complessità del software basata sul conteggio degli operatori e degli operandi nel codice sorgente. Queste metriche rivelano che l'AI tende a produrre codice con un alto numero di operatori unici ma una varieta limitata di operandi.

Perché è importante monitorare la complessità del codebase nel tempo e non solo come singola misura puntuale?

È importante monitorare la complessità del codebase nel tempo perché un codebase sano mantiene la complessità stabile o in diminuzione. Un aumento costante della complessità, specialmente correlato all'adozione di AI coding tools, è un segnale di allarme che richiede intervento immediato.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti