Scrittura di codice robusto rispetto a overengineering

33

Come sapete voi che state scrivendo il codice più robusto possibile senza l'overengineering?

Mi trovo a pensare troppo a ogni possibile percorso che il mio codice può intraprendere e talvolta mi sembra una perdita di tempo. Immagino che dipenda dal tipo di programma che stai scrivendo, ma non voglio usare troppo del mio tempo tenendo conto delle situazioni che non accadrà mai.

    
posta Fran Sevillano 23.09.2011 - 14:08
fonte

8 risposte

39

How do you guys know that you are writing the most robust code possible without overengineering?

Che cosa consideri un codice robusto? Codice che è già a prova di futuro e così potente da poter gestire qualsiasi situazione? Sbagliato, nessuno può predire il futuro! E di nuovo sbagliato, perché sarà un pasticcio complicato e non gestibile.

Seguo vari principi: in primo luogo YAGNI (ancora) e KISS , quindi non scrivo codice non necessario. Ciò impedisce efficacemente la sovrastruttura. Riforizzo l'applicazione quando sono necessarie estensioni. I moderni strumenti di refactoring ti consentono di creare interfacce e scambiare facilmente le implementazioni in seguito quando ne hai bisogno.

Quindi cerco di rendere il codice che scrivo il più robusto possibile, che include l'eliminazione di quanti percorsi il programma può prendere (e anche gli stati) possibili e un po 'di Programmazione spartana . Un grande aiuto sono funzioni / metodi "atomici" che non si basano su stati esterni o almeno non lasciano il programma in uno stato di incoerenza quando falliscono. Se lo fai bene, è anche molto improbabile che ti ritroverai con il codice spaghetti ed è anche una benedizione per la manutenibilità. Inoltre, nella progettazione orientata agli oggetti, i principi SOLID sono un'ottima guida per un codice robusto.

Ho davvero scoperto che spesso è possibile ridurre la complessità, ad esempio le esplosioni combinatorie di percorsi o stati del programma, riflettendo profondamente su come progettarlo come il percorso più diretto possibile. Cerca di mantenere le combinazioni possibili al minimo scegliendo il miglior ordine delle tue subroutine e progettandole per questo scopo.

Il codice robusto è sempre il codice più semplice e pulito, ma la semplicità è un tratto che non è sempre facilmente raggiungibile. Eppure, dovresti lottare per questo. Scrivi sempre il codice più semplice possibile e aggiungi complessità solo quando non hai altra scelta.

La semplicità è solida, la complessità è fragile.

La complessità uccide.

    
risposta data 23.09.2011 - 14:13
fonte
8

Cerco di mantenere un equilibrio, concentrandomi su

  • gestendo tutti i possibili percorsi di esecuzione nei casi d'uso esistenti (questa è la parte "robustezza"),
  • abilitare funzioni / requisiti Sono abbastanza sicuro che arriveranno nel prossimo futuro e
  • cose che so per esperienza che saranno necessarie per la manutenibilità a lungo termine del codice di base (ovvero mantenendo il codice pulito e verificabile).

È un'area di confine sfocata - a volte riesco a fare un lavoro non necessario, a volte non riesco a fare qualcosa che si rivela necessario in seguito. Se le mancate non sono grandi, sto bene. Ad ogni modo, mi sforzo di imparare dai miei errori.

    
risposta data 23.09.2011 - 15:17
fonte
5

La differenza tra robusto e overengineering è la differenza tra gestire con garbo tutti i possibili casi d'uso, persino i casi d'uso bizzarri e marginali che NON DEVONO accadere. Quando dico con garbo intendo, l'utente entra in una bizzarra eccezione o si imbatte in una situazione che richiede una funzione non supportata o non specificata che non è stata definita e il codice esce senza interruzioni o informa l'utente di funzionalità non supportate.

L'overengineering, d'altro canto, potrebbe rientrare nel campo dell'implementazione completa di funzionalità che non erano necessarie o richieste (alcune funzionalità che il cliente HA BISOGNO ma non sono mai state richieste!) OPPURE può essere definito derivando un progetto troppo complesso o codice troppo complesso per gestire un problema relativamente semplice.

    
risposta data 23.09.2011 - 14:43
fonte
4

1) Ottieni requisiti.

2) Scrivi il codice minimo per soddisfare i requisiti. Se qualcosa è ambiguo, fai un'ipotesi istruita. Se è super ambiguo, torna a 1.

3) Invia a test.

4) Se i tester dicono che è buono, documenta l'approvazione. Se qualcosa non è attivo, torna a 1.

Concentrati sul superamento dei test, non sulla previsione dei test. Se non hai tester, prendi i tester! Sono essenziali non solo per verificare la correttezza del codice, ma per l'intero processo di sviluppo.

    
risposta data 23.09.2011 - 14:43
fonte
3

In primo luogo, mantenere i dati normalizzati (non ridondanti) il più possibile. Se i dati sono completamente normalizzati, nessun singolo aggiornamento ai dati può renderlo incoerente.

