È preferibile progettare dall'alto verso il basso o dal basso verso l'alto?


31

A quanto ho capito, il design top-down consiste nel perfezionare il concetto astratto di alto livello in parti più concrete e comprensibili, fino a quando non viene definito il blocco più piccolo. D'altra parte, dal basso verso l'alto definisce le parti di basso livello, quindi costruisce gradualmente blocchi di livello superiore fino a formare l'intero sistema.

In pratica, si dice che sia meglio combinare i due metodi: inizia con specifiche di alto livello per specificare completamente la conoscenza del dominio, le sue relazioni e i suoi vincoli. Una volta compreso il problema, vengono creati i blocchi di costruzione più piccoli per costruire il sistema.

Il processo di:

  • Creazione delle specifiche dei requisiti
  • Crea una specifica di progettazione (con diagrammi)
  • Strumento
  • consegnare
  • Ripeti (nello sviluppo iterativo, piuttosto che fare un pezzo intero in ogni fase, facciamo un po 'più volte ciascuno, e ci incontriamo quotidianamente per adattarci alle esigenze dinamiche del cliente)

mi sembra perfettamente normale (con specifiche come piani). Ha i suoi difetti ma è per questo che abbiamo ottenuto uno sviluppo iterativo: invece di passare il tempo in una fase, dice, l'analisi dei requisiti per studiare ogni cosa possibile nella conoscenza del dominio che è soggetta a cambiamenti (possibilmente ogni giorno), facciamo un po 'di analisi, un po 'di design e poi implementarlo.

Un altro modo è che ogni iterazione è una moda mini-cascata, in cui l'analisi viene eseguita in pochi giorni (o una settimana). Lo stesso vale per il design. Il resto del tempo viene impiegato per l'implementazione. C'è qualcosa di intrinsecamente sbagliato nell'approccio top-down in combinazione con lo sviluppo iterativo?

Nel suo saggio Programmazione dal basso verso l'alto , Paul Graham sembra incoraggiare la costruzione dal basso verso l'alto o programmarla dal basso verso l'alto, ma non la fase di analisi / progettazione dei requisiti:

I programmatori esperti di Lisp dividono i loro programmi in modo diverso. Oltre al design top-down, seguono un principio che potrebbe essere chiamato design bottom-up, cambiando la lingua per adattarla al problema.

Per quanto ne so, intendeva dire che Lisper esegue ancora un progetto top-down, ma programma bottom-up, è vero? Un altro punto che ha scritto:

Vale la pena sottolineare che il design dal basso non significa solo scrivere lo stesso programma in un ordine diverso. Quando lavori dal basso, di solito finisci con un programma diverso. Invece di un singolo programma monolitico, otterrai un linguaggio più ampio con operatori più astratti e un programma più piccolo scritto in esso. Invece di un architrave, otterrai un arco.

Ciò significa che durante il periodo di scrittura di un programma in Lisp, si finisce con uno strumento generico?


Forse se fornisci un link al libro, alla carta o all'articolo a cui ti riferisci, altri potrebbero essere in grado di fare una dichiarazione più motivata riguardo alla tua domanda. Inoltre, non è chiaro quale sia la tua domanda specifica. Stai chiedendo consigli su alcuni aspetti specifici di un progetto che desideri implementare o stai ponendo domande sull'articolo a cui ti riferisci. Forse questo sarebbe più facile da leggere e rispondere se lo abbassi in un paio di domande poste separatamente.
S.Robins,

@ S.Robins Per riassumere, ho dimostrato il mio solito approccio top-down per lo sviluppo di software. Tuttavia, alcune persone preferiscono dal basso verso l'alto, e uno di questi è Paul Graham, che è una persona famosa. Chiedo di capire come il design dal basso aiuta in generale, e in particolare in Lisp in quanto ha caratteristiche speciali da incoraggiare (come suggerito da Paul).
Amumu,

1
Amumu ho eliminato l'esempio, poiché entrambe le risposte sembrano averlo ignorato comunque. Si prega di provare a mantenere le domande concise e porre solo una domanda per domanda .
yannis,

Risposte:


35

Il top-down è un ottimo modo per descrivere cose che sai o per ricostruire cose che hai già costruito.

Il più grande problema dall'alto verso il basso è che abbastanza spesso semplicemente non esiste un "top". Cambierai idea di cosa dovrebbe fare il sistema durante lo sviluppo del sistema e durante l'esplorazione del dominio. Come può essere il tuo punto di partenza qualcosa che non conosci (ovvero cosa vuoi che faccia il sistema)?

Un top down "locale" è una buona cosa ... pensare in anticipo sulla programmazione è chiaramente buono. Ma pensare e pianificare troppo non lo è, perché quello che stai immaginando non è il vero scenario (a meno che tu non sia già stato lì prima, cioè se non stai costruendo, ma ricostruendo). Il top-down globale quando si costruiscono cose nuove è semplicemente una sciocchezza.

Il bottom-up dovrebbe essere (a livello globale) l'approccio, a meno che non si conosca il 100% del problema, è necessario solo la soluzione nota da codificare e non ti interessa cercare possibili soluzioni alternative.

L'approccio di Lisp è il distillato dal basso verso l'alto. Non solo costruisci dal basso verso l'alto, ma puoi anche modellare i mattoni nel modo in cui ne hai bisogno. Niente è fisso, la libertà è totale. Naturalmente la libertà si assume la responsabilità e puoi fare cose orribili abusando di questo potere.

Ma un codice orribile può essere scritto in qualsiasi lingua. Anche in linguaggi modellati come gabbie per la mente, progettati con la speranza che con quelle lingue anche le scimmie potessero mettere in moto programmi validi (un'idea così sbagliata su così tanti livelli che fa male anche solo a pensarci).

Il tuo esempio riguarda un server web. Ora nel 2012 questo è un problema ben definito, hai delle specifiche da seguire. Un web server è solo un problema di implementazione. Soprattutto se stai mirando a scrivere un server web sostanzialmente identico agli altri gajillion di server web che sono là fuori, nulla è davvero poco chiaro, tranne alcune minuzie. Anche il tuo commento su RSA parla ancora di un problema chiaramente definito, con specifiche formali.

Con un problema ben definito, con specifiche formali e soluzioni già note, la codifica si collega semplicemente nei punti. In alto va bene per quello. Questo è il paradiso del project manager.

In molti casi, tuttavia, non esiste un approccio ben noto per collegare i punti. In realtà molto spesso è difficile dire anche quali siano i punti.

Supponiamo, ad esempio, che ti venga chiesto di indicare a una macchina da taglio automatica di allineare le parti da tagliare a un materiale stampato che non è perfettamente conforme al teorico logo ripetitivo. Vengono fornite le parti e le immagini del materiale prese dalla macchina.

Che cos'è una regola di allineamento? Tu decidi. Cos'è uno schema, come rappresentarlo? Tu decidi. Come allineare le parti? Tu decidi. Le parti possono essere "piegate"? Dipende, alcuni no e alcuni sì, ma ovviamente non troppo. Cosa fare se il materiale è troppo deformato per una parte per tagliarlo in modo accettabile? Tu decidi. Tutti i rotoli di materiale sono identici? Certo che no, ma non puoi bug all'utente di adattare le regole di allineamento per ogni lancio ... sarebbe impraticabile. Quali immagini vedono le telecamere? Il materiale, qualunque cosa possa significare ... può essere colore, può essere nero su nero dove solo il riflesso della luce rende evidente il motivo. Cosa significa riconoscere un modello? Tu decidi.

Ora prova a progettare la struttura generale di una soluzione per questo problema e dai un preventivo, in termini di tempo e denaro. La mia scommessa è che anche l'architettura del tuo sistema ... (sì, l'architettura) sarà sbagliata. La stima dei costi e dei tempi sarà costituita da numeri casuali.

Lo abbiamo implementato e ora è un sistema funzionante, ma abbiamo cambiato idea sulla stessa forma del sistema un gran numero di volte. Abbiamo aggiunto interi sottosistemi che ora non possono nemmeno essere raggiunti dai menu. Abbiamo cambiato i ruoli master / slave nei protocolli più di una volta. Probabilmente ora abbiamo abbastanza conoscenze per tentare di ricostruirla meglio.

Ovviamente altre aziende hanno risolto lo stesso problema ... ma a meno che tu non sia in una di queste società molto probabilmente il tuo progetto dettagliato top-down sarà uno scherzo. Possiamo progettarlo dall'alto verso il basso. Non puoi perché non l'hai mai fatto prima.

Probabilmente puoi risolvere anche lo stesso problema. Lavorando dal basso verso l'alto comunque. A partire da ciò che sai, imparando ciò che non sai e sommando.

Vengono sviluppati nuovi sistemi software complessi, non progettati. Di tanto in tanto qualcuno inizia a progettare da zero un nuovo grande sistema software mal specificato. oppure c] entrambi ... e molto spesso [c] è il caso).

