Python pratico

Refactoring automatico del codice generato con AST e tooling Python

14 min di lettura
Laptop chiuso su scrivania scura accanto a fogli di codice Python: uno barrato in rosso, uno con spunte terracotta
Windows macOS Linux
Foto: Alex Lunalbi · ai riparo · Proprietario

Ripulisci lo spaghetti code dell'IA con ast, rope e ruff: pipeline pratica in venv per standardizzare e refactorare codice Python generato, senza siti esterni.

L’intelligenza artificiale è un ottimo generatore di codice, ma può essere un pessimo manutentore. Se usi regolarmente LLM (Claude, Cursor, ChatGPT) per script Python, dopo poche iterazioni il codice funziona e diventa un monolite: import inutilizzati, nomi casuali, funzioni da duecento righe che mescolano business logic e I/O.

La tentazione è chiedere all’IA di «pulire il codice». Spesso questo può portare a regressioni: il modello dimentica un caso limite, toglie una dipendenza necessaria o riscrive un blocco funzionante.

La strada più stabile non è un altro prompt vago, ma strumenti deterministici. Se ti serve solo una sonda stdlib sugli indizi tipici (except silenziosi, import doppi, funzioni monstre), parti dal micro analisi statica AST. Qui usiamo ast, ruff e rope per mappare, formattare e rinominare. Tutto offline, in un venv isolato, senza caricare i sorgenti su siti esterni.

Questa pipeline non sostituisce una code review umana e non trova da sola le falle tipiche del codice IA. Serve a riprendere il controllo stilistico e strutturale prima che il progetto diventi ingestibile — e a preparare il terreno per un eventuale spezzamento in moduli (tema vicino al disaccoppiamento core vs UI).

L’IA contro gli strumenti deterministici

Di fronte a un file caotico, separa i compiti. L’IA è utile per la logica nuova; per il riordino sintattico e i rename cross-file preferisci regole matematiche.

Operazione di pulizia Chi dovrebbe farla? Perché e strumento
Rinominare variabili e funzioni Cambiare nomi in tutto il progetto Tooling (Rope) Rope aggiorna i riferimenti via AST, non con regex. L’IA **può** dimenticare chiamate in altri file.
Formattazione e import Spaziature, virgolette, import inutili Tooling (Ruff) Ruff applica PEP 8 in millisecondi. Chiederlo all’IA spreca token e **può** alterare la logica.
Mappatura del codice Elencare funzioni e classi Tooling (AST) `ast` legge l’albero reale. L’IA **può** allucinare simboli che non esistono.
Spezzare un god-file o disaccoppiare UI Separare logica e presentazione Umano + IA Serve decisione architetturale ([manifest / confini](/blog/manifest-progetto-confini-architetturali-llm/)). I tool preparano il terreno; poi guida a mano o con prompt mirati.

ℹ️ Nota / Prerequisito: Lab verificato con Python 3.12+ (test locale su 3.14), Ruff 0.16.x e Rope 1.15.x su macOS; i comandi valgono su Windows e Linux (cambia solo l’attivazione del venv). Versioni minori possono variare i dettagli di output, non il flusso.

Lab 0: ambiente e codice «spaghetti»

Crea un ambiente isolato. Installare ruff o rope nel Python globale può creare conflitti con altri progetti.

mkdir refactoring_lab
cd refactoring_lab

Windows (PowerShell):

python -m venv venv
.\venv\Scripts\Activate.ps1

macOS / Linux:

python3 -m venv venv
source venv/bin/activate

Installa i tool nel venv:

python -m pip install ruff rope

Crea spaghetti_demo.py con codice volutamente disordinato (tipico di una prima bozza IA):

import os
import sys
import datetime, time
import math
from json import loads

def do_stuff(x, y):
  # funzione creata dall'IA che fa troppe cose
  res = x + y
  print("risultato: "+str(res))
  f = open('log.txt','a')
  f.write(str(datetime.datetime.now()) + " calcolo fatto\n")
  f.close()
  return res

class user_manager:
    def __init__(self):
        self.Users=[]
    def Add(self,n):
        self.Users.append(n)
    def P(self):
        for u in self.Users:
            print(u)

a=10
b=20
do_stuff(a, b)

Problemi evidenti: import inutilizzati, formattazione incoerente, nomi non PEP 8 (user_manager, P), funzione che mescola calcolo e I/O su file.

AST: mappare la struttura senza allucinazioni

Il modulo ast (Abstract Syntax Tree) è nella libreria standard. Non riscrive il file: te lo descrive. Su un file da mille righe generato dall’IA, elencare classi e funzioni reali evita di fidarti di un riepilogo conversazionale.

