Estou avaliando uma biblioteca cuja API pública se parece atualmente com esta:
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);
Observe que enh
é um identificador genérico, usado como identificador para vários tipos de dados diferentes ( mecanismos e ganchos ).
Internamente, é claro que a maioria dessas APIs lança o "identificador" para uma estrutura interna que eles 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); }
Pessoalmente, odeio esconder as coisas por trás de typedef
s, especialmente quando isso compromete a segurança do tipo. (Dado um enh
, como sei o que realmente está se referindo?)
Então, enviei uma solicitação pull, sugerindo a seguinte alteração na API (depois de modificar a biblioteca inteira para estar em conformidade):
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);
Obviamente, isso faz com que as implementações internas da API pareçam muito melhores, eliminando conversões e mantendo a segurança do tipo de / para a perspectiva do consumidor.
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);
}
Eu prefiro isso pelos seguintes motivos:
- Adicionado tipo de segurança
- Maior clareza dos tipos e sua finalidade
- Eliminações removidas
typedef
es - Segue o padrão recomendado para tipos opacos em C
No entanto, o proprietário do projeto recusou a solicitação de recebimento (parafraseada):
Pessoalmente, não gosto da ideia de expor o
struct engine
. Ainda acho que a maneira atual é mais limpa e amigável.Inicialmente, usei outro tipo de dados para identificador de gancho, mas depois decidi mudar para usar
enh
, para que todos os tipos de identificadores compartilhem o mesmo tipo de dados para simplificar. Se isso é confuso, certamente podemos usar outro tipo de dados.Vamos ver o que os outros pensam sobre este PR.
Atualmente, essa biblioteca está em um estágio beta privado, portanto, ainda não há muito código do consumidor com o que se preocupar. Além disso, ofusquei os nomes um pouco.
Como um identificador opaco é melhor do que um struct opaco nomeado?
Nota: Eu fiz essa pergunta na Code Review , onde foi fechada.