Siempre veo que la abstracción es una característica muy útil que OO proporciona para administrar la base de código. Pero, ¿cómo se gestionan las grandes bases de código no OO? ¿O eventualmente se convierten en una " Gran Bola de Barro "?
Actualización:
Parecía que todo el mundo estaba pensando que la 'abstracción' era solo modularización u ocultación de datos. Pero en mi humilde opinión, también significa el uso de 'clases abstractas' o 'interfaces', que es imprescindible para la inyección de dependencia y, por lo tanto, para las pruebas. ¿Cómo las bases de código no OO manejan esto? Y además, aparte de la abstracción, la encapsulación también ayuda mucho a administrar bases de código grandes, ya que define y restringe la relación entre datos y funciones.
Con C, es muy posible escribir código pseudo-OO. No sé mucho sobre otros idiomas que no sean OO. Entonces, ¿es LA manera de administrar grandes bases de código C?
fuente
Respuestas:
Parece pensar que la POO es el único medio para lograr la abstracción.
Si bien OOP es muy bueno para hacerlo, de ninguna manera es la única forma. Los grandes proyectos también pueden mantenerse manejables mediante una modularización intransigente (solo mire Perl o Python, los cuales han sobresalido en eso, y también lo hacen los lenguajes funcionales como ML y Haskell), y mediante el uso de mecanismos como plantillas (en C ++).
fuente
static
modificador de acceso adjunto.Módulos, funciones (externas / internas), subrutinas ...
Como dijo Konrad, OOP no es la única forma de administrar grandes bases de código. De hecho, se escribió una gran cantidad de software antes (antes de C ++ *).
fuente
El principio de modularidad no se limita a los lenguajes orientados a objetos.
fuente
Siendo realistas cambios poco frecuentes (piense en los cálculos de jubilación de la Seguridad Social) y / o conocimiento profundamente arraigado porque las personas que mantienen tal sistema lo han estado haciendo por un tiempo (la toma cínica es seguridad laboral).
Las mejores soluciones son la validación repetible, por lo que me refiero a la prueba automatizada (por ejemplo, pruebas unitarias) y pruebas en humanos que siguen los pasos proscritos (por ejemplo, pruebas de regresión) "en lugar de hacer clic y ver qué se rompe".
Para comenzar a avanzar hacia algún tipo de prueba automatizada con una base de código existente, recomiendo leer Michael Feather's Working Effectively with Legacy Code , que detalla los enfoques para llevar las bases de código existentes hasta algún tipo de marco de prueba repetible OO o no. Esto lleva al tipo de ideas que otros han respondido, como la modularización, pero el libro describe el enfoque correcto para hacerlo sin romper las cosas.
fuente
Aunque la inyección de dependencia basada en interfaces o clases abstractas es una muy buena manera de hacer pruebas, no es necesaria. No olvide que casi cualquier lenguaje tiene un puntero de función o una evaluación, que puede hacer cualquier cosa que pueda hacer con una interfaz o clase abstracta (el problema es que pueden hacer más , incluidas muchas cosas malas, y que no lo hacen ' t en sí mismos proporcionan metadatos). Tal programa realmente puede lograr la inyección de dependencia con estos mecanismos.
Me ha resultado muy útil ser riguroso con los metadatos. En los lenguajes OO, las relaciones entre los bits de código están definidas (hasta cierto punto) por la estructura de clase, de una manera lo suficientemente estandarizada como para tener cosas como una API de reflexión. En lenguajes de procedimiento, puede ser útil inventarlos usted mismo.
También he encontrado que la generación de código es mucho más útil en un lenguaje de procedimiento (en comparación con un lenguaje orientado a objetos). Esto garantiza que los metadatos estén sincronizados con el código (ya que se usa para generarlo) y le da algo parecido a los puntos de corte de la programación orientada a aspectos: un lugar donde puede inyectar código cuando lo necesite. A veces es la única forma de hacer programación DRY en un entorno que puedo entender.
fuente
En realidad, como ha descubierto recientemente , las funciones de primer orden son todo lo que necesita para la inversión de dependencia.
C admite funciones de primer orden e incluso cierres hasta cierto punto . Y las macros C son una característica poderosa para la programación genérica, si se manejan con el cuidado necesario.
Todo esta ahí. SGLIB es un buen ejemplo de cómo se puede usar C para escribir código altamente reutilizable. Y creo que hay mucho más por ahí.
fuente
Incluso sin abstracción, la mayoría de los programas se dividen en secciones de algún tipo. Esas secciones generalmente se relacionan con tareas o actividades específicas y usted trabaja en ellas de la misma manera que trabajaría en los bits más específicos de los programas abstraídos.
En proyectos pequeños a medianos, esto es realmente más fácil de hacer con una implementación OO purista a veces.
fuente
La abstracción, las clases abstractas, la inyección de dependencias, la encapsulación, las interfaces, etc., no son la única forma de controlar grandes bases de código; Esta es una forma justa y orientada a objetos.
El secreto principal es evitar pensar OOP al codificar sin OOP.
La modularidad es la clave en los idiomas que no son OO. En C esto se logra tal como David Thornley acaba de mencionar en un comentario:
fuente
Una forma de administrar el código es descomponerlo en los siguientes tipos de código, siguiendo las líneas de la arquitectura MVC (modelo-vista-controlador).
Este método de organización del código funciona bien para el software escrito en cualquier lenguaje OO o no OO porque los patrones de diseño comunes a menudo son comunes a cada una de las áreas. Además, este tipo de límites de código son a menudo los más débilmente acoplados, excepto los algoritmos porque vinculan los formatos de datos de las entradas al modelo y luego a las salidas.
La evolución del sistema a menudo toma la forma de que su software maneje más tipos de entradas, o más tipos de salidas, pero los modelos y las vistas son las mismas y los controladores se comportan de manera muy similar. O, con el tiempo, un sistema puede necesitar admitir más y más tipos diferentes de salidas, aunque las entradas, los modelos y los algoritmos sean los mismos, y los controladores y las vistas sean similares. O se puede aumentar un sistema para agregar nuevos modelos y algoritmos para el mismo conjunto de entradas, salidas similares y vistas similares.
Una forma en que la programación OO dificulta la organización del código es porque algunas clases están profundamente ligadas a las estructuras de datos persistentes, y otras no. Si las estructuras de datos persistentes están íntimamente relacionadas con cosas tales como relaciones en cascada 1: N o relaciones m: n, es muy difícil decidir los límites de clase hasta que haya codificado una parte significativa y significativa de su sistema antes de saber que lo hizo bien . Cualquier clase vinculada a las estructuras de datos persistentes será difícil de evolucionar cuando cambie el esquema de los datos persistentes. Las clases que manejan algoritmos, formateo y análisis tienen menos probabilidades de ser vulnerables a los cambios en el esquema de las estructuras de datos persistentes. El uso de un tipo de organización de código MVC aísla mejor los cambios de código más desordenados en el código del modelo.
fuente
Cuando se trabaja en idiomas que carecen de estructura incorporada y características de organización (por ejemplo, si no tiene espacios de nombres, paquetes, ensamblajes, etc.) o donde estos son insuficientes para mantener bajo control una base de código de ese tamaño, la respuesta natural es desarrollar nuestras propias estrategias para organizar el código.
Esta estrategia de organización probablemente incluye estándares relacionados con dónde se deben guardar los diferentes archivos, cosas que deben suceder antes / después de ciertos tipos de operaciones, y convenciones de nombres y otros estándares de codificación, así como mucho "así es como está configurado - ¡No te metas con eso! " escriba comentarios, que son válidos siempre que expliquen por qué.
Debido a que lo más probable es que la estrategia se adapte a las necesidades específicas del proyecto (personas, tecnologías, entorno, etc.) es difícil dar una solución única para la administración de bases de código grandes.
Por lo tanto, creo que el mejor consejo es adoptar la estrategia específica del proyecto y hacer que la gestión sea una prioridad clave: documentar la estructura, por qué es así, los procesos para realizar cambios, auditarla para asegurarse de que se cumpla, y crucial: cámbielo cuando necesite cambiar.
La mayoría de las veces estamos familiarizados con las clases y métodos de refactorización, pero con una gran base de código en dicho lenguaje, es la estrategia de organización en sí misma (completa con la documentación) la que necesita ser refactorizada cuando sea necesario.
El razonamiento es el mismo que para la refactorización: desarrollará un bloqueo mental para trabajar en pequeñas partes del sistema si siente que la organización general es un desastre y eventualmente permitirá que se deteriore (al menos esa es mi opinión sobre eso).
Las advertencias también son las mismas: use la prueba de regresión, asegúrese de que puede revertir fácilmente si la refactorización sale mal, y diseñe para facilitar la refactorización en primer lugar (¡o simplemente no lo hará!).
Estoy de acuerdo en que es mucho más complicado que refactorizar el código directo, y es más difícil validar / ocultar el tiempo de los gerentes / clientes que pueden no entender por qué debe hacerse, pero estos también son los tipos de proyectos más propensos a la descomposición del software causado por diseños inflexibles de alto nivel ...
fuente
Si está preguntando sobre la administración de una base de código grande, está preguntando cómo mantener su base de código bien estructurada en un nivel relativamente grueso (bibliotecas / módulos / construcción de subsistemas / uso de espacios de nombres / tener los documentos correctos en los lugares correctos etc.) Los principios OO, especialmente 'clases abstractas' o 'interfaces', son principios para mantener su código limpio internamente, en un nivel muy detallado. Por lo tanto, las técnicas para mantener manejable una base de código grande no difieren para el código OO o no OO.
fuente
La forma en que se maneja es que descubres los bordes de los elementos que usas. Por ejemplo, los siguientes elementos en C ++ tienen un borde claro y cualquier dependencia fuera del borde debe pensarse cuidadosamente:
Combinando estos elementos y reconociendo sus bordes, puede crear casi cualquier estilo de programación que desee dentro de c ++.
Un ejemplo de esto es que una función sería reconocer que es malo llamar a otras funciones desde una función, porque causa dependencia, en su lugar, solo debe llamar a las funciones miembro de los parámetros de la función original.
fuente
El mayor desafío técnico es el problema del espacio de nombres. La vinculación parcial se puede utilizar para solucionar esto. El mejor enfoque es diseñar utilizando estándares de codificación. De lo contrario, todos los símbolos se convierten en un desastre.
fuente
Emacs es un buen ejemplo de esto:
Las pruebas de Emacs Lisp usan
skip-unless
ylet-bind
para hacer funciones de detección y prueba de accesorios:Como es SQLite. Aquí está su diseño:
sqlite3_open () → Abra una conexión a una base de datos SQLite nueva o existente. El constructor para sqlite3.
sqlite3 → El objeto de conexión de la base de datos. Creado por sqlite3_open () y destruido por sqlite3_close ().
sqlite3_stmt → El objeto de declaración preparado. Creado por sqlite3_prepare () y destruido por sqlite3_finalize ().
sqlite3_prepare () → Compila texto SQL en código de bytes que hará el trabajo de consultar o actualizar la base de datos. El constructor para sqlite3_stmt.
sqlite3_bind () → Almacenar datos de la aplicación en parámetros del SQL original.
sqlite3_step () → Avanzar un sqlite3_stmt a la siguiente fila de resultados o hasta completarlo.
sqlite3_column () → Valores de columna en la fila de resultados actual para un sqlite3_stmt.
sqlite3_finalize () → Destructor para sqlite3_stmt.
sqlite3_exec () → Una función de contenedor que hace sqlite3_prepare (), sqlite3_step (), sqlite3_column () y sqlite3_finalize () para una cadena de una o más instrucciones SQL.
sqlite3_close () → Destructor para sqlite3.
SQLite utiliza una variedad de técnicas de prueba que incluyen:
Referencias
Vistas conceptuales de la arquitectura de Emacs (pdf)
La interfaz del sistema operativo SQLite o "VFS"
El mecanismo de tabla virtual de SQLite
Una introducción a la interfaz SQLite C / C ++
Programación Emacs-Elisp · GitHub
Pruebas y su entorno - Pruebas de regresión de Emacs Lisp
Cómo se prueba SQLite
fuente