Crea analizza_ast.py:

import ast

def mappa_struttura(percorso_file):
    with open(percorso_file, "r", encoding="utf-8") as f:
        codice = f.read()

    albero = ast.parse(codice)

    print(f"--- Mappatura di {percorso_file} ---")
    for nodo in albero.body:
        if isinstance(nodo, ast.FunctionDef):
            print(f"[Funzione] {nodo.name} (riga {nodo.lineno})")
        elif isinstance(nodo, ast.ClassDef):
            print(f"[Classe]   {nodo.name} (riga {nodo.lineno})")
            for sottomembro in nodo.body:
                if isinstance(sottomembro, ast.FunctionDef):
                    print(f"  └── [Metodo] {sottomembro.name}")

if __name__ == "__main__":
    mappa_struttura("spaghetti_demo.py")
python analizza_ast.py

Output atteso (verificato nel lab):

--- Mappatura di spaghetti_demo.py ---
[Funzione] do_stuff (riga 7)
[Classe]   user_manager (riga 16)
  └── [Metodo] __init__
  └── [Metodo] Add
  └── [Metodo] P

Se nel chat l’IA parla di processData e l’AST non la mostra, sai subito che stai rincorrendo un’allucinazione — non un simbolo reale.

💡 Estensione utile: puoi contare nodi ast.Call verso eval, exec, subprocess o open come spia di pattern da revisionare a mano (non come scanner di sicurezza completo).

Ruff: lint e formattazione in secondi

Ruff (scritto in Rust) in molti progetti moderni ha sostituito Flake8 + Black + isort perché è veloce e deterministico. Il compito: regole stilistiche e fix sicuri (es. import inutilizzati), non riscritture semantiche.

Prima crea un ruff.toml minimo nella radice del lab (così i risultati sono riproducibili):

line-length = 88
target-version = "py312"

[lint]
select = ["E", "F", "W", "I"]
ignore = []

[format]
quote-style = "double"

Poi:

ruff check spaghetti_demo.py

Con questa selezione vedrai soprattutto gli F401 (import inutilizzati), tipicamente:

spaghetti_demo.py:1:8: F401 [*] `os` imported but unused
spaghetti_demo.py:2:8: F401 [*] `sys` imported but unused
spaghetti_demo.py:3:18: F401 [*] `time` imported but unused
spaghetti_demo.py:4:8: F401 [*] `math` imported but unused
spaghetti_demo.py:5:18: F401 [*] `json.loads` imported but unused
Found 5 errors.
[*] 5 fixable with the `--fix` option.

L’asterisco [*] indica fix automatico considerato sicuro. Applica:

ruff check --fix spaghetti_demo.py
ruff format spaghetti_demo.py

Dopo il format, il file usa indentazione a 4 spazi, spazi attorno agli operatori e virgolette doppie coerenti. Resta la classe user_manager e il metodo P: Ruff non inventa nomi di dominio — quello è lavoro di Rope (o tuo).

⚠️ Attenzione: senza ruff.toml, Ruff può abilitare regole extra (context manager, timezone, …). Non sono «sbagliate», ma cambiano l’output rispetto a questo lab. Parti da E,F,W,I e amplia solo se sai cosa stai accettando.

Rope: rename basato sull’AST

Ruff pulisce la sintassi; Rope modifica la struttura dei simboli. Capisce scope e riferimenti: meglio di un «trova e sostituisci» testuale che può toccare commenti, stringhe o omonimi in altri ambiti. Lab dedicato (offset, tmp → invoice_total, git diff): ridenominazione sicura con rope.

Molti IDE usano Rope (o motori simili) dietro le quinte. Qui lo usiamo via API Python per vedere cosa succede e per avere uno script riproducibile.

🚨 Avviso critico — modifica sorgenti: Rope riscrive i file. Anche se ha uno storico interno (project.history.undo()), committa su Git prima di lanciare script di refactoring.

Crea refactor_rope.py:

from rope.base.project import Project
from rope.refactor.rename import Rename

def rinomina_con_rope(percorso_cartella):
    progetto = Project(percorso_cartella)
    modulo = progetto.get_resource("spaghetti_demo.py")

    codice = modulo.read()
    offset_classe = codice.find("class user_manager") + 6  # punta alla 'u'

    cambiamento_classe = Rename(
        progetto, modulo, offset_classe
    ).get_changes("UserManager")
    progetto.do(cambiamento_classe)
    print("Classe rinominata in UserManager.")

    codice_aggiornato = modulo.read()
    offset_metodo = codice_aggiornato.find("def P(") + 4  # punta a 'P'
    if offset_metodo > 3:
        cambiamento_metodo = Rename(
            progetto, modulo, offset_metodo
        ).get_changes("print_users")
        progetto.do(cambiamento_metodo)
        print("Metodo P rinominato in print_users.")

    progetto.close()

