"Facile ragionare" - cosa significa? [chiuso]

48

Ho sentito molte volte quando altri sviluppatori usano quella frase per "pubblicizzare" alcuni modelli o sviluppare le migliori pratiche. La maggior parte delle volte questa frase viene utilizzata quando si parla di vantaggi della programmazione funzionale.

La frase "Facile da ragionare" è stata usata così com'è, senza alcuna spiegazione o esempio di codice. Quindi per me diventa come la prossima "parola d'ordine", che gli sviluppatori più "esperti" usano nei loro discorsi.

Domanda: puoi fornire alcuni esempi di "Non facile ragionare", quindi può essere confrontato con gli esempi "Facile da ragionare"?

    
posta Fabio 20.06.2017 - 11:06
fonte

11 risposte

58

A mio parere, la frase "facile ragionare", si riferisce a un codice che è facile da "eseguire nella tua testa".

Quando guardi un pezzo di codice, se è breve, scritto in modo chiaro, con buoni nomi e una minima mutazione di valori, allora lavorare mentalmente su ciò che fa il codice è un compito (relativamente) facile.

Un lungo pezzo di codice con nomi scadenti, variabili che cambiano costantemente valore e ramificazione contorta normalmente richiedono ad esempio una penna e un pezzo di carta per tenere traccia dello stato corrente. Tale codice quindi non può essere facilmente elaborato solo nella tua testa, quindi tale codice non è facile da ragionare.

    
risposta data 20.06.2017 - 11:13
fonte
47

Un meccanismo o una parte di codice è facile da ragionare su quando è necessario prendere in considerazione alcune cose per prevedere cosa farà e le cose che devi tenere in considerazione sono facilmente disponibili.

Le vere funzioni senza effetti collaterali e senza stato sono facili da ragionare perché l'output è completamente determinato dall'input, che è proprio lì nei parametri.

Al contrario, un oggetto con stato è molto più difficile da ragionare, perché devi prendere in considerazione in quale stato si trova l'oggetto quando viene chiamato un metodo, il che significa che devi pensare a quali altre situazioni potrebbero portare all'oggetto essere in uno stato particolare.

Ancora peggio sono le variabili globali: per ragionare sul codice che legge una variabile globale, è necessario capire in quale punto del codice è possibile impostare la variabile e perché - e potrebbe anche non essere facile trovare tutti quei posti.

La cosa più difficile da ragionare è la programmazione multithread con lo stato condiviso, perché non solo hai uno stato, hai più thread che lo modificano contemporaneamente, quindi ragiona su cosa fa un pezzo di codice quando viene eseguito da un thread devi consentire la possibilità che in ogni singolo punto di esecuzione, qualche altro thread (o alcuni di essi!) possa eseguire praticamente qualsiasi altra parte del codice e modificare i dati su cui stai lavorando proprio sotto il tuo occhi. In teoria, ciò può essere gestito con mutex / monitor / sezioni critiche / qualsiasi cosa tu chiami, ma in pratica nessun umano è in grado di farlo in modo affidabile a meno che non limita drasticamente lo stato condiviso e / o il parallelismo a molto piccolo sezioni del codice.

    
risposta data 20.06.2017 - 14:41
fonte
9

Nel caso della programmazione funzionale, il significato di "Facile ragionare" è soprattutto che è deterministico. Con ciò intendevo dire che un dato input porterà sempre allo stesso risultato. Puoi fare tutto ciò che vuoi per il programma, finché non tocchi quella parte di codice, non si romperà.

D'altro canto, OO è in genere più difficile da ragionare perché l'output prodotto dipende dallo stato interno di ogni oggetto coinvolto. Il tipico modo in cui si manifesta sono gli effetti collaterali inaspettati: quando si modifica una parte del codice, una parte apparentemente non correlata si rompe.

... il lato negativo della programmazione funzionale è ovviamente che in pratica molto di ciò che vuoi fare è IO e stato di gestione.

Tuttavia, ci sono molte altre cose a cui è più difficile ragionare, e sono d'accordo con @Kilian sul fatto che la concorrenza è un ottimo esempio. Anche sistemi distribuiti.

    
risposta data 20.06.2017 - 13:24
fonte
6

Evitare discussioni più ampie e affrontare la domanda specifica:

Can you provide some examples of "Not easy to reason about", so it can be compared with "Easy to reason about" examples?

Ti rimando a "La storia di Mel, un vero programmatore" , un pezzo di folklore programmatore che risale a 1983 e quindi conta come "leggenda", per la nostra professione.

Racconta la storia di un programmatore che scrive codice che preferiva le tecniche arcane laddove possibile, incluso il codice autoreferenziale e auto-modificante e lo sfruttamento deliberato dei bug della macchina:

an apparent infinite loop had in fact been coded in such a way as to take advantage of a carry-overflow error. Adding 1 to an instruction that decoded as "Load from address x" normally yielded "Load from address x+1". But when x was already the highest possible address, not only did the address wrap around to zero, but a 1 was carried into the bits from which the opcode would be read, changing the opcode from "load from" to "jump to" so that the full instruction changed from "load from the last address" to "jump to address zero".

Questo è un esempio di codice che è "difficile ragionare su".

Ovviamente, Mel non sarebbe d'accordo ...

    
risposta data 21.06.2017 - 13:58
fonte
5

Posso fornire un esempio e molto comune.

Considera il seguente codice C #.

// items is List<Item>
var names = new List<string>();
for (var i = 0; i < items.Count; i++)
{
    var item = items[i];
    var mangled = MyMangleFunction(item.Name);
    if (mangled.StartsWith("foo"))
    {
        names.Add(mangled);
    }
}

Ora considera questa alternativa.

// items is List<Item>
var names = items
    .Select(item => MyMangleFunction(item.Name))
    .Where(s => s.StartsWith("foo"))
    .ToList();

Nel secondo esempio, so esattamente cosa sta facendo questo codice a colpo d'occhio. Quando vedo Select , so che un elenco di elementi viene convertito in un elenco di qualcos'altro. Quando vedo Where , so che alcuni elementi vengono filtrati. A colpo d'occhio, posso capire che cos'è names e farne un uso efficace.

Quando vedo un ciclo for , non ho idea di cosa stia succedendo finché non leggo effettivamente il codice. E a volte devo rintracciarlo per essere sicuro di aver tenuto conto di tutti gli effetti collaterali. Devo fare un po 'di lavoro anche per capire quali sono i nomi (oltre la definizione del tipo) e come usarli efficacemente. Quindi, il primo esempio è più difficile da ragionare rispetto al secondo.

In definitiva, essere facilmente ragionevoli qui dipende anche dalla comprensione dei metodi LINQ Select e Where . Se non li conosci, allora il secondo codice è più difficile da ragionare inizialmente. Ma paghi solo il costo per capirli una volta. Paghi il costo per capire un ciclo for ogni volta che ne usi uno e di nuovo ogni volta che cambia. A volte vale la pena pagare il prezzo, ma in genere "più facile ragionare" è molto più importante.

    
risposta data 21.06.2017 - 17:09
fonte
2

Una frase correlata è (parafrasa),

It's not enough for code to have "no obvious bugs": instead, it should have "obviously no bugs".

Un esempio relativamente "facile da ragionare" potrebbe essere RAII .

Un altro esempio potrebbe essere evitare abbracciare mortalmente : se riesci a tenere un lucchetto e acquisire un altro lucchetto, e ci sono molti di serrature, è difficile essere sicuri che non ci sia uno scenario in cui possa verificarsi un abbraccio mortale. L'aggiunta di una regola come "c'è solo un blocco (globale)" o "non ti è permesso di acquisire un secondo blocco mentre tieni un primo blocco", rende il sistema relativamente facile da ragionare.

    
risposta data 20.06.2017 - 19:48
fonte
2

Il punto cruciale della programmazione è l'analisi del caso. Alan Perlis ha sottolineato questo aspetto in Epigram # 32: I programmatori non devono essere misurati dalla loro ingegnosità e dalla loro logica, ma dalla completezza della loro analisi del caso.

Una situazione è facile da ragionare se l'analisi del caso è facile. Ciò significa che ci sono pochi casi da considerare, o, in mancanza, alcuni casi speciali - potrebbero esserci spazi ampi di casi, ma che collassano a causa di alcune regolarità, o soccombere a una tecnica di ragionamento come l'induzione.

Una versione ricorsiva di un algoritmo, per esempio, è di solito più facile da ragionare rispetto a una versione imperativa, perché non contribuisce a casi superflui che sorgono attraverso la mutazione di variabili di stato di supporto che non compaiono nella versione ricorsiva . Inoltre, la struttura della ricorsione è tale da inserirsi in un modello matematico di prova per induzione. Non dobbiamo considerare le complessità come le varianti del ciclo e le precondizioni rigorose più deboli e quant'altro.

Un altro aspetto di questo è la struttura dello spazio del caso. È più facile ragionare su una situazione che ha una divisione piatta, o per lo più piatta, in casi rispetto a una situazione gerarchica: casi con casi secondari e sub-sub e così via.