Questi sono i tipici progetti di grandi aziende con migliaia e migliaia di ore gettati nelle diapositive powerpoint e nei soli diagrammi UML. Invariabilmente falliscono completamente dopo aver bruciato quantità imbarazzanti di risorse ... o in alcuni casi eccezionali, alla fine forniscono un software troppo costoso che implementa solo una piccola parte delle specifiche iniziali. E quel software invariabilmente è profondamente odiato dagli utenti ... non il tipo di software che compreresti, ma il tipo di software che usi perché sei costretto a farlo.

Questo significa che penso che dovresti pensare solo al codice? Ovviamente no. Ma secondo me la costruzione dovrebbe iniziare dal basso (mattoni, codice concreto) e dovrebbe aumentare ... e la tua attenzione e attenzione ai dettagli dovrebbero in un certo senso "svanire" man mano che ti allontani da ciò che hai. Il top-down è spesso presentato come se dovessi mettere lo stesso livello di dettaglio all'intero sistema in una sola volta: tienilo sempre diviso ogni nodo fino a quando tutto è ovvio ... nei moduli di realtà, il sottosistema viene "cresciuto" dalle subroutine. Se non si ha una precedente esperienza nel problema specifico, la progettazione dall'alto in basso di un sottosistema, modulo o libreria sarà orribile. Puoi progettare una buona libreria una volta che sai quali funzioni inserire, non viceversa.

