¿Hay alguna pauta comúnmente aceptada sobre cómo escribir una C moderna?

13

Tengo una sólida experiencia en Java / Groovy y he sido asignado a un equipo que mantiene una base de código C bastante grande para un software administrativo.

Algunos puntos débiles, como tratar con blob en la base de datos o generar informes en PDF y Excel, se han externalizado al servicio web de Java.

Sin embargo, como desarrollador de Java, estoy un poco confundido por algunos aspectos del código:

  • es detallado (especialmente cuando se trata de 'excepción')
  • hay muchos métodos enormes (muchos métodos de más de 2000 líneas)
  • no hay estructuras de datos avanzadas (extraño mucho List, Set and Map)
  • sin separación de preocupaciones (SQL se mezcla alegremente en todo el código)

Como resultado, siento que el negocio está oculto en toneladas de código técnico y mi cerebro, conformado con objetos orientados y una pizca de programación funcional, no está a gusto.

El lado bueno del proyecto es que el código es sencillo: no hay marco, no hay manipulación de código de bytes en tiempo de ejecución, no hay AOP. Y el servidor puede responder simultáneamente a más de 10000 usuarios con una sola máquina utilizando menos memoria de la que Java necesita para escupir "hola mundo".

Quiero aprender a escribir código C de acuerdo con los principios modernos comúnmente aceptados. ¿Hay algún principio comúnmente aceptado sobre cómo se debe escribir y estructurar la C moderna?

Algo parecido al equivalente del libro 'Java efectivo', pero para C.

Edite a la luz de las respuestas y comentarios:

  • Intentaré adaptar mi mentalidad al código C y no intentar reflejarlo en OOP.
  • Comencé a leer y leer las guías de estilo de codificación recomendadas del comentario (The GNU Coding Standards y The Linux Kernel Coding Style).
  • Luego trataré de proponer este estilo de código a mis compañeros de trabajo. La parte más difícil podría ser convencer a los compañeros de trabajo de que un método enorme podría dividirse en partes más pequeñas y que la ayuda de un método podría evitar las mismas 4 líneas de código de manejo de errores.
Guillaume
fuente
55
¿La aplicación realmente necesita modernizarse, o simplemente cree que lo hace porque la forma en que fue escrita no es familiar?
Blrfl
3
Posiblemente relevante: heredé 200 mil líneas de código de espagueti, ¿y ahora qué?
Dan Pichelman el
1
@Blrfl, creo que la aplicación se ha escrito con un estándar desactualizado. Solo quiero saber qué es hoy (2016) estándar para (administrativo) C. Si hay uno. No quiero refactorizar o remodelar la aplicación actual, quiero tener una idea de cómo debo escribir la siguiente parte del código.
Guillaume
3
@antlersoft: una función de 2,000 líneas que hace una larga lista de cosas simples, una tras otra, no es absolutamente ningún problema y no necesita una excusa. No responda con argumentos circulares como "no debe escribir 2,000 funciones de línea porque no debe escribir 2,000 funciones de línea".
gnasher729

Respuestas:

14

Puedo leer de su pregunta que el problema no es que el código sea viejo C sino que simplemente sea una mala programación. La mayoría de los problemas que mencionó, como la verbosidad, las enormes funciones de más de 2000 líneas o la no separación de preocupaciones, se aplican a cualquier lenguaje, C o Java por igual.

Se mencionó la verbosidad en el contexto del manejo de errores. No proporcionó un ejemplo, así que solo puedo recordar que el código de manejo de errores también es código . No hay excusa para las secciones repetitivas del código repetitivo. Factorizarlo; ya sea a una función o (si no vale la pena crear una función separada) haga el goto Error;patrón y mueva el manejo de errores y la limpieza de recursos a una Error:sección en la parte inferior de la función.

