Occuparsi di non conoscere i nomi dei parametri di una funzione quando la si chiama

13

Ecco un problema di programmazione / lingua su cui mi piacerebbe sentire le tue opinioni.

Abbiamo sviluppato convenzioni che la maggior parte dei programmatori (dovrebbe) seguire non fanno parte della sintassi dei linguaggi ma servono a rendere il codice più leggibile. Questi sono sempre una questione di dibattito, ma ci sono almeno alcuni concetti chiave che la maggior parte dei programmatori trovano gradevoli. Assegnare nomi alle variabili in modo appropriato, nominare in generale, rendere le linee non scandalosamente lunghe, evitare lunghe funzioni, incapsulamenti, quelle cose.

Tuttavia, c'è un problema che devo ancora trovare qualcuno che commenta e che potrebbe essere il più grande del gruppo. È il problema degli argomenti che sono anonimi quando chiami una funzione.

Le funzioni derivano dalla matematica in cui f (x) ha un significato chiaro perché una funzione ha una definizione molto più rigorosa che solitamente fa nella programmazione. Le funzioni pure in matematica possono fare molto meno di quanto possano fare nella programmazione e sono uno strumento molto più elegante, di solito prendono solo un argomento (che di solito è un numero) e restituiscono sempre un valore (anche di solito un numero). Se una funzione accetta più argomenti, sono quasi sempre solo dimensioni extra del dominio della funzione. In altre parole, un argomento non è più importante degli altri. Sono esplicitamente ordinati, certo, ma a parte questo, non hanno ordini semantici.

In programmazione, tuttavia, abbiamo più libertà nella definizione delle funzioni, e in questo caso direi che non è una buona cosa. Una situazione comune, hai una funzione definita in questo modo

func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}

Guardando la definizione, se la funzione è scritta correttamente, è perfettamente chiaro cosa è cosa. Quando si chiama la funzione, si potrebbe anche avere un po 'di magia di completamento del codice / intellectense in corso nel tuo IDE / editor che ti dirà quale dovrebbe essere il prossimo argomento. Ma aspetta. Se ho bisogno di questo quando sto effettivamente scrivendo la chiamata, non c'è qualcosa che ci manca qui? La persona che legge il codice non ha il vantaggio di un IDE e, a meno che non salti alla definizione, non ha idea di quale dei due rettangoli sia passato come argomento per cosa.

Il problema va anche oltre. Se i nostri argomenti provengono da alcune variabili locali, potrebbero esserci situazioni in cui non sappiamo nemmeno quale sia il secondo argomento poiché vediamo solo il nome della variabile. Prendi ad esempio questa riga di codice

DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

Questo problema viene alleviato in varie lingue in diverse lingue, ma anche in lingue tipizzate rigorosamente e anche se le tue variabili sono nominate in modo sensibile, non menzioni nemmeno il tipo di variabile quando lo passi alla funzione.

Come di solito accade con la programmazione, ci sono molte potenziali soluzioni a questo problema. Molti sono già implementati nelle lingue popolari. Ad esempio, i parametri denominati in C #. Tuttavia, tutto ciò che so ha degli svantaggi significativi. Assegnare un nome a ogni parametro su ogni chiamata di funzione non può portare a un codice leggibile. Sembra quasi che stiamo superando le possibilità offerte da una semplice programmazione di testo. Ci siamo spostati dal testo JUST in quasi tutte le aree, ma continuiamo a codificare lo stesso. Ulteriori informazioni sono necessarie per essere visualizzate nel codice? Aggiungi altro testo. Ad ogni modo, questo sta diventando un po 'tangenziale quindi mi fermerò qui.

Una risposta che ho ottenuto con il secondo frammento di codice è che probabilmente dovresti prima decomprimere l'array con alcune variabili nominate e poi usarle, ma il nome della variabile può significare molte cose e il modo in cui viene chiamato non indica necessariamente il modo in cui dovrebbe essere interpretato nel contesto della funzione chiamata. Nell'ambito locale, potresti avere due rettangoli di nome leftRectangle e rightRectangle perché è ciò che rappresentano semanticamente, ma non è necessario estenderli a ciò che rappresentano quando vengono assegnati a una funzione.