if __name__ == "__main__":
    rinomina_con_rope(".")
python refactor_rope.py

Esito tipico verificato nel lab: class UserManager e def print_users(self). Rope non si limita a sostituire la stringa: aggiorna solo le occorrenze come simbolo, nello scope corretto.

L’offset «trova la stringa + N» è didattico. In un progetto vero preferisci: editor / LSP rename, oppure calcolare l’offset da ast (lineno + col_offset). Evita offset a occhio su file lunghi.

Estrarre un metodo (cenno)

Rope espone anche ExtractMethod / ExtractVariable. Il flusso concettuale è lo stesso: selezioni un intervallo di codice (offset inizio/fine), chiedi un nome, Rope crea la nuova funzione e sostituisce il blocco con una chiamata. È utile quando do_stuff mescola somma + print + scrittura su file: estrai prima append_log(...), poi rinomina, poi sposti il log in un modulo dedicato.

Non automatizzare l’estrazione «a caso» su centinaia di righe senza test: un’estrazione sbagliata può catturare variabili di scope in modo sorprendente. Un pezzo alla volta, smoke test dopo ogni project.do(...).

Undo rapido

progetto.history.undo()  # annulla l’ultimo cambiamento Rope

Meglio ancora: Git. Lo storico Rope vive nel progetto Rope; non sostituisce un commit chiaro con messaggio «pre-rename UserManager».

Pipeline numerata: dall’Accept al commit

Ordine consigliato dopo un Accept «grosso» da Cursor/ChatGPT:

  1. Git commit (o almeno stash) — punto di ripristino obbligatorio.
  2. AST map — analizza_ast.py (o equivalente) sul file toccato: cosa c’è davvero?
  3. Ruff check –fix + ruff format — rumore stilistico via, import fantasma via.
  4. Rope rename (o rename IDE) — simboli leggibili, un concetto alla volta.
  5. Test smoke — esegui lo script / i test minimi; se rompi qualcosa, git checkout e rivedi.
  6. Review 15 minuti — checklist umana: secret, shell=True, verify=False, bind 0.0.0.0.
  7. Solo allora chiedi all’IA di estrarre moduli o disaccoppiare UI — con confini già in SPEC/manifest.

Invertire i passi (chiedere «pulisci tutto» all’IA prima di Ruff) può produrre un diff enorme e opaco: più difficile da Accept riga per riga.

Mini checklist da stampare

  • venv attivo, niente pip globale
  • ruff.toml versionato nel repo
  • commit prima di Rope
  • un rename alla volta, poi test
  • sicurezza = review umana, non Ruff

Opzione: regole in pyproject.toml

Se il repo ha già un pyproject.toml, puoi evitare un secondo file:

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "W", "I"]

[tool.ruff.format]
quote-style = "double"

Ruff legge entrambi; scegline uno e resta coerente nel team.

Gate pre-commit (idea minima)

Un hook o uno script scripts/lint.sh che esegue solo:

ruff check .
ruff format --check .

in CI o prima del push. --check sul format fallisce se qualcuno ha Acceptato codice non formattato, senza riscrivere i file in CI. In locale usi ruff format senza --check per sistemare.

Cosa questa pipeline non fa (limiti di sicurezza)

Ruff e Rope non certificano che il codice sia sicuro. Esempi di buchi che restano intatti dopo una formattazione perfetta:

  • API key hardcodate (Ruff non le «vede» come segreto se sono stringhe valide).
  • subprocess(..., shell=True) con input utente.
  • requests.get(..., verify=False).
  • SQL con f-string.
  • Cookie di sessione senza HttpOnly / Secure.

Per quei pattern usa vincoli misurabili prima di generare (libreria prompt) e la review dopo. La pipeline di questo articolo riduce il rumore: così nella review trovi i problemi veri invece di litigare con gli import.

Neanche Rope «sistema» un god-file: rinominare do_stuff in calcola_somma non separa I/O e logica. Per spezzare moduli resta lavoro umano + IA guidata, sulla scia dell’architettura vibe-coded.

Quando ha senso tornare all’IA