Una proprietà di sistemi che semplifica il ragionamento è ortogonalità : questa è la proprietà che i casi che governano i sottosistemi rimangono indipendenti quando questi sottosistemi sono combinati. Nessuna combinazione dà luogo a "casi speciali". Se un caso di quattro casi è combinato con un caso di tre casi ortogonalmente, ci sono dodici casi, ma idealmente ogni caso è una combinazione di due casi che rimangono indipendenti. In un certo senso, non ci sono davvero dodici casi; le combinazioni sono solo "fenomeni casuali emergenti" di cui non dobbiamo preoccuparci. Ciò significa che abbiamo ancora quattro casi a cui possiamo pensare senza considerare gli altri tre nell'altro sottosistema e viceversa. Se alcune delle combinazioni devono essere identificate in modo specifico e dotate di logica aggiuntiva, allora il ragionamento è più difficile. Nel peggiore dei casi, ogni combinazione ha un trattamento speciale, e poi ci sono davvero dodici nuovi casi, che si aggiungono ai quattro e ai tre originali.

    
risposta data 21.06.2017 - 03:56
fonte
0

Certo. Accetta la concorrenza:

Sezioni critiche applicate dai mutex: facili da capire perché esiste un solo principio (due thread di esecuzione non possono entrare simultaneamente nella sezione critica), ma soggetti a inefficienza e deadlock.

Modelli alternativi, ad es. programmazione o attori senza blocco: potenzialmente molto più elegante e potente, ma terribilmente difficile da capire, perché non puoi più fare affidamento su concetti (apparentemente) fondamentali come "ora scrivi questo valore in quel posto" .

Essere facili da ragionare riguarda l'aspetto uno di un metodo. Ma scegliere quale metodo utilizzare richiede di considerare gli aspetti tutti in combinazione.

    
risposta data 20.06.2017 - 11:12
fonte
0

Limitiamoci al ragionamento formale. Perché il ragionamento umoristico o inventivo o poetico hanno leggi diverse.

Anche così, l'espressione è definita in modo fioco e non può essere impostata in modo rigoroso. Ma non significa che dovrebbe rimanere così debole per noi. Immaginiamo che una struttura stia superando un test e ottenendo segni per punti diversi. I buoni voti per OGNI punto indicano che la struttura è conveniente sotto ogni aspetto e quindi, "Facile ragionare".

La struttura "Facile da ragionare" dovrebbe ottenere buoni voti per quanto segue:

  • I termini interni hanno nomi ragionevoli, facilmente distinguibili e definiti. Se gli elementi hanno una gerarchia, la differenza tra i nomi dei genitori e dei figli dovrebbe essere diversa dalla differenza tra i nomi dei fratelli.
  • Il numero di tipi di elementi strutturali è basso
  • I tipi di elementi strutturali usati sono cose facili a cui siamo abituati.
  • Gli elementi difficilmente comprensibili (ricorsioni, meta passi, 4+ geometria dimensionale ...) sono isolati - non direttamente combinati tra loro. (per esempio, se proverai a pensare ad alcune regole di modifica che cambiano per i cubi 1,2,3,4..n..dimensional, sarà molto complicato, ma se trasferirai ciascuna di queste regole ad alcune formule a seconda di n, avrai separatamente una formula per ogni n-cubo e separatamente una regola di ricorsione per tale formula e che due strutture separatamente possono essere facilmente pensate)
  • I tipi di elementi strutturali sono ovviamente diversi (ad esempio, non usando matrici miste a partire da 0 e da 1)

Il test è soggettivo? Sì, naturalmente lo è. Ma anche l'espressione stessa è soggettiva. Ciò che è facile per una persona, non è facile per un'altra persona. Quindi, i test dovrebbero essere diversi per i diversi domini.

    
risposta data 20.06.2017 - 16:31
fonte
0

L'idea che i linguaggi funzionali siano possibili per ragionare deriva dalla loro storia, in particolare ML che è stata sviluppata come linguaggio di programmazione analogo ai costrutti utilizzati per il ragionamento Logica per funzioni computabili . La maggior parte dei linguaggi funzionali sono più vicini ai calcoli di programmazione formale di quelli imperativi, quindi la traduzione dal codice nell'input di un sistema di sistemi di ragionamento è meno onerosa.

Per un esempio di un sistema di ragionamento, nel pi-calcolo, ogni posizione di memoria mutabile in un linguaggio imperativo deve essere rappresentata come un processo parallelo separato, mentre una sequenza di operazioni funzionali è un singolo processo. A quarant'anni dal test del teorema di LFC, stiamo lavorando con GB di RAM, quindi avere centinaia di processi è meno problematico: ho usato il pi-calcolo per rimuovere potenziali deadlock da poche centinaia di righe di C ++, nonostante la rappresentazione abbia centinaia di elabora il ragionatore ha esaurito lo spazio di stato in circa 3 GB e cura un bug intermittente. Questo sarebbe stato impossibile negli anni '70 o richiesto un supercomputer nei primi anni '90, mentre lo spazio di stato di un programma di linguaggio funzionale di dimensioni simili era abbastanza piccolo da ragionare a quei tempi.

Dalle altre risposte, la frase sta diventando una frase di moda anche se gran parte della difficoltà che ha reso difficile ragionare sulle lingue imperative è stata erosa dalla legge di Moore.

    
risposta data 21.06.2017 - 23:27
fonte
-2

È facile ragionare su un termine culturalmente specifico, motivo per cui è così difficile trovare esempi concreti. È un termine che è ancorato alle persone che devono fare il ragionamento.

"Facile ragionare" è in realtà una frase molto auto-descrittiva. Se uno sta guardando il codice e vuole ragionare su ciò che fa, è facile =)

Va bene, scomposizione. Se stai guardando il codice, di solito lo vuoi fare qualcosa. Vuoi assicurarti che faccia ciò che pensi che dovrebbe fare. Quindi sviluppate teorie su cosa dovrebbe fare il codice, e poi ragionate su di esso per cercare di argomentare perché il codice funziona davvero. Cerchi di pensare al codice come un essere umano (piuttosto che come un computer) e prova a razionalizzare gli argomenti su ciò che il codice può fare.

Il caso peggiore per "facile da ragionare" è quando l'unico modo per dare un senso a ciò che fa il codice è quello di andare riga per riga attraverso il codice come una macchina di Turing per tutti gli input. In questo caso, l'unico modo per ragionare qualsiasi cosa sul codice è trasformarti in un computer ed eseguirlo nella tua testa. Questi esempi di casi peggiori si vedono facilmente in concorsi di programmazione ovattati, come queste 3 righe di PERL che decodificano RSA:

#!/bin/perl -sp0777i<X+d*lMLa^*lN%0]dsXx++lMlN/dsM0<j]dsj
$/=unpack('H*',$_);$_='echo 16dio\U$k"SK$/SM$n\EsN0p[lN*1
lK[d2%Sa2/d0$^Ixp"|dc';s/\W//g;$_=pack('H*',/((..)*)$/)

Per quanto sia facile ragionare, di nuovo, il termine è altamente culturale. Devi considerare:

  • Che abilità ha il ragionatore? Quanta esperienza?
  • Che tipo di domande potrebbe avere il ragionatore sul codice?
  • quanto deve essere sicuro il ragionatore?

Ognuno di questi influenza "facilmente ragionare" in modo diverso. Prendi le abilità del ragionatore come esempio. Quando ho iniziato con la mia azienda, mi è stato consigliato sviluppare i miei script in MATLAB perché è "facile ragionare". Perché? Bene, tutti in compagnia conoscevano MATLAB. Se scegliessi una lingua diversa, sarebbe più difficile per chiunque capirmi. Non importa che la leggibilità di MATLAB sia atroce per alcune attività, semplicemente perché non è stata progettata per loro. Più tardi, con il progredire della mia carriera, Python divenne sempre più popolare. All'improvviso il codice MATLAB è diventato "difficile da ragionare" e Python era il linguaggio di preferenza per la scrittura di codice su cui era facile ragionare.

Prendi in considerazione anche gli idoms che il lettore può avere. Se puoi contare sul tuo lettore per riconoscere una FFT in una particolare sintassi, è "più facile ragionare" sul codice se ti attieni a quella sintassi. Permette loro di guardare il file di testo come una tela su cui è stata dipinta una FFT, piuttosto che dover entrare nei dettagli nitidi. Se stai usando C ++, scopri quanto i tuoi lettori sono a tuo agio con la libreria std . Quanto apprezzano la programmazione funzionale? Alcuni degli idiomi che escono dalle librerie dei contenitori dipendono molto dallo stile idematico che preferisci.

È anche importante capire che tipo di domande il lettore può essere interessato a rispondere. I tuoi lettori sono per lo più preoccupati della comprensione superficiale del codice o stanno cercando bug negli intestini?

In che misura il lettore deve essere effettivamente interessante. In molti casi, il ragionamento nebuloso è in realtà sufficiente per portare il prodotto fuori dalla porta. In altri casi, come il software di volo FAA, il lettore vorrà avere un ragionamento ironico. Mi sono imbattuto in un caso in cui sostenevo di usare RAII per un compito particolare, perché "Puoi semplicemente installarlo e dimenticarlo ... farà la cosa giusta". Mi è stato detto che avevo torto su questo. Quelli che stavano per ragionare su questo codice non erano il tipo di persone che "vogliono solo dimenticare i dettagli". Per loro, RAII era più simile a un ciondolo sospeso, costringendoli a pensare a tutte le cose che possono accadere quando si lascia il campo di applicazione. Coloro che stavano leggendo quel codice in realtà preferivano le chiamate alle funzioni esplicite alla fine dello scope in modo che potessero essere sicuri che il programmatore ci pensasse.

    
risposta data 20.06.2017 - 19:54
fonte