2009-03-19 22 views
64

Tengo poco más que habilidades de nivel C para principiantes y me gustaría saber si hay algún "estándar" de facto para estructurar una aplicación algo compleja en C. Incluso las basadas en GUI.¿Cómo debería estructurar proyectos complejos en C?

Siempre he estado usando el paradigma de OO en Java y PHP y ahora que quiero aprender C. Me temo que podría estructurar mis aplicaciones de la manera incorrecta. No sé qué pautas seguir para modularidad, desacoplamiento y sequedad con un lenguaje de procedimiento.

¿Tiene alguna lectura que sugerir? No pude encontrar ningún marco de aplicación para C, incluso si no uso frameworks, siempre encontré buenas ideas navegando en su código.

+2

La filosofía de Unix es muy útil para organizar proyectos grandes: [http://www.faqs.org/docs /artu/ch01s06.html](http://www.faqs.org/docs/artu/ch01s06.html) – newDelete

Respuesta

2

Te sugiero que revises el código de cualquier proyecto C de código abierto popular, como ... hmm ... Linux kernel, o Git; y mira cómo lo organizan.

2

La regla numérica para aplicaciones complejas: debe ser fácil de leer.

Para hacer la aplicación compleja simplier, utilizo Divide and conquer.

43

La clave es la modularidad. Esto es más fácil de diseñar, implementar, compilar y mantener.

  • Identifique los módulos en su aplicación, como las clases en una aplicación OO.
  • Interfaz e implementación separadas para cada módulo, poner en interfaz solo lo que necesitan otros módulos. Recuerde que no hay espacio de nombre en C, por lo que debe hacer que todo en sus interfaces sea único (por ejemplo, con un prefijo).
  • Ocultar variables globales en la implementación y usar funciones de acceso para lectura/escritura.
  • No piense en términos de herencia, pero en términos de composición. Como regla general, no intente imitar C++ en C, esto sería muy difícil de leer y mantener.

Si usted tiene tiempo para el aprendizaje, echar un vistazo a cómo se estructura una aplicación Ada, con su obligatoria package (módulo de interfaz) y package body (implementación del módulo).

Esto es para la codificación.

Para mantener (recuerde que codifica una vez, pero la mantiene varias veces), sugiero que documente su código; Doxygen es una buena opción para mí. También sugiero construir un conjunto de pruebas de regresión fuerte, que le permita refactorizar.

10

La GNU coding standards ha evolucionado en un par de décadas. Sería una buena idea leerlos, incluso si no los sigues al pie de la letra. Pensando en los puntos planteados en ellos te da una base más firme sobre cómo estructurar tu propio código.

+4

No a todos les gustan, de http://lxr.linux.no/linux+v2.6.29/Documentation/CodingStyle: "En primer lugar, sugiero imprimir una copia de los estándares de codificación GNU, y NO leerlo. Grabarlos, es un gran gesto simbólico". No los he leído en muchos años, pero Linus tiene algunas objeciones válidas. – hlovdal

+0

@hlovdal: Por supuesto, no a todos les gusta un estándar de codificación en particular, por eso hay más de un estándar para casos de uso similares. La parte importante es que su coherencia dentro de sus propios proyectos, que se siga al menos algún estándar, en lugar de incoherencia ad hoc de facto. – TechZilla

27

Es un concepto erróneo común que las técnicas de OO no se pueden aplicar en C. La mayoría puede, es solo que son un poco más difíciles de manejar que en los lenguajes con sintaxis dedicada al trabajo.

Uno de los fundamentos del diseño robusto del sistema es la encapsulación de una implementación detrás de una interfaz. FILE* y las funciones que funcionan con él (fopen(), fread() etc.) es un buen ejemplo de cómo se puede aplicar la encapsulación en C para establecer interfaces. (Por supuesto, dado que C carece de especificadores de acceso, no se puede forzar que nadie mire dentro de un struct FILE, pero solo un masoquista lo haría.)

Si es necesario, se puede tener un comportamiento polimórfico en C usando tablas de indicadores de función. Sí, la sintaxis es feo, pero el efecto es el mismo que las funciones virtuales:

struct IAnimal { 
    int (*eat)(int food); 
    int (*sleep)(int secs); 
}; 

/* "Subclass"/"implement" IAnimal, relying on C's guaranteed equivalence 
* of memory layouts */ 
struct Cat { 
    struct IAnimal _base; 
    int (*meow)(void); 
}; 

int cat_eat(int food) { ... } 
int cat_sleep(int secs) { ... } 
int cat_meow(void) { ... } 

/* "Constructor" */ 
struct Cat* CreateACat(void) { 
    struct Cat* x = (Cat*) malloc(sizeof (struct Cat)); 
    x->_base.eat = cat_eat; 
    x->_base.sleep = cat_sleep; 
    x->meow = cat_meow; 
} 

struct IAnimal* pa = CreateACat(); 
pa->eat(42);      /* Calls cat_eat() */ 

((struct Cat*) pa)->meow();  /* "Downcast" */ 
+10

Un codificador de C puro se pierde al leer este código ... – mouviciel

+14

@mouviciel: ¡Basura! La mayoría de los codificadores C entienden los indicadores de función (o al menos deberían), y no hay nada más allá de eso aquí. Al menos en Windows, los controladores de dispositivo y los objetos COM proporcionan su funcionalidad de esta manera. –

+8

Mi punto no es sobre la incompetencia, se trata de una complicación innecesaria. Los punteros de función son comunes para un codificador C (por ejemplo, devoluciones de llamada), la herencia no lo es. Prefiero que un codificador de C++ que codifica en C use su tiempo para aprender C que para crear clases de pseudo C++. Dicho esto, su enfoque puede ser útil en algunos casos. – mouviciel

3

Si sabe cómo estructurar su código en Java o C++, a continuación, puede seguir los mismos principios con el código C. La única diferencia es que no tienes el compilador a tu lado y tienes que hacer todo extra cuidadosamente de forma manual.

Como no hay paquetes ni clases, debe comenzar por diseñar cuidadosamente sus módulos. El enfoque más común es crear una carpeta de origen separada para cada módulo. Debe confiar en las convenciones de nomenclatura para diferenciar el código entre diferentes módulos. Por ejemplo, prefija todas las funciones con el nombre del módulo.

No puede tener clases con C, pero puede implementar fácilmente "Tipos de datos abstractos". Crea un archivo .C y .H para cada tipo de datos abstracto. Si lo prefiere, puede tener dos archivos de encabezado, uno público y otro privado. La idea es que todas las estructuras, constantes y funciones que se deben exportar vayan al archivo de encabezado público.

Sus herramientas también son muy importantes. Una herramienta útil para C es lint, que puede ayudarlo a encontrar malos olores en su código. Otra herramienta que puedes usar es Doxygen, que puede ayudarte a generar documentation.

2

La encapsulación siempre es clave para un desarrollo exitoso, independientemente del lenguaje de desarrollo.

Un truco que he usado para ayudar a encapsular métodos "privados" en C es no incluir sus prototipos en el archivo ".h".

12

Todas las buenas respuestas.

Solo agregaría "minimizar estructura de datos". Esto podría ser aún más fácil en C, porque si C++ es "C con clases", OOP está tratando de alentarlo a tomar cada sustantivo/verbo en su cabeza y convertirlo en una clase/método. Eso puede ser muy derrochador.

Por ejemplo, supongamos que tiene una matriz de lecturas de temperatura en puntos en el tiempo, y desea mostrarlas como un gráfico de líneas en Windows. Windows tiene un mensaje PAINT, y cuando lo recibe, puede recorrer el conjunto haciendo funciones LineTo, escalando los datos a medida que avanza para convertirlos en coordenadas de píxeles.

Lo que he visto demasiadas veces es que, dado que el cuadro consta de puntos y líneas, las personas construirán una estructura de datos que consta de objetos puntuales y objetos de línea, cada uno capaz de dibujar yo mismo, y luego hacerlo persistente, en la teoría de que eso es de alguna manera "más eficiente", o que quizás, tal vez, tienen que poder pasar el ratón sobre partes del gráfico y mostrar los datos numéricamente, de modo que construyen métodos en los objetos para manejar eso, y eso , por supuesto, implica crear y eliminar aún más objetos.

Así que terminas con una gran cantidad de código que es muy fácil de leer y simplemente gasta el 90% de su tiempo en la gestión de objetos.

Todo esto se hace en nombre de "buenas prácticas de programación" y "eficiencia".

Al menos en C, la manera simple y eficiente será más obvia, y la tentación de construir pirámides será menos fuerte.

+0

Me encanta tu respuesta, y es totalmente cierto. OOP debe morir! –

+0

@JoSo: uso OOP, pero mínimamente. –

+0

Para mí "mínimamente" (aunque no sé lo que eso significa para usted) realmente no cuenta como programación orientada a objetos, que está orientada a objetos *, objetos por defecto. –

2

Sugiero leer un libro de texto C/C++ como primer paso. Por ejemplo, C Primer Plus es una buena referencia.Si examina los ejemplos, obtendrá una idea sobre cómo asignar su OO de Java a un lenguaje más procesal, como C.

Cuestiones relacionadas