Come incapsulare al meglio le funzionalità nel codice e attivarle / disabilitarle in fase di runtime?

3

Mi piacerebbe essere in grado di astrarre ogni funzionalità sviluppata dal team in modo che possa essere abilitata o disabilitata in fase di esecuzione utilizzando un sistema di configurazione condivisa distribuito. Le funzionalità sono piccole e distribuite quotidianamente.

Quali pratiche posso utilizzare per mantenere pulito il codice ed evitare serie di if / else che ingombrano il codice? Il codebase è principalmente Python, qualcuno ha mai provato a usare decoratori per questo scopo?

    
posta Trasplazio Garzuglio 23.05.2015 - 13:47
fonte

3 risposte

6

I would like to be able to abstract each ticket developed by the team in a way that it can be enabled or disabled at runtime by using a distributed shared configuration system.

Ideally yes, everything. I won't go down this path, but I think being able to toggle immediately any change, at runtime, is a very powerful feature. I am just wondering if anybody had ever tried and has some experience to share.

Non farlo! Davvero!

1) C'è nessun senso nel farlo

Qual è la ragione per cui attiva / disattiva la funzionalità una correzione? Qualcuno vuole il comportamento errorneous indietro? Io non la penso così

2) Finisci per ingombrare il tuo codice con molti pulsanti di funzione non necessari e una configurazione non gestibile.

3) Il tuo codice diventa nel suo complesso un bersaglio mobile. Più funzionalità si alternano, maggiore è il rischio di effetti collaterali derivanti da una cattiva costellazione di diverse funzionalità / correzioni di bug attivate / disattivate.

Per farmi capire bene: Gli interruttori di funzione di per sé sono utili - senza dubbio.

Non sto sostenendo contro i commutatori, ma contro il fatto di usarlo per cambiare letteralmente ogni .

    
risposta data 24.05.2015 - 21:58
fonte
3

Quindi hai effettivamente bisogno (o vuoi solo?) di avere due codici sorgente per la situazione pre-ticket e post-ticket - ad es. comportamento modificato del ticket di un metodo, quindi idealmente vuoi essere in grado di passare dalla precedente implementazione (pre-ticket) del metodo alla nuova implementazione (post-ticket).

Questo può essere fatto usando il polimorfismo - ne hai alcuni ad es. classe di servizio con un metodo - il ticket cambia il metodo - lo fai creando una sottoclasse di questo servizio, sovrascrivendo il metodo e modificando l'implementazione. A seconda dell'interruttore, si utilizza la classe precedente o la nuova sottoclasse. Questa commutazione può essere eseguita utilizzando per es. Iniezione DI, in cui le classi vengono istanziate in base alla configurazione. Poiché è necessario essere in grado di eseguire commutazioni nel runtime, probabilmente è necessario utilizzare i proxy anziché le implementazioni reali come dipendenze iniettate.

Certo, le cose sono più difficili una volta che l'applicazione ha un certo stato. Se modifichi le classi con stato, devi essere in grado di convertire tutte le istanze dalla vecchia classe alla nuova classe e viceversa in runtime - ciò significa che devi avere alcuni convertitori (per entrambi i modi), un registro globale di tutti oggetti e il processo di conversione dovrebbe probabilmente essere atomico (ad esempio è necessario "fermare il mondo" dell'applicazione fino al completamento della conversione) per garantire che non ci saranno problemi nel periodo di transizione.

Poi c'è il problema che i biglietti spesso non sono indipendenti gli uni dagli altri - cosa succede se il metodo di ticket 1 modificato helloWorld (), settimana dopo ticket 2 ha modificato lo stesso metodo. La prima domanda è - su quale classe base ha implementato la funzionalità - probabilmente sulla sottoclasse dal ticket 1. Quindi è possibile disattivare il ticket 1 e abilitare il ticket 2 - si ha dipendenza tra questi ticket che dovrebbero essere in qualche modo risolti (altrimenti è possibile avere un sistema rotto).

Nel mondo reale - questo è pazzo e non farlo. Il cambio di funzione è una pratica normale, ma su una scala e una granularità molto diverse - a volte parti della funzionalità (moduli) vengono attivate o disattivate in base alle licenze, a volte parti del sistema hanno più strategie di implementazione, ma questi casi sono generalmente ben definiti e giustificato e anche in questo caso sono tutt'altro che gratuiti in termini di maggiore complessità, test ecc.

    
risposta data 24.05.2015 - 16:01
fonte
2

What practices can I use to maintain the code clean and avoid series of if/else cluttering the code

Anche se il tuo piano originale sembra un po 'pazzo, se sostituisci il requisito di "ogni ticket" con "molte caratteristiche diverse", le domande sopra riportate rendono IMHO molto più sensato. Secondo la mia esperienza, la chiave per mantenere il codice gestibile quando si hanno molte opzioni di configurazione diverse è

scomposizione di caratteristiche diverse in sottofunzioni molto piccole, per lo più ortogonali .

"Ortogonale" qui significa che ogni sottofunzione è indipendente l'una dall'altra. Se lasci che l'utente finale abbia accesso diretto all'interruttore di ogni sottofunzione, o se raggruppi sottofunzionalità e lasci che l'utente scelga solo tra alcuni "pacchetti di configurazione di sottofunzioni" completi, dipende da te, ma l'ortogonalità assicurerà che l'intera operazione continui continua a essere testabile. Ad esempio, se hai 3 clienti e 10 sottofunzionamenti diversi, per il cliente A li abiliti tutti, per il cliente B li disabiliti tutti e per il cliente C attivi ogni secondo caratteristica - questo rende 3 bundle, e puoi fornire un set di configurazione "config A", "B" o "C" come gruppo.

E anche se ci sono alcune dipendenze tra le sottofunzioni, purché le dipendenze siano locali e puoi esprimerle in termini come "questo elenco di sottofunzioni è disponibile solo quando la sottofunzione X è attiva", tale configurazione può ancora essere gestita.

Questa non è teoria. Attualmente sto lavorando a un prodotto in cui almeno parti sono altamente configurabili, con centinaia di opzioni diverse, e ha fatto non diventare un disastro perché ci siamo concentrati rigorosamente sull'ortogonalità laddove possibile. Oppure guarda l'elenco delle opzioni di configurazione disponibili in un software come Firefox Internet Browser ("about: config"). Un tale livello di configurabilità è IMHO possibile solo prestando attenzione all'ortogonalità.

Tuttavia, questo tipo di design non emerge automaticamente dai tuoi "ticket". Per ogni ticket (o requisito, o "user story"), devi pensare a se e come ha senso mapparlo ad alcune caratteristiche configurabili (s) o sottofunzione (s). E devi anche prendere una decisione individuale su come implementarlo nel codice (forse un condizionale è la strada da seguire, magari sottoclasse / polimorfismo, magari utilizzando una tabella decisionale) - non esiste una soluzione "one size fits all". Questa è una delle parti importanti del design del software: abbattere i requisiti per le astrazioni nel codice.

    
risposta data 26.05.2015 - 16:55
fonte