Quanto è necessario seguire le pratiche di programmazione difensiva per il codice che non sarà mai reso pubblico?


45

Sto scrivendo un'implementazione Java di un gioco di carte, quindi ho creato un tipo speciale di Collection che sto chiamando Zone. Tutti i metodi di modifica di Java's Collection non sono supportati, ma esiste un metodo nell'API Zone move(Zone, Card), che sposta una scheda da una determinata zona a se stessa (realizzata mediante tecniche pacchetto-private). In questo modo, posso garantire che nessuna carta sia estratta da una zona e svanisca semplicemente; possono essere spostati solo in un'altra zona.

La mia domanda è: quanto è necessario questo tipo di codifica difensiva? È "corretto" e sembra la pratica giusta, ma non è come se l'API di zona farà mai parte di alcune librerie pubbliche. È solo per me, quindi è un po 'come se stessi proteggendo il mio codice da me stesso quando probabilmente avrei potuto essere più efficiente usando solo le raccolte standard.

Quanto lontano dovrei portare questa idea di zona? Qualcuno può darmi qualche consiglio su quanto dovrei pensare di preservare i contratti nelle classi che scrivo, specialmente per quelli che non saranno realmente disponibili al pubblico?


4
= ~ s / necessario / raccomandato / gi
GrandmasterB

2
I tipi di dati dovrebbero essere corretti per costruzione, oppure su cosa stai costruendo? Dovrebbero essere incapsulati in modo tale che, mutabili o meno, possano essere sempre e solo in stati validi. Solo se è impossibile imporre questo staticamente (o irragionevolmente difficile) si dovrebbe generare un errore di runtime.
Jon Purdy,

1
Mai dire mai. A meno che il tuo codice non sia mai usato, non puoi mai sapere con certezza dove finirà il tuo codice. ;)
Izkata,

1
Il commento di @codebreaker GrandmasterB è un'espressione sostitutiva. Significa: sostituire "necessario" con "consigliato".
Ricardo Souza l'

1
Il Codice senza codice n. 116 Trust Nessuno è probabilmente particolarmente appropriato qui.

Risposte:


72

Non affronterò il problema di progettazione, ma solo la questione se fare le cose "correttamente" in un'API non pubblica.

è solo per me, quindi è un po 'come se stessi proteggendo il mio codice da me stesso

Questo è esattamente il punto. Forse ci sono programmatori là fuori che ricordano le sfumature di ogni classe e metodo che abbiano mai scritto e che non chiamano mai erroneamente in loro con il contratto sbagliato. Non sono uno di loro. Spesso dimentico come dovrebbe funzionare il codice che ho scritto poche ore dopo averlo scritto. Dopo aver pensato di aver capito bene una volta, la tua mente tenderà a cambiare marcia sul problema a cui stai lavorando ora .

Hai strumenti per combatterlo. Questi strumenti includono convenzioni (in nessun ordine particolare), unit test e altri test automatizzati, controllo delle condizioni preliminari e documentazione. Io stesso ho riscontrato che i test unitari sono preziosi perché entrambi ti costringono a pensare a come verrà utilizzato il tuo contratto e forniranno la documentazione in seguito su come è stata progettata l'interfaccia.


Buono a sapersi. In passato ero solito programmare nel modo più efficiente possibile, quindi a volte faccio fatica ad abituarmi a idee come questa. Sono contento di andare nella giusta direzione.
codebreaker

15
"Efficientemente" può significare molte cose diverse! Nella mia esperienza, i novizi (non che io stia dicendo che lo sei) spesso trascurano quanto efficientemente saranno in grado di supportare il programma. Il codice di solito passa molto più a lungo nella fase di supporto del suo ciclo di vita del prodotto rispetto alla fase di "scrittura di un nuovo codice", quindi penso che sia un'efficienza che dovrebbe essere considerata attentamente.
Charlie Kilian,

2
Sono assolutamente d'accordo. Al college non ci ho mai pensato, però.
codebreaker

25

Di solito seguo alcune semplici regole:

  • Prova a programmare sempre per contratto .
  • Se un metodo è pubblicamente disponibile o riceve input dal mondo esterno , applicare alcune misure difensive (ad es IllegalArgumentException.).
  • Per tutto il resto accessibile solo internamente, utilizzare le asserzioni (ad es assert input != null.).

