Perché esiste un supporto così limitato per Design by Contract nella maggior parte dei linguaggi di programmazione moderni?


40

Di recente ho scoperto Design by Contract (DbC) e lo trovo un modo estremamente interessante per scrivere codice. Tra l'altro, sembrerebbe offrire:

  • Migliore documentazione. Poiché il contratto è la documentazione, è impossibile che uno non sia aggiornato. Inoltre, poiché il contratto specifica esattamente cosa fa una routine, aiuta a supportare il riutilizzo.
  • Debug più semplice. Poiché l'esecuzione del programma si interrompe nel momento in cui un contratto fallisce, gli errori non possono propagarsi e la presunta violazione violata verrà presumibilmente evidenziata. Questo offre supporto durante lo sviluppo e durante la manutenzione.
  • Migliore analisi statica. DbC è fondamentalmente solo un'implementazione della logica Hoare e dovrebbero applicare gli stessi principi.

I costi, a confronto, sembrano essere piuttosto piccoli:

  • Digitazione extra delle dita. Dal momento che i contratti devono essere precisati.
  • Prende una certa quantità di formazione per mettersi a proprio agio con la scrittura di contratti.

Ora, avendo familiarità con Python principalmente, mi rendo conto che in realtà è possibile scrivere precondizioni (solo lanciando eccezioni per input inappropriati) ed è persino possibile usare asserzioni per testare nuovamente alcune postcondizioni. Ma non è possibile simulare alcune funzionalità come "vecchio" o "risultato" senza qualche magia aggiuntiva che alla fine sarebbe considerata non-pitonica. (Inoltre, ci sono alcune librerie che offrono supporto, ma alla fine ho l'atmosfera che sarebbe sbagliato usarle, come la maggior parte degli sviluppatori non.) Presumo che sia un problema simile per tutte le altre lingue (tranne ovviamente , Eiffel).

La mia intuizione mi dice che la mancanza di supporto deve essere il risultato di un qualche tipo di rifiuto della pratica, ma la ricerca online non è stata fruttuosa. Mi chiedo se qualcuno può chiarire perché la maggior parte delle lingue moderne sembrano offrire così poco supporto? DbC è difettoso o eccessivamente costoso? O è semplicemente obsoleto a causa della programmazione estrema e di altre metodologie?


Sembra un modo eccessivamente complicato per eseguire la programmazione guidata dai test, senza il vantaggio di testare anche il programma.
Dan

3
@Dan, non proprio, ci penso più come un'estensione del sistema di tipi. ad es. una funzione non accetta solo un argomento intero, ma richiede un numero intero che è contrattualmente obbligato ad essere maggiore di zero
Carson63000

4
I contratti con codice @Dan riducono in modo significativo il numero di test che devi eseguire.
Rei Miyasaka,

24
@Dan, preferirei dire che TDD è il contratto del povero, non viceversa.
Logica SK

In un linguaggio dinamico puoi "decorare" i tuoi oggetti con contratti basati su una bandiera opzionale. Ho un'implementazione di esempio che utilizza flag ambientali per facoltativamente scimmiettare gli oggetti esistenti con i contratti. Sì, il supporto non è nativo, ma è facile da aggiungere. Lo stesso vale per i cablaggi di prova, non sono nativi ma sono facili da aggiungere / scrivere.
Raynos,

Risposte:


9

Probabilmente sono supportati praticamente in ogni linguaggio di programmazione.

Ciò di cui hai bisogno sono le "asserzioni".

Questi sono facilmente codificati come istruzioni "if":

if (!assertion) then AssertionFailure();

Con questo, è possibile scrivere contratti posizionando tali asserzioni in cima al codice per i vincoli di input; quelli nei punti di ritorno sono vincoli di output. Puoi anche aggiungere invarianti in tutto il tuo codice (anche se in realtà non fanno parte del "progetto per contratto").

Quindi sostengo che non sono molto diffusi perché i programmatori sono troppo pigri per codificarli, non perché non puoi farlo.

Puoi renderli un po 'più efficienti nella maggior parte delle lingue definendo una costante booleana in fase di compilazione "controllando" e rivedendo un po' le istruzioni:

if (checking & !Assertion) then AssertionFailure();

Se non ti piace la sintassi, puoi ricorrere a varie tecniche di astrazione del linguaggio come le macro.

Alcuni linguaggi moderni ti danno una buona sintassi per questo, ed è quello che penso tu intenda per "supporto del linguaggio moderno". Questo è supporto, ma è piuttosto sottile.

Ciò che la maggior parte persino delle lingue moderne non ti dà sono affermazioni "temporali" (su stati arbitrari precedenti o seguenti [operatore temporale "eventualmente"], di cui hai bisogno se vuoi scrivere contratti davvero interessanti. Le dichiarazioni IF non ti aiuteranno tu qui.


Il problema che riscontro solo con l'accesso alle asserzioni è che non esiste un modo efficace per controllare le postcondizioni sui comandi poiché spesso è necessario confrontare lo stato postcondizione con lo stato precondizione (Eiffel chiama questo "vecchio" e lo passa automaticamente alla routine postcondizione .) In Python, questa funzionalità potrebbe essere banalmente ricreata usando i decoratori, ma diventa breve quando arriva il momento di disattivare le affermazioni.
Ceasar Bautista,

Quanto del precedente stato salva effettivamente Eiffel? Dal momento che ragionevolmente non è in grado di sapere a quale parte è possibile accedere / modificare senza risolvere il problema di arresto (analizzando la propria funzione), è necessario salvare lo stato completo della macchina o, in quanto euristico, solo una parte molto superficiale di esso. Sospetto quest'ultimo; e questi possono essere "simulati" con semplici assegnazioni scalari prima del presupposto. Sarei felice di sapere che Eiffel fa diversamente.
Ira Baxter,

7
... ho appena controllato come funziona Eiffel. "old <exp>" è il valore di <exp> all'ingresso della funzione, quindi sta eseguendo copie superficiali all'ingresso della funzione come mi aspettavo. Puoi farlo anche tu. Concordo sul fatto che il compilatore attui la sintassi per pre / post / old è più conveniente che fare tutto questo manualmente, ma il punto è che uno può farlo a mano e non è davvero difficile. Siamo tornati ai programmatori pigri.
Ira Baxter,

@IraBaxter No. Il codice diventa più semplice se è possibile separare il contratto dalla logica effettiva. Inoltre, se il compilatore è in grado di distinguere contratto e codice, può ridurre di molto la duplicazione . Ad esempio in D è possibile dichiarare un contratto su un'interfaccia o una superclasse e le asserzioni verranno applicate a tutte le classi di implementazione / estensione, indipendentemente dal codice nelle loro funzioni. Ad esempio con Python o Java, devi invocare l'intero supermetodo e possibilmente buttare via i risultati se vuoi che i contratti vengano controllati senza duplicazione. Questo aiuta davvero a implementare un codice pulito conforme a LSP.
Marstato,

@marstato: ho già concordato che il supporto nella lingua è una buona cosa.
Ira Baxter,

15

Come dici tu, Design by Contract è una caratteristica di Eiffel, che è stato a lungo uno di quei linguaggi di programmazione che è molto rispettato nella comunità ma che non ha mai preso piede.

DbC non è in nessuno dei linguaggi più popolari perché è solo relativamente recentemente che la comunità di programmazione tradizionale ha accettato che l'aggiunta di vincoli / aspettative al loro codice è una cosa "ragionevole" da aspettarsi dai programmatori. È comune ora per i programmatori capire quanto sia prezioso il test unitario, e ciò ha significato che i programmatori accettino maggiormente di inserire il codice per convalidare i loro argomenti e vedere i vantaggi. Ma un decennio fa, probabilmente la maggior parte dei programmatori avrebbe detto "questo è solo un lavoro extra per cose che sai che andrà sempre bene".

Penso che se dovessi andare dallo sviluppatore medio oggi e parlare di post-condizioni, annuirebbero con entusiasmo e direbbero "OK, è come un test unitario". E se parli di pre-condizioni, direbbero "OK, è come la validazione dei parametri, cosa che non facciamo sempre, ma, sai, immagino sia ok ..." E poi se parli di invarianti , avrebbero iniziato a dire "Accidenti, quanto costa questo? Quanti altri bug cattureremo?" eccetera.

Quindi penso che ci sia ancora molta strada da fare prima che il DbC sia adottato in modo molto ampio.


OTOH, i programmatori tradizionali erano abituati a scrivere asserzioni da un po 'di tempo. La mancanza di un preprocessore utilizzabile nei linguaggi mainstream più moderni ha reso inefficace questa bella pratica, ma è ancora comune per C e C ++. Sta tornando ora con i Contratti di codice Microsoft (basato su AFAIK, una riscrittura bytecode per le build di rilascio).
SK-logic,

8

La mia intuizione mi dice che la mancanza di supporto deve essere il risultato di un qualche tipo di rifiuto della pratica, ...

Falso.

È una pratica di progettazione . Può essere incorporato esplicitamente nel codice (stile Eiffel) o implicitamente nel codice (la maggior parte delle lingue) o nei test unitari. La pratica del design esiste e funziona bene. Il supporto linguistico è su tutta la mappa. È, tuttavia, presente in molte lingue nel framework di unit test.

Mi chiedo se qualcuno può chiarire perché la maggior parte delle lingue moderne sembrano offrire così poco supporto? DbC è difettoso o eccessivamente costoso?

È costoso. E. Ancora più importante, ci sono alcune cose che non possono essere dimostrate in una determinata lingua. La terminazione del loop, ad esempio, non può essere dimostrata in un linguaggio di programmazione, richiede una capacità di prova "di ordine superiore". Quindi alcuni tipi di contratti sono tecnicamente inesprimibili.

O è semplicemente obsoleto a causa della programmazione estrema e di altre metodologie?

No.

Utilizziamo principalmente test unitari per dimostrare che DbC è soddisfatto.

Per Python, come hai notato, il DbC va in diversi punti.

  1. I risultati del test docstring e docstring.

  2. Asserzioni per validare input e output.

  3. Test unitari.

Ulteriore.

È possibile adottare strumenti letterali di tipo programmazione in modo da scrivere un documento che includa le informazioni DbC e che generi script Python plus unit test. L'approccio letterale alla programmazione ti consente di scrivere un bel pezzo di letteratura che include i contratti e la fonte completa.


Puoi provare casi banali di terminazione di loop, come l'iterazione su una sequenza finita fissa. Si tratta di un ciclo generalizzato che non può essere mostrato per terminare banalmente (dal momento che potrebbe essere alla ricerca di soluzioni a congetture matematiche "interessanti"); questa è l'essenza del problema di Halting.
Donal Fellows,

+1. Penso che tu sia l'unico che ha coperto il punto più critico - there are some things which cannot be proven. La verifica formale può essere ottima, ma non tutto è verificabile! Quindi questa funzione limita effettivamente ciò che il linguaggio di programmazione può effettivamente fare!
Dipan Mehta,

@DonalFellows: poiché il caso generale non può essere provato, è difficile incorporare un gruppo di funzionalità che sono (a) costose e (b) note per essere incomplete. Il mio punto in questa risposta è che è più facile evitare tutte quelle caratteristiche ed evitare di impostare false aspettative delle prove formali di correttezza in generale, quando ci sono limitazioni. Come esercizio di progettazione (al di fuori della lingua) possono (e dovrebbero) essere utilizzate molte tecniche di prova.
S.Lott

Non è affatto costoso in un linguaggio come C ++ in cui il controllo del contratto viene compilato nella build di rilascio. E l'uso di DBC tende a rilasciare un codice di build più leggero, poiché si eseguono meno controlli di runtime per il programma in uno stato legale. Ho perso il conto del numero di basi di codice che ho visto in cui numerose funzioni verificano lo stato illegale e restituiscono false, quando non dovrebbe MAI trovarsi in quello stato in una build di rilascio correttamente testata.
Kaitain,

6

Tiravo a indovinare. Forse parte del motivo per cui non è così popolare è perché "Design by Contract" è un marchio registrato da Eiffel.


3

Un'ipotesi è che per un programma sufficientemente ampio e complesso, specialmente quelli con un obiettivo mobile, la massa dei contratti stessi può diventare buggy e difficile da eseguire il debug, o più, del solo codice del programma. Come con qualsiasi modello, potrebbe esserci un utilizzo oltre i rendimenti decrescenti, nonché chiari vantaggi se usato in modo più mirato.

Un'altra possibile conclusione è che la popolarità dei "linguaggi gestiti" è l'attuale prova del supporto progettuale per contratto per le funzionalità gestite selezionate (limiti di array per contratto, ecc.)


> la massa dei contratti stessi può diventare buggy e difficile da eseguire il debug, o anche di più, rispetto al solo codice del programma che non ho mai visto.
Kaitain,

2

La ragione per cui la maggior parte delle lingue tradizionali non ha funzionalità DbC nella lingua è il rapporto costi / benefici dell'implementazione è troppo alto per l'implementatore della lingua.

un lato di questo è già stato esaminato nelle altre risposte, unit test e altri meccanismi di runtime (o anche alcuni meccanismi di compilazione con meta-programmazione template) possono già darti gran parte della bontà di DbC. Pertanto, mentre c'è un vantaggio, è probabilmente visto come abbastanza modesto.

L'altro lato è il costo, il retrodb DbC in una lingua esistente è probabilmente un cambiamento troppo grande e molto complesso da avviare. Introdurre nuova sintassi in una lingua senza rompere il vecchio codice è difficile. Aggiornare la tua libreria standard esistente per utilizzare un cambiamento così vasto sarebbe costoso. Pertanto, possiamo concludere che l'implementazione delle funzionalità DbC in una lingua esistente ha un costo elevato.

Vorrei anche notare che i concetti che sono praticamente contratti per modelli, e quindi in qualche modo correlati a DbC, sono stati abbandonati dall'ultimo standard C ++ poiché anche dopo anni di lavoro su di essi è stato stimato che necessitavano ancora di anni di lavoro. Questo tipo di grandi, ampie, ampie modifiche alle lingue sono semplicemente troppo difficili da implementare.


2

DbC verrebbe utilizzato in modo più ampio se i contratti potessero essere controllati in fase di compilazione in modo che non sarebbe possibile eseguire un programma che violasse qualsiasi contratto.

Senza il supporto del compilatore, "DbC" è solo un altro nome per "controlla invarianti / assunzioni e genera un'eccezione se violato".


Non è questo il problema dell'arresto?
Ceasar Bautista,

@Ceasar Dipende. Alcuni presupposti possono essere verificati, altri no. Ad esempio, esistono sistemi di tipi che consentono di evitare di passare un elenco vuoto come argomento o di restituirne uno.
Ingo,

Bel punto (+1) anche se Bertrand Meyer nella sua parte di "Masterminds of Programming" ha menzionato anche il loro sistema di creazione di classi casuali e la richiesta di verificare la violazione dei contratti. Quindi è un approccio misto tempo di compilazione / tempo di esecuzione, ma dubito che questa tecnica
funzioni

Questo è vero in una certa misura, anche se dovrebbe essere un fallimento catastrofico piuttosto che un'eccezione (vedi sotto). Il vantaggio chiave di DBC è la metodologia, che in realtà porta a programmi meglio progettati, e la certezza che ogni dato metodo DEVE essere in uno stato legale al momento dell'entrata, il che semplifica gran parte della logica interna. Generalmente non dovresti essere coinvolto nel generare eccezioni in caso di violazione di un contratto. Si dovrebbero usare eccezioni dove il programma può LEGALMENTE essere nello stato ~ X, e il codice client deve gestirlo. Un contratto dirà che ~ X è semplicemente illegale.
Kaitain,

1

Ho una semplice spiegazione, la maggior parte delle persone (inclusi i programmatori) non vogliono lavoro extra a meno che non lo ritengano necessario. Programmazione avionica in cui la sicurezza è considerata molto importante Non ho visto la maggior parte dei progetti senza di essa.

Ma se stai prendendo in considerazione la programmazione di siti Web, desktop o dispositivi mobili, gli arresti anomali e i comportamenti imprevisti a volte non sono considerati così negativi e i programmatori eviteranno semplicemente il lavoro extra quando segnalano i bug e in seguito risolverli è considerato abbastanza adeguato.

Questo è probabilmente il motivo per cui penso che Ada non sia mai stata presa al di fuori del settore della programmazione aeronautica perché richiede più lavoro di codifica sebbene Ada sia un linguaggio fantastico e se vuoi costruire un sistema affidabile, è la lingua migliore per il lavoro (escluso SPARK quale proprietario lingua basata su Ada).

Progettate da librerie di contratti per C # sono state sperimentali da Microsoft e sono molto utili per la creazione di software affidabile, ma non hanno mai preso slancio sul mercato, altrimenti ormai le avresti viste come parte del linguaggio C # di base.

Le asserzioni non sono le stesse del supporto completamente funzionale per le condizioni pre / post e invarianti. Anche se può provare a emularli, ma un linguaggio / compilatore con un supporto adeguato esegue l'analisi "albero di sintassi astratto" e verifica la presenza di errori logici che semplicemente le asserzioni non possono fare.

Modifica: ho fatto una ricerca e la seguente è una discussione correlata che potrebbe essere utile: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala


-2

Principalmente i motivi sono i seguenti:

  1. È disponibile solo in lingue che non sono popolari
  2. Non è necessario poiché le stesse cose possono essere fatte in modo diverso nei linguaggi di programmazione esistenti da chiunque voglia effettivamente farlo
  3. È difficile da capire e usare - richiede conoscenze specializzate per farlo correttamente, quindi ci sono poche persone che lo fanno
  4. richiede grandi quantità di codice per farlo - i programmatori preferiscono ridurre al minimo la quantità di caratteri che scrivono - se richiede un lungo pezzo di codice, qualcosa deve essere sbagliato
  5. i vantaggi non ci sono: non riesce a trovare abbastanza bug per renderlo utile

1
La tua risposta non è ben argomentata. Stai semplicemente affermando che non ci sono vantaggi, che richiede grandi quantità di codice e che non è necessario poiché può essere fatto con le lingue esistenti (l'OP ha affrontato in modo specifico questo problema!).
Andres F.

Non sto pensando a affermazioni, ecc. In sostituzione di esso. Questo non è il modo corretto di farlo nelle lingue esistenti (ovvero non è stato ancora affrontato)
tp1

@ tp1, se i programmatori volessero davvero minimizzare la digitazione, non cadrebbero mai in qualcosa di così dettagliato ed eloquente come Java. E sì, la programmazione stessa richiede "conoscenze specialistiche" "per farlo correttamente". Coloro che non sono in possesso di tale conoscenza non dovrebbero essere autorizzati a codificare.
Logica SK

@ Sk-logic: Beh, sembra che metà del mondo stia sbagliando OO semplicemente perché non vogliono scrivere funzioni di inoltro dalle funzioni dei membri alle funzioni dei membri dei dati. È un grosso problema nella mia esperienza. Ciò è causato direttamente riducendo al minimo il numero di caratteri da scrivere.
tp1

@ tp1, se le persone volessero davvero minimizzare la digitazione, non toccherebbero nemmeno l'OO. OOP è naturalmente eloquente, anche nelle sue migliori implementazioni, come Smalltalk. Non direi che è una cattiva proprietà, a volte l'eloquenza aiuta.
Logica SK
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.