2009-07-20 11 views
8

Quando inizio a scrivere codice da zero, ho la cattiva abitudine di scrivere rapidamente tutto in una funzione, tutto il tempo pensando "lo renderò più modulare in seguito". Poi, quando arriva più tardi, ho un prodotto funzionante e qualsiasi tentativo di risolverlo significherebbe creare funzioni e dover capire cosa devo passare.Superare la cattiva abitudine di "aggiustarlo più tardi"

Diventa peggio perché diventa estremamente difficile riprogettare le classi quando il progetto è quasi finito. Per esempio, di solito faccio qualche pianificazione prima di iniziare a scrivere il codice, poi quando il mio progetto è terminato, mi sono reso conto che avrei potuto rendere le classi più modulari e/o avrei potuto usare l'ereditarietà. Fondamentalmente, non penso di fare abbastanza pianificazione e non ottengo più di un livello di astrazione.

Quindi, alla fine, sono bloccato con un programma con una grande funzione principale, una classe e alcune funzioni di supporto. Inutile dire che non è molto riutilizzabile.

Qualcuno ha avuto lo stesso problema e ha qualche consiglio per superare questo? Una cosa che avevo in mente era scrivere la funzione principale con pseduocode (senza molti dettagli ma abbastanza per vedere quali oggetti e funzioni hanno bisogno). Essenzialmente un approccio top-down.

Questa è una buona idea? Qualche altro suggerimento?

+1

Ho chiesto di recente una simile procedura riguardante la pianificazione del progetto, ma i nostri problemi sono leggermente diversi. Forse alcune risposte potrebbero esserti utili. http://stackoverflow.com/questions/1100819/how-do-you-design-object-oriented-projects – Victor

+0

Mi ricompenso con il cioccolato. – Maxpm

risposta

5

"Prima facciamo le nostre abitudini, poi ci fanno."

Questo sembra applicarsi sia per le buone che per le cattive abitudini. Sembra che una persona cattiva si sia impadronita di te.

La pratica è più modulare in avanti fino a quando "è solo il modo in cui faccio le cose".

3

Sì, la soluzione è semplice, anche se ci vuole tempo per abituarsi. Non dire mai che ci sarà un "dopo", dove ti siedi e fai solo il refactoring. Invece, continua ad aggiungere funzionalità al tuo codice (o test) e durante questa fase esegui piccoli, incrementali refactoring. Il "dopo" sarà fondamentalmente "sempre", ma nascosto nella fase in cui stai facendo ogni volta qualcosa di nuovo.

1

Trovo che la disciplina TDD Red-Green-Refactor faccia miracoli.

12

Prendo l'approccio completamente opposto quando scrivo qualcosa al volo senza una fase di progettazione in anticipo. Cioè, scrivo la funzione principale nel modo in cui apparirebbe "in un mondo perfetto" usando oggetti e metodi immaginari, quindi creo scheletri di tutto così che il programma compili, poi torni indietro e faccia funzionare. Ciò garantisce un design modulare e il codice di alto livello è molto facile da capire. Se non altro, lo svantaggio è che il codice può diventare troppo modulare, poiché si è tentati di scrivere doFoo() invece di implementare foo inline.

+1

Sì, seguo lo stesso approccio.Penso che alla fine della giornata non si possa mai bussare al codice modulare, ma nella mia esperienza il vantaggio reale è la gestibilità e il fatto di avere una buona struttura a tutto tondo. Gli aspetti del riuso sono minimi, ma sicuramente esistono. –

+0

Esattamente il mio approccio. L'unico possibile pericolo è che il codice finisca in questo modo: main() {doFoo(); } foo() {doFoo(); } doFoo() {reallyDoFoo(); } reallyDoFoo() {reallyReallyDoFoo(); } –

2

La mia regola generale è che qualsiasi cosa più lunga di 20 LoC dovrebbe essere pulita. IME ogni progetto si basa su alcuni "just-a-proof-of-concept" che non sono mai stati destinati a finire nel codice di produzione. Poiché ciò sembra inevitabile, anche 20 righe di codice di proof-of-concept dovrebbero essere chiare, perché potrebbero finire per essere uno dei fondamenti di un grande progetto.