In effetti, se le variabili sono denominate nel contesto della funzione chiamata di quella che stai introducendo meno informazioni di quelle che potresti potenzialmente con quella chiamata di funzione e a un certo livello se portano a codice codice peggiore. Se si dispone di una procedura che si traduce in un rettangolo archiviato in rectForClipping e quindi in un'altra procedura che fornisce rectForDrawing, la chiamata effettiva a DrawRectangleClipped è solo una cerimonia. Una linea che non significa nulla di nuovo ed è lì solo così il computer sa esattamente cosa vuoi, anche se lo hai già spiegato con la tua denominazione. Questa non è una buona cosa.

Mi piacerebbe davvero sentire nuove prospettive su questo. Sono sicuro di non essere il primo a considerare questo problema, quindi come si risolve?

    
posta Darwin 21.05.2014 - 18:04
fonte

5 risposte

9

Sono d'accordo sul fatto che il modo in cui le funzioni vengono spesso utilizzate può essere una parte confusa della scrittura del codice e, in particolare, della lettura del codice.

La risposta a questo problema dipende in parte dalla lingua. Come hai detto, C # ha nominato parametri. La soluzione di Objective-C a questo problema coinvolge nomi di metodi più descrittivi. Ad esempio, stringByReplacingOccurrencesOfString:withString: è un metodo con parametri chiari.

In Groovy, alcune funzioni accettano mappe, consentendo una sintassi simile alla seguente:

restClient.post(path: 'path/to/somewhere',
            body: requestBody,
            requestContentType: 'application/json')

In generale, puoi risolvere questo problema limitando il numero di parametri che passi a una funzione. Penso che 2-3 sia un buon limite. Se sembra che una funzione abbia bisogno di più parametri, mi fa pensare nuovamente al design. Ma questo può essere più difficile da rispondere in generale. A volte stai provando a fare troppo in una funzione. A volte ha senso considerare una classe per la memorizzazione dei parametri. Inoltre, in pratica, trovo spesso che le funzioni che richiedono un gran numero di parametri normalmente ne hanno molte come opzionali.

Anche in un linguaggio come Objective-C ha senso limitare il numero di parametri. Una ragione è che molti parametri sono opzionali. Per un esempio, vedere rangeOfString: e le sue variazioni in NSString .

Un pattern che uso spesso in Java consiste nell'usare una classe in stile fluente come parametro. Ad esempio:

something.draw(new Box().withHeight(5).withWidth(20))

Questo usa una classe come parametro, e con una classe fluent-style, rende il codice facilmente leggibile.

Lo snippet Java sopra indicato aiuta anche dove l'ordinamento dei parametri potrebbe non essere così ovvio. Normalmente assumiamo con le coordinate che X precede Y. E normalmente vedo l'altezza prima della larghezza come una convenzione, ma non è ancora chiaro ( something.draw(5, 20) ).

Ho visto anche alcune funzioni come drawWithHeightAndWidth(5, 20) ma anche queste non possono richiedere troppi parametri, altrimenti inizi a perdere leggibilità.

    
risposta data 21.05.2014 - 18:59
fonte
11

Per lo più è risolto con una buona denominazione di funzioni, parametri e argomenti. L'hai già esplorato e hai trovato delle carenze, comunque. La maggior parte di queste carenze viene attenuata mantenendo le funzioni piccole, con un numero limitato di parametri, sia nel contesto chiamante che nel contesto chiamato. Il tuo esempio particolare è problematico perché la funzione che stai chiamando sta provando a fare diverse cose contemporaneamente: specifica un rettangolo di base, specifica un'area di ritaglio, disegna e riempilo con un colore specifico.

Questo è un po 'come cercare di scrivere una frase usando solo gli aggettivi. Metti più verbi (chiamate di funzione) in là, crea un soggetto (oggetto) per la tua frase, ed è più facile da leggere:

