Quali cambiamenti sono troppo grandi per essere facili da progettare correttamente?


11

Questa è una domanda piuttosto vaga, ma è qualcosa a cui non ho mai sentito una risposta soddisfacente quando leggo del design corretto.

Generalmente, quando si impara sulla programmazione orientata agli oggetti, l'astrazione, il factoring, ecc., Il santo graal del design - e il motivo per cui affermano sempre che si stanno utilizzando le tecniche di sviluppo in questione - è che renderà il tuo programma "facile da cambiare" , "manutenibile", "flessibile" o uno qualsiasi dei sinonimi utilizzati per esprimere un tale concetto dal suono produttivo. Contrassegnando gli ivar come privati, suddividendo il codice in molti piccoli metodi autonomi, mantenendo le interfacce generali, si presume che si ottenga la possibilità di modificare il programma con totale facilità e grazia.

Per cambiamenti relativamente piccoli, questo ha funzionato bene per me. Le modifiche alle strutture di dati interne utilizzate da una classe per aumentare le prestazioni non sono mai state una grande difficoltà, né hanno avuto cambiamenti alla fine dell'interfaccia utente, indipendentemente dall'API, come riprogettare un sistema di immissione del testo o revisionare la grafica per un elemento di gioco .

Tutti questi cambiamenti sembrano intrinsecamente autonomi. Nessuno di essi comporta modifiche al comportamento o alla progettazione del componente del programma che viene modificato, per quanto riguarda il codice esterno in questione. Indipendentemente dal fatto che tu stia scrivendo proceduralmente o in uno stile OO, con funzioni grandi o piccole, queste sono semplici modifiche da apportare anche se hai solo un design moderatamente buono.

Tuttavia, ogni volta che le modifiche diventano grandi e pelose, ovvero modifiche all'API, nessuno dei miei preziosi "schemi" viene mai in soccorso. Il grande cambiamento rimane grande, il codice interessato rimane interessato e molte ore di lavoro di generazione di bug sono davanti a me.

Quindi, la mia domanda è questa. Quanto è grande un cambiamento che il design appropriato afferma di poter facilitare? Esiste qualche ulteriore tecnica di progettazione, che non mi è nota o che non sono riuscita a implementare, che rende davvero semplici le modifiche dal suono appiccicoso, o è quella promessa (che ho sentito fare da così tanti paradigmi diversi) semplicemente una bella idea, completamente disconnesso dalle immutabili verità dello sviluppo del software? Esiste uno "strumento di modifica" che posso aggiungere alla mia cintura?

In particolare, il problema che sto affrontando che mi ha portato ai forum è questo: ho lavorato sull'implementazione di un linguaggio di programmazione interpretato (implementato in D, ma non è pertinente) e ho deciso che gli argomenti delle mie chiusure dovrebbero essere basati su parole chiave, anziché posizionali come sono attualmente. Ciò richiede la modifica di tutto il codice esistente che chiama funzioni anonime, che, fortunatamente, è piuttosto piccolo perché sono all'inizio dello sviluppo del mio linguaggio (<2000 righe), ma sarebbe enorme se avessi preso questa decisione in un secondo momento. In una situazione del genere, esiste un modo per cui con una lungimiranza adeguata della progettazione avrei potuto rendere più semplice questa modifica o alcuni cambiamenti (la maggior parte) intrinsecamente profondi? Sono curioso di sapere se questo è in qualche modo un fallimento delle mie capacità progettuali - se lo è, io '

