c++ architecture frameworks software-design

c++ - ¿Es una práctica común abstraer las dependencias de la biblioteca de la implementación?



architecture frameworks (2)

En primer lugar, la API que exponga definitivamente debe estar libre de POCO, boost, qt o cualquier otro tipo que no sea parte de la biblioteca estándar de C ++. Esto se debe a que las bibliotecas base tienen su propio ciclo de publicación, distinto del ciclo de publicación de su biblioteca. Si los usuarios de su biblioteca también usan boost, pero una versión diferente e incompatible, necesitarían pasar tiempo para resolver la incompatibilidad. La única excepción a esta regla es cuando diseña una biblioteca que se lanzará como parte de un marco más amplio, por ejemplo, una adición al conjunto de herramientas de POCO. En este caso, el lanzamiento de su biblioteca está vinculado al lanzamiento de todo el conjunto de herramientas.

Sin embargo, internamente, debe evitar usar sus propios contenedores, a menos que la biblioteca que está abstrayendo sea una verdadera "biblioteca de productos básicos" 1 . La razón de esto es que cuando oculta una biblioteca externa detrás de sus clases, la mayoría de las veces imita el nivel de abstracción de la biblioteca que está ocultando. El código que usa su contenedor se programará al nivel de abstracción dictado por la biblioteca externa. Cuando intercambia la implementación detrás de su envoltorio por un marco diferente, es muy probable que (1) adapte el nuevo marco para que se ajuste al nivel de abstracción del viejo marco, o (2) necesitará cambiar la forma de hacerlo. que usas tu envoltorio Ambos casos son muy sospechosos: si lo hace (1), tal vez no debería cambiar en primer lugar, y si lo hace (2), entonces sus envoltorios resultan ser inútiles.

1 Por "biblioteca de productos" me refiero a una biblioteca que proporciona un nivel de abstracción que se encuentra comúnmente en otras bibliotecas que tienen un propósito similar.

Mi respuesta a esta pregunta sería "no". Pero mis compañeros de trabajo no están de acuerdo.

Estamos reconstruyendo nuestro producto y tenemos que tomar muchas decisiones críticas en el corto plazo.

Mientras hacía algo de mi propio trabajo, noté que teníamos algunas clases internas de C ++ para abstraer algunas API POSIX (hilos, mutexes, semáforos y bloqueos rw) y otras clases de utilidades. Tenga en cuenta que estas clases son básicas y no se han portado desde Linux (la portabilidad es un factor en la reconstrucción). También estamos utilizando las bibliotecas POCO C ++.

Traje esto a la atención de mis compañeros de trabajo y les sugerí que abandonáramos nuestras clases internas a favor de sus equivalentes de POCO. Quiero aprovechar al máximo la biblioteca que ya estamos utilizando. Sugirieron que deberíamos implementar nuestras clases internas utilizando POCO, y abstraer más las clases de POCO adicionales según sea necesario, para no depender de ninguna biblioteca específica de C ++ (citando futuras incógnitas, ¿y si queremos usar una lib / framework diferente como QT o boost, ¿qué sucede si el que elegimos no es bueno o el desarrollo se vuelve inactivo, etc.?

Tampoco quieren refactorizar el código heredado, y al abstraer partes de POCO con nuestras propias clases, podemos implementar funcionalidades adicionales (OOP clásico). Ambos argumentos puedo apreciar. Sin embargo, defiendo que si estamos recodificando deberíamos ir a lo grande, o irnos a casa. Ahora sería el momento de refactorizar y realmente no debería ser tan malo, especialmente teniendo en cuenta la similitud entre nuestras clases y las de POCO (hilos, etc.). No sé qué decir con respecto al segundo punto: ¿deberíamos usar solo? clases ampliadas donde la funcionalidad es necesaria?

Mis compañeros de trabajo tampoco quieren ensuciar el espacio de nombres de POCO en todo el lugar. Yo defiendo que debemos elegir una biblioteca / framework / toolkit, y atenernos a ella. Aproveche al máximo sus características. ¿No es esta una práctica típica? El único proyecto que he visto que abstrae un marco completo es Freeswitch (que proporciona su propia interfaz para APR).

Una sugerencia es que la API que exponemos entre nosotros, y los clientes potenciales, debería estar libre de POCO, pero estaría presente en la implementación (lo cual tiene sentido).

Ninguno de nosotros realmente tiene experiencia en este tipo de decisiones de diseño, y se nota en el producto actual. Habiendo estado en esto desde que era joven, tengo algo de intuición que me ha traído aquí, pero tampoco experiencia práctica. Realmente quiero evitar soluciones pobres a problemas que ya están resueltos.

Creo que mi pregunta se reduce a esto: al construir un producto, ¿deberíamos a) elegir un marco dominante en el que basar la mayor parte de nuestro código, yb) esperar que ese marco esté estrechamente vinculado con el producto? ¿No es ese el objetivo de un marco? (¿Es el marco o la biblioteca más apropiado para POCO?)


