Trattare con intersezioni di feature

10

Recentemente ho assistito a sempre più problemi simili a quelli spiegati in questo articolo sulle intersezioni delle funzionalità . Un altro termine per questo sarebbe linee di prodotto, anche se tendo ad attribuirle a prodotti realmente diversi, mentre di solito incontro questi problemi sotto forma di possibili configurazioni di prodotto.

L'idea di base di questo tipo di problema è semplice: aggiungi una funzione a un prodotto, ma in qualche modo le cose si complicano a causa di una combinazione di altre funzionalità esistenti. Alla fine, il QA trova un problema con una rara combinazione di funzionalità che nessuno ha mai pensato prima e che avrebbe dovuto essere una semplice correzione di bug potrebbe persino trasformarsi in una richiesta di modifiche di progettazione importanti.

Le dimensioni di questo problema di intersezione delle feature sono di una complessità strabiliante. Supponiamo che la versione attuale del software abbia funzioni N e tu aggiunga una nuova funzionalità. Semplifichiamo anche le cose dicendo che ognuna delle funzioni può essere attivata o disattivata solo, quindi hai già 2^(N+1) di possibili combinazioni di funzioni da considerare. A causa della mancanza di termini migliori di parole / ricerca, mi riferisco all'esistenza di queste combinazioni come problema di intersezione di caratteristiche . (Punti bonus per una risposta inclusi riferimenti (s) per un termine più stabilito.)

Ora la domanda con cui lotto è come affrontare questo problema di complessità a ogni livello del processo di sviluppo. Per ovvi motivi di costo, non è pratico fino al punto di essere utopico, di voler affrontare ciascuna combinazione individualmente. Dopotutto, cerchiamo di evitare gli esponenziali algoritmi di complessità per una buona ragione, ma trasformare lo stesso processo di sviluppo in un mostro di dimensioni esponenziali è destinato a portare a un totale fallimento.

Quindi, come ottenere il miglior risultato in modo sistematico che non esploda i budget e sia completo in un modo accettabile, utile e professionalmente accettabile.

  • Specifica: quando specifichi una nuova funzione: come ti assicuri che funzioni bene con tutti gli altri bambini?

    Vedo che è possibile esaminare sistematicamente ciascuna funzione esistente in combinazione con la nuova funzione, ma ciò sarebbe isolato rispetto alle altre funzionalità. Data la natura complessa di alcune funzionalità, questa visione isolata è spesso già così coinvolta da richiedere un approccio strutturato tutto in sé, per non parlare del fattore 2^(N-1) causato dalle altre funzionalità che uno ha volutamente ignorato.

  • Implementazione: quando implementi una funzione, come assicurati che il tuo codice interagisca / intersechi correttamente in tutti i casi.

    Ancora una volta, mi sto interrogando sulla pura complessità. Conosco varie tecniche per ridurre il potenziale di errore di due feature intersecanti, ma nessuna che possa scalare in modo ragionevole. Presumo però che una buona strategia durante le specifiche dovrebbe tenere a bada il problema durante l'implementazione.

  • Verifica: quando collaudi una funzione, come gestisci il fatto che puoi testare solo una frazione di questo spazio di intersezione della funzione?

    È abbastanza difficile sapere che testare una singola funzione in isolamento non garantisce nulla vicino a un codice privo di errori, ma quando si riduce che a una frazione di 2^-N sembra che centinaia di test non lo siano anche coprendo una singola goccia d'acqua in tutti gli oceani combinati. Ancor peggio, gli errori più problematici sono quelli che derivano dall'intersezione di caratteristiche, che non ci si potrebbe aspettare che possano causare problemi - ma come si fa a testare per questi se non si prevede un incrocio così strong?

Mentre mi piacerebbe sentire come gli altri affrontano questo problema, sono principalmente interessato alla letteratura o agli articoli che analizzano l'argomento in modo più approfondito. Quindi, se segui personalmente una certa strategia, sarebbe bello includere le fonti corrispondenti nella tua risposta.

    
posta Frank 08.01.2013 - 20:59
fonte

2 risposte

5

Sapevamo già matematicamente che la verifica di un programma è impossibile in un tempo finito nel caso più generale, a causa del problema dell'arresto. Quindi questo tipo di problema non è nuovo.

In pratica, un buon design può fornire il disaccoppiamento in modo tale che il numero di feature intersecanti sia molto inferiore a 2 ^ N, sebbene sembri certamente superiore a N anche in sistemi ben progettati.

Per quanto riguarda le fonti, mi sembra che quasi tutti i libri o blog sulla progettazione del software stiano effettivamente cercando di ridurre quel 2 ^ N il più possibile, anche se non conosco nessuno che abbia gettato il problema nello stesso termini come te.

Per un esempio di come la progettazione potrebbe essere d'aiuto, nell'articolo menzionato alcune delle intersezioni della funzione sono avvenute perché sia la replica che l'indicizzazione sono state attivate dall'eTag. Se avessero avuto a disposizione un altro canale di comunicazione per segnalare la necessità di ciascuno di essi separatamente, probabilmente avrebbero potuto controllare più facilmente l'ordine degli eventi e avere meno problemi.

O forse no. Non so nulla di RavenDB. L'architettura non può impedire problemi di intersezione delle feature se le caratteristiche sono davvero intrecciate in modo inesplicabile e non potremo mai sapere in anticipo che non vogliamo una funzionalità che abbia il peggior caso di intersezione 2 ^ N. Ma l'architettura può almeno limitare le intersezioni dovute a problemi di implementazione.

Anche se ho torto su RavenDB e eTags (e lo sto usando solo per argomento - sono persone intelligenti e probabilmente lo fanno bene), dovrebbe essere chiaro in che modo può aiuto. La maggior parte dei modelli di cui si parla sono progettati esplicitamente con l'obiettivo di ridurre il numero di modifiche al codice richieste da funzionalità nuove o modificate. Ciò avviene molto indietro, ad esempio "Modelli di progettazione, elementi del software orientato agli oggetti riutilizzabili", l'introduzione afferma "Ogni modello di progettazione consente ad alcuni aspetti dell'architettura di variare indipendentemente da altri aspetti, rendendo così un sistema più robusto per un particolare tipo di cambiamento".

Il mio punto è che si può avere un senso delle intersezioni di Big O delle feature in pratica, bene, guardando a quello che succede nella pratica. Nella ricerca di questa risposta, ho trovato che la maggior parte dell'analisi dei punti di funzione / sforzo di sviluppo (cioè produttività) ha trovato meno della crescita lineare dello sforzo del progetto per punto di funzione, o leggermente al di sopra della crescita lineare. Che ho trovato un po 'sorprendente. Questo ha un esempio abbastanza leggibile.

Questo (e studi simili, alcuni dei quali usano punti funzione invece di linee di codice) non dimostra che l'intersezione della funzione non si verifica e causa problemi, ma sembra ragionevole provare che non è devastante nella pratica.

    
risposta data 08.01.2013 - 21:21
fonte
0

Questa non sarà la migliore risposta in alcun modo, ma ho riflettuto su alcune cose che si intersecano con i punti della tua domanda, quindi ho pensato di menzionarli:

Supporto strutturale

Da quel poco che ho visto, quando le funzionalità sono bug e / o non si adattano bene agli altri, ciò è in gran parte dovuto allo scarso supporto fornito dalla struttura / struttura principale del programma per gestirli / coordinarli. Spendere più tempo per completare e arrotondare il nucleo, penso, dovrebbe facilitare l'aggiunta di nuove funzionalità.

Una cosa che ho trovato comune nelle applicazioni in cui lavoro è che la struttura di un programma è stata configurata per gestire uno di un tipo di oggetto o processo, ma un sacco di le estensioni che abbiamo fatto o che vogliamo fare hanno a che fare con la gestione di molti di un tipo. Se questo fosse preso in considerazione più all'inizio della progettazione dell'applicazione, allora avrebbe aiutato ad aggiungere queste funzionalità in seguito.

Questo diventa piuttosto critico quando si aggiunge il supporto per più X che implicano codice threaded / asincrono / evento guidato perché quella roba può andare male abbastanza rapidamente - ho avuto il piacere di eseguire il debug di una serie di problemi relativi a questo. / p>

Probabilmente è difficile giustificare questo tipo di sforzo in anticipo, specialmente per prototipi o progetti one-off - anche se alcuni di questi prototipi o pezzi unici continueranno ad essere riutilizzati o come (la base) del sistema finale, cioè la spesa sarebbe valsa la pena a lungo termine.

design

Quando si progetta il nucleo di un programma, iniziare con un approccio dall'alto verso il basso può aiutare a trasformare le cose in blocchi gestibili e ti permette di girare la testa intorno al dominio del problema; dopodiché penso che dovrebbe essere usato un approccio bottom-up - questo aiuterà a rendere le cose più piccole, più flessibili e migliori per aggiungere a più tardi. (Come menzionato nel link, fare le cose in questo modo rende più piccole implementazioni delle funzionalità, il che significa meno conflitti / bug.)

Se ti concentri sui blocchi fondamentali del sistema e assicurati che tutti interagiscano bene, allora tutto ciò che è stato creato con essi si comporta probabilmente bene e dovrebbe integrarsi meglio con il resto del sistema.

Quando viene aggiunta una nuova funzionalità, penso che un percorso simile potrebbe essere preso nel progettarlo come è stato fatto progettando il resto del framework: scomporlo poi andando verso il basso. Se riusciate a riutilizzare uno qualsiasi dei blocchi originali dal framework nell'implementazione della feature, ciò sarebbe sicuramente utile; una volta che hai finito puoi aggiungere tutti i nuovi blocchi che ottieni dalla funzione a quelli già nel core framework, testandoli con il set originale di blocchi, in modo che siano compatibili con il resto del sistema e utilizzabili in futuro caratteristiche pure.

Semplificare!

Ultimamente ho preso una posizione minimalista sul design, iniziando con la semplificazione del problema, quindi semplificando la soluzione. Se il tempo può essere fatto per un secondo, semplificando l'iterazione del progetto su un progetto, potrei vedere che è molto utile quando si aggiungono le cose in seguito.

Comunque, questo è il mio 2c.

    
risposta data 17.01.2013 - 14:31
fonte

Leggi altre domande sui tag