Molte delle idee di Lisp stanno diventando più popolari (funzioni di prima classe, chiusure, tipizzazione dinamica di default, garbage collection, metaprogrammazione, sviluppo interattivo) ma Lisp è ancora oggi (tra le lingue che conosco) abbastanza unico in quanto è facile modellare il codice per quello che ti serve.

I parametri delle parole chiave, ad esempio, sono già presenti, ma se non fossero presenti potrebbero essere aggiunti. L'ho fatto (inclusa la verifica delle parole chiave al momento della compilazione) per un compilatore Lisp giocattolo con cui sto sperimentando e non richiede molto codice.

Con C ++ invece il massimo che puoi ottenere è un gruppo di esperti C ++ che ti dicono che i parametri delle parole chiave non sono così utili, o un'implementazione del modello incredibilmente complessa, spezzata e con metà backup che in effetti non è così utile. Le classi C ++ sono oggetti di prima classe? No e non c'è niente che tu possa fare al riguardo. Puoi avere introspezione in fase di esecuzione o in fase di compilazione? No e non c'è niente che tu possa fare al riguardo.

Questa flessibilità linguistica di Lisp è ciò che la rende eccezionale per l'edilizia dal basso. Puoi creare non solo subroutine, ma anche la sintassi e la semantica della lingua. E in un certo senso Lisp stesso è dal basso verso l'alto.


Sì, quello che intendevo era un locale dall'alto verso il basso. Continui a perfezionare il requisito e lo evolvi attraverso le iterazioni. Il punto di top-down è che vogliamo ottenere la struttura del codice il più corretta possibile. Refactoring continuo. Ad esempio, all'inizio si desidera passare una stringa a una funzione, ma in seguito è necessaria un'intera classe per soddisfare le modifiche e il refactoring richiede tempo se la funzione è già utilizzata ovunque.
Amumu,

Ricostruire qualcosa di chiaramente noto in modo chiaramente noto, quindi dall'alto in basso va bene. Il tuo browser web ne è un esempio. Anche usare un linguaggio come C ++ che ti costringe ad anticipare e specificare formalmente (usando una terribile sintassi) tutti i tipi per i valori contenuti in tutte le variabili del tuo programma è ancora un'opzione praticabile.
6502,

