Quando si implementano nuove funzionalità simili ad altre funzionalità, si rendono più generiche le funzionalità meno recenti?

1

Questo può risultare un po 'confuso, ma è una domanda che continuo a trovarmi chiedendo mentre accumulo sempre più responsabilità nei confronti dei vecchi sistemi e funzionalità che ho progettato in precedenza. Cercare di arrivare al nocciolo della mia domanda è un po 'difficile, ma cercherò di illustrarlo con alcuni esempi.

Un paio di anni fa, mi è stato assegnato il compito di creare un sistema di modifica dei menu altamente personalizzabile per il nostro sito. Questo sistema di modifica del menu è stato progettato con uno scopo molto specifico, che era quello di consentire agli utenti finali di configurare menu di navigazione personalizzati per specifici ruoli utente nel sistema. Il sistema non era eccessivamente complesso o troppo robusto, ma era abbastanza funzionale da gestire ogni possibile caso d'uso che potessimo immaginare per creare un nuovo menu di navigazione. Abbiamo costruito tutta la nostra logica personalizzata e la gestione speciale in. Abbiamo scritto centinaia di righe di codice per soddisfare le nostre regole di business specifiche per questa parte di funzionalità, e l'abbiamo rinforzata.

Circa un anno dopo aver creato il sistema di menu del sito, mi è stato assegnato lo sviluppo di un sistema di menu dinamico per la nostra applicazione mobile che ci permettesse di creare menu personalizzati basati sulla società per cui l'utente lavorava. Questo nuovo sistema era molto simile al vecchio sistema e abbiamo deciso che era meglio riutilizzare le tabelle e le funzionalità del menu del sito. Tutto quello che dovevamo fare era espanderli leggermente, e per lo più si adattavano al nostro bisogno. Tuttavia, c'erano ancora alcune differenze importanti, quelle erano:

  • I menu mobili non utilizzavano esclusivamente Font Awesome come la loro icona come il nostro menu del sito web. Invece, ha usato immagini recuperate da una posizione statica sul nostro server web. Permetteva anche il caricamento e l'archiviazione di immagini personalizzate nel database. Quindi ora dovevamo controllare se questa voce di menu mobile popolava il campo url o il campo del file.
  • I menu mobili erano basati sulla società dell'utente piuttosto che sul ruolo assegnato nel sistema. Ciò ha reso la nostra tabella MenuRoles inutile per questa funzione.
  • Il menu del cellulare non ha navigato su pagine diverse sul nostro sito web. Invece, corrispondeva a determinate azioni nell'applicazione, ma poteva anche lanciare una webview per un URL personalizzato. Abbiamo attenuato questo facendo una tabella delle azioni e includendo un campo URL.

Ora, per evitare di ingombrare la tabella del menu principale con colonne in eccesso, ho creato un'altra tabella chiamata MobileMenuItem che fa riferimento alla nostra tabella MenuItem . Questa tabella di voci di menu mobile conteneva tutte le nostre colonne aggiuntive e abbiamo semplicemente inserito il codice per ignorare le colonne irrilevanti nella tabella MenuItem originale. Non troppo grande di un problema.

Successivamente abbiamo dovuto aggiornare il modo in cui sono state controllate le autorizzazioni del menu. Fortunatamente, nell'ultimo anno, abbiamo anche creato una funzione di FeatureSet incredibilmente robusta che può controllare gerarchicamente attraverso molti scenari diversi per determinare a quali funzioni un singolo utente ha avuto accesso. Sfortunatamente, è stato originariamente progettato per determinare semplicemente se un utente avesse accesso a una funzione specifica. Queste caratteristiche erano semplicemente una stringa nel database a cui si accedeva attraverso le costanti nel codice, e gli algoritmi che abbiamo messo in atto semplicemente sputavano un vero / falso sul fatto che un utente avesse accesso a quella caratteristica.