Hay dos situaciones en las que creo que vale la pena tener tus propios contenedores:

1) Ha examinado varias implementaciones mutex diferentes en diferentes sistemas / bibliotecas, ha establecido un conjunto común de requisitos que todos pueden satisfacer y que son suficientes para su software. Luego, defina esa abstracción e impleméntela una o más veces, sabiendo que ha planificado con anticipación la flexibilidad. El resto de su código está escrito para confiar únicamente en su abstracción, no en las propiedades incidentales de la (s) implementación (es) actual (es). He hecho esto en el pasado, aunque no en el código que puedo mostrarte.

Un ejemplo clásico de esta "interfaz menos común" sería cambiar el rename en la abstracción del sistema de archivos, sobre la base de que Windows no puede implementar un cambio de nombre atómico sobre un archivo existente. Por lo tanto, su código no debe depender del reemplazo de cambio de nombre atómico si en el futuro podría cambiar su implementación * nix actual por una que no pueda hacer eso. Debes restringir la interfaz desde el principio.

Cuando se hace bien, este tipo de interfaz puede facilitar considerablemente cualquier tipo de migración futura, ya sea a un sistema nuevo o porque desee cambiar las dependencias de la biblioteca de terceros. Sin embargo, es probable que un marco completo sea demasiado grande para lograrlo con éxito, esencialmente inventarías y escribirías tu propio marco de trabajo, lo cual no es una tarea trivial y es concebible que sea una tarea más grande que escribir tu software actual.

2) Desea poder simular / resguardar / simular / suplantar / plagiar / cualquiera que sea la siguiente técnica inteligente, la exclusión mutua en las pruebas, y decidir que le resultará más fácil si se le arroja su propio envoltorio que si Estamos tratando de meternos con los símbolos de las bibliotecas de terceros, o que están integrados.

Tenga en cuenta que la definición de sus propias funciones llamada wrap_pthread_mutex_init , wrap_pthread_mutex_init , etc., que imitan con precisión las funciones pthread_* , y toman exactamente los mismos parámetros, pueden satisfacer (2) pero no satisfacen (1). Y de todos modos, hacer (2) probablemente requiera más que solo envoltorios, por lo general también desea insertar las dependencias en su código.

Hacer más trabajo bajo el título de flexibilidad, sin proporcionar flexibilidad, es una pérdida de tiempo. Puede ser muy difícil o incluso demostrablemente imposible implementar un entorno de enhebrado en términos de otro. Si decide en el futuro cambiar de pthreads a std::thread en C ++, entonces haber usado una abstracción que se parece exactamente a la API pthreads bajo diferentes nombres es (aproximadamente) sin ayuda en absoluto.

Para otro posible cambio que pueda hacer, la implementación de la API pthreads completa en Windows es más o menos posible, pero probablemente más difícil que solo implementar lo que realmente necesita. Entonces, si transfiere a Windows, toda su abstracción le ahorra tiempo para buscar y reemplazar todas las llamadas en el resto de su software. Todavía tendrá que (a) conectar una implementación completa de Posix para Windows, o (b) hacer el trabajo para descubrir qué es lo que realmente necesita y solo implementarlo. Su envoltorio no ayudará con el trabajo real.