Non credo che il mio esempio sia un modo chiaramente noto, perché presumo che sia stato creato da qualcuno con una conoscenza di base della comunicazione dei dati, e il codice C ++ è il risultato di una progettazione di base basata sulla conoscenza del dominio (in questo caso, la rete) . È un esempio di design evolutivo. Nella prossima iterazione, lo sviluppatore impara di più sulla conoscenza del dominio, quindi estende il suo design per adattarlo alla conoscenza evoluta (come aspetti di sicurezza aggiunti in ogni livello del server Web). Quindi perfeziona l'implementazione secondo il nuovo design.
Amumu,

Il punto qui è che la soluzione effettiva deriva da un linguaggio indipendente dal dominio (come il linguaggio naturale, il linguaggio matematico ... dipende dal dominio). L'atto di codificare è solo una mappatura dettagliata dalla soluzione generata al codice.
Amumu,

1
Ciao sig. 6502. Dopo un anno, mentre ho imparato più cose, inizio a vedere quello che hai detto diventare più vero. Ho fatto un altro thread: programmers.stackexchange.com/questions/179000/… . Se hai tempo, spero che chiarirai di nuovo il mio pensiero: D.
Amumu,

6

Non sono sicuro di come questa risposta si applicherebbe a Lisp, ma ho appena finito di leggere Principi, modelli e pratiche Agili e l'autore, lo zio Bob , sostiene fortemente l'approccio top-down per C # (applicabile anche a C ++) con il quale io essere d'accordo.

Tuttavia, a differenza di alcune altre risposte che hanno portato alla conclusione che l'approccio top-down significa che nella prima interazione fornisci solo documenti e design complessivo, il libro punta a un altro metodo: TDD combinato con il design evolutivo.

L'idea è di iniziare dall'alto e definire i livelli di astrazione più elevati (o i più alti locali) e, non appena definiti, li fai fare un lavoro utile in modo che la funzione n. 1 funzioni immediatamente. Quindi, quando aggiungi sempre più funzionalità, rifletti il ​​tuo codice e fai evolvere il design secondo necessità, tenendo sempre conto dei principi SOLIDI. In questo modo non finirai con troppi livelli di astrazione e non finirai con un design di basso livello che non si adatta all'architettura generale. Se non sei sicuro di cosa significhi, il libro che ho menzionato sopra ha un intero capitolo con un esempio in cui gli sviluppatori prendono concetti e iniziano con diagrammi UML e classi di basso livello, solo per rendersi conto che metà di quelle classi non sono necessarie una volta iniziata la codifica. In sostanza con questo approccio, il codice di basso livello viene naturalmente spinto verso il basso man mano che vengono definiti più dettagli di alto livello nel progetto.

E infine, se pratichi SOLID, non dovresti imbatterti in una situazione in cui hai definito astrazioni di alto livello e poi entrare nei dettagli e all'improvviso scopri che non ci sono OOD o astrazioni. Non è proprio colpa del design top-down, ma ingegneria pigra.

Se sei interessato a leggere di più su XP e il design evolutivo, ecco una buona lettura di Martin Fowler, un altro grande autore: "Is Design Dead?"


Perfezionare. Questo è ciò di cui sto parlando. Inoltre è bello conoscere il principio SOLID e Agile supporta l'approccio top-down (ho pensato che con TDD e XP, le persone passassero al codice al più presto ed evitassero la documentazione).
Amumu,

@Amumu: ho aggiunto un link ad un altro articolo scritto da Fowler. Puoi leggere di più sulla differenza tra la codifica da cowboy senza design / documentazione rispetto a ciò che XP / Agile sta effettivamente promuovendo. Mentre personalmente, credo fermamente che la documentazione abbia valore e ho promosso attivamente il mio team per mantenere i nostri documenti di progettazione, ho gradualmente spostato le mie opinioni. Ora le nostre "attività di progettazione" sono tutte cose di tipo lavagna / tovagliolo, mentre i documenti effettivi vengono aggiornati solo dopo che la storia è stata scritta. Abbiamo anche ridimensionato ciò che è effettivamente documentato, quindi i documenti coprono solo argomenti di alto livello / architettura
DXM,

3

Per me, le osservazioni più importanti che Paul Graham fa nel suo articolo sono queste:

