Sto valutando una libreria la cui API pubblica è attualmente simile a questa:
libengine.h
/* Handle, used for all APIs */ typedef size_t enh; /* Create new engine instance; result returned in handle */ int en_open(int mode, enh *handle); /* Start an engine */ int en_start(enh handle); /* Add a new hook to the engine; hook handle returned in h2 */ int en_add_hook(enh handle, int hooknum, enh *h2);
Si noti che enh
è un handle generico, utilizzato come handle per diversi tipi di dati ( motori e hook ).
Internamente, la maggior parte di queste API ovviamente lancia il "handle" su una struttura interna che hanno malloc
:
engine.c
struct engine { // ... implementation details ... }; int en_open(int mode, *enh handle) { struct engine *en; en = malloc(sizeof(*en)); if (!en) return -1; // ...initialization... *handle = (enh)en; return 0; } int en_start(enh handle) { struct engine *en = (struct engine*)handle; return en->start(en); }
Personalmente, odio nascondere le cose dietro di typedef
s, specialmente quando compromette la sicurezza dei tipi. (Dato un enh
, come faccio a sapere a cosa si riferisce effettivamente?)
Quindi ho inviato una richiesta pull, suggerendo la seguente modifica API (dopo aver modificato l' intera libreria per renderla conforme):
libengine.h
struct engine; /* Forward declaration */
typedef size_t hook_h; /* Still a handle, for other reasons */
/* Create new engine instance, result returned in en */
int en_open(int mode, struct engine **en);
/* Start an engine */
int en_start(struct engine *en);
/* Add a new hook to the engine; hook handle returned in hh */
int en_add_hook(struct engine *en, int hooknum, hook_h *hh);
Naturalmente, questo rende le implementazioni API interne molto migliori, eliminando i cast e mantenendo la sicurezza dei tipi dalla / alla prospettiva del consumatore.
libengine.c
struct engine
{
// ... implementation details ...
};
int en_open(int mode, struct engine **en)
{
struct engine *_e;
_e = malloc(sizeof(*_e));
if (!_e)
return -1;
// ...initialization...
*en = _e;
return 0;
}
int en_start(struct engine *en)
{
return en->start(en);
}
Preferisco questo per i seguenti motivi:
- Aggiunto tipo di sicurezza
- Migliore chiarezza dei tipi e dei loro scopi
- Cast e
typedef
s rimossi - Segue il modello raccomandato per i tipi opachi in C
Tuttavia, il proprietario del progetto si è opposto alla richiesta pull (parafrasato):
Personalmente non mi piace l'idea di esporre il
struct engine
. Penso ancora che il modo attuale sia più pulito e più amichevole.Inizialmente ho usato un altro tipo di dati per l'handle hook, ma poi ho deciso di passare all'uso
enh
, quindi tutti i tipi di handle condividono lo stesso tipo di dati per mantenerlo semplice. Se questo è confuso, possiamo certamente usare un altro tipo di dati.Vediamo cosa ne pensano gli altri di questo PR.
Questa libreria è attualmente in una fase beta privata, quindi non c'è molto codice di cui preoccuparsi (ancora). Inoltre, ho offuscato un po 'i nomi.
In che modo una maniglia opaca è migliore di una struttura opaca con nome?
Nota: ho posto questa domanda a Code Review , dove era chiusa.