Esistono studi scientificamente rigorosi sui principi dello stile di programmazione? [chiuso]


25

Un principio di stile di codifica, ad esempio il principio dell'uscita singola, è davvero una buona cosa? Sempre o solo qualche volta? Quanta differenza fa davvero?

Qualunque sia la tua opinione, queste sono ovviamente domande soggettive. O lo sono?

Qualcuno ha tentato di fare uno studio oggettivo e scientificamente rigoroso dei principi dello stile di programmazione?

Non riesco a immaginare come si farebbe uno studio in doppio cieco sulla leggibilità, ma forse è possibile un doppio ignorante: utilizzare studenti che non conoscono il principio studiato come materie e non programmatori per amministrare lo studio.


5
Potresti essere interessato a leggere il codice completo. Tutto non è misurabile, ma molto è, e troverai una buona panoramica con dati grezzi o fonti in questo libro.
deadalnix,

Inoltre, dipende fortemente dalla lingua, alcuni principi si applicano a lingue specifiche e non ad altri. Ad esempio il single-exit principlenon si applica davvero al C ++ a causa di RAII
Martin York,


@Loki - Ho dovuto pensarci, e non sono sicuro di essere d'accordo. È vero che RAII è progettato in gran parte per far fronte a eccezioni, che sono punti di uscita alternativi, ma (almeno per alcune persone) contano come punti di uscita alternativi alternativi - non contano davvero rispetto al principio di uscita singola nel modo in cui break, gotoo returnfare. L'uscita singola IOW non è un assoluto in C ++, ma è praticamente la mia visione in C e nella maggior parte degli altri linguaggi. Ma è ancora rilevante in senso non rigoroso.
Steve314,

1
@ Steve314, l'articolo è almeno lontanamente rilevante: delinea un progetto per una metodologia di tale esperimento, che è abbastanza importante a causa di un'ovvia mancanza di prove sperimentali correttamente registrate in questo settore.
SK-logic

Risposte:


11

Sto facendo eco al commento di deadalnix: leggi il codice completo 2 . L'autore (Steve McConnell) discute approfonditamente dello stile di programmazione e spesso fa riferimento a documenti e dati.


Panoramica fondamentale e ben presentata sullo sviluppo di software professionale, spero che un giorno ne troverò uno simile per la garanzia della qualità. I capitoli sulla programmazione difensiva e sulla programmazione pseudocodice mi sono stati particolarmente utili. Il capitolo sulle pratiche di sviluppo collaborativo sembra essere il più convincente di tutto ciò che ho letto finora su questi argomenti.
moscerino il

Non ho letto questo libro, e forse dovrei, ma - sulla base dei commenti nella risposta dei moscerini - questi documenti referenziati sono davvero scientificamente rigorosi e obiettivi? Se la risposta è "quanto possono essere", quali compromessi erano necessari? Come ho suggerito nella domanda, è stato necessario sostituire il doppio cieco con uno standard più debole?
Steve314,

@ Steve314: non lo so, non ho controllato le fonti! Ma non è sempre necessario il rigore scientifico per stabilire le migliori pratiche. Una discussione dei pro e dei contro è talvolta sufficiente.
M. Dudley,

@emddudley - assolutamente vero, ma non proprio di cosa trattasse questa domanda.
Steve314,

@ Steve314: Code Complete sarebbe un ottimo punto di partenza per te e sono fiducioso che alcuni dei suoi riferimenti affrontino il problema dell'analisi scientifica dello stile di codifica.
M. Dudley,

12

Dubito fortemente della possibilità che uno studio sull'argomento produca risultati oggettivi e rimarrò scettico fino a quando non mi verranno mostrate delle ricerche convincenti.

I programmatori che hanno trascorso anni a leggere e scrivere codici che seguivano un certo stile di codifica lo troveranno ovviamente più leggibile di uno stile di codifica perfetto che vedrebbero per la prima volta nella loro vita.

È esattamente lo stesso con il layout di digitazione QWERTY più comune - è facile dimostrare che è abbastanza subottimale in termini di ergonomia (pensi che tutti i caratteri della parola TYPEWRITER siano stati messi nella riga superiore con la nostra comodità quotidiana in mente?) .

Ma alternative migliorate come Dvorak o Colemak non hanno mai preso piede e sono improbabili. E quindi le persone non sono più produttive con loro - fatto. Anche se sono superiori in un senso astratto.

Inoltre, sarebbe difficile trovare soggetti con nessuna esposizione prima della programmazione (in quanto ciò avrebbe contaminato il risultato del nostro studio), MA un'attitudine per la programmazione, e la volontà di partecipare a uno studio per un periodo sufficientemente lungo per mostrare sia breve benefici a lungo termine e benefici a lungo termine in modo che possano essere ponderati a vicenda ... (Non so se si escludono a vicenda, ma i ricercatori non potevano semplicemente supporre che non lo siano mai).


1
Fantastico, non avevo mai sentito parlare di Colemak prima
CaffGeek,

1
@Chad ancora meno conosciuto è Carpal X, con cui ho giocato per un po '. L'ho trovato più bello di Colemak (ho raggiunto 90-100 wpm con carpalx). Anche se non intendi passare a layout esotici, il sito web carpalx offre una lettura estremamente interessante sulla valutazione e l'ottimizzazione dei layout della tastiera e sull'utilizzo di algoritmi genetici per questa categoria di problemi. Vedi mkweb.bcgsc.ca/carpalx
Konrad Morawski,

1
A volte i vantaggi marginali di un approccio alternativo saranno abbastanza grandi da giustificare il costo di adottarlo; altrimenti saremmo tutti programmatori di assembler e fortran. Questa risposta non risponde realmente alla domanda originale sull'opportunità o meno di vantaggi marginali. Nell'esempio di Dvorak, ci sono certamente ed è stato dimostrato, ma non sono abbastanza grandi benefici da giustificare l'apprendimento di Dvorak.
Jeremy,

@Jeremy "questa risposta in realtà non risponde alla domanda originale sull'opportunità o meno di vantaggi marginali" - il PO non ha chiesto direttamente i risultati di tali studi, ha chiesto se qualcuno ha tentato di portare a termine tali studi, che è una domanda più aperta. Ho risposto sottolineando un paio di ragioni logiche per cui sarebbe tecnicamente difficile e perché qualsiasi risultato di uno studio del genere sarebbe probabilmente significativamente contaminato dal rumore statistico. Quindi, se la mia risposta è stata ritenuta non utile per i motivi che mi hai dato, penso di essere stata ingiustamente sottoposta a downgrade.
Konrad Morawski,

1
@Jeremy l'essenza di questi costi di adozione è che le persone si comportano meglio con uno strumento inferiore purché abbiano avuto più pratica con esso. E questo è esattamente ciò che apparirebbe in qualsiasi studio che tenta di esaminare in che misura i suoi soggetti affrontano stili di codifica diversi. Il rumore causato dalla loro precedente familiarità / non familiarità con gli stili di codifica che vorresti che usassero ridurrebbe l'impatto di qualsiasi qualità innata di questi stili. A meno che tu non abbia livellato il parco giochi prendendo principianti completi. Ma ciò pone una difficoltà pratica, come ho sottolineato nell'ultimo paragrafo della mia risposta.
Konrad Morawski,

4

La risposta è un NO definitivo! `Break` e` continue` sono cattive pratiche di programmazione? è un sottoinsieme di questa domanda, quindi inizierò con una risposta appena modificata a questo ...

Puoi [ri-scrivere programmi senza istruzioni di break (o ritorna dal mezzo di loop, che fanno la stessa cosa). Nel fare ciò, potrebbe essere necessario introdurre ulteriori variabili e / o duplicazione del codice, che in genere rendono il programma più difficile da comprendere. Pascal (il linguaggio di programmazione della fine degli anni '60) era molto cattivo soprattutto per i programmatori principianti per questo motivo.

C'è un risultato di informatica chiamato gerarchia delle strutture di controllo di Kosaraju, che risale al 1973 e che è menzionato nel (più) famoso documento di Knuth Programmazione strutturata con dichiarazioni del 1974. Ciò che S. Rao Kosaraju dimostrò nel 1973 è che non lo è possibile riscrivere tutti i programmi con interruzioni multilivello di profondità n in programmi con profondità di interruzione inferiore a n senza introdurre variabili aggiuntive. Ma diciamo che è solo un risultato puramente teorico. (Basta aggiungere alcune variabili extra ?! Sicuramente puoi farlo per sentirti in gruppo con gli utenti 3K + su stackexchange ...)

Ciò che è molto più importante dal punto di vista dell'ingegneria del software è un articolo più recente del 1995 di Eric S. Roberts intitolato Loop Exits and Structured Programming: Reopening the Debate (doi: 10.1145 / 199688.199815). Roberts riassume diversi studi empirici condotti da altri prima di lui. Ad esempio, quando a un gruppo di studenti del tipo CS101 è stato chiesto di scrivere codice per una funzione che implementa una ricerca sequenziale in un array, l'autore dello studio ha dichiarato quanto segue su quegli studenti che hanno usato una pausa / ritorno per uscire dal sequenziale ciclo di ricerca proprio quando l'elemento è stato trovato:

Devo ancora trovare una sola persona che ha tentato un programma usando [questo stile] che ha prodotto una soluzione errata.

Roberts afferma inoltre che:

Gli studenti che hanno tentato di risolvere il problema senza utilizzare un ritorno esplicito dal ciclo for sono andati molto meno bene: solo sette dei 42 studenti che hanno tentato questa strategia sono riusciti a generare soluzioni corrette. Tale cifra rappresenta un tasso di successo inferiore al 20%.

Sì, potresti essere più esperto degli studenti CS101, ma senza usare l'istruzione break (o equivalentemente restituire / andare dal centro dei loop), alla fine scriverai un codice che mentre nominalmente ben strutturato è abbastanza peloso in termini di logica aggiuntiva variabili e duplicazione del codice che qualcuno, probabilmente te stesso, inserirà in esso dei bug logici mentre cerca di seguire un'idea passe dello stile di codifica "corretto".

E qui c'è un problema più grande oltre alle dichiarazioni return / break-type, quindi questa domanda è un po 'più ampia di quella sulle interruzioni. I meccanismi di gestione delle eccezioni violano anche il paradigma del punto di uscita singolo secondo alcuni

Quindi, fondamentalmente, chiunque abbia discusso sopra che il principio dell'uscita singola è ancora utile oggi sta anche discutendo contro il paradigma di gestione delle eccezioni, a meno che non sia usato nel modo estremamente restrittivo descritto in quest'ultimo collegamento; queste linee guida sostanzialmente limitano tutte le eccezioni a una funzione da lanciare (), cioè non è consentita affatto la propagazione di eccezioni interfunzionali. Goditi il ​​tuo nuovo Pascal con una sintassi simile al C ++.

Vedo da dove viene l'idea di "un solo ritorno"?che l'opinione prevalente su questo sito è contraria a ciò che ho pubblicato qui, quindi capisco perfettamente perché sono già stato votato verso il basso, anche se sono la prima risposta qui a fornire effettivamente qualcosa che la domanda ha posto: alcune informazioni sugli attuali test di usabilità incentrati sul problema dell'uscita singola. Immagino che non dovrei lasciare che la conoscenza ostacoli i preconcetti, specialmente su un sito di gamification. Da ora in poi continuerò a modificare Wikipedia. Almeno lì le informazioni da buone fonti sono apprezzate e le affermazioni vaghe o errate che fingono di essere supportate da fonti alla fine guadagnano un divieto. In questo sito accade esattamente il contrario: dominano opinioni prive di fondamento di fatti. Mi aspetto che una mod elimini quest'ultima parte, ma almeno quel tipo saprà perché mi hai perso per sempre come collaboratore qui.


Non ho minimizzato questo, ma sul tuo "Ma così facendo potresti dover introdurre ulteriori variabili e / o duplicazione del codice che in genere rendono il programma più difficile da capire." punto, questa è un'affermazione soggettiva. Concordo sul fatto che l'aggiunta di una variabile o la duplicazione del codice rende difficile la comprensione, ma probabilmente l'aggiunta di un goto rende difficile anche la comprensione, inoltre è possibile mitigare il danno causato dalla duplicazione fattorizzando il codice duplicato in una funzione (sebbene l'IMO si sposti la complessità nel grafico delle chiamate non lo elimina automaticamente).
Steve314,

Ho visto il tuo punto sull'articolo del 1995 solo dopo quell'ultimo commento e ho deciso di votare - punto interessante. Penso che il tuo downvote potrebbe essere più perché il tuo post è lungo e inizia con un punto soggettivo, quindi probabilmente il downvoter non ha letto tutto (lo stesso di me, all'inizio). Fondamentalmente, è una buona idea presentare presto il tuo vero punto.
Steve314,

Ad ogni modo, penso che molte persone considerino le eccezioni come un tipo di punti di uscita alternativi alternativi - perché sono pensati per casi di errore (in qualche modo) che non contano davvero. Capisco che è un po 'sensibile alla cultura del linguaggio, però. In alcuni linguaggi "eccezione" è più che il nome: un caso di successo eccezionale è valido (e IIRC Stroustrup ha detto qualcosa del genere sul C ++, sollevando un punto filosofico sul fatto che un errore sia un errore se viene gestito). Alcuni sostengono addirittura che le eccezioni siano solo un altro flusso di controllo da utilizzare ogni volta che fornisce il flusso di controllo necessario.
Steve314,

1
@ Steve314 " più probabilmente il danno fatto dalla duplicazione può essere mitigato prendendo in considerazione il codice duplicato in una funzione " Mettere fuori linea e fuori dalla vista immediata parte di una logica della funzione, una parte che non ha alcun senso isolata. Rendere ancora più difficile comprendere la logica della funzione.
curiousguy,

1
@curiousguy - sì, è vero, e probabilmente parte dell'intento del mio punto "spostare la complessità nel grafico della chiamata". La mia religione è che ogni scelta che fai è un compromesso, quindi sii consapevole di tutte le opzioni plausibili e dei loro vantaggi e svantaggi, e conoscere le mitigazioni comuni è importante ma fai attenzione nel caso in cui la cura sia peggiore della malattia. Tranne ovviamente che parte del compromesso è quanto tempo spendi (o sprechi) a discutere delle cose.
Steve314,

1

http://dl.acm.org/citation.cfm?id=1241526

http://www.springerlink.com/content/n82qpt83n8735l7t/

http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=661092

[Le tue domande sembrano avere una risposta con una sola parola, "sì". Mi è stato detto, tuttavia, che fornire risposte brevi è "sprezzante" della domanda. Se ritieni che io sia stato sprezzante, ti preghiamo di contrassegnare la risposta in modo che un moderatore possa eliminarla.]


1
@ luis.espinal: verso quale fine? Quali informazioni contiene il testo? La domanda si allarga un po '. Quale parte della domanda dovrebbe essere affrontata con del testo?
S. Lott,

1
Per motivi di stile, e forse per fornire ulteriori informazioni che gli abstract dei link possono fornire (considerando che non sappiamo se l'OP è un membro ACM / IEEE / Springer Verlag pagante con accesso agli articoli completi e trova le risposte a le sue domande.) Ad esempio, l'abstract dell'articolo ACM non menziona lo stile di codifica. Al massimo parla di corroborare il teorema del programma strutturato (che di per sé non parla del problema del ritorno singolo o multiplo). Quindi avresti potuto spiegare perché quel link è rilevante.
luis.espinal,

1
Il terzo articolo (per fortuna ho accesso a IEEE Xplore) non sembra correlato a ciò che l'OP chiede, per quanto ne so. È un articolo meraviglioso, intendiamoci, che sto stampando per una lettura più dedicata in un secondo momento. Quindi forse potresti anche aver spiegato come questo articolo aiuta l'OP a rispondere alla sua domanda. Nel complesso, sembra che tu abbia semplicemente messo insieme un sacco di collegamenti. Non è un modo di essere sprezzante (a meno che non fosse questa la tua intenzione), ma ancora una volta, non riesco a vedere come ciò abbia aiutato il PO. Ed è per questo che un poster dovrebbe aggiungere del testo lungo i suoi link. Quindi ora sai perché l'ho detto;)
luis.espinal,

1
dalla bocca del PO Is a coding style principle - e.g. the single-exit principle - really a good thing?- questo dà contesto alla domanda che pone, sugli stili di codifica. Inoltre, lo stile di codifica non è lo stesso della metodologia di programmazione, in particolare i metodi di progettazione di alto livello che sono al centro dell'articolo IEEE (chiaramente affermato dagli autori.) Ecco perché dico "no" - gli ambiti sono completamente diversi.
luis.espinal,

1
Ho il sospetto da dove provenga l'OP. Sta chiaramente affermando stili di codifica (non metodologie) e, in particolare, rendimenti singoli o multipli. Ho dovuto farcela un paio di volte con un codice ben scritto, intrinsecamente evidente, usando più istruzioni return che venivano riscritte in versioni più contorte usando single return (in particolare nelle grandi organizzazioni grandi nella burocrazia) * as per "processo". E ci si chiede (e sfida con prove) la validità, l'usabilità e l'efficacia in termini di costi di tali mandati arbitrari. Le persone che impongono tali mandati vivono ancora negli anni '60: /
luis.espinal

1

È un principio di stile di codifica, ad esempio il principio dell'uscita singola

Alla fine degli anni '60, le persone che ancora protestano per un'uscita singola o per un'uscita multipla sono ancora bloccate. All'epoca, una simile discussione era importante poiché eravamo agli inizi del programmatore strutturato, e c'era un campo abbastanza numeroso che proclamava che i risultati alla base del teorema del programma strutturato Bohm-Jacopini non erano universalmente applicabili a tutti i costrutti di programmazione.

È qualcosa che avrebbe dovuto essere risolto molto tempo fa. Bene, è stato risolto (quasi 4 decenni per essere precisi, sia nel mondo accademico che nel settore), ma le persone (coloro che sono assolutamente pro o contro) non hanno prestato attenzione.

Per quanto riguarda il resto delle mie risposte, è tutto relativo (cosa non è nel software?):

  • davvero una buona cosa?

Sì. Il più delle volte per il caso generale, con avvertenze specifiche per casi limite e costrutti di programmazione specifici del linguaggio.

Sempre o solo qualche volta?

La maggior parte delle volte.

Quanta differenza fa davvero?

Dipende.

Codice leggibile vs codice illeggibile. Una maggiore complessità (che dovremmo sapere ora aumenta la probabilità di introdurre errori) rispetto a una complessità più semplice (ed ergo, una minore probabilità di errori.) Lingue i cui compilatori non aggiungono un ritorno implicito (diciamo, Pascal, Java o C #) e quelli che impostazione predefinita su int (C e C ++).

Alla fine, è un'abilità affinata con l'uomo / ore dietro una tastiera. A volte, è ok avere più dichiarazioni di ritorno, come qui (in alcuni pseudocodici di Pascal):

function foo() : someType
  begin
  if( test1 == true )
  then
    return x;
  end
  doSomethignElseThatShouldnHappenIfTest1IsTrue();
  return somethingElse();
end;

L'intento è chiaro e l'algoritmo è abbastanza piccolo e abbastanza semplice da non giustificare la creazione di una variabile "flag" che contiene l'eventuale valore di ritorno utilizzato in un singolo punto di ritorno. L'algoritmo potrebbe essere in errore, ma la sua struttura è abbastanza semplice da rendere (molto probabilmente) trascurabile lo sforzo di rilevare un errore.

A volte non lo è (qui usando uno pseudocodice di tipo C):

switch(someVal)
{
case v1 : return x1;
case v2 : return x2:
case v3 : doSomething(); // fall-through
case v4: // fall-through
case v5: // fall-through
case v6: return someXthingie;
...
...
default:
   doSomething(); // no return statement yet
}

Qui, l'algoritmo non ha una struttura semplice e l'istruzione switch (una di tipo C) consente passaggi di fall-through che possono o meno essere eseguiti intenzionalmente come parte dell'algoritmo.

Forse l'algoritmo è corretto, ma scritto male.

O forse, da forze esterne al di là delle capacità del programmatore, questa è la rappresentazione effettiva (e corretta) di un algoritmo legittimamente necessario.

Forse è sbagliato.

Per scoprire la verità di tutto ciò è necessario uno sforzo molto maggiore rispetto all'esempio precedente. E qui sta qualcosa in cui credo fermamente (badate che non ho studi formali per sostenere questo):

Supponendo che uno snippet di codice sia corretto:

  1. Le dichiarazioni di ritorno multiple aumentano la leggibilità e la semplicità di un tale frammento di codice, se il frammento rappresenta un algoritmo semplice con una struttura di flusso intrinsecamente semplice. Per semplice, non intendo piccolo, ma intendo intrinsecamente comprensibile o prova di sé , ciò che non richiede uno sforzo di lettura sproporzionato (né indurre le persone a vomitare, maledire la madre di qualcuno o ingoiare un proiettile quando devono leggerlo. )

  2. Una singola dichiarazione di ritorno aumenta la leggibilità e la semplicità di tale codice se il valore di ritorno viene calcolato durante l'esecuzione dell'algoritmo o se i passaggi dell'algoritmo responsabile del calcolo possono essere raggruppati in un'unica posizione all'interno della struttura dell'algoritmo .

  3. Una singola dichiarazione di ritorno riduce la leggibilità e la semplicità di tale codice se richiede assegnazioni a una o più variabili flag, con le posizioni di tali assegnazioni non posizionate uniformemente in tutto l'algoritmo.

  4. Le dichiarazioni di ritorno multiple riducono la leggibilità e la semplicità di tale codice se le dichiarazioni di ritorno non sono distribuite uniformemente in tutto l'algoritmo e se delimitano blocchi di codice reciprocamente esclusivi che non sono uniformi nelle dimensioni o nella struttura tra di loro.

Ciò è strettamente correlato alla complessità di uno snippet di codice in questione. E questo a sua volta è correlato a misure di complessità ciclomatica e di stabilità. Da ciò, si potrebbe osservare quanto segue:

Maggiore è la dimensione di una subroutine o di una funzione, più ampia e complessa è la struttura del flusso di controllo interno e maggiore è la probabilità che si debba affrontare la questione se utilizzare dichiarazioni di ritorno multiple o singole.

La conclusione è: mantieni piccole le tue funzioni facendo una cosa e solo una cosa (e facendola bene). Se esibiscono metriche nominalmente piccole di complessità ciclomatica e di stabilità, non solo saranno probabilmente corrette e saranno implementate attività comprensibili, ma anche le loro strutture interne saranno relativamente evidenti.

Quindi, e solo allora puoi abbastanza facilmente e senza perdere molto sonno, puoi decidere se utilizzare un singolo ritorno e più ritorni senza correre molti rischi di introdurre errori con entrambe le scelte.

Si potrebbe anche considerare tutto ciò e suggerire che quando le persone lottano con il problema dei rendimenti singoli o dei rendimenti multipli, è perché - per inesperienza, stupidità o mancanza di etica del lavoro - non scrivono codice pulito e tendono a scrivere funzioni mostruose con totale disprezzo delle misure ciclomatiche e del fermo.


1
Il tipo di ritorno C ++ non è predefinito in int: non esiste un tipo di ritorno predefinito, quindi deve essere specificato in tutti i casi.
Sjoerd,

Da prima ho scritto questa domanda - programmers.stackexchange.com/questions/58237/… . Fondamentalmente, sto sostenendo la consapevolezza del principio, ma non seguendolo rigorosamente - se tutti i punti di uscita sono ovvi, sono felice. Il mio punto qui - solo perché cito un principio come esempio non significa che sto sostenendo tale principio, e certamente non nella sua forma rigorosa. La mia opinione soggettiva è proprio questo, però: forse c'è un argomento più forte per il mio punto di vista, o forse c'è un argomento forte sul fatto che mi sbaglio.
Steve314,

In cosa consiste "default to int"?
curiousguy,

Voglio dire, e avrei dovuto qualificarlo, che la maggior parte dei compilatori "sposterà" semplicemente il valore di un registro accumulatore come valore di ritorno se il codice ha un ramo di esecuzione senza un valore di ritorno esplicito. Ciò in effetti significa restituire il risultato dell'ultima operazione aritmetica (qualunque immondizia possa essere) in forma int. E quello sarebbe sicuramente spazzatura (ed ergo, comportamento indefinito) indipendentemente da ciò che la funzione intendeva fare in primo luogo. C e C ++ possono avvisarti, ma le compilazioni ti permetteranno di compilare a meno che tu non usi -Werror o qualcosa di simile.
luis.espinal,
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.