Si pasar el error por la cadena de llamadas parece ser el problema, pregúntese: ¿la función allí arriba realmente necesita saber que algún pequeño aquí abajo tuvo un problema? Los mecanismos de excepción integrados en un lenguaje facilitan hacerlo, pero en general es mejor manejar las excepciones temprano (en cualquier idioma) para que la condición de error no contamine la lógica del código de alto nivel. Y si la función allí arriba realmente necesita saber, hay formas de emular excepciones con setjmpy longjmp.

Creo que el único problema realmente relacionado con C mencionado es la falta de contenedores estándar. Si bien Seten general se puede reemplazar con una matriz ordenada y Map(en su mayor parte) con una matriz de pares o un struct(si conoce el conjunto de claves de antemano, se map[key] = valueconvierte en s.key = value), pero el hecho es que no hay un contenedor de matriz dinámica en el estándar biblioteca. En C99, al menos puede declarar una matriz de longitud variable en la pila ( int array[len]), pero necesita calcular de lenantemano (por lo general, no es difícil) y, por supuesto, no puede devolverla como un objeto asignado a la pila. La mayoría de los proyectos terminan escribiendo su propio contenedor de matriz dinámica o adoptando uno de código abierto.

En una nota final, me gustaría señalar que he estado allí. He sido el programador de Java que se mudó a C ++ y C. puro. Me gustaría aconsejar "leer el libro X para aprender un buen C", pero no hay ninguno como Java. El camino a seguir es absorber todas las particularidades del lenguaje y la biblioteca estándar; google mucho, lee mucho y codifica mucho hasta que comiences a pensar en C. Intentar escribir cosas en C como lo harías en Java es tan frustrante como intentar escribir una oración en un idioma extranjero con palabras directamente traducidas de tu madre lengua; tanto usted como el lector se avergonzarán. La buena noticia es que aprender una buena programación es lento, pero aprender otro idioma es rápido. así que si escribes código decente en Java,

Una lechuza
fuente
1
En general, esta es una muy buena respuesta. Simplemente me opondría a ver setjmp()/ longjmp()como una herramienta válida: ni siquiera intenta realizar ninguna limpieza. Cualquier asignación se filtrará, cualquier bloqueo retenido no se liberará, ningún archivo abierto se cerrará y cualquier inconsistencia de datos transitorios se volverá permanente. En mi humilde opinión, este par de funciones es básicamente el peor truco jamás inventado, con la única justificación de que fue posible implementarlo. Al final, en realidad solo hay una forma válida de manejar errores en C: códigos de error explícitos.
cmaster - reinstalar monica
@cmaster sí. Personalmente, setjmp/longjmpparece un pez fuera del agua en C y nunca los usé. Me sentí obligado a incluirlos solo debido a los numerosos tutoriales / bibliotecas en Internet para imitar excepciones, por lo que pensé que hay personas que realmente lo usan.
Un búho
7

El lado bueno del proyecto es que el código es sencillo: no hay marco, no hay manipulación de código de bytes en tiempo de ejecución, no hay AOP. Y el servidor puede responder simultáneamente a más de 10000 usuarios con una sola máquina utilizando menos memoria de la que Java necesita para escupir "hola mundo".

Le recomendaría que tenga cuidado de si vale la pena su tiempo y el dinero de la compañía para gastar recursos en "modernizar" un software que funcione con baja complejidad de código y que funcione bien. Es muy probable que introduzcas nuevos errores tú mismo, especialmente porque parece ser un sistema con el que no estás familiarizado.

Si todavía quieres seguir esa ruta, te sugiero lo siguiente:

  • Hacer (o generar) un diagrama de estado del software / código
  • Sumérgete en el código y haz una lista de las partes más complejas o críticas del código respectivamente
  • Encuentre a alguien que conozca esa base de código y pregúntele por qué se ha creado de esta manera y qué se sabe que causa problemas.
  • Escribe documentación de lo que has aprendido

En este punto, usted decidirá si vale la pena explorarlo o no. Si la cultura de su empresa no recompensa el fracaso, obtenga la luz verde de un superior o un gerente.

  • Compartimentar los diferentes bloques de construcción del software y escribir pruebas unitarias para cada uno.
  • Itere hasta que pueda unir los diferentes módulos
  • Realice más pruebas que simulen la interacción real del usuario (pruebas de estrés, etc.)

