Scrivere codice robusto vs. overengineering


33

Come fate a sapere che state scrivendo il codice più robusto possibile senza ingegnerizzare eccessivamente?

Mi ritrovo a pensare troppo a ogni possibile percorso che il mio codice può prendere, e a volte 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 accadranno mai.


2
Codificalo in Agda2
SK-logic,

un esempio concreto aiuterebbe molto a chiarire il tuo punto. :)
João Portela,

Posso solo verificare che tu stia davvero chiedendo della robustezza, ovvero "la capacità di un sistema di continuare a funzionare in presenza di input non validi o condizioni ambientali stressanti", perché alcune risposte sembrano pensare che stai parlando di estensibilità.
DJClayworth,

Lavoro con scadenze folli e inoltre è per demo-ware, quindi posso felicemente scappare via velocemente senza paralisi della perfezione.
Giobbe

1
Ecco un articolo che parla dell'argomento: code-tag.com/2017/04/02/…
San

Risposte:


39

Come fate a sapere che state scrivendo il codice più robusto possibile senza ingegnerizzare eccessivamente?

Cosa consideri un codice affidabile? Codice che è già a prova di futuro e così potente da poter gestire qualsiasi situazione? Sbagliato, nessuno può prevedere il futuro! E di nuovo sbagliato, perché sarà un casino complicato, non mantenibile.

Seguo vari principi: innanzitutto YAGNI (ancora) e KISS , quindi non scrivo codice non necessario. Ciò impedisce efficacemente anche l'ingegnerizzazione eccessiva. Rifattorizzo l'applicazione quando sono necessarie estensioni. I moderni strumenti di refactoring ti consentono di creare facilmente interfacce e scambiare implementazioni quando ne hai bisogno.

Quindi provo a rendere il codice che scrivo il più robusto possibile, includendo l'eliminazione di quanti più percorsi il programma può (e anche gli stati) il più possibile e un po 'di programmazione Spartan . Un grande aiuto sono le funzioni / i metodi "atomici" che non si basano su stati esterni o almeno non lasciano il programma in uno stato incoerente quando falliscono. Se lo fai bene, è anche molto improbabile che finirai mai 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 esplosioni combinatorie di percorsi o stati del programma, pensando profondamente a come poterlo progettare come il percorso più semplice possibile. Cerca di mantenere al minimo le possibili combinazioni scegliendo il miglior ordinamento delle tue subroutine e progettandole a tale scopo.

Il codice robusto è quasi sempre un codice semplice e pulito, ma la semplicità è un tratto che non è sempre facilmente raggiungibile. Tuttavia, 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.


2
Perché non ha tonnellate di classi, fabbriche e astrazioni. È un paradosso, ma ad alcune persone piace quella roba. Non ho idea del perché.
Coder

5
Questa è Sparta!!!
Tom Squires,

4
Le persone che non lo fanno da vent'anni non capiscono come la complessità possa ucciderti. Pensano di essere così intelligenti. Sono stupidi, non intelligenti. Quella complessità ti ucciderà morta.
PeterAllenWebb,

1
La robustezza non riguarda l'impermeabilità al futuro - si tratta di continuare a funzionare con input non validi o ambienti stressanti - Codice completo p464.
DJClayworth,

5
A meno che l'interrogante non stia usando "robusto" in un senso diverso da quello che ho capito, stai rispondendo a una domanda diversa. Non sta chiedendo "dovrei programmare per consentire requisiti futuri", sta chiedendo "quali casi di input insoliti dovrei gestire". Nessuno di YAGNI, KISS e SOLID sono rilevanti. Devi consentire a un milione di utenti di provare ad accedere contemporaneamente? Cosa succederà se un nome di accesso inizia con una barra rovesciata? Nessuna di queste domande risponde a YAGNI.
DJClayworth,

8

Cerco di mantenere un equilibrio, concentrandomi su

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

È una zona 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 i fallimenti non sono grandi, sto bene. Ad ogni modo, mi sforzo di imparare dai miei errori.


Un esempio sarebbe ottimo qui che gestisce tutti i possibili percorsi di esecuzione nei casi d'uso esistenti
CodeYogi,

5

La differenza tra robusto e ingegneristico è la differenza tra gestire con grazia tutti i possibili casi d'uso, anche i casi bizzarri e marginali che NON DOVREBBE accadere. Quando dico con garbo intendo, l'utente inserisce un bizzarro caso di eccezione o si imbatte in una situazione che richiede una funzione non supportata o non specificata che non è stata definita e il codice esce con grazia senza crash o informa l'utente di funzionalità non supportate.

