2009-04-12 7 views
6

Ho sempre avuto dubbi quando si tratta di progettare un report di esecuzione corretto.Segnalazione di informazioni durante l'esecuzione del codice: miglior design

Dire di avere il seguente (stupido, per essere semplice) caso. Userò Python.

def doStuff(): 
    doStep1() 
    doStep2() 
    doStep3() 

Ora, supponiamo si vuole dare un report delle varie fasi, se qualcosa va storto, ecc Non proprio debug: solo il comportamento informativo dell'applicazione.

Una prima soluzione semplice è quello di mettere le stampe

def doStuff(): 
    print "starting doing stuff" 
    print "I am starting to do step 1" 
    doStep1() 
    print "I did step 1" 
    print "I am starting to do step 2" 
    doStep2() 
    print "I did step 2" 
    print "I am starting to do step 3" 
    doStep3() 
    print "I did step 3" 

In generale, questo è piuttosto male. Supponiamo che questo codice finisca in una libreria. Non mi aspetterei che la mia biblioteca stampi qualcosa. Mi aspetterei che facesse il lavoro in silenzio. Tuttavia, a volte vorrei fornire informazioni, non solo in situazioni di debug, ma anche per mantenere l'utente informato che qualcosa è effettivamente in procinto di essere fatto. Anche la stampa è cattiva perché non hai il controllo sulla gestione dei tuoi messaggi. va solo allo stdout e non c'è nulla che tu possa fare al riguardo, eccetto il reindirizzamento.

Un'altra soluzione è avere un modulo per la registrazione.

def doStuff(): 
    Logging.log("starting doing stuff") 
    Logging.log("I am starting to do step 1") 
    doStep1() 
    Logging.log("I did step 1") 
    Logging.log("I am starting to do step 2") 
    doStep2() 
    Logging.log("I did step 2") 
    Logging.log("I am starting to do step 3") 
    doStep3() 
    Logging.log("I did step 3") 

Questo ha il vantaggio che si ordina di conoscere un luogo unico per il vostro servizio di registrazione, e si può sperimentare questo servizio tanto quanto si desidera. Puoi silenziarlo, reindirizzarlo su un file, su stdout o anche su una rete. Lo svantaggio è che si ottiene un accoppiamento molto forte con il modulo di registrazione. Fondamentalmente ogni parte del tuo codice dipende da questo e hai chiamate per accedere ovunque.

La terza opzione è quella di avere un oggetto report con un'interfaccia chiara, e si passa in giro

def doStuff(reporter=NullReporter()): 
    reporter.log("starting doing stuff") 
    reporter.log("I am starting to do step 1") 
    doStep1() 
    reporter.log("I did step 1") 
    reporter.log("I am starting to do step 2") 
    doStep2() 
    reporter.log("I did step 2") 
    reporter.log("I am starting to do step 3") 
    doStep3() 
    reporter.log("I did step 3") 

Alla fine, si può anche passare l'oggetto reporter di doStepX() se hanno altro da dire. Vantaggio: riduce l'accoppiamento con un modulo, ma introduce l'accoppiamento con l'istanziazione dell'oggetto NullReporter. Questo può essere risolto utilizzando Nessuno come predefinita e il controllo prima di chiamare di registro, che è goffo, perché in pitone si deve scrivere un condizionale ogni volta (in C si potrebbe definire una macro)

def doStuff(reporter=None): 
    if reporter is not None: 
     reporter.log("starting doing stuff") 
     # etc... 

Edit: altro l'opzione è lavorare in Qt e avere una strategia di segnale emit(). Mentre il codice viene eseguito, emette informazioni con i codici di stato appropriati e chiunque sia interessato può iscriversi ai segnali e fornire informazioni. Bello e pulito, molto disaccoppiato, ma richiede un po 'di codifica, in quanto non penso che questo possa essere fatto rapidamente con la batteria python inclusa.

Infine, è possibile generare eccezioni con un messaggio di errore significativo, ma questo ovviamente può essere utilizzato solo se si sta uscendo da una condizione di errore. non funziona per rapporti occasionali.

Modifica: Vorrei chiarire il fatto che la situazione è più generale e non limitata solo a una sequenza di passaggi invocati. potrebbe anche coinvolgere le strutture di controllo:

if disconnected: 
    print "Trying to connect" 
    connect() 
else: 
    print "obtaining list of files from remote host" 
    getRemoteList() 

Il rapporto potrebbe essere anche nelle routine reali, in modo si avrebbe un "stampa" nelle routine connect() e getRemoteList() come prima dichiarazione.

