Design di denominazione delle funzioni in una libreria C.

2

Sto scrivendo una libreria C (c99) che opera su stringhe. Sto riscontrando un problema di progettazione durante la scrittura di funzioni che verranno eseguite in diverse modalità.

Ad esempio; la funzione Trova può cercare:

  • il primo, ultimo, n-esimo elemento
  • caso di lettere diverse
  • restituisce la posizione prima o dopo l'elemento trovato
  • cerca in una sottostringa

Queste modalità causano la denominazione della funzione esponenziale. La mia soluzione è una funzione per ogni combinazione di modalità:

Find(), FindCase(), FindSubstring(), FindSubstringCase(), FindSubstringCaseEnd(), FindEnd(), ...

e così via fino a quando non viene creata ogni combinazione. Ho scritto le funzioni di ricerca in un modo in cui ogni modalità è solo un wrapper di una funzione di ricerca generica chiamata internamente per evitare la duplicazione. Ciò ha reso facile aggiungere nuove modalità di funzionamento, l'unico svantaggio è il conteggio delle funzioni insolito.

Non penso che verranno aggiunte altre modalità, quindi il conteggio della funzione corrente è a due cifre. Ancora non sono sicuro che sia la soluzione migliore.

Le soluzioni alternative che ho considerato e che non ho trovato degne sono:

Avere una funzione generica con molti parametri. Questo è inferiore perché è più facile ricordare il nome della funzione rispetto ai suoi parametri. È anche soggetto a errori e pone un carico inutile all'utente.

Avere una funzione che accetta una serie di modalità (come fopen ()). Stessi problemi di cui sopra.

Le funzioni degli argomenti variabili non meritano di essere considerate.

Penso che la mia soluzione sia decente, dal momento che l'utente deve solo ricordare il nome, la denominazione è coerente, quindi puoi "indovinare" il nome e non puoi davvero commettere un errore. Nel peggiore dei casi è necessario cercare il nome e il nome potrebbe contenere fino a ~ 20 caratteri, che richiede comunque meno spazio rispetto ai parametri aggiuntivi.

Nota inoltre che questo problema si applica ad altre funzioni (inserisci, cancella, ...) in misura minore.

Tuttavia, sto cercando soluzioni migliori se ce ne sono.

    
posta reader 22.03.2015 - 10:19
fonte

2 risposte

3

La quantità di variabili (e quindi la grandezza dell'esplosione combinatoria) può essere ridotta con un'API più generale e modulare. Ad esempio, invece di creare Find supporta sottostringhe, crea una funzione separata che estrae una sottostringa da una stringa. Invece di restituire l'inizio della fine della partita, restituisci sempre l'inizio e rendi più facile ed economico calcolare la fine da quella.

Se è un'implementazione generica a cui delegare tutte le varianti, dovresti probabilmente esporla, se solo per consentire al codice client di chiamare tale funzione con la configurazione di run-time (ad esempio, ignora caso o meno a seconda del valore di una variabile). Quindi è ancora possibile aggiungere funzioni di convenienza per casi comuni mentre si selezionano quelli non comuni. printf è solo fprintf con il primo argomento fissato a stdout .

    
risposta data 22.03.2015 - 12:02
fonte
5

Utilizzando i nomi delle funzioni per distinguere tra i comportamenti n vero / falso si ottengono 2 funzioni n . Questo è facile da gestire quando n è un valore piccolo come 2 o 3, ma diventa molto rapidamente fuori mano con qualcosa di più grande. La chiamata POSIX open (2) ha nove varianti true / false, e se ciò è stato scritto come una funzione per combinazione, starai a guardare 512 funzioni tra cui scegliere.

Scegli con nome fa un sacco di lavoro per i chiamanti della tua funzione. Se il mio programma ha una finestra di dialogo con una casella di testo che consente all'utente di inserire una stringa e due caselle di controllo per abilitare le varianti "case" e "fine", il mio codice finisce per essere simile a questo:

char * string = get_pointer_to_string_typed_in_dialog_box();
_bool case_checked = dialogbox_case_checked();
_bool end_checked = dialogbox_end_checked();

if ( !case_checked && !end_checked ) { Find(string); }
else if ( case_checked && !end_checked ) { FindCase(string); }
else if ( !case_checked && end_checked ) { FindEnd(string); }
else if ( case_checked && end_checked ) { FindCaseEnd(string); }
else { assert(false);  // This shouldn't happen. }

Aggiungi una terza casella di controllo e la dimensione di quel else if ladder raddoppia e aggiunge un controllo aggiuntivo a ciascuna condizione. Moltiplichiamo per il numero di volte che la funzione viene chiamata e puoi praticamente vedere dove è diretto.

Una cosa che è praticamente idiomatica in C (e il linguaggio assembly da cui è disceso) è l'uso di flag a singolo bit ORed insieme in un intero per abilitare uno o più comportamenti:

typedef size_t FindFlags;

#define FIND_CASE        ((FindFlags)1 << 0)
#define FIND_SUBSTRING   ((FindFlags)1 << 1)
#define FIND_END         ((FindFlags)1 << 2)
// Should do a static assertion to make sure the highest bit
// used isn't beyond what FindFlags can hold.

SomeReturnType find(char * string, FindFlags flags);

Qualcuno che vuole chiamare la tua funzione usa lo stesso identificatore ogni volta e assembla i flag di cui ha bisogno:

void find(some_string, FIND_END | FIND_CASE);

Il vantaggio nascosto in questo è che consente di creare una serie di flag a livello di codice senza la mostruosità else if mostrata sopra:

char * string = get_pointer_to_string_typed_in_dialog_box();
FindFlags flags = 0;
if ( dialogbox_case_checked() ) { flags |= FIND_CASE; }
if ( dialogbox_end_checked()  ) { flags |= FIND_END; }
find(string, flags);

In the worst case you have to look-up the name...

Questo è un caso molto peggiore di quello che pensi sia. Dovresti stabilire una convenzione per come le combinazioni sono messe insieme. L'ordine alfabetico ( FindCase() , FindCaseEnd() , FindCaseEndSubstring() ) sarebbe intuitivo. Se stai seguendo le linee guida che dicono che dovresti scrivere il tuo codice come se le persone che lo utilizzano fossero psicotici e sapessero dove vivi, costringendoli ad alfabetizzare la loro lista di comportamenti desiderati solo per capire il nome della funzione è una ricetta per ricevere visite da sviluppatori che brandiscono l'ascia.

... and the name could be up to ~20 characters, which still takes less space than additional parameters.

A meno che non stiate pianificando di eseguire questo codice nel 1963, anche se lo sviluppatore deve digitare una dozzina di caratteri aggiuntivi per una chiamata di funzione, non dovrebbe nemmeno essere nel vostro radar. Rendilo leggibile e facile da usare per i tuoi chiamanti.

    
risposta data 22.03.2015 - 15:08
fonte

Leggi altre domande sui tag