Fortunatamente, alcuni mesi prima, avevamo personalizzato il set di funzionalità in modo da poter recuperare le stringhe da una tabella di risorse in base alle funzioni a cui un utente aveva accesso e quali caratteristiche individuali erano state sostituite. Ma ancora una volta, questo sistema non era proprio adatto per andare a prendere il menu corretto mentre cercavamo le stringhe, non i riferimenti ai menu. Quindi, di nuovo, abbiamo adattato il sistema per essere in grado di gestire anche i menu. Ancora una volta, ignorando le funzionalità implementate in precedenza e dovendo codificare ciò che avevamo costruito in precedenza.

Alla fine, questo portò a una serie di 3-4 sistemi indipendenti che erano abbastanza simili da riutilizzare alcune parti della loro funzionalità, ma abbiamo rotto la specificità di quei sistemi nel processo.

Quindi oggi sono seduto qui, costruendo un 3%% di co_de che personalizza la schermata iniziale del nostro sito in base a società personalizzate (proprio come l'applicazione mobile). Tuttavia, è solo abbastanza diverso che devo modificare la tabella Menu Editor originale per includere molte proprietà che sono state incluse nella tabella MenuItem . Così ora mi rimangono 3 editor di menu indipendenti con colonne ambigue tra tabelle, logica specifica per ogni singolo sistema e codice spaghetti a perdita d'occhio.

Quindi la mia grande domanda si riduce fondamentalmente a, qual è l'approccio giusto? Dovresti disaccoppiare completamente le funzionalità della tua applicazione, anche se sono incredibilmente simili? Dovresti avere potenzialmente dozzine e dozzine di tabelle e algoritmi quasi identici anche se sono solo leggermente diversi.

Oppure, dovresti provare a riutilizzare il maggior numero possibile di altri tuoi sistemi?

    
posta JD Davis 26.08.2018 - 02:17
fonte

3 risposte

4

Ciò che descrivi è ciò che chiamo architecture creep dove un sistema progettato in un determinato momento viene adattato ripetutamente, ogni volta per scopi diversi fino a raggiungere un punto in cui il sistema diventa non mantenibile .

So my big question basically boils down to, what is the right approach?

Le persone evitano grandi riscritture a causa dei costi e dei rischi, che sono inevitabilmente alti in entrambi i casi. Tuttavia, alla fine arriva un momento in cui il costo e i rischi di apportare modifiche al codice legacy, supera quello di ricominciare da capo e le persone devono affrontare l'inevitabile riscrittura. Ho visto succedere spesso in pratica.

L'approccio giusto dipende da dove ti trovi: in fase di progettazione o Alla fine di anni di utilizzo e mod e estensioni ecc. Se in fase di progettazione, devi essere in grado di prevedere cosa può succedere e quindi progettare un sistema che è capace di adattarsi quando necessario. È qui che una buona comprensione dei modelli di progettazione OOP aiuterebbe a costruire un sistema robusto ed estensibile. Se è troppo tardi per tutto ciò, devi valutare dove ti trovi sulla curva di scorrimento dell'architettura!

Should you completely decouple your features of your application, even though they are incredibly similar?

Il principio DRY (Do not Repeat Yourself) è buono, quindi le modifiche devono essere apportate solo in un posto. Puoi portarlo agli estremi. Nel tuo caso, se riacquisisci il tuo sistema potresti prendere in considerazione un'analisi dei dati molto accurata e creare un insieme di tabelle e viste che minimizzano o eliminano gli stessi dati in più di un luogo.

Should you basically have potentially dozens and dozens of nearly identical tables and algorithms even though they they are only just slightly different.

Vedi il commento sulle tabelle sopra. Per quanto riguarda gli algoritmi, se sono diversi sono diversi e dovresti evitare un sacco di dichiarazioni di if-else/switch all'interno delle funzioni per implementare piccole variazioni. Utilizzare invece le sottoclassi e sovrascrivere i metodi di conseguenza. Questo è un must.

Molto spesso, in una situazione commerciale, uno dei programmatori vedrà la necessità di una riscrittura, ma altri resisteranno a causa delle pressioni sul tempo, dei costi e delle valutazioni del rischio (che potrebbero non essere ben comprese). In tal caso, il programmatore potrebbe prendere in considerazione l'idea di creare una versione scheletrica di un nuovo sistema e dimostrare i suoi vantaggi. Questo è un ottimo modo per ottenere padroni, clienti e membri del team da acquistare.

    
risposta data 26.08.2018 - 05:11
fonte
2

I momenti che definiscono sono quelli in cui vai "Questo fa ciò di cui ho bisogno, potrei usarlo per il mio nuovo scopo?". Quello che dovresti porci nella buona tradizione dell'OOP è " È questo di cui ho bisogno?"

Funzionalmente potresti avere

  • Un insieme di comandi
  • Una struttura di navigazione ad albero
  • Una classe di definizione dello stile della voce di menu
  • Un sistema di gestione dei diritti

Ottieni il miglior riutilizzo quando tieni tutti questi elementi separati, ma non è così che queste cose sono in genere sviluppate e ora sai come funziona. Si inizia con le richieste modeste e gradualmente si aggiunge più funzionalità alla stessa cosa perché "è solo un piccolo cambiamento" e quindi si ha ciò di cui si ha bisogno.

Ogni volta che ci troviamo di fronte a una nuova richiesta di funzionalità, non dobbiamo affrettarci nel modo più rapido per inserirla e farla funzionare, ma piuttosto chiederci se stiamo ancora lavorando alla stessa cosa. O che sta emergendo qualche nuova bestia.

Un sacco di if in una classe è in genere un segno che dovresti avere una gerarchia di classi con il polimorfismo. La mia prima impressione per il tuo menu sarebbe un evento basato su: quando è il momento di visualizzare un elemento, il menu genera un evento e l'oggetto MenuEventArgs ha proprietà che indicano al menu se visualizzare l'elemento e, in tal caso, come (come disabilitato o abilitato, con un segno di spunta, eccetera). Queste proprietà potrebbero essere impostate dall'implementatore del gestore che chiama AccessRightsManager e forse un UIStateManager. Un altro evento potrebbe essere usato per rendere / dipingere l'oggetto. In questo modo è possibile mantenere la struttura del menu separata dalla gestione degli accessi, dallo stato e dai dettagli della presentazione.

Il fatto che "molti campi siano gli stessi" non è mai una buona ragione per mettere qualche entità intrinsecamente diversa in una tabella esistente. Se è non è la stessa cosa, puoi scommettere la tua vita che alla fine divergeranno e sarà sempre più difficile hackerare nuove funzionalità in (e ridirigere il lotto in qualcosa che avrebbe dovuto essere dall'inizio ).

    
risposta data 26.08.2018 - 13:59
fonte
2

Fai un nuovo inizio?

Partire da zero è allettante. Ma l'approccio è buono e liberatorio come sembra, soprattutto se hai una base lavorativa relativamente di successo?

Il fantastico articolo di Joel " Cose che non dovresti mai fare "conclude che non lo è.

You say you want a revolution
Well, you know
We all want to change the world

- The Beatles

Evoluzione anziché rivoluzione?

Il grande software è principalmente costituito da design e codice che si sono evoluti da versione a versione. Prendi Linux come esempio. Non era destinato a diventare un successo planetario . Ma oggi, milioni di righe di codice aggiunte e modificate in seguito, ci rendono ancora felici.

Con un approccio evolutivo, non c'è bisogno di prevedere il futuro e avere un design perfetto. Distribuisci software di lavoro in piccoli incrementi, con un design che è abbastanza buono per evolvere.

Leggendo la tua storia, penso che tu abbia avuto un tale successo con questo approccio alla tua versione precedente. Congratulazioni! Dai, il tuo software è sicuramente ottimo.

Quindi la mia raccomandazione per la tua nuova versione è: Adatta, espandi, refactor , e di nuovo . Questo include la generalizzazione di alcuni pezzi esistenti per mantenerli idonei allo scopo. L'esperienza passata con nuovi approfondimenti ti aiuterà a migliorare il design esistente, senza il rischio di perderti in un design completamente nuovo.

    
risposta data 26.08.2018 - 11:01
fonte

Leggi altre domande sui tag