[...] I programmatori Lisp [...] seguono un principio che potrebbe essere chiamato design dal basso verso l'alto, cambiando la lingua per adattarla al problema. In Lisp, non solo scrivi il tuo programma verso la lingua, ma costruisci anche la lingua verso il tuo programma.

Oppure, come è noto nei circoli C ++: Library Design is Language Design (Bjarne Stroustrup)

L'idea principale del design top down è: prima pianifichi, poi codifichi. Beanow ha ragione quando scrive che ci sono problemi quando il codice eseguibile arriva tardi nel processo. Nel design dal basso hai sempre il codice e quel codice che può essere testato.

Inoltre, il tuo codice non è piatto. Voglio dire, tende ad avere più livelli di astrazioni più piccole. Nel design dall'alto verso il basso le persone spesso finiscono con grandi astrazioni fino a un livello arbitrario e al di sotto che nessuna astrazione. Il codice progettato dal basso verso l'alto OTOH contiene spesso strutture di controllo e dati di livello inferiore poiché è probabile che vengano sottratte.


2

Idealmente, scrivere un programma in qualsiasi lingua, non solo Lisp, ti consente di scrivere un intero set di strumenti generici che possono accelerare il tuo prossimo programma o accelerare i miglioramenti a quello attuale.

In pratica, tenere traccia di questi strumenti può essere difficile. La documentazione è scarsa e disorganizzata e le persone che ne sono a conoscenza lasciano. In pratica, il riutilizzo del codice è spesso più un problema di quanto non valga la pena. Ma se il codice è documentato e organizzato correttamente e i programmatori restano in giro (o mantengono la propria scorta di codice utile), una grande quantità di lavoro può essere salvata afferrando il codice dal magazzino anziché ricostruendolo.

Tutto il design deve essere dall'alto verso il basso o non sapresti cosa stavi facendo. Stai costruendo un capannone o una macchina? Non riesci a capire quello con un design dal basso. Ma se hai intenzione di costruire una ruota anteriore sinistra, potresti pensare che potresti aver bisogno di più ruote in seguito, sia per questo progetto che per gli altri. E se costruisci una ruota riutilizzabile, ne avrai quattro al prezzo di una. (E 18 per quella roulotte che stai costruendo dopo, il tutto gratuitamente.)

Nota che, a differenza delle vere auto e delle vere ruote, se hai creato una "ruota" software ne hai costruite un numero infinito.

Altro sul design top-down: mentre devi iniziare con questo, mi sento obbligato a sottolineare che se non riesci a costruire una ruota, devi scoprirlo prima di fare molto lavoro sulla tua auto. Quindi devi lavorare dal basso verso l'alto quasi in parallelo con il lavoro dall'alto verso il basso. Inoltre, sapere che puoi costruire una ruota potrebbe suggerire molti progetti a cui non avresti mai pensato prima, come il rimorchio del trattore. Penso che l'approccio top-down debba dominare, ma con un tocco molto leggero.

Quindi, per continuare a parafrasare Paul Graham, idealmente quando scrivi un programma finisci con molte parti riutilizzabili che possono far risparmiare molto tempo sia al programma originale che ad altri. Per distanziarmi leggermente da Paul Graham, questo funziona in qualsiasi lingua (anche se alcuni incoraggiano il processo più di altri).

Il nemico di questo processo quasi magico sono programmatori con prospettive a breve termine. Ciò può derivare da difetti di personalità, ma più comunemente dal passaggio di lavori e responsabilità troppo rapidamente, sia all'interno che tra le aziende che assumono.


Wow, ho ricevuto risposta dal 16 febbraio e la posta in arrivo non ha mostrato nulla. Sono d'accordo con te, è sempre necessario avere un piano, anche molto leggero, e poi costruire un po ', quindi perfezionare il piano. È perfettamente ragionevole. Tuttavia, non mi sembra di capire perché molti sviluppatori adorino pianificare tutto ciò che hanno in testa mentre scrivono codice. È possibile risparmiare un bel po 'di tempo e possono concentrarsi per lavorare di più sul problema reale piuttosto che sul continuo refactoring dell'architettura del codice lungo il processo di implementazione.
Amumu,