Creo que es un buen mapa de ruta y lo llevará a donde lo necesite. Sin conocer los detalles de este proyecto, es difícil ayudarlo mucho. No descarte mi descargo de responsabilidad como excesivamente alarmista. Toneladas de excelentes programadores han batido el polvo al tratar de reescribir un proyecto existente en su idioma favorito o utilizando herramientas "modernas". Esa es una decisión que debe pensarse cuidadosamente y le insto a que no se vuelva pícaro y lo haga por su cuenta sin el apoyo de la gerencia o la asistencia de sus colegas.

Jerry Dean
fuente
2
Realizo que mi pregunta no estaba clara en absoluto. No quiero refactorizar el código. En absoluto. Quiero mantener la base de código existente tal como está. Sin embargo, quiero aprender a escribir C moderno para la nueva característica. Y aquí estoy perdido. La mayor parte de la documentación que he encontrado trata sobre cómo codificar en C, no sobre cómo escribir 'moderno' C. Quizás no haya tal cosa como 'moderno' C ...
Guillaume
1

Si prefiere un lenguaje de nivel superior, hay algunos lenguajes como C ++ u Objective-C que se pueden mezclar con código C con bastante facilidad.

Alternativamente, C y C ++ son razonablemente compatibles. Es posible que pueda compilar toda la base de código como C ++ con pocos cambios; tendrá la variable ocasional denominada "clase" o "plantilla" que necesita cambiar de nombre, pero en la práctica eso será todo. (sizeof ('a') es diferente en C y C ++, pero creo que nunca lo he usado).

Si sigue esa ruta, considere que el próximo responsable de mantenimiento podría no ser demasiado fluido con C ++. No te dejes llevar. Aproveche C ++, pero solo hasta el punto en que un programador de C pueda entenderlo fácilmente.

gnasher729
fuente
1
Tengo que estar en desacuerdo aquí. C y C ++ son lenguajes distintos, y parte del código requerido por un compilador de C ++ (explícitamente emitir el valor de retorno de malloc) se considera una mala práctica en C. El significado de consty inlinetambién es muy diferente entre C y C ++ y, por supuesto, C ++ no comprende __restrict. No trate los idiomas como intercambiables, incluso en el subconjunto de fuentes que se compilan en ambos.
Angew ya no está orgulloso de SO
1

Básicamente, escribir un buen código C es lo mismo que escribir un buen código C ++ o Java: desea una clase, use a struct. Desea herencia, incluya la base structcomo primer miembro sin nombre. Si desea funciones virtuales, agregue un puntero a una estática structde punteros de función. Y así sucesivamente, etc. Es exactamente lo que C ++ hace bajo el capó, la única diferencia es que es explícito en C. Por lo tanto, puedes hacer una programación perfectamente orientada a objetos en C, simplemente se ve un poco diferente y más repetitivo de lo que crees. se utilizan para.

El punto es que una buena programación se trata de paradigmas, no de características del lenguaje. Es cierto que siempre es bueno si las características de su idioma brindan un buen soporte para los paradigmas que desea usar, pero las características del idioma no son un requisito. Una vez que se dé cuenta de esto, puede escribir un buen código en casi cualquier idioma (aparte de algunos lenguajes esotéricos como brainfuck o INTERCAL, es decir).

Por supuesto, el problema sigue siendo que la biblioteca C estándar no contiene ninguna de esas ingeniosas clases de contenedor a las que está acostumbrado. Desafortunadamente, eso significa que necesitará usar el suyo propio o solucionar esta falta mediante el uso de matrices asignadas dinámicamente. Pero apuesto a que pronto descubrirá que todo lo que realmente necesita son matrices dinámicas ( malloc()) y listas / árboles vinculados que se implementan a través de miembros de puntero dentro de sus clases.

cmaster - restablecer monica
fuente