Per essere chiari, non sono in alcun modo scettico nei confronti di OOP o di qualsiasi altro modello comunemente usato. Per me, tuttavia, le loro virtù sono nella scrittura originale, piuttosto che nel mantenimento, delle basi di codice. L'ereditarietà ti consente di astrarre bene schemi ripetitivi, il polimorfismo ti consente di separare il codice in base alla funzione umana (quale classe) piuttosto che all'effetto compreso in macchina (quale ramo switchdell'istruzione), e piccole funzioni autonome ti permettono di scrivere in uno stile "bottom-up" molto piacevole. Tuttavia, sono scettico sulle loro pretese di flessibilità.


Non vedo come il cambiamento nella tua lingua non tocchi altro che il parser. Potresti chiarire questo?
scarfridge il

In un linguaggio simile a smalltalk, è vero che avresti solo bisogno di modificare il parser, perché sarebbe una semplice questione di trattamento foo metarg1: bar metarg2: bazcome foo.metarg1_metarg2_(bar, baz). Tuttavia, la mia lingua sta apportando una modifica più ampia, vale a dire i parametri basati su elenco a parametri basati su dizionario, che influisce sul tempo di esecuzione ma non sul parser, in realtà, a causa delle proprietà specifiche della mia lingua non entrerò in questo momento. Poiché non esiste una mappatura chiara tra parole chiave non ordinate e argomenti posizionali, il runtime è il problema principale.
esiste - forall

Risposte:


13

A volte una modifica è abbastanza grande da dover progettare un percorso di migrazione. Anche se i punti di inizio e fine sono ben progettati, spesso non puoi semplicemente cambiare il tacchino freddo. Molti designer altrimenti bravi non riescono a progettare percorsi di migrazione validi.

Questo è il motivo per cui penso che ogni programmatore dovrebbe fare un software di scrittura stint per ambienti di produzione 24/7. C'è qualcosa nelle perdite che vengono quotate nell'ordine del tuo stipendio annuale al minuto che ti motiva a imparare a scrivere un codice di migrazione robusto.

Ciò che le persone fanno naturalmente per un grande cambiamento è strappare alla vecchia maniera, metterlo nella nuova maniera, quindi passare un paio di giorni a correggere errori di compilazione di bazillion, fino a quando il codice non viene nuovamente compilato in modo da poter iniziare i test. Dato che il codice non è stato testato per due giorni, improvvisamente hai un gran casino di bug intricati da risolvere.

Per una grande modifica del design come il passaggio da argomenti posizionali a parole chiave, un buon percorso di migrazione sarebbe innanzitutto aggiungere il supporto per gli argomenti delle parole chiave senza rimuovere il supporto posizionale . Quindi si modifica metodicamente il codice chiamante per utilizzare gli argomenti delle parole chiave. Questo è simile al modo in cui lo hai fatto prima, tranne che questa volta puoi testarlo mentre procedi, perché il codice chiamante invariato funziona ancora. Poiché le modifiche sono più piccole tra i test, i bug sono più facili da correggere in precedenza. Quindi, quando tutto il codice chiamante è stato modificato, è banale rimuovere il supporto posizionale.

Questo è il motivo per cui le API pubblicate pubblicamente deprecano i vecchi metodi invece di rimuoverli dalla Turchia fredda. Fornisce un percorso di migrazione continuo per chiamare il codice. Potrebbe sembrare un po 'più di lavoro per supportare entrambi per un po', ma perdi tempo nei test.

Quindi, per rispondere alla tua domanda come originariamente formulato, non ci sono quasi cambiamenti troppo grandi per essere semplificati da una progettazione appropriata. Se la modifica sembra troppo grande, è sufficiente progettare alcune fasi intermedie temporanee per la migrazione del codice.


+1 per supportare il nuovo caso e il vecchio caso in modo da poter uscire gradualmente.
Erik Reppen,

9

Ti consiglierei di leggere il saggio di Big Ball of Mud .

Fondamentalmente il punto che il design tende a deteriorarsi man mano che avanzi con lo sviluppo e devi dedicare il lavoro al contenimento della complessità. La complessità stessa non può essere eliminata, solo contenuta, perché è inerente al problema che stai cercando di risolvere.

Questo porta ai principi fondamentali dello sviluppo agile. I due più rilevanti sono "non ne avrai bisogno" che ti dicono di non preparare il design per le funzionalità che non hai ancora intenzione di implementare, perché è improbabile che tu riesca comunque a farlo bene e che "refactor senza pietà" ti dice di lavorare sul mantenimento della sanità mentale del codice durante tutto il progetto.

Sembra che la risposta sia che nessun progetto è in grado di facilitare qualsiasi cambiamento al di là degli esempi inventati progettati specificamente per mostrare che il design è più forte di quanto non sia in realtà.

Su una nota a margine, dovresti essere scettico sulla programmazione orientata agli oggetti. È un ottimo strumento in molti contesti, ma devi essere consapevole dei suoi limiti. Soprattutto l'uso improprio dell'eredità può portare a un codice incredibilmente contorto. Ovviamente dovresti essere altrettanto scettico su qualsiasi altra tecnica di progettazione. Ognuno ha i suoi usi e ognuno ha i suoi punti deboli. Non esiste un proiettile d'argento.


1
+1: il nuovo design iniziale ha raramente successo. I progetti di successo sono ricapitolazioni di progetti comprovati o sono stati ripetutamente perfezionati.
Kevin Cline,

1
+1 dovresti essere scettico su tutti i concetti di programmazione, solo attraverso critiche oggettive applichiamo le nostre capacità analitiche per trovare la soluzione giusta piuttosto che solo una soluzione .
Jimmy Hoffa,

4

In termini generali, ci sono "pezzi di codice" (funzioni, metodi, oggetti, qualunque cosa) e "interfacce tra pezzi di codice" (API, dichiarazioni di funzioni, qualunque cosa, incluso il comportamento).

Se un pezzo di codice può essere modificato senza cambiare l'interfaccia da cui dipendono altri pezzi di codice, la modifica sarà più semplice (e non importa se usi OOP o meno).

Se un pezzo di codice non può essere modificato senza cambiare l'interfaccia da cui dipendono altri pezzi di codice, allora la modifica sarà più dura (e non importa se usi OOP o meno).

I principali vantaggi di OOP sono che le "interfacce" pubbliche sono chiaramente contrassegnate (ad es. I metodi pubblici di un oggetto) e che altri codici non possono usare interfacce interne (ad es. I metodi privati ​​dell'oggetto). Poiché le interfacce pubbliche sono chiaramente contrassegnate, le persone tendono ad essere più attente a progettare quelle interfacce pubbliche (il che aiuta a evitare i cambiamenti più difficili). Poiché le interfacce interne non possono essere utilizzate da altri codici, è possibile modificarle senza preoccuparsi di altri codici a seconda di essi.


Un altro vantaggio di OOP è che l'incapsulamento dei dati con la logica che opera su di esso porta a interfacce pubbliche più piccole (se fatto bene).
Michael Borgwardt,

2

Non esiste una metodologia di progettazione che può salvarti dalle seccature di un grande cambiamento di requisiti / ambito. È semplicemente impossibile immaginare e spiegare tutti i possibili cambiamenti che potrebbero essere pensati in un secondo momento.

Quando un disegno buon fa aiuto voi è per aiutare a capire come tutti i pezzi si incastrano e quale sarà l'incidenza della modifica proposta.
Inoltre, un buon design può limitare le parti interessate dalla maggior parte delle modifiche proposte.

In breve, tutte le modifiche sono rese più facili da un buon design, ma un buon design non può trasformare un grande cambiamento in uno piccolo. (un design cattivo / no d'altra parte può trasformare qualsiasi piccolo cambiamento in uno grande.)


1

Dal momento che il design è destinato a portare un progetto di sviluppo da zero fogli bianchi a un prodotto finale affidabile funzionante (almeno il design per uno), e questo è il più grande cambiamento in un progetto che possa esserci, dubito che ci sia qualcosa come un cambiamento troppo grande da gestire.

Semmai è il contrario. Alcuni cambiamenti sono troppo piccoli per disturbare qualsiasi "design" o pensiero fantasioso. Correzioni di bug semplici, ad esempio.

Ricorda che i modelli di progettazione aiutano a pensare chiaramente e a trovare buone soluzioni di progettazione per situazioni comuni, come la creazione di un numero enorme di piccoli oggetti di tipo identico. Nessun elenco di modelli è completo. Tu e tutti noi avremo problemi di progettazione che non sono comuni, che non si adattano a nessun buco di piccione. Tentare di compiere ogni piccola mossa in un processo di sviluppo software secondo uno schema ufficiale o un altro, è un modo eccessivamente religioso per fare le cose e porta a pasticci non mantenibili.

Tutto il lavoro nel software genera naturalmente bug. I calciatori subiscono infortuni. I musicisti ottengono calli o spasmi delle labbra a seconda del loro strumento. (Se ricevi spasmi delle labbra mentre suoni la chitarra, stai sbagliando.) Gli sviluppatori di software ottengono dei bug. Adoriamo trovare modi per ridurre il loro tasso di generazione, ma non sarà mai zero.


3
La posizione di partenza può anche essere negativa. Non sottovalutare il potere di Legacy :)
Maglob

Progettare il progetto da zero è la parte facile. Ma anche se inizi un nuovo progetto da zero, cosa rara in sé, ti divertirai a costruire dal nulla solo per i primi giorni. La prima decisione di progettazione iniziale risulta essere stata sbagliata e le cose inizieranno solo a scendere da lì. Il design da zero è totalmente irrilevante nella pratica.
Jan Hudec,

1

Il costo delle modifiche radicali è spesso proporzionale alla dimensione della base di codice. Pertanto, la riduzione delle dimensioni della base di codice dovrebbe essere sempre uno degli obiettivi di qualsiasi progetto adeguato.

Nel tuo esempio, una corretta progettazione ha già semplificato il tuo lavoro: dover apportare modifiche su 2000 linee di base di codice, anziché su 20000 o 200000 linee di base di codice.

La progettazione corretta riduce le modifiche radicali, ma non le elimina.

Le modifiche radicali sono abbastanza facilmente automatizzabili se c'è un supporto adeguato dagli strumenti di analisi del linguaggio. Una modifica di refactoring generale può essere applicata all'intera base di codice in uno stile di ricerca e sostituzione (nel rispetto delle regole della lingua). Il programmatore deve solo esprimere un giudizio sì / no per ogni hit di ricerca.


+1 per il suggerimento di automazione. Penso che lo userò davvero per molte delle mie funzioni di libreria che chiamano chiusure - è una semplice questione di rilevare quali funzioni richiedono blocchi e modificarle in modo che abbiano un parametro simbolo aggiuntivo per il nome dell'argomento per passare il valore come.
esiste - forall
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.