Non è possibile mantenere sempre i dati normalizzati, in altre parole potresti non essere in grado di eliminare la ridondanza, nel qual caso può avere stati incoerenti. La cosa da fare è quindi tollerare l'incoerenza e ripararla periodicamente con un qualche tipo di programma che lo attraversa e lo aggiusta.

C'è una strong tendenza a cercare di gestire strettamente la ridondanza tramite notifiche. Questi non sono solo difficili da accertare che siano corretti, ma possono portare a enormi inefficienze. (Parte della tentazione di scrivere notifiche sorge perché in OOP sono praticamente incoraggiati.)

In generale, tutto ciò che dipende dalla sequenza temporale di eventi, messaggi, ecc., sarà vulnerabile e richiederà tonnellate di codifica difensiva. Gli eventi e i messaggi sono caratteristici dei dati con ridondanza, poiché comunicano le modifiche da una parte all'altra, cercando di prevenire l'incoerenza.

Come ho detto, se devi avere ridondanza (e le probabilità sono abbastanza buone devi), è meglio essere in grado di a) tollerare e b) ripararlo. Se si tenta di prevenire l'incoerenza esclusivamente tramite messaggi, notifiche, trigger, ecc., Sarà molto difficile renderlo solido.

    
risposta data 23.09.2011 - 18:38
fonte
3
  • scrivi per riutilizzare.
  • scrivi test. banali, non banali, alcuni assurdamente complessi per vedere come si gestisce in tali condizioni. i test ti aiuteranno anche a determinare la forma dell'interfaccia.
  • scrivere il programma in modo fallito (ad esempio, asserzione). il mio codice ha un sacco di riutilizzo e io collaudo per una tonnellata di casi - c'è più controllo / gestione degli errori dell'implementazione effettiva (basata sul conteggio delle righe).
  • riutilizzo.
  • risolve immediatamente le cose che vanno male.
  • impara e costruisci dall'esperienza.

gli errori saranno emergono lungo la strada, ma saranno (fortunatamente) localizzati e saranno (nella maggior parte dei casi) mostrati molto presto nei test. l'altro vantaggio del riutilizzo è che il client / chiamante può salvare gran parte del controllo degli errori / scaffolding usando quello che viene portato dall'impianto.

i test devono quindi definire le capacità del tuo programma e quanto sono robusti - continuare ad aggiungere test fino a quando non sei soddisfatto delle percentuali e degli input di successo; migliorare, estendere e rafforzare se necessario.

    
risposta data 24.09.2011 - 10:11
fonte
2

Faccio questa distinzione scrivendo codice con un comportamento ben definito, ma non necessariamente ottimale per passaggi di esecuzione molto improbabili. Ad esempio, quando sono abbastanza sicuro (provato, ma non testato) che una matrice sarà definita positiva, inserisco un'asserzione o un'eccezione nel programma per testare lo stato, ma non scrivo un percorso di codice per esso. In tal modo, il comportamento è definito, ma non ottimale.

    
risposta data 23.09.2011 - 14:15
fonte
2

Robustezza: il grado in cui un sistema continua a funzionare in presenza di input non validi o condizioni ambientali stressanti. (Codice completo 2, p464)

La domanda importante qui è di chiedersi quanto sia importante la robustezza per te. Se sei su Facebook, è molto importante che il tuo sito web continui a funzionare quando qualcuno inserisce caratteri speciali nell'input e che il tuo server rimane attivo quando 100 milioni di utenti sono connessi simultaneamente. Se stai scrivendo uno script per eseguire un'operazione comune che solo tu fai, non ti importa molto. In mezzo ci sono molti livelli. Stabilire un giudizio su quanta robustezza hai bisogno è una delle abilità importanti che uno sviluppatore dovrebbe imparare.

Il principio di YAGNI si applica all'aggiunta di funzionalità di cui un programma potrebbe aver bisogno. Ma quel principio non si applica alla robustezza. I programmatori tendono a sovrastimare la probabilità che sia necessaria una determinata estensione futura (soprattutto se è interessante) ma sottovalutano la probabilità che qualcosa vada storto. Inoltre, se risulta che una funzione omessa è necessaria in seguito, il programmatore può scriverla in un secondo momento. Se si scopre che è necessario un controllo di errore omesso, il danno potrebbe essere fatto.

Pertanto, è meglio in effetti sbagliare dal fare controlli per condizioni di errore insolite. Ma c'è un equilibrio. Alcune delle cose da considerare in questo equilibrio:

  • Quante volte potrebbe verificarsi questo errore?
  • Qual è il costo di questo errore?
  • È per uso interno o esterno?

Non dimenticare che le persone possono provare a utilizzare il tuo programma in modi imprevisti. È meglio se succede qualcosa di prevedibile quando lo fa.

Come ultima linea di difesa, usa l'assert o l'arresto. Se succede qualcosa che non riesci a capire come gestirlo, spegni il programma. Di solito è meglio che permettere al programma di andare avanti e fare qualcosa di imprevedibile.

    
risposta data 23.09.2011 - 22:10
fonte

Leggi altre domande sui tag