Il mio approccio è top-down. Scrivo

while(obj = get_next_obj(data)) { 
    wibble(obj); 
    fumble(obj); 
    process(filter(obj)); 
} 

e inizio solo a scrivere tutte queste funzioni in seguito. (Di solito sono inline e vanno nello spazio dei nomi senza nome, a volte diventano one-liner e quindi potrei eliminarli in seguito.)

In questo modo evito anche di dover commentare gli algoritmi: i nomi delle funzioni sono una spiegazione sufficiente.

0

Hai identificato il problema. Non avendo abbastanza pianificazione. Passa un po 'di tempo ad analizzare la soluzione che stai per sviluppare, scomposizione in parti di funzionalità, identificare come sarebbe meglio implementarle e provare a separare i livelli dell'applicazione (interfaccia utente, logica aziendale, livello di accesso ai dati, eccetera).

Pensare in termini di OOP e refattatore non appena ha senso. È molto più economico che farlo dopo che tutto è stato costruito.

0

Scrivere la funzione principale in modo minimale, con quasi nulla in essa. Nella maggior parte dei programmi di gui, programmi di giochi sdl, open gl o qualsiasi cosa con qualsiasi tipo di interfaccia utente, la funzione principale dovrebbe essere nient'altro che un ciclo di eventi. Deve esserlo, o ci saranno sempre lunghi periodi di tempo in cui il computer sembra non rispondere, e il sistema operativo pensa che consideri forse chiuderlo perché non risponde ai messaggi.

Una volta ottenuto il ciclo principale, bloccarlo rapidamente, solo per essere modificato per correggere i bug, non per nuove funzionalità. Questo potrebbe finire per spostare il problema su un'altra funzione, ma avere una funzione monolitica è piuttosto difficile da fare comunque in un'applicazione basata su eventi. Avrai sempre bisogno di un milione di piccoli gestori di eventi.

Forse hai una lezione monolitica. L'ho fatto. Principalmente il modo per affrontarlo è cercare di mantenere una mappa mentale o fisica delle dipendenze e annotare dove ci sono ... diciamo perforazioni, fessure in cui un gruppo di funzioni non dipende esplicitamente da uno stato o da variabili condivise con altre funzioni nella classe. Qui puoi trasformare quel gruppo di funzioni in una nuova classe. Se è davvero una classe enorme, e davvero aggrovigliato, lo definirei un odore di codice. Pensa a ridisegnare una cosa del genere per essere meno grande e interdipendente.

Un'altra cosa che puoi fare è come stai codificando, nota che quando una funzione cresce a una dimensione in cui non si adatta più a un singolo schermo, probabilmente è troppo grande, e a quel punto inizia a pensare a come rompere giù in più funzioni più piccole.

0

Il refactoring è molto meno spaventoso se si dispone di buoni strumenti per farlo. Vedo che hai taggato la tua domanda come "C++" ma lo stesso vale per qualsiasi lingua. Ottieni un IDE in cui estrarre e rinominare metodi, estrarre variabili, ecc. Sia facile da fare e quindi imparare a utilizzare efficacemente quell'IDE. Quindi i "piccoli e incrementati rifacimenti" citati da Stefano Borini saranno meno scoraggianti.

0

Il tuo approccio non è necessariamente negativo: prima un design più modulare poteva essere considerato un over-engineering.

È necessario il refactoring - questo è un dato di fatto. La domanda è quando? Troppo tardi, e il refactoring è un compito troppo grande e troppo incline al rischio. Troppo presto, e potrebbe essere troppo ingegnoso. E, col passare del tempo, avrai bisogno di refactoring di nuovo .. e ancora. Questo è solo parte del naturale ciclo di vita del software.

Il trucco è quello di refactoring presto, ma non troppo presto. E frequentemente, ma non troppo spesso. Quanto tempo e con che frequenza? Ecco perché è un'arte e non una scienza :)

Problemi correlati