Proyecto C evitando conflictos de nombres

13

Estoy luchando por encontrar consejos pragmáticos del mundo real sobre convenciones de nombres de funciones para un proyecto de biblioteca C de tamaño mediano. El proyecto de mi biblioteca está separado en unos pocos módulos y submódulos con sus propios encabezados, y sigue libremente un estilo OO (todas las funciones toman una cierta estructura como primer argumento, no globales, etc.). Se ha puesto algo así como:

MyLib
  - Foo
    - foo.h
    - foo_internal.h
    - some_foo_action.c
    - another_foo_action.c
    - Baz
      - baz.h
      - some_baz_action.c
  - Bar
    - bar.h
    - bar_internal.h
    - some_bar_action.c

En general, las funciones son demasiado grandes para (por ejemplo) pegarse some_foo_actiony another_foo_actionen un foo.carchivo de implementación, hacer que la mayoría de las funciones sean estáticas y llamarlo un día.

Puedo tratar de quitar mis símbolos internos ("módulo privado") cuando construyo la biblioteca para evitar conflictos para mis usuarios con sus programas cliente, pero la pregunta es cómo nombrar símbolos en mi biblioteca. Hasta ahora he estado haciendo:

struct MyLibFoo;
void MyLibFooSomeAction(MyLibFoo *foo, ...);

struct MyLibBar;
void MyLibBarAnAction(MyLibBar *bar, ...);

// Submodule
struct MyLibFooBaz;
void MyLibFooBazAnotherAction(MyLibFooBaz *baz, ...);

Pero estoy terminando con nombres de símbolos largos y locos (mucho más largos que los ejemplos). Si no prefijo los nombres con un "espacio de nombres falso", los nombres de los símbolos internos de los módulos chocan.

Nota: No me importa el caso Camelcase / Pascal, etc., solo los nombres en sí.

Dan Halliday
fuente

Respuestas:

10

Prefijar (bueno, fijar) es realmente la única opción. Algunos patrones que verá son <library>_<name>(por ejemplo, OpenGL, ObjC runtime), <module/class>_<name>(por ejemplo, partes de Linux), <library>_<module/class>_<name>(por ejemplo, GTK +). Tu esquema es perfectamente razonable.

Los nombres largos no son necesariamente malos si son predecibles. El hecho de que esté terminando con nombres largos y funciones locos que son demasiado grandes para seguir con funciones relacionadas en un solo archivo fuente plantea diferentes preocupaciones. ¿Tienes algunos ejemplos más concretos?

usuario2313838
fuente
Veo de dónde vienes: me he preguntado si soy demasiado pedante para dividirme en archivos separados, pero ayuda mucho con la legibilidad, el mantenimiento y el git mergerendimiento. Como ejemplo, tengo un módulo para dibujar la interfaz de usuario con OpenGL, y tengo .carchivos separados para cada elemento que necesito ( slider.c, indicator.cetc.). Estas implementaciones de elementos tienen una función de dibujo principal de unos cientos de líneas de largo y una buena cantidad de staticayudantes. También llaman a algunas funciones de geometría pura desde el módulo UI. ¿Suena bastante típico?
Dan Halliday
Un mejor ejemplo de los nombres largos podría ser mi módulo de audio: tengo una jerarquía como la Audio Module > Engines > Channels > Filtersque significa algo así MyLibAudioEngines<EngineName>Channel<ActionName>. O en mi submódulo de filtros: MyLibAudioFilters<FilterName><Type><Action>por ejemplo. MyLibAudioFiltersBigSoundingCompressorFloat32Process
Dan Halliday
Nada de eso parece irrazonable. Unos cientos de líneas para una función parecen un poco largas, pero si lo que está dibujando es complicado, puede ser difícil de evitar. ¿Es pesado o solo muchas instrucciones?
user2313838
Re: los nombres del módulo de audio, podría abreviar AudioFilters / AudioEngines, ya que creo que sería fácil saber si es un filtro o módulo basado en el nombre. Los calificadores de tipo de datos como Float32 también podrían abreviarse (por ejemplo, 'd', 'f') ya que tales abreviaturas son comunes en la programación en C.
user2313838
Gracias por sus respuestas, a veces parece casi imposible obtener buena información sobre la arquitectura del programa C (especialmente en comparación con los lenguajes de nivel superior). ¡Muchos libros de C que he leído apenas consideran la idea de tener múltiples módulos o incluso más de un archivo! En general, no creo que cambie mucho, excepto considerar si vivir con abreviaturas para algunos de los nombres más largos.
Dan Halliday
5

La convención habitual para las bibliotecas C es usar el nombre de la biblioteca como prefijo para los nombres que se pueden usar externamente, p. Ej.

struct MyLibFoo;
void MyLibAFooAction(...);

Para los nombres internos de la biblioteca que aún deben estar accesibles en varias unidades de la biblioteca, no existe una convención estricta, pero usaría un prefijo del nombre de la biblioteca y una indicación de que es una función interna. Por ejemplo:

struct MyLibInternalFooBaz;
void MyLibInternalFooBazAction();

Estoy de acuerdo en que esto puede conducir a nombres que son bastante largos y difíciles de escribir, pero desafortunadamente ese es el precio que tenemos que pagar por no tener un mecanismo como los espacios de nombres C ++. Para reducir la longitud de los nombres, a costa de cierta claridad, puede optar por usar abreviaturas en sus nombres, pero debe sopesar cuidadosamente las ventajas y desventajas.

Bart van Ingen Schenau
fuente
3

Si desea evitar los prefijos largos, puede abreviar los nombres de las bibliotecas como lo que Apple hace en iOS y OS X:

  • NSString es una cadena de las raíces NextStep del sistema operativo
  • CALayer es una capa de animación principal
Mouviciel
fuente
2

¿Qué pasa con tener una variable de estructura global precargada con punteros de función?

lib.h

#pragma once

typedef struct
{
    void (*doFoo)(int x);
    const char *(*doBar)(void *p);
} YourApi;

extern const YourApi yourApi;

lib.c:

#include "lib.h"

#include <stdio.h>

static void doFoo(int x)
{
    printf("Doing foo %d\n", x);
}

static const char *doBar(void *p)
{
    printf("Doing bar: %p\n", p);
    return "Hello";
}

const YourApi yourApi = {
    doFoo,
    doBar};

Aprovechar:

#include "lib.h"

int main()
{
    yourApi.doFoo(42);
    yourApi.doBar("asd");
}

La palabra clave estática limita el alcance a la unidad de traducción para que no choque con otros.

El usuario puede acortarlo usando un puntero como YourApi *ya = &yourApi, luego usando ya->doFoo(...).

También proporciona una buena manera de burlarse de su biblioteca para realizar pruebas.

Calmarius
fuente
Patrón genial, requiere un poco de repetitivo, pero esto hará que el autocompletado sea mucho más útil.
Rick Love