2013-03-12 25 views
10

Nota/Dichiarazione di non responsabilità: dopo alcune ricerche, la cosa più vicina che ho visto in questo post è un post su SO (Method chaining and the finishing problem) che è simile alla mia domanda, ma in realtà non risponde - ma comunque, spero che questa non sia una domanda doppia.Accertarsi che venga effettuata una chiamata per terminare una catena di metodi

Quello che sto facendo:

Ho creato un fluente interfaceas una facciata su un quadro di registrazione esistente per una serie di chiamate di metodo - così la mia sintassi è un po 'come questo:

Logger.Debug().Message("Debug message!").WriteToLog(); 
Logger.Error().Message("An exception occured").Exception(ex).WriteToLog(); 

Sto passando un oggetto interno da una chiamata di metodo all'oggetto successivo in modo che quando viene effettuata la chiamata finale (il metodo WriteToLog); il messaggio è scritto in un file di registro da qualche parte.

Il bit Credo che profuma

Al fine di verificare (solo quando l'applicazione è costruita in modalità debug), ho una proprietà su una classe di contesto (solo un oggetto elenco di proprietà), che viene passato da chiamata di metodo all'oggetto restituito fino a quando la catena non termina; è un booleano e il valore predefinito è falso.

Questa proprietà viene valutata nel distruttore della classe di contesto utilizzando un Debug.Assert per determinare se viene chiamato il metodo finale per terminare la catena in modo da poter rilevare eventuali errori di registrazione durante lo sviluppo. (la proprietà, il codice che imposta la proprietà e il distruttore stesso sono tutti creati nel contesto di una #if direttiva pre-processore DEBUG, quindi se è incorporato nel rilascio o se il simbolo non esiste, il codice non viene compilato.)

I sapere utilizzare un distruttore è errato in C# 2.0 e versioni successive e che potrei non avere accesso alle proprietà perché credo che non ci siano garanzie sull'ordine di finalizzazione. Questo è il motivo per cui accade solo quando è costruito in modalità Debug, e perché mi piacerebbe andarmene.

La ragione per cui sto cercando di costruire un assertation a è perché è molto facile dimenticare e finiscono per scrivere codice come

Logger.Debug().Message("Debug message!"); 

il che significa che nulla viene registrato, anche se a uno sguardo superficiale sembra che dovrebbe.

La mia domanda

Quello che voglio sapere è - chiunque può pensare ad un altro modo di verificare che il metodo finale è sempre chiamato? Questi messaggi sono richiesti solo durante lo sviluppo per evidenziare allo sviluppatore che una catena di metodi non è terminata - Non voglio che gli utenti finali trovino messaggi di errore relativi all'accesso nel prodotto finale.

+0

+1 per un elegantemente presentato/domanda formattata. –

+3

Perché avere l'API fluente? cosa c'è di sbagliato semplicemente con 'Logger.Debug (" Debug message! ");' e 'Logger.Error (" Si è verificata un'eccezione ", ex);'? Mi sembra che tu stia rendendo la tua API di registrazione più complicata di quanto dovrebbe essere ... Le API fluide sono un concetto piacevole ma non sono una pallottola d'argento, non provarci e usale ovunque solo perché puoi. –

+0

@TrevorPilley Perché è simile a quello che ho al momento e causa più problemi di questo. Esistono sovraccarichi per la formattazione di stringhe, l'accettazione di dettagli di eccezioni o entrambi che portano gli sviluppatori a dimenticare di passare in un argomento formato stringa o dettaglio eccezione che finisce per generare un errore. L'ultima cosa che voglio è uno strumento diagnostico che genera un errore, ed è per questo che voglio un'interfaccia fluente, così posso essere esplicito su ciò che viene passato dove. Non è qualcosa che posso scambiare, ma si spera possa mettere una facciata finita. – Jay

risposta

9

Prima di tutto vorrei mettere in discussione la necessità di un'interfaccia fluida in questo caso a tutti, sembra si può facilmente ottenere con un'interfaccia molto più semplice:

Logger.Debug.Message("Test"); 

o anche solo:

Logger.Debug("Test"); 

Tuttavia, se avete davvero bisogno di un'interfaccia fluente, un modo diverso per farlo sarebbe quello di far funzionare l'interfaccia fluente su un parametro per il metodo, anziché sul valore di ritorno.

Così, invece di fare questo:

Method1().Method2().Method3(); 

e poi dimenticare l'ultima chiamata:

Method1().Method2().Method3().Execute(); 

si sarebbe invece di organizzare il codice, forse in questo modo:

Method1(o => o.Method2().Method3()); 

Per fai questo, definiresti un oggetto sul quale chiamerai tutti i metodi fluenti:

public class LoggerOptions 
{ 
    public LoggerOptions Debug() { LoggerType = LoggerType.Debug; return this; } 
    public LoggerOptions Error() { LoggerType = LoggerType.Error; return this; } 
    public LoggerOptions Message(string message) { ...; return this; } 

    public LoggerType Type { get; set; } 
    ... 
} 

Ogni chiamata di metodo qui modifica l'oggetto LoggerOptions e quindi restituisce la stessa istanza indietro, per continuare l'interfaccia fluente.

e poi:

public static class Logger 
{ 
    public static void Log(Func<LoggerOptions, LoggerOptions> options) 
    { 
     LoggerOptions opts = options(new LoggerOptions()); 
     // do the logging, using properties/values from opts to guide you 
    } 
} 

Si potrebbe quindi chiamare in questo modo:

Logger.Log(opts => opts.Debug().Message("Debug message")); 

Se si dispone di alcuni metodi di terminale è assolutamente necessario chiamare prima di finalizzare l'impostazione delle opzioni oggetto, è possibile fare oggetti diversi:

public class LoggerOptions 
{ 
    public LoggerOptions Debug() { LoggerType = LoggerType.Debug; return this; } 
    public LoggerOptions Error() { LoggerType = LoggerType.Error; return this; } 
    public LoggerOptions Message(string message) { ...; return this; } 

    public LoggerType Type { get; set; } 
    ... 

    public LoggerFinalOptions ToEventLog() { ...; return new LoggerFinalOptions(this); } 
    public LoggerFinalOptions ToFile(string fileName) { ...; return new LoggerFinalOptions(this); } 
} 

e quindi:

public static class Logger 
{ 
    public static void Log(Func<LoggerOptions, LoggerFinalOptions> options) 
    { 
     LoggerFinalOptions opts = options(new LoggerOptions()); 
     // do the logging, using properties/values from opts to guide you 
    } 
} 

Questo sarebbe quindi garantire che non si poteva compilare il codice senza terminare la catena di metodi con una chiamata a qualcosa che restituisce le opzioni finali di oggetti espliciti:

// will not compile 
Logger.Log(opts => opts.Debug().Message("Test")); 

// will compile 
Logger.Log(opts => opts.Debug().Message("Test").ToFile("log.log")); 
+0

Ottima risposta, appena testata e questo è esattamente quello che stavo cercando. Con la leggera modifica apportata alla firma del metodo Log (Expression > expr) sono in grado di ottenere un'interfaccia fluida verificabile che viene compilata solo (quindi fa solo cose come la formattazione di stringhe) se la modalità di registrazione è abilitata. Grazie! – Jay

Problemi correlati