La domanda quindi sono:

  • Cosa pensi sia il miglior design per un certo codice (in particolare nel caso di una libreria) di essere allo stesso tempo in silenzio quando il rumore potrebbe essere dirompente per il cliente , ma prolisso quando utile?
  • Come gestire un intermix bilanciato tra codice logico e codice di reporting?
  • Il mescolamento tra codice e controllo errori è stato risolto con eccezioni. Cosa si potrebbe fare per partizionare il "rumore" dei rapporti dalla logica del codice?

Modifica: più pensieri per la mente

penso che sia non solo una questione di disaccoppiamento del codice di registrazione dal codice logica. Penso che si tratti anche di disaccoppiare la produzione di informazioni dal consumo di informazioni. Esistono già tecniche simili, in particolare per gestire gli eventi dell'interfaccia utente, ma in realtà non vedo gli stessi pattern applicati al problema di registrazione.


Edit: ho accettato la risposta da Marcelo perché egli sottolinea in elementi di fatto che un compromesso è la soluzione migliore in questo caso, e non c'è pallottola d'argento. Tuttavia, tutti gli altri erano anche delle risposte interessanti e sono stato davvero lieto di revocarne tutti. Grazie per l'aiuto!

+0

Ho aggiunto una taglia per vedere se è possibile approfondire la discussione su questa domanda. Inoltre, questa è la prima apertura della taglia, quindi ero anche curioso di farlo. –

risposta

1

Penso che ci sia un punto in cui devi tracciare una linea e fare un compromesso. Non vedo alcun modo per disaccoppiare completamente la registrazione dal sistema perché devi inviare quei messaggi da qualche parte e in un modo che qualcuno capisca.

Vorrei andare con il modulo di registrazione predefinito, perché ...è il modulo predefinito. È ben documentato e viene fornito con la libreria predefinita, quindi nessun problema di dipendenza qui. Inoltre, ti risparmi dal reinventare la ruota.

Detto questo, se si sta veramente facendo qualcosa di nuovo, si potrebbe creare un oggetto reporter globale. È possibile creare un'istanza e configurarlo all'inizio del processo (registrazione, nessuna registrazione, reindirizzamento di flussi, ecc. Anche in base al processo/funzione/passo) e chiamarlo da qualsiasi luogo, non è necessario passarlo (forse in un ambiente multi-thread, ma sarebbe minimo).

È inoltre possibile inserirlo in un altro thread e registrare gli eventi del registro a la Qt.

4

Penso che la soluzione migliore per una libreria sia quella di aggiungere per es.

Log.Write(...) 

in cui viene rilevato il comportamento di Log dall'ambiente ambientale (ad esempio app.config o una variabile di ambiente).

(penso anche che questo è un problema che è stato affrontato e risolto molte volte, e mentre ci sono un paio di 'sweet spot' nello spazio di progettazione, la risposta di cui sopra è meglio IMO per la situazione che descrivi.)

Non vedo alcun modo valido per "disaccoppiare" la parte "normale" del codice dalla parte "registrazione". La registrazione tende ad essere relativamente non intrusiva; Non riesco a trovare Log.Write (...) occasionale come distrazione del codice del mondo reale.

+0

Beh, anche se non invadente, penso che si tratti di disaccoppiare non il codice stesso, ma la nozione stessa di dare informazioni a entità esterne sul progresso. –

2

Un'altra opzione sarebbe quella di scrivere il codice senza registrazione e quindi applicare alcune trasformazioni per inserire le istruzioni di registrazione appropriate prima di eseguire il codice. Le tecniche effettive per farlo dipenderebbero molto dalla lingua, ma sarebbero piuttosto simili al processo di scrittura di un debugger.

Probabilmente non vale la complessità aggiunta però ...

+0

una sorta di "tessitore" in AOP. ma o trova difficile implementare realmente qualcosa di simile. Affronta un sacco di ostacoli pratici. –

+0

Un modo semplice per implementare quello sarebbe modificare temporaneamente i commenti incorporati nelle chiamate di funzione. –

1