D'altra parte, l'ingegnerizzazione eccessiva potrebbe cadere nel regno della completa implementazione di funzionalità che non erano necessarie o richieste (alcune funzionalità di cui il cliente ha BISOGNO, ma che non sono mai state richieste!) OPPURE può essere definito derivando un design eccessivamente complesso o eccessivamente complesso codice per gestire un problema relativamente semplice.


4

1) Ottieni requisiti.

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

3) Invia a test.

4) Se i tester dicono che va bene, documentare l'approvazione. Se qualcosa è spento, torna a 1.

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


1
+1 per Focus sul superamento dei test, non sulla previsione dei test, tuttavia ci si aspetta che molti sviluppatori come me facciano entrambi in mancanza di forti analisti aziendali.
maple_shaft

@maple_shaft - Molto vero. Il punto è che questi problemi sorgono a causa dell'incompetenza di qualcun altro. Sottolineare sul lavoro di qualcun altro è il percorso verso il burnout. Se la mia compagnia fosse abbastanza stupida da farmi fare i crediti per il mese, non sarei troppo deluso di me stesso se non andasse bene. La definizione dei requisiti di solito comporta solo la descrizione di ciò che fai ogni giorno, in modo che possa essere automatizzato. Se nessuno dei dipendenti può farcela, beh ... la compagnia potrebbe essere nei guai.
Morgan Herlocker,

3

Innanzitutto, mantieni i dati normalizzati (non ridondanti) il più possibile. Se i dati sono completamente normalizzati, nessun singolo aggiornamento può renderli incoerenti.

Non è sempre possibile mantenere i dati normalizzati, in altre parole potrebbe non essere possibile eliminare la ridondanza, nel qual caso possono 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 corregge.

Vi è una forte tendenza a cercare di gestire strettamente la ridondanza mediante notifiche. Questi non sono solo difficili da accertare che siano corretti, ma possono portare a enormi inefficienze. (Parte della tentazione di scrivere notifiche nasce 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 codice difensivo. Eventi e messaggi sono caratteristici dei dati con ridondanza, perché comunicano le modifiche da una parte all'altra, cercando di prevenire incoerenze.

Come ho detto, se devi avere la ridondanza (e le probabilità sono abbastanza buone che 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 robusto.


3
  • scrivere per il riutilizzo.
  • scrivere test. banali, non banali, alcuni assurdamente complessi per vedere come gestisce in tali condizioni. i test ti aiuteranno anche a determinare la forma dell'interfaccia.
  • scrivere il programma per fallire duramente (es. asserzione). il mio codice ha un sacco di riutilizzo e collaudo per un sacco di casi - c'è più controllo / gestione degli errori rispetto all'implementazione effettiva (basata sul conteggio delle righe).
  • riutilizzare.
  • risolvere immediatamente le cose che vanno male.
  • impara e costruisci dall'esperienza.

errori saranno venire lungo la strada, ma saranno (per fortuna) essere localizzati e saranno (in molti casi) presentarsi molto presto in fase di test. l'altro vantaggio del riutilizzo è che il cliente / chiamante può salvare la maggior parte del controllo / impalcatura degli errori utilizzando ciò che viene portato dall'impianto.

i test devono quindi definire le capacità del programma e la loro robustezza: continuare ad aggiungere test fino a quando non si è soddisfatti delle percentuali di successo e degli input; migliorare, estendere e fortificare secondo necessità.


2

Faccio questa distinzione scrivendo un 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 proprio percorso di codice per esso. Pertanto, il comportamento è definito, ma non ottimale.


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 è porsi quanto sia importante la robustezza per te. Se sei Facebook, è davvero importante che il tuo sito Web continui a funzionare quando qualcuno inserisce caratteri speciali nell'input e che il tuo server rimanga attivo quando 100 milioni di utenti sono connessi contemporaneamente. Se stai scrivendo uno script per eseguire un'operazione comune che solo tu fai, non ti interessa molto. Nel mezzo ci sono molti livelli. Valutare quanta robustezza è necessaria è 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 questo principio non si applica alla solidità. I programmatori tendono a sopravvalutare la probabilità che sia necessaria una data estensione futura (soprattutto se è una figata) ma sottovalutano la probabilità che qualcosa vada storto. Inoltre, se si scopre che dopo è necessaria una funzione omessa, il programmatore può scriverla in seguito. Se si scopre che dopo tutto è necessario un controllo degli errori omesso, il danno potrebbe essere fatto.

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

  • Con quale frequenza potrebbe verificarsi questo errore?
  • Qual è il costo del verificarsi di questo errore?
  • È per uso interno o esterno?

Non dimenticare che le persone possono - e cercheranno - di provare a utilizzare il tuo programma in modi inaspettati. È meglio se succede qualcosa di prevedibile quando lo fanno.

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

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.