@Amumu Uno dei motivi della pianificazione durante la scrittura: alcuni programmatori sanno molto di ciò che stanno facendo la tastiera e il mouse sono i loro colli di bottiglia nella codifica, non il processo di pianificazione. (Il codice riutilizzabile consentirebbe loro di svolgere un lavoro nuovo e creativo che permetterebbe di pensare di nuovo alla parte difficile.)
RalphChapin,

@Amumu Un'altra ragione per pianificare durante la scrittura: con alcune combinazioni lingua / programmatore / problema, la lingua è il modo migliore per pensare al problema. La lingua è il modo più semplice per mettere i tuoi pensieri su "carta". Ho letto solo piccole parti qua e là su Lisp, ma da questo sospetto che sia particolarmente utile per questo. (Alcune lingue sono state inizialmente create come un modo per esprimere un problema piuttosto che convincere una macchina a risolverlo.) Se è facile riorganizzare il codice come i tuoi pensieri, può anche codificare. Ma il programmatore, la lingua e il problema devono fondersi perfettamente affinché questo funzioni.
RalphChapin,

È affidabile. L'idea di pianificazione è quella di separare la conoscenza del dominio e le identificazioni delle relazioni tra i concetti del dominio al processo di programmazione effettivo. Immagina se una classe viene utilizzata da dozzine di altre classi, senza un piano ben preciso sulla creazione dell'interfaccia per comunicare con altre classi, possiamo facilmente entrare nell'integrazione e refactoring dell'inferno. In questo caso, il tempo sprecato per digitare e ristrutturare il codice supera di gran lunga il tempo impiegato per l'identificazione su carta.
Amumu,

Facciamo un esempio: supponiamo di scrivere un'applicazione client / server. La prima cosa da fare è identificare il protocollo definendo i messaggi di scambio, come il messaggio è disposto in memoria (ovvero intestazione 16 byte, id 4 byte ....). Il diagramma non è uguale alla modellazione / pianificazione: abstratt.com/blog/2009/05/03/on-code-being-model . Non è necessario utilizzare UML o diagramma per modellare, poiché UML si concentra praticamente solo su OOP e il tempo impiegato a digitare nel diagramma di classe UML non è inferiore alla digitazione del codice effettivo, se non di più.
Amumu,

1

Cosa c'è di sbagliato nell'approccio top-down in combinazione con lo sviluppo iterativo?

L'uso di un approccio top-down con sviluppo iterativo non fornisce alcun codice funzionante nelle prime iterazioni. Fornisce documenti di progettazione e tali che il cliente avrà difficoltà a fornire feedback. Le risposte come "sì, suppongo (questo è troppo astratto per me)" non ti aiuteranno a specificare ulteriormente le specifiche del sistema desiderato da parte del cliente.

Invece si crea solo una panoramica generale di ciò che è richiesto. (Requisiti) da utilizzare come linea guida generale per ciò che definisce un prodotto finito e ciò che merita priorità di attuazione. Da lì in poi crei prodotti funzionanti per ogni iterazione con cui il cliente può effettivamente giocare per vedere se questo era ciò che avevano in mente. In caso contrario, non sarà un problema in quanto non sono state impiegate centinaia di ore nella progettazione di un sistema che funziona in modo diverso da quello che il cliente chiede ora.

Paul Graham ha incoraggiato la costruzione dal basso verso l'alto? O semplicemente programmarlo dal basso verso l'alto, ma non la fase di analisi / desing dei requisiti?

Non parlerò per un'altra persona. Inoltre non ho letto il suo saggio. La risposta che ti sto dando proviene dalla mia educazione e dalle mie esperienze.

Ciò significa che, durante il periodo di scrittura di un programma in Lisp, si finisce con uno strumento generico che può essere usato per scrivere programmi simili a quello originale, no?

L'uso di questo approccio significa che finirai con blocchi più astratti. Semplicemente perché dovrai costruire sottosistemi autonomi che possono essere presentati immediatamente non puoi creare un design top-down fortemente intrecciato e che deve essere implementato tutto in una volta. Migliora la riusabilità, la manutenibilità, ma soprattutto consente una risposta più flessibile ai cambiamenti.


1
Il grassetto, ai miei occhi, non è necessario quando stai già citando un blocco ...
Mc
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.