Ci dovrebbe essere utensili per consentire i messaggi di log boilerplate a la "entrare metodo A con i parametri (1,2,3)", "di ritorno da il metodo B con valore X, ha impiegato 10 ms "per essere generato automaticamente (e in modo selettivo) (controllato durante l'esecuzione o il tempo di distribuzione). Scrivere queste cose a mano è troppo noioso/ripetitivo/soggetto a errori.

Non so se c'è, però.

Se si intende scrivere messaggi di registro manuali, assicurarsi di includere alcune informazioni contestuali utili (ID utente, URL che viene visualizzato, query di ricerca o simile), in modo che se qualcosa non funziona si ottiene più informazioni oltre al nome del metodo.

+0

Penso che ciò che è necessario sia una sorta di sistema di eccezione, dove l'eccezione non è usata per controllare l'esecuzione, solo per segnalare che si è verificato un evento. –

2

Ho trovato this durante la ricerca di Aspect Oriented Programming per python. Concordo con altri autori sul fatto che tali preoccupazioni non dovrebbero essere mescolate con la logica di base.

In sostanza, i punti in cui si desidera inserire la registrazione potrebbero non essere sempre arbitrari, potrebbero essere concetti come "tutti i punti prima dell'errore" potrebbero essere identificati da un collegamento di punti. Altri punti totalmente arbitrari possono essere catturati utilizzando semplici tecniche di registrazione.

+0

AO per python? Sono rimasto affascinato dalle idee di AO che ho letto sull'anno scorso, ma ho pensato di dover immergermi in C#, Java o qualcos'altro ... e wow, questo è del 2003? Freddo. – DarenW

+0

Non conosco molto AOP, tuttavia l'utilizzo di AOP consente di intercettare i collegamenti di punti comuni, ma non risolve il problema di inserire punti arbitrari nel codice in cui si desidera fornire un messaggio significativo per una condizione di errore che è prossima a capita, o uno stato informativo. Ho ragione ? –

+0

Sì, hai ragione. Ma stavo dicendo che i punti in cui vuoi mettere potrebbero non essere sempre arbitrari, potrebbero essere concetti come "tutti i punti prima dell'errore" potrebbero essere identificati da un punto di vista. Altri punti totalmente arbitrari possono essere catturati utilizzando semplici tecniche di registrazione. – Surya

1

Vorrei utilizzare il modulo standard logging che è stato parte della libreria standard da Python 2.3.

In questo modo, c'è una buona probabilità che le persone che guardano il tuo codice sappiano già come funziona il modulo logging. E se devono imparare, almeno è ben documentato e la loro conoscenza è trasferibile ad altre librerie che usano anche logging.

C'è qualche funzione che si desidera ma non è possibile trovare nel modulo standard logging?

+0

Per quanto vedo, il modulo di registrazione è molto potente. I gestori possono essere utilizzati per definire qualsiasi comportamento. Quindi, in linea di principio, "abusando" del modulo di registrazione, è possibile inviare eventi in giro. Questo modulo si inserisce nella seconda categoria che ho illustrato e si lega alla libreria standard. –

1

Penso che la soluzione più semplice sia la migliore qui. Questo dipende dalla lingua, ma basta usare un identificatore molto breve, accessibile a livello globale - in PHP uso una funzione personalizzata trace($msg) - e quindi solo implementare e ri-implementare quel codice come meglio credi per il particolare progetto o fase.

La versione automatica in-compilatore di questo è il debugger standard. Se vuoi vedere etichette significative, devi scrivere tu stesso quelle etichette, sfortunatamente :)

Oppure potresti provare a trasformare temporaneamente i commenti incorporati in chiamate di funzione, ma non sono sicuro che funzionerebbe.

1

In risposta alla modifica relativa alla produzione/consumo di informazioni: si tratta di una preoccupazione valida per il caso generale, ma la registrazione non è il caso generale. In particolare, non dovresti affidarti alla registrazione dell'output da una parte del tuo programma per influenzare l'esecuzione di un'altra parte. Ciò accopperebbe infatti strettamente il consumatore all'attuazione del produttore.

La registrazione deve essere considerata come accessorio per l'esecuzione principale. I tuoi sistemi non dovrebbero conoscere o preoccuparsi della presenza o del contenuto di tali file di log (con l'eccezione forse di strumenti di monitoraggio). Stando così le cose, la nozione di disaccoppiamento del "consumo" dei registri dalla loro produzione è irrilevante. Dal momento che non stai usando i log per fare qualcosa di significativo, l'accoppiamento non è un problema.

+0

In effetti la registrazione è un tipo particolare di invio e gestione degli eventi. Non mi affiderei ai messaggi di registro, ma potrei contare su eventi di registro per controllare il mio flusso. Esempio: una libreria client FTP potrebbe segnalare che il file è stato scaricato sia come evento di log sia come evento di controllo (ad esempio callback) –

2

Spesso uso DTrace per questo. Su OS X, python e ruby ​​sono già stati impostati con gli hook DTrace. Su altre piattaforme, probabilmente dovresti farlo tu stesso. Ma essere in grado di allegare tracce di debug a un processo in esecuzione è, beh, fantastico.

Tuttavia, per il codice di libreria (diciamo che stai scrivendo una libreria client http), l'opzione migliore è quella di passare un logger facoltativo come parametro, come hai menzionato. DTrace è utile per aggiungere la registrazione a qualcosa che è andato storto nella produzione (e talvolta altrove), ma se altre persone potrebbero aver bisogno di accedere ai log per eseguire il debug del codice che successivamente chiama nella tua, allora un logger opzionale come parametro è assolutamente il modo andare.