¿Omitir "destructores" en C está llevando a YAGNI demasiado lejos?

9

Estoy trabajando en una aplicación mediana incrustada en C usando técnicas similares a OO. Mis "clases" son módulos .h / .c que usan estructuras de datos y estructuras de punteros de función para emular la encapsulación, el polimorfismo y la inyección de dependencia.

Ahora, uno esperaría que una myModule_create(void)función venga con una myModule_destroy(pointer)contraparte. Pero el proyecto está incrustado, los recursos que se instancian de manera realista nunca deben ser lanzados.

Quiero decir, si tengo 4 puertos seriales UART y creo 4 instancias UART con sus pines y configuraciones requeridas, no hay absolutamente ninguna razón para querer destruir UART # 2 en algún momento durante el tiempo de ejecución.

Entonces, siguiendo el principio YAGNI (no lo vas a necesitar), ¿debería omitir los destructores? Esto me parece extremadamente extraño, pero no puedo pensar en un uso para ellos; los recursos se liberan cuando el dispositivo se apaga.

Asics
fuente
44
Si no proporciona una forma de deshacerse del objeto, está pasando un mensaje claro de que tienen una vida "infinita" una vez creados. Si esto tiene sentido para su aplicación, le digo: hágalo.
glampert
44
Si va a llegar tan lejos al acoplar el tipo a su caso de uso particular, ¿por qué incluso tiene una myModule_create(void)función? Simplemente puede codificar las instancias específicas que espera utilizar en la interfaz que expone.
Doval
@Doval, lo pensé. Soy un interno que utiliza partes y fragmentos de código de mi supervisor, así que estoy tratando de hacer malabarismos con "hacerlo bien", experimentando el estilo OO en C para obtener experiencia y coherencia con los estándares de la compañía.
Asics
2
@glampert lo clava; Agregaría que debe limpiar la vida útil infinita esperada en la documentación de la función de creación.
Blrfl

Respuestas:

11

Si no proporciona una forma de deshacerse del objeto, está pasando un mensaje claro de que tienen una vida "infinita" una vez creados. Si esto tiene sentido para su aplicación, le digo: hágalo.

Glampert tiene razón; No hay necesidad de destructores aquí. Simplemente crearían una acumulación de código y una trampa para los usuarios (usar un objeto después de que se llama a su destructor es un comportamiento indefinido).

Sin embargo, debe estar seguro de que realmente no hay necesidad de deshacerse de los objetos. Por ejemplo, ¿necesita tener un objeto para un UART que no esté actualmente en uso?

Demi
fuente
3

La forma más fácil que he encontrado para detectar pérdidas de memoria es poder salir limpiamente de su aplicación. Muchos compiladores / entornos proporcionan una forma de verificar la memoria que aún está asignada cuando su aplicación sale. Si no se proporciona uno, generalmente hay una manera de agregar algo de código justo antes de salir que puede resolverlo.

Entonces, ciertamente proporcionaría constructores, destructores y lógica de apagado incluso en un sistema embebido que "teóricamente" nunca debería salir para facilitar la detección de pérdidas de memoria por sí sola. En realidad, la detección de pérdida de memoria es aún más importante si el código de la aplicación nunca debe salir.

Remojar
fuente
Tenga en cuenta que esto no se aplica si la única vez que realiza asignaciones es durante el inicio, que es un patrón que consideraría seriamente en los dispositivos de restricción de memoria.
CodesInChaos
@Codes: no hay razón para limitarse. Si bien es posible que se te ocurra el gran diseño que asigna previamente la memoria en el inicio, cuando las personas que vienen después de ti no conozcan este gran esquema o no vean la importancia del mismo, asignarán memoria en el vuela y ahí va tu diseño. Simplemente hazlo bien y asigna / desasigna y verifica que lo que implementaste realmente funcione. Si realmente tiene un dispositivo con memoria limitada, lo que generalmente se hace es anular el nuevo operador / malloc y reservar bloques de asignación.
Dunk
3

En mi desarrollo, que hace un uso extensivo de tipos de datos opacos para fomentar un enfoque tipo OO, yo también luché con esta pregunta. Al principio, decididamente estaba en el campo de eliminar el destructor desde la perspectiva de YAGNI, así como la perspectiva del "código muerto" de MISRA. (Tenía mucho espacio de recursos, eso no era una consideración).

Sin embargo ... la falta de un destructor puede dificultar las pruebas, como en las pruebas automatizadas de unidad / integración. Convencionalmente, cada prueba debe admitir una configuración / desmontaje para que los objetos puedan crearse, manipularse y luego destruirse. Se destruyen para asegurar un punto de partida limpio y sin contaminación para la siguiente prueba. Para hacer esto, la clase necesita un destructor.

Por lo tanto, en mi experiencia, el "aint't" en YAGNI resulta ser un "are" y terminé creando destructores para cada clase, ya sea que pensara que lo necesitaba o no. Incluso si omití las pruebas, al menos existe un destructor diseñado correctamente para el pobre vago que me sigue, tendrá uno. (Y, en un valor mucho menor, hace que el código sea más reutilizable, ya que puede usarse en un entorno donde sería destruido).

Si bien eso aborda YAGNI, no aborda el código muerto. Para eso, encuentro que una macro de compilación condicional, como #define BUILD_FOR_TESTING, permite eliminar el destructor de la compilación de producción final.

Haciéndolo de esta manera: tiene un destructor para probar / reutilizar en el futuro, y satisface los objetivos de diseño de YAGNI y las reglas de "sin código muerto".

Greg Willits
fuente
Tenga cuidado con # ifdef'ing su código de prueba / producción. Es razonablemente seguro cuando se aplica a una función completa, como usted describe, porque si la función es realmente necesaria, la compilación fallará. Sin embargo, usar #ifdef en línea dentro de una función es mucho más riesgoso ya que ahora está probando una ruta de código diferente de la que se ejecuta en prod.
Kevin
0

Podrías tener un destructor sin operación, algo así como

  void noop_destructor(void*) {};

luego configure el destructor de Uarttal vez usando

  #define Uart_destructor noop_destructor

(agregue el yeso adecuado si es necesario)

No te olvides de documentar. Tal vez quieras incluso

 #define Uart_destructor abort

Alternativamente, el caso especial en el código común que llama al destructor es el caso cuando la función del puntero del destructor es NULLevitar llamarlo.

Basile Starynkevitch
fuente