Se un client è davvero interessato, troveranno sempre un modo per comportarsi male nel codice. Possono sempre farlo attraverso la riflessione, almeno. Ma questa è la bellezza del design per contratto . Non approvi tale uso del tuo codice e quindi non puoi garantire che funzionerà in tali scenari.

Per quanto riguarda il tuo caso specifico, se Zonenon si suppone che venga utilizzato e / o vi si acceda da estranei, rendere la classe pacchetto-privata (e possibilmente final), o preferibilmente, utilizzare le raccolte già fornite da Java. Sono testati e non è necessario reinventare la ruota. Si noti che ciò non impedisce di utilizzare asserzioni in tutto il codice per assicurarsi che tutto funzioni come previsto.


1
+1 per menzionare Design by Contract. Se non riesci a vietare completamente il comportamento (ed è difficile da fare) almeno chiarisci che non ci sono garanzie su comportamenti scorretti. Mi piace anche lanciare un IllegalStateException o UnsupportedOperationException.
user949300

@ user949300 Certo. Mi piace credere che tali eccezioni siano state introdotte con uno scopo significativo. I contratti d'onore sembrano adattarsi a tale ruolo.
afsantos,

16

La programmazione difensiva è un'ottima cosa.
Fino a quando non inizia a intralciare la scrittura del codice. Quindi non è una cosa così buona.


Parlando un po 'più pragmaticamente ...

Sembra che tu sia sul punto di portare le cose troppo lontano. La sfida (e la risposta alla tua domanda) sta nel comprendere quali sono le regole o i requisiti aziendali del programma.

Usando l'API del tuo gioco di carte come esempio, ci sono alcuni ambienti in cui tutto ciò che può essere fatto per evitare imbrogli è fondamentale. Possono essere coinvolte grandi quantità di denaro reale, quindi ha senso mettere in atto un gran numero di controlli per assicurarsi che non si possano verificare imbrogli.

D'altra parte, è necessario rimanere consapevoli dei principi SOLIDI, in particolare della singola responsabilità. Chiedere alla classe container di controllare efficacemente dove stanno andando le carte può essere un po 'troppo. Potrebbe essere meglio disporre di un livello di controllo / controllo tra il contenitore della carta e la funzione che riceve le richieste di spostamento.


In relazione a tali preoccupazioni, è necessario comprendere quali componenti dell'API sono esposti pubblicamente (e quindi vulnerabili) rispetto a ciò che è privato e meno esposto. Non sono un sostenitore totale di un "rivestimento esterno duro con un interno morbido", ma il miglior ritorno dei tuoi sforzi è quello di indurire l'esterno della tua API.

Non penso che l'utente finale previsto di una biblioteca sia critico nei confronti di una determinazione su quanta programmazione difensiva abbia messo in atto. Anche con i moduli che scrivo per mio uso, ho comunque messo in atto una misura di controllo per assicurarmi che in futuro non avessi commesso un errore involontario nel chiamare la biblioteca.


2
+1 per "Fino a quando non inizia a intralciare la scrittura del codice." Soprattutto per progetti personali di breve durata, la codifica difensiva può richiedere molto più tempo di quanto valga la pena.
Corey,

2
D'accordo, anche se vorrei aggiungere che è una buona cosa essere / in grado / programmare in modo difensivo, ma è anche fondamentale poter programmare in modo prototipico. La possibilità di fare entrambe le cose ti consentirà di scegliere l'azione più appropriata che è di gran lunga migliore rispetto a molti programmatori che conosco che sono in grado di programmare (in qualche modo) in modo difensivo.
David Mulder,

13

La codifica difensiva non è solo una buona idea per il codice pubblico. È un'ottima idea per qualsiasi codice che non venga immediatamente gettato via. Certo, sai come dovrebbe essere chiamato ora , ma non hai idea di quanto bene ti ricorderai tra sei mesi da quando tornerai al progetto.

La sintassi di base di Java offre molta difesa integrata rispetto a un linguaggio di livello inferiore o interpretato come C o Javascript rispettivamente. Supponendo che tu chiami i tuoi metodi in modo chiaro e non abbia un "metodo di sequenziamento" esterno, probabilmente puoi scappare semplicemente specificando gli argomenti come un tipo di dati corretto e includendo un comportamento sensibile se i dati correttamente digitati possono ancora essere non validi.

