2010-07-02 15 views
5

Ho iniziato a lavorare sullo sviluppo di API scritte in C. Vedo alcune subroutine che prevedono 8 (otto) parametri e per me sembra brutto e ingombrante passare 8 parametri durante la chiamata quella particolare subroutine. Mi stavo chiedendo se fosse possibile implementare una soluzione più accettabile e più pulita.Best practice per il numero di argomenti per le subroutine in C

risposta

2

8 potrebbe essere un numero corretto. oppure potrebbe essere che molti di quegli 8 dovrebbero appartenere tutti a una classe appropriata come membri, quindi potresti passare una singola istanza della classe ... difficile da dire solo da questo tipo di discussione ad alto livello.

modifica: in c - le classi sarebbero simili alle strutture in questo caso.

+0

C non ha classi. –

+0

meh - ho appena notato 'c' - quindi la lezione di classe potrebbe farmi dei commenti negativi. – Randy

+0

ma hai sparpagliato un'idea: passare a OOP non è male ... o prendere in prestito qualcosa dal mondo OO anche in C, potrebbe essere una buona idea, a seconda del problema reale dell'utente. - qualcosa di simile a ciò che Eli Bendersky ha già spiegato nella sua risposta – ShinTakezou

5

Un numero elevato di argomenti in una chiamata di funzione di solito mostra un problema di progettazione. Esistono modi per ridurre apparentemente il numero di parametri, con mezzi come la creazione di strutture che vengono trasmesse al posto delle singole variabili o aventi variabili globali. Ti consiglierei di NON fare nessuna di queste cose e di occuparti del design. Nessuna correzione rapida o semplice, ma le persone che devono mantenere il codice ti ringrazieranno per questo.

+0

Grazie Brian, questa non essendo un'API di basso livello e anche il progetto essendo agile, implementerà la struttura e chiederà all'utente di compilare il membro dettagli. – Amit

+0

+1, risposta corretta. Ho cercato di elaborare un po 'nella mia risposta. Era troppo lungo per un commento. –

+1

Kumar, forse non mi sono reso chiaro; Ti raccomando di non farlo. Ti suggerirei di chiarire la tua progettazione per evitare di passare masse di parametri; passare una struttura per nascondere il gran numero di parametri finirà solo in lacrime. Esempio: quali parametri ha la seguente chiamata hanno ... int do_foo (foo_params struct * params) { /* qualcosa */ } –

0

Se l'API sembra macchinosa con molti parametri, utilizzare un puntatore a una struttura per passare i parametri che sono relativi a in qualche modo.

Non ho intenzione di giudicare il tuo progetto senza vederlo in prima persona. Esistono funzioni legittime che richiedono un numero elevato di parametri, ad esempio:

  1. Filtri con un numero elevato di coefficienti polinomiali.
  2. Lo spazio colore si trasforma con maschere sub-pixel e indici LUT.
  3. Aritmetica geometrica per poligoni irregolari n-dimensionali.

Ci sono anche alcuni progetti molto poveri che portano a grandi prototipi di parametri. Condividi maggiori informazioni sul tuo design se cerchi risposte più precise.

6

Se un numero di argomenti può essere raggruppato logicamente, si può prendere in considerazione la creazione di una struttura che li contiene e semplicemente passare tale struttura come argomento. Ad esempio, invece di passare due valori di coordinate x e y, potresti invece passare una struttura POINT.

Ma se un tale raggruppamento non è applicabile, allora qualsiasi numero di argomenti dovrebbe andare bene se davvero ne hai bisogno, anche se potrebbe essere un segnale che la tua funzione fa un po 'troppo e che potresti diffondere il lavoro su più , ma funzioni più piccole.

+0

Amardeep, Johannes, penso che potrebbe essere poco saggio suggerire questo, nel caso in cui le funzioni finiscano con parametri come struct bucket {int this, char * che, float the_other}, come ho sofferto dai miei ex colleghi per questo motivo su progetti precedenti. E 'un po' un mio piccolo sospetto, quindi spero che mi perdonerai di parlarne. –

+1

@Brian Hooper: i tuoi dubbi sono validi. Quando si danno consigli è sempre rischioso poiché il consulente è libero di fare qualcosa di orribile con qualunque cosa offri. Penso che sia per questo che entrambi i suggerimenti consigliano che i raggruppamenti siano logici o correlati in qualche modo. –

+1

In C++ potrei essere d'accordo. In C, farlo è spesso più di un PITA di quanto valga. Se deve passare 8 righe di codice prima e dopo la chiamata per impacchettare e decomprimere la struttura, che cosa ha guadagnato? –

0

Uno schema utilizzato da alcune API (come pthreads) è "oggetti attributo" che vengono passati a funzioni anziché a un gruppo di argomenti discreti. Questi oggetti attributo sono strutture opache, con funzioni per creare, distruggere, modificare e interrogare. Tutto sommato, questo richiede più codice che semplicemente scaricare 10 argomenti in una funzione, ma è un approccio più solido. A volte un po 'di codice in più che può rendere il tuo codice molto più comprensibile vale lo sforzo.

