¿Cómo debería estructurar proyectos complejos en C? [cerrado]

79

Tengo poco más que un nivel de principiante en C y me gustaría saber si existen "estándares" de facto para estructurar una aplicación algo compleja en C. Incluso las basadas en GUI.

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

¿Tiene alguna lectura que sugerir? No pude encontrar ningún marco de aplicación para C, incluso si no uso marcos, siempre he encontrado buenas ideas al navegar por su código.

Stephen
fuente
3
La filosofía Unix es bastante útil para organizar grandes proyectos: http://www.faqs.org/docs/artu/ch01s06.html
newDelete

Respuestas:

51

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

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

Si tiene tiempo para aprender, eche un vistazo a cómo está estructurada una aplicación Ada, con su package(interfaz de módulo) obligatoria y package body(implementación de módulo).

Esto es para codificar.

Para mantener (recuerde que codifica una vez, pero mantiene varias veces) le sugiero que documente su código; Doxygen es una buena elección para mí. Sugiero también crear un conjunto de pruebas de regresión sólido, que le permita refactorizar.

Mouviciel
fuente
30

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

Una de las bases del diseño de un sistema robusto es la encapsulación de una implementación detrás de una interfaz. FILE*y las funciones que trabajan 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 puede hacer cumplir que nadie mire dentro de a struct FILE, pero solo un masoquista lo haría).

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

j_random_hacker
fuente
11
Un codificador C puro se perdería leyendo este código ...
mouviciel
15
@mouviciel: ¡Basura! La mayoría de los codificadores de C entienden los punteros de función (o al menos deberían hacerlo), y aquí no hay nada más que eso. En Windows, al menos, los controladores de dispositivo y los objetos COM proporcionan su funcionalidad de esta manera.
j_random_hacker
8
Mi punto no se trata de incompetencia, se trata de complicaciones innecesarias. 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 construir pseudo clases de C ++. Dicho esto, su enfoque puede ser útil en algunos casos.
mouviciel
3
En realidad, incluso puede simular especificadores de acceso utilizando el idioma pimpl de C ++. Si los miembros privados de un tipo están encapsulados en un tipo que solo es visible en la implementación (también conocido como "el archivo .c"), los usuarios de la interfaz tendrán dificultades para cambiarlos (por supuesto, pueden escribir basura en el puntero pimpl, si quieren joderte intencionalmente , pero puedes hacer lo mismo en C ++).
máscara de bits
2
Votante negativo: ¿te importaría comentar?
j_random_hacker
15

Todas buenas respuestas.

Solo agregaría "minimizar la estructura de datos". Esto podría ser incluso 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 un desperdicio.

Por ejemplo, suponga 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 la matriz haciendo funciones LineTo, escalando los datos a medida que avanza para convertirlos a coordenadas de píxeles.

Lo que he visto demasiadas veces es que, dado que el gráfico consta de puntos y líneas, la gente construirá una estructura de datos que consta de objetos puntuales y objetos de línea, cada uno capaz de DrawMyself, y luego lo hará persistente, en la teoría de que eso es de alguna manera "más eficiente", o que quizás, solo tal vez, tengan que poder pasar el mouse sobre partes del gráfico y mostrar los datos numéricamente, por lo que crean métodos en los objetos para lidiar con eso, y eso, por supuesto, implica la creación y eliminación de aún más objetos.

Así que terminas con una gran cantidad de código que es muy legible y simplemente pasa el 90% de su tiempo administrando objetos.

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

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

Mike Dunlavey
fuente
1
Amo tu respuesta, y es totalmente cierto. ¡OOP debe morir!
Jo So
@JoSo: uso OOP, pero mínimamente.
Mike Dunlavey
Para mí, "mínimamente" (aunque no sé lo que eso significa para usted) no cuenta realmente como POO, que es programación orientada a objetos, objetos por defecto.
Jo So
También uso algo de "OOP". Principalmente para mis módulos C, de los cuales muchos tienen rutinas init () y exit ().
Jo So
11

Los estándares de codificación GNU han evolucionado durante un par de décadas. Sería una buena idea leerlos, incluso si no los sigue al pie de la letra. Pensar en los puntos planteados en ellos le da una base más sólida sobre cómo estructurar su propio código.


fuente
4
No a todo el mundo le gustan, de lxr.linux.no/linux+v2.6.29/Documentation/CodingStyle : "En primer lugar, sugeriría imprimir una copia de los estándares de codificación GNU y NO leerla. Quemarlos, es un gran gesto simbólico ". No los he leído en muchos años, pero Linus tiene algunas objeciones válidas.
hlovdal
1
@hlovdal: Por supuesto que no a todo el mundo le 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 sea coherente con sus propios proyectos, que se siga al menos algún estándar, en lugar de una inconsistencia ad hoc de facto.
JM Becker
4

Si sabe cómo estructurar su código en Java o C ++, puede seguir los mismos principios con el código C. La única diferencia es que no tiene el compilador a su lado y necesita hacer todo con más cuidado manualmente.

Dado que no hay paquetes ni clases, debe comenzar diseñando 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, anteponga 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 necesitan exportarse vayan al archivo de encabezado público.

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

kgiannakakis
fuente
4

La encapsulación es siempre 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".

Nate
fuente
3

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

Ivan Krechetov
fuente
3

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

Para simplificar las aplicaciones complejas, utilizo Divide y vencerás .

Mr Valdez
fuente
2

Sugeriría leer un libro de texto de C / C ++ como primer paso. Por ejemplo, C Primer Plus es una buena referencia. Mirar los ejemplos le dará una idea de cómo mapear su Java OO a un lenguaje más procedimental como C.

Tosa
fuente