(A parte questo, se le carte devono sempre trovarsi in una zona, penso che tu possa ottenere un miglior rapporto qualità-prezzo facendo in modo che tutte le carte in gioco vengano referenziate da una raccolta globale al tuo oggetto di gioco e che Zone sia una proprietà di ogni carta. Ma dal momento che non so cosa fanno le tue Zone oltre a tenere le carte, è difficile sapere se sia appropriato.)


1
Ho considerato la zona come una proprietà della carta, ma poiché le mie carte funzionano meglio come oggetti immutabili, ho deciso che questa era la cosa migliore. Grazie per il consiglio.
codebreaker

3
@codebreaker una cosa che può aiutare in quel caso è l'incapsulamento della carta in un altro oggetto. Un asso di picche è quello che è. La posizione non definisce la sua identità e una carta probabilmente dovrebbe essere immutabile. Forse una zona contiene carte: forse una CardDescriptorche contiene una carta, la sua posizione, lo stato scoperto / addirittura la rotazione per i giochi a cui importa. Quelle sono tutte proprietà mutabili che non alterano l'identità di una carta.

1

Per prima cosa crea una classe che conserva un elenco di Zone in modo da non perdere una Zona o le carte in essa contenute. È quindi possibile verificare che un trasferimento si trovi all'interno del proprio ZoneList. Questa classe sarà probabilmente una sorta di singleton, poiché avrai bisogno solo di un'istanza, ma potresti desiderare gruppi di Zone in seguito, quindi tieni aperte le tue opzioni.

In secondo luogo, non avere Zone o ZoneList implementare Collection o qualsiasi altra cosa a meno che non ti aspetti di averne bisogno. Cioè, se una Zone o ZoneList verrà passata a qualcosa che si aspetta una Collezione, quindi implementarla. Puoi disabilitare un sacco di metodi facendo in modo che generino un'eccezione (UnimplementedException o qualcosa del genere) o semplicemente facendo in modo che non facciano nulla. (Pensa intensamente prima di utilizzare la seconda opzione. Se lo fai perché è facile scoprirai che ti mancano i bug che potresti aver scoperto all'inizio.)

Ci sono domande reali su cosa sia "corretto". Ma una volta capito di cosa si tratta, vorrai fare le cose in quel modo. In due anni ti sarai dimenticato di tutto questo, e se provi a usare il codice, ti arrabbi molto con il ragazzo che lo ha scritto in modo così controintuitivo e non ha spiegato nulla.


2
La tua risposta si concentra un po 'troppo sul problema attuale invece che sulle domande più ampie poste dall'OP in merito alla programmazione difensiva in generale.

In realtà passo le Zone ai metodi che accettano le Collezioni, quindi l'implementazione è necessaria. Una specie di registro delle zone all'interno del gioco è comunque un'idea interessante.
codebreaker

@ GlenH7: trovo che lavorare con esempi specifici spesso aiuti più della teoria astratta. L'OP ne ha fornito uno piuttosto interessante, quindi ci sono andato.
RalphChapin,

1

La codifica difensiva nella progettazione dell'API generalmente riguarda la convalida dell'input e la selezione accurata di un meccanismo di gestione degli errori adeguato. Vale la pena notare anche le cose menzionate da altre risposte.

In realtà non è questo il tuo esempio. Stai limitando la tua superficie API, per un motivo molto specifico. Come menziona GlenH7 , quando il set di carte deve essere usato in un gioco reale, con un mazzo ('usato' e 'non usato'), un tavolo e mani per esempio, vuoi sicuramente mettere in atto dei controlli per assicurarti che ogni la carta del set è presente una volta e una sola volta.

Che tu l'abbia progettato con "zone", è una scelta arbitraria. A seconda dell'implementazione (una zona può essere solo una mano, un mazzo o un tavolo nell'esempio sopra) potrebbe benissimo essere un progetto accurato.

Tuttavia, tale implementazione suona come un tipo derivato di un Collection<Card>set di carte più simile, con un'API meno restrittiva. Ad esempio, quando vuoi costruire un calcolatore del valore della mano, o un'intelligenza artificiale, sicuramente vuoi essere libero di scegliere quali e quante carte di ogni iterare.

Quindi è bene esporre un'API così restrittiva, se l'unico obiettivo di quell'API è assicurarsi che ogni carta sia sempre in una zona.

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.