Dopo Ruff/Rope, sì: «Sposta append_log in io_log.py e lascia in core.py solo la funzione pura che restituisce la somma. Non cambiare la firma pubblica. Non aggiungere dipendenze.»

Prima di Ruff/Rope, di solito no: «Rendi il file pulito e professionale» è un prompt performativo — lo stesso problema descritto sui vincoli misurabili.

Errori frequenti (e come uscirne)

«Ruff ha cancellato un import che mi serviva»
Di solito l’import era davvero inutilizzato in quel file. Se lo usavi solo in un blocco commentato o in una stringa, ripristina da Git e importa dove serve davvero.

«Rope ha rinominato anche una stringa / docstring»
Con le API di default Rope è cauto sulle stringhe; se hai passato opzioni tipo documentazione o unsure, può essere più aggressivo. Controlla il diff. In dubbio: project.history.undo().

«Offset sbagliato → rename sul simbolo errato»
Se find("def P(") prende un’altra occorrenza, Rope rinomina quella. Preferisci offset da AST o rename dall’IDE sul cursore.

«Ho lanciato Rope senza venv / nella home»
Project(".") indizia la cartella corrente. Lancialo dentro il repo, non dalla home utente, altrimenti può scansionare troppi file e diventare lento o confuso.

«L’IA ha riscritto il file dopo Ruff e ha rimesso il caos»
Dopo Accept grandi, rilancia ruff format e ruff check --fix. Tratta Ruff come gate pre-commit (hook o CI), non come one-shot.

SyntaxError su ast.parse
Il file non è Python valido: l’IA può aver lasciato markdown o fence ``` dentro al .py. Sistema la sintassi prima di AST/Rope.

Windows: Activate.ps1 bloccato
Se PowerShell rifiuta lo script, in molti ambienti basta Set-ExecutionPolicy -Scope CurrentUser RemoteSigned (valuta la policy della tua macchina) oppure usa venv\Scripts\activate.bat da Prompt dei comandi. Il resto della pipeline è identico.

Ruff non trovato dopo pip install
Di solito il terminale non è nel venv: il prompt deve mostrare (venv). Verifica con python -m ruff --version (modulo via interprete attivo) se il PATH degli Scripts dà noia su Windows.

Laboratorio: prima / dopo in sintesi

Step Cosa cambia
AST Vedi do_stuff, user_manager, metodi Add / P
Ruff fix + format Via os/sys/math/…; PEP 8; resta datetime usato
Rope UserManager, print_users

Il file resta un esempio didattico (ancora mescola print, file e calcolo): ma ora è leggibile e pronto per un refactor semantico guidato da te, non per un «pulisci tutto» cieco.

Domande Frequenti (FAQ)

Ruff sostituisce Black e isort?

Per molti progetti sì: format + ordinamento import + lint in un solo binario. Se il team ha già Black in CI, puoi adottare Ruff gradualmente (prima solo check, poi format).

Devo imparare Rope se uso Cursor / VS Code?

Non obbligatorio. Il rename dell’IDE basta per casi singoli. Lo script Rope serve a capire il meccanismo, automatizzare batch e lavorare headless (CI, lab).

Posso usare questa pipeline su codice non generato dall’IA?

Sì. Il valore è alto sul codice IA perché spesso arriva rumoroso; sui progetti umani maturi Ruff resta utile come standard di team.

ast può riscrivere il codice?

Sì in teoria (ast.unparse / trasformazioni), ma è fragile per refactor grossi. Per rename strutturati preferisci Rope o l’IDE; tieni ast per analisi e report.

Funziona solo su un file o su tutto il progetto?

ruff check . / ruff format . sul tree. Rope, con Project, può aggiornare riferimenti cross-file nel rename — motivo per cui il commit preventivo è obbligatorio.

Dove metto ruff.toml in un repo reale?

Nella radice del progetto (accanto a pyproject.toml se c’è). Allinea target-version alla versione Python del venv di progetto.

In sintesi

  1. Non chiedere all’IA di «pulire» quando puoi usare tool deterministici.
  2. AST per vedere la mappa vera; Ruff per stile e import; Rope per rename sicuri.
  3. Commit → map → lint/format → rename → test → review sicurezza.
  4. La sicurezza resta umana: questa pipeline riduce il rumore, non il threat model.

Letture opzionali

Alex Lunalbi

Alex Lunalbi

Security & privacy

Dona su PayPal

Scrivo di sicurezza digitale pratica e codice IA. Su ai riparo ogni procedura è verificata su macchine reali.

Tag
  • python,
  • vibe coding,
  • refactoring,
  • ruff,
  • ast