Ancora una volta, per un buon esempio di questo modello, vedere l'API pthreads. Ho scritto a lengthy article sul design dell'API pthreads un paio di mesi fa, e questo è uno degli aspetti che ho affrontato.

0

Sembra anche interessante considerare questa domanda non dal punto di vista della bruttezza/non della bruttezza, ma dal punto di vista della performance.

So che ci sono alcuni x86 calling conventions che possono utilizzare i registri per passare due primi argomenti e lo stack per tutti gli altri argomenti. Quindi penso che se si usa questo tipo di convenzione di chiamata e si usi sempre un puntatore per strutturare per passare argomenti in situazioni quando una funzione ha bisogno di più di 2 parametri nel complesso, la chiamata di funzione potrebbe essere più veloce. Sui registri Itanium vengono sempre utilizzati per passare i parametri a una funzione.

Penso che valga la pena provare.

+0

vale la pena provare anche per sapere quali convenzioni sono in uso, anche se penso che il "classico" "cdecl" sia ampiamente utilizzato anche al di fuori del mondo x86 (è stato ad esempio utilizzato per implementazioni C che ho visto su Amiga, erano il sistema operativo API utilizza registri ed evita l'uso di pragma (per specificare i registri da utilizzare) che hanno wrapper che hanno preso argomenti dallo stack e li hanno spostati in registri per chiamare questa o quella funzione di sistema. – ShinTakezou

4

Sì, 8 è quasi certamente troppo.

Ecco alcuni termini di ingegneria del software della vecchia scuola per voi. Coesione e accoppiamento Cohesion è il modo in cui una subroutine tiene insieme da sola, e coupling è la pulizia delle interfacce tra le routine (o quanto sono autosufficienti le routine).

Con l'accoppiamento, generalmente il più flessibile, meglio è. L'interfacciamento solo tramite parametri ("accoppiamento dati") è un buon accoppiamento basso, mentre l'uso di variabili globali ("accoppiamento comune") è un accoppiamento molto elevato. Quando si dispone di un numero elevato di parametri, ciò che è solitamente il caso è che qualcuno ha cercato di nascondere il loro accoppiamento comune con un rivestimento sottile di accoppiamento dati. Il suo cattivo design con un lavoro di verniciatura.

Con coesione, più alto (più coesivo), meglio è. Qualsiasi routine che modifica otto cose diverse è anche abbastanza facile da soffrire di bassa coesione. Dovrei vedere il codice per vedere di sicuro, ma sarei disposto a scommettere che sarebbe molto difficile spiegare chiaramente cosa fa questa routine in una frase breve. Vista invisibile, direi che è temporalmente coesa (solo un mucchio di cose che devono essere fatte all'incirca nello stesso periodo).

+1

Vorrei raccomandare "Structured Design" di Yourdon e Costantino come il libro da leggere su questo argomento: è molto vecchio, ma molto saggio. –

1

Suggerisco anche l'uso di strutture. Forse vuoi ripensare il design delle tue API.

Ricordare che le API verranno utilizzate dagli sviluppatori e sarebbe difficile utilizzare una chiamata di funzione a 8 parametri.

+0

Esattamente, questo è ciò che mi ha messo a disagio. – Amit

0

Un'altra cosa che puoi fare è convertire alcuni dei parametri per dichiarare. funzioni OpenGL avere un minor numero di parametri perché hai chiamate come:

glBindFramebuffer(..) ; 
glVertexAttribPointer(..) ; 
glBindTexture(..) ; 
glBindBuffer(..) ; 
glBindVertexArray(..) ; 

// the actual draw call 
glDrawArrays(..) ; 

Tutti questi (chiamate glBind* tipo) rappresentare "cambiamento di stato", ognuno dei quali influenzeranno la chiamata sorteggio successivo. Immagina una chiamata con 20 argomenti, assolutamente ingestibili!

Anche la vecchia API di Windows C per il disegno era di stato, memorizzata negli oggetti "puntatore opaco" (HDC, HWND ..). Un puntatore opaco è fondamentalmente il modo in cui C crea membri di dati privati ​​a cui non è possibile accedere direttamente. Ad esempio, nell'API di disegno di Windows, si creerebbe un puntatore opaco HDC tramite createDC. È possibile impostare i valori interni della DC tramite le funzioni , ad esempio SetDCBrushColor.

Ora che si dispone di un controller DC con un colore e tutto, è possibile utilizzare la funzione Rectangle per disegnare nella CC. Hai passato un HDC come primo parametro, che conteneva informazioni su quale pennello colore usare ecc. Rectangle prende solo 5 parametri, lo hdc, x, y, larghezza e altezza.