rect.clip(clipRect).fill(color)

Anche se clipRect e color hanno nomi terribili (e non dovrebbero), puoi ancora discernere i loro tipi dal contesto.

Il tuo esempio deserializzato è problematico perché il contesto di chiamata sta cercando di fare troppo in una volta: deserializzare e disegnare qualcosa. È necessario assegnare nomi che abbiano un senso e separino chiaramente le due responsabilità. Come minimo: qualcosa del genere:

(rect, clipRect, color) = deserializeClippedRect()
rect.clip(clipRect).fill(color)

Molti problemi di leggibilità sono causati dal cercare di essere troppo concisi, saltare gli stadi intermedi che gli umani richiedono per discernere contesto e semantica.

    
risposta data 21.05.2014 - 18:57
fonte
3

In pratica, è risolto da un design migliore. È eccezionalmente raro che funzioni ben scritte richiedano più di 2 input e, quando si verifica, è raro che quei molti input non siano in grado di essere aggregati in un pacchetto coesivo. Questo rende piuttosto facile suddividere funzioni o aggregare parametri in modo da non far funzionare troppo una funzione. Uno ha due input, diventa facile nominarlo e molto più chiaro su quale input è quale.

Il mio linguaggio giocattolo aveva il concetto di frasi per gestire questo e altri linguaggi di programmazione più incentrati sul linguaggio naturale hanno avuto altri approcci per affrontarlo, ma tutti tendono ad avere altri aspetti negativi. Inoltre, anche le frasi sono poco più di una bella sintassi intorno al fatto che le funzioni abbiano nomi migliori. Sarà sempre difficile creare un buon nome di funzione quando ci vuole un sacco di input.

    
risposta data 21.05.2014 - 18:40
fonte
1

In Javascript (o ECMAScript ), ad esempio, molti programmatori si sono abituati a

passing parameters as a set of named object properties in a single anonymous object.

E come pratica di programmazione ha ottenuto dai programmatori alle loro librerie e da lì ad altri programmatori che sono cresciuti piacendoli e li usano e scrivono altre librerie ecc.

Esempio

Invece di chiamare

function drawRectangleClipped (rectToDraw, fillColor, clippingRect)

come questo:

drawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

, che è uno stile valido e corretto, si chiama

function drawRectangleClipped (params)

come questo:

drawRectangleClipped({
    rectToDraw: deserializedArray[0], 
    fillColor: deserializedArray[1], 
    clippingRect: deserializedArray[2]
})

, che è valido e corretto e bello per quanto riguarda la tua domanda.

Certamente, ci devono essere condizioni adatte per questo - in Javascript questo è molto più praticabile rispetto a, per esempio, C. In javascript, questo ha anche dato vita a una notazione strutturale ora ampiamente utilizzata che è diventata popolare come controparte più leggera di XML. Si chiama JSON (probabilmente ne hai già sentito parlare).

    
risposta data 22.05.2014 - 10:29
fonte
0

Dovresti usare oggettivo-C quindi, ecco una definizione di funzione:

- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

E qui è usato:

[someObject performSelector:someSelector withObject:someObject2 withObject:someObject3];

Penso che Ruby abbia costrutti simili e puoi simularli in altre lingue con elenchi di valori-chiave.

Per le funzioni complesse in Java mi piace definire variabili fittizie nel testo delle funzioni. Per il tuo esempio di sinistra-destra:

Rectangle referenceRectangle = leftRectangle;
Rectangle targetRectangle = rightRectangle;
doSomeWeirdStuffWithRectangles(referenceRectangle, targetRectangle);

Sembra più codifica, ma puoi ad esempio usare leftRectangle e poi rifattorizzare il codice in seguito con "Estrai la variabile locale" se pensi che non sia comprensibile per un futuro manutentore del codice, che potrebbe essere o non essere tu .

    
risposta data 22.05.2014 - 00:47
fonte

Leggi altre domande sui tag