Ho cercato alcune risposte e cercato su Google, ma non sono riuscito a trovare nulla di utile (ovvero che non avrebbe effetti collaterali imbarazzanti).
Il mio problema, in astratto, è che ho un oggetto e ho bisogno di eseguire una lunga sequenza di operazioni su di esso; Lo considero una sorta di catena di montaggio, come costruire un'auto.
Credo che questi oggetti si chiamerebbero Method Method .
Quindi in questo esempio ad un certo punto avrei un CarWithoutUpholstery su cui avrei quindi bisogno di eseguire installBackSeat, installFrontSeat, installWoodenInserts (le operazioni non interferiscono l'una con l'altra e potrebbero anche essere eseguite in parallelo). Queste operazioni vengono eseguite da CarWithoutUpholstery.worker () e producono un nuovo oggetto che sarebbe un CarWithUpholstery, sul quale verrei eseguito forse cleanInsides (), verifichiamo NoUpholsteryDefects () e così via.
Le operazioni in una sola fase sono già indipendenti, ovvero sto già affrontando un sottoinsieme di esse che può essere eseguito in qualsiasi ordine (i sedili anteriori e posteriori possono essere installati in qualsiasi ordine).
La mia logica attualmente utilizza Reflection per semplicità di implementazione.
Cioè, una volta che ho un CarWithoutUpholstery, l'oggetto controlla se stesso per i metodi chiamati performSomething (). A quel punto esegue tutti questi metodi:
myObject.perform001SomeOperation();
myObject.perform002SomeOtherOperation();
...
controllando errori e cose. Mentre l'ordine di funzionamento non è importante, ho assegnato un ordine lessicografico nel caso in cui scoprissi che un ordine è importante dopo tutto. Ciò contraddice YAGNI , ma è costato pochissimo - un semplice ordinamento () - e potrebbe salvare un enorme metodo di ridenominazione (o introdurre qualche altro metodo per eseguire test, ad esempio una serie di metodi) lungo la linea.
Un esempio diverso
Diciamo che invece di costruire un'auto devo compilare un rapporto della polizia segreta su qualcuno e inviarlo al mio Evil Overlord . Il mio oggetto finale sarà un ReadyReport. Per costruirlo comincio raccogliendo informazioni di base (nome, cognome, coniuge ...). Questa è la mia fase A. A seconda che ci sia un coniuge o meno, potrei quindi procedere alle fasi B1 o B2 e raccogliere dati sulla sessualità su una o due persone. Questo è composto da diverse domande a diversi Minion malvagi che controllano la vita notturna, le videocamere di strada, le ricevute di vendita dei sex shop e quant'altro. E così via e così via.
Se la vittima non ha famiglia, non entrerò nemmeno nella fase GetInformationAboutFamily, ma se lo faccio, allora è irrilevante se prendo di mira prima il padre o la madre o i fratelli (se presenti). Ma non posso farlo se non ho eseguito FamilyStatusCheck, che quindi appartiene a una fase precedente.
Funziona tutto meravigliosamente ...
- se ho bisogno di un'operazione aggiuntiva, devo solo aggiungere un metodo privato,
- se l'operazione è comune a più fasi posso averla ereditata da una superclasse,
- le operazioni sono semplici e indipendenti. Nessun valore da un'operazione è mai richiesto da tutti gli altri (operazioni che non vengono eseguiti in una fase diversa),
- gli oggetti lungo la linea non devono eseguire molti test poiché non potrebbero nemmeno esistere se i loro oggetti creatore non avessero verificato quelle condizioni in primo luogo. Vale a dire, quando si posizionano gli inserti nella dashboard, si pulisce la dashboard e si verifica la dashboard, non è necessario verificare che una dashboard sia effettivamente presente .
- consente un facile test. Posso facilmente deridere un oggetto parziale ed eseguire qualsiasi metodo su di esso, e tutte le operazioni sono scatole nere deterministiche.
...ma...
Il problema è sorto quando ho aggiunto un'ultima operazione in uno dei miei oggetti metodo, che ha causato il superamento di un indice di complessità obbligatorio da parte del modulo complessivo ("meno di N metodi privati").
Ho già preso la questione al piano di sopra e suggerito che in questo caso, la ricchezza di metodi privati non è una rivelazione del disastro. La complessità è lì, ma è lì perché l'operazione è complessa, e in realtà non è poi così complessa - è solo lunga .
Usando l'esempio di Evil Overlord, il mio problema è che l'Evil Overlord (aka Lui che non sarà negato ) dopo aver richiesto tutte le informazioni dietetiche, i miei Minion dietetici mi hanno detto che devo interrogare ristoranti, angoli cottura, venditori ambulanti, venditori ambulanti senza licenza, serra proprietari ecc., e il Male (sub) Overlord - noto come Colui che non verrà negato - lamentandosi del fatto che sto eseguendo troppe query nella fase GetDietaryInformation.
Nota : sono consapevole che da diversi punti di vista questo non è affatto un problema (ignorando possibili problemi di prestazioni, ecc.). Tutto ciò che sta accadendo è che una metrica specifica è infelice e c'è una giustificazione per questo.
Quello che penso di poter fare
A parte la prima, tutte queste opzioni sono fattibili e, credo, difendibili.
- Ho verificato che posso essere subdolo e dichiarare metà dei miei metodi
protected
. Ma sfrutterei una debolezza nella procedura di test e, oltre a giustificarmi quando colto, non mi piace. Inoltre, è una misura di stopgap. Cosa succede se il numero di operazioni richieste raddoppia? Improbabile, ma allora? - Posso dividere arbitrariamente questa fase in AnnealedObjectAlpha, AnnealedObjectBravo e AnnealedObjectCharlie e avere un terzo delle operazioni eseguite in ogni fase. Ho l'impressione che ciò in realtà aggiunga complessità (N-1 più classi), senza alcun beneficio se non il superamento di un test. Ovviamente posso affermare che un CarWithFrontSeatsInstalled e un CarWithAllSeatsInstalled sono fasi logicamente successive . Il rischio che un metodo Bravo venga richiesto in seguito da Alpha è piccolo, e ancora più piccolo se lo gioco bene. Ma ancora.
- Posso raggruppare diverse operazioni, da remoto simili, in una sola.
performAllSeatsInstallation()
. Questa è solo una misura di stopgap e aumenta la complessità della singola operazione. Se mai avessi bisogno di eseguire le operazioni A e B in un ordine diverso e le avessi imballate all'interno di E = (A + C) e F (B + D), dovrò separare E e F e mescolare il codice in giro . - Posso usare una serie di funzioni lambda e evitare accuratamente il controllo, ma lo trovo goffo. Questa è tuttavia la migliore alternativa finora. Si libererebbe della riflessione. I due problemi che ho è che probabilmente mi verrebbe chiesto di riscrivere tutti gli oggetti del metodo, non solo l'ipotetico
CarWithEngineInstalled
, e mentre sarebbe un'ottima sicurezza del lavoro, in realtà non fa molto appello; e che il controllo della copertura del codice ha problemi con lambdas (che sono risolvibili, ma comunque ).
Così...
- Quale, secondo te, è la mia migliore opzione?
- C'è un modo migliore che non ho preso in considerazione? ( forse è meglio che venga pulito e chieda direttamente che cos'è? )
- Questo design è irrimediabilmente imperfetto, ed è meglio che ammetta la sconfitta e il fossato - questa architettura del tutto? Non va bene per la mia carriera, ma scrivere codice mal progettato sarebbe meglio a lungo termine?
- La mia scelta attuale è davvero la One True Way e devo lottare per installare metriche (e / o strumentazione) di qualità migliore? Per quest'ultima opzione avrei bisogno di riferimenti ... Non posso semplicemente agitare la mia mano su @PHB mentre mormoro Queste non sono le metriche che stai cercando . Non importa quanto mi piacerebbe poterlo fare