Sviluppo / mantenimento di una libreria interna in un'azienda in crescita con circa 12 altri sviluppatori che scrivono codice. Questa libreria è usata principalmente per il comportamento di scripting del gioco e abbiamo molti progetti diversi che ne fanno uso.
Questa libreria implementa aggregazione di eventi semplice, tipizzata in una classe statica EventBus
.
Ha i metodi previsti per sottoscrivere e annullare l'iscrizione di gestori di eventi:
%codice%
e
Subcribe<TEvent>(Action<TEvent> handler)
Il mio enigma è un "metodo di convenienza" che ho aggiunto: Unsubscribe<TEvent>(Action<TEvent> handler)
), che consente di creare gestori di eventi (anonimi) che possono decidere se desiderano ricevere ulteriori messaggi di evento restituendo un valore booleano. Essenzialmente, verranno annullati dopo che alcune condizioni sono state soddisfatte.
Esempio:
EventBus.SubscribeUntilTrue<DialogEvent.End>(e =>
{
if (e.Dialog.Name == "MyDialog")
{
DoSomething();
return true;
}
return false;
});
L'obiettivo di questo metodo è consentire allo sviluppatore di utilizzarlo per segnalare l'intento in modo più chiaro, ridurre lo standard di stampa ed evitare di dichiarare metodi che vengono chiamati una sola volta. Di seguito sono riportati due esempi di codice che fanno la stessa cosa dello snippet di cui sopra in un modo che a mio avviso è meno leggibile / intuitivo / manutenibile:
Primo esempio: utilizzando un metodo denominato, che ...
EventBus.Subscribe<DialogEvent.End>(EndHandler);
... è dichiarato altrove.
private void EndHandler(DialogEvent.End e)
{
if (e.Dialog.Name == "MyDialog")
{
DoSomething();
EventBus.Unsubscribe<DialogEvent.End>(EndHandler);
}
}
Questo non è l'ideale, perché l'intento può essere comunicato solo attraverso il nome del metodo del gestore al momento della sottoscrizione e non è chiaro se vi siano altri chiamanti per il gestore. Ho il sospetto che in molti casi il nome possa facilmente diventare molto prolisso e impreciso in quanto è probabile che il codice tipico all'interno di un gestore di questo tipo cambi spesso mentre è in fase di sviluppo, pur essendo breve.
Secondo esempio: Uno sviluppatore che eviti la dichiarazione di un metodo "una volta chiamato" potrebbe utilizzare un metodo delegato / anonimo auto-cancellato che è un po 'disordinato / brutto:
Action<DialogEvent.End> endHandler = null;
endHandler = e =>
{
if (e.Dialog.Name == "MyDialog")
{
DoSomething();
EventBus.Unsubscribe(endHandler);
}
};
EventBus.Subscribe(endHandler);
Gli sviluppatori che non hanno familiarità con questo modello possono facilmente confondersi.
Non sono sicuro che il nome SubscribeUntilTrue<TEvent>(Func<TEvent, bool> handler
sia adeguato affinché le persone comprendano intuitivamente cosa fa il metodo e quale significato assegna il valore di ritorno richiesto al gestore.
È generalmente considerato un odore includere una condizione (al) nel nome di un metodo, come SubscribeUntil True . Inizialmente era chiamato solo SubscribeUntil ma dovevo spiegare a un altro sviluppatore cosa faceva e ha suggerito di aggiungere la parte True .
Ho avuto un'idea di cambiare la firma del metodo (o di aggiungere un sovraccarico) in modo che il valore di ritorno della funzione di gestione sia una struttura di tipo enum sigillata con due proprietà statiche che fungono da 'costanti' che denoterebbero l'uso previsto più chiaramente:
EventBus.SubscribeUntil<DialogEvent.End>(e =>
{
if (e.Dialog.Name == "MyDialog")
{
DoSomething();
return EventHandlerResult.Unsubscribe;
}
return EventHandlerResult.KeepSubscribed;
});
Questa struttura "enum-like" con solo due valori possibili ha conversioni implicite in / da bool in modo che il passaggio tra il sovraccarico utilizzato sia più conveniente.
public struct EventHandlerResult
{
private readonly bool shouldUnsubscribe;
public static readonly EventHandlerResult Unsubscribe = true;
public static readonly EventHandlerResult KeepSubscribed = false;
private EventHandlerResult(bool shouldUnsubscribe)
{
this.shouldUnsubscribe = shouldUnsubscribe;
}
public static implicit operator bool(EventHandlerResult state)
{
return state.shouldUnsubscribe;
}
public static implicit operator EventHandlerResult(bool value)
{
return new EventHandlerResult(value);
}
}
Anche se questo ha alcuni problemi ovvi come; Se cambio la firma, costringerebbe i consumatori a cambiare la firma dei gestori che hanno già scritto. Potrei comunque tenere entrambe le versioni.
Qualcuno là fuori ha qualche idea di un nome migliore per questo metodo? Inoltre, la struttura enum-like è un approccio buono / cattivo per guidare / forzare la chiarezza / l'intento del codice dei consumatori scritti della libreria?