¿Cuándo es una mala idea una biblioteca 'núcleo'?

8

Cuando desarrollo software, a menudo tengo una biblioteca 'central' centralizada que contiene código útil que puede ser compartido y referenciado por diferentes proyectos.

Ejemplos:

  • un conjunto de funciones para manipular cadenas
  • expresiones regulares de uso común
  • código de implementación común

Sin embargo, algunos de mis colegas parecen estar alejándose de este enfoque. Tienen inquietudes como la sobrecarga de mantenimiento del código de reevaluación utilizado por muchos proyectos una vez que se soluciona un error. Ahora estoy reconsiderando cuándo debería estar haciendo esto.

¿Cuáles son los problemas que hacen que usar una biblioteca 'núcleo' sea una mala idea?

Alex Angas
fuente
Tener una biblioteca central es una buena idea cuando el código se reutiliza comúnmente, pero debe ser probado religiosamente, incluidas las pruebas unitarias y otras tecnologías espaciales.
Trabajo
Es una buena idea cuando se ha estabilizado y no cambia.
Martin York
La preocupación de volver a probar es muy válida. ¿Le gustaría descubrir que rompió un proyecto de mantenimiento hace 6 meses?
No puedo imaginar reescribir todo mi código de utilidad cada vez que lo necesito.

Respuestas:

12

Las bibliotecas principales son malas cuando comienzan a sufrir un deslizamiento de características y muy malas cuando no están bien mantenidas.

Puede encontrar este artículo interesante para un punto de vista extendido (con el que estoy totalmente de acuerdo):

http://www.yosefk.com/blog/redundancy-vs-dependencies-which-is-worse.html


Don Knuth: "Para mí, el 'código reeditable' es mucho, mucho mejor que una caja negra o un juego de herramientas intocables ... nunca me convencerás de que el código reutilizable no es principalmente una amenaza".

Denis de Bernardy
fuente
3

Usar la idea de que una biblioteca central es mala cuando varios proyectos dependen de ella, es como decir que no debes usar jQuery para la web, libxml en tus aplicaciones * nix, o cualquier otro marco o biblioteca. Mire todo el ecosistema del desarrollo moderno (DRY, OOP, etc.) y cada aplicación está construida a partir de un conjunto de bibliotecas y marcos.

Lo que puede ser malo es si no tiene ningún tipo de pruebas unitarias, no realiza una prueba de regresión y no utiliza ningún tipo de API / ABI con su biblioteca. Si todas sus aplicaciones tienen pruebas adecuadas, su biblioteca tiene pruebas adecuadas, y se asegura de que si interrumpe las llamadas de función, actualice el número de versión de la API adecuadamente.

Para una cobertura completa, lo que probablemente uno desearía es cuando se realicen cambios en la Biblioteca, puede ejecutar un conjunto de pruebas que verifiquen que la API no se haya roto y que la ejecución de todo el código esté libre de errores. Luego puede incorporar la última actualización de la biblioteca a su aplicación y ejecutar el mismo conjunto de pruebas. Si actualiza la API, debe documentarse para que sepa qué debe hacer en su aplicación para actualizarla. De cualquier manera, cuando ejecuta las pruebas para su aplicación, puede estar tan seguro como en sus pruebas de que nada se ha roto.

Al usar jquery, mootools, cualquier biblioteca o framework javascript, no puede usar ciegamente la nueva versión, lamentablemente no puede incluso con una versión menor de 1.6.z a veces.

Ryan Gibbons
fuente
3

Tienen inquietudes como la sobrecarga de mantenimiento del código de reevaluación utilizado por muchos proyectos una vez que se soluciona un error.

Si tiene un conjunto completo de pruebas unitarias para la biblioteca principal; Eso no es un problema. No se registrará ningún código a menos que se pasen todas las pruebas. Si introduce un defecto, escribe una prueba fallida para reproducir el defecto y corregirlo; entonces siempre estarás probando ese error también. Siempre.

Además, la funcionalidad que describe es muy fácil de escribir para pruebas unitarias.

Como un problema secundario, es posible que desee tener más de una biblioteca principal para no tener que incluir el código RegEx a menos que lo desee.

mcottle
fuente
2

Ofreceré una versión ligeramente diferente de esto. ¡Una biblioteca central, en muchos casos, es una excelente idea!

Si tiene dos proyectos separados, deben estar en dos depósitos de código separados. Ahora dependen de una funcionalidad común. Consideremos, por ejemplo, las aplicaciones de procesamiento de paquetes. La funcionalidad común puede incluir:

  • Asignadores de memoria
  • Protocolo de resolucion de DIRECCION
  • Árbol AVL
  • Código de serialización para protocolos binarios
  • Matriz dinámica
  • Lista de hash de estilo kernel de Linux con cabeza unida individualmente y nodos medios doblemente vinculados
  • Tabla de picadillo
  • Código de procesamiento de encabezado TCP / IP
  • Lista regular vinculada con cabeza doblemente vinculada y nodos medios doblemente vinculados
  • Biblioteca de registro
  • Varios (confía en mí, ¡necesitas esto para cosas pequeñas y triviales o tu número de módulos diferentes será tan grande como 100!)
  • Biblioteca de captura de paquetes
  • Biblioteca de interfaz de E / S de paquetes
  • Estructura de paquete de datos
  • Bloqueo de cola para comunicación entre subprocesos
  • Generadores de números aleatorios
  • Árbol rojo-negro
  • Algún tipo de implementación de temporizador

Ahora, diferentes aplicaciones de procesamiento de paquetes pueden necesitar un subconjunto diferente de estos. ¿Debería implementar una biblioteca central con un repositorio de código fuente, o debería tener 18 repositorios diferentes para cada uno de estos módulos? Recuerde que estos módulos pueden tener interdependencias, por lo que la mayoría de estos módulos pueden depender, por ejemplo, del módulo misceláneo.

Afirmaré que tener una biblioteca central es el mejor enfoque. Reduce la sobrecarga de muchos repositorios de código fuente. Reduce el infierno de dependencias: una versión particular de los asignadores de memoria puede necesitar una versión particular de un módulo misceláneo. ¿Y si desea que la versión 1.7 del asignador de memoria dependa de varios 2.5 y la versión 1.2 del árbol AVL según varios 2.6? Es posible que no pueda vincular varios 2.5 y 2.6 varios al mismo tiempo a su programa.

Entonces, adelante e implemente la siguiente estructura:

  • Repositorio de la biblioteca principal
  • Proyecto # 1 repositorio
  • Proyecto # 2 repositorio
  • ...
  • Proyecto #N repositorio

He visto que cambiar a este tipo de estructura desde la estructura:

  • Proyecto # 1 repositorio
  • Proyecto # 2 repositorio
  • ...
  • Proyecto #N repositorio

Ha llevado a un mantenimiento reducido y a un mayor intercambio de código a través de mecanismos sin copia.

También he visto proyectos que utilizan la siguiente estructura:

  • Repositorio de asignadores de memoria
  • Depósito de protocolos de resolución de direcciones
  • Repositorio de árboles AVL
  • Código de serialización para repositorio de protocolos binarios
  • Repositorio de matriz dinámica
  • Lista de hash de estilo kernel de Linux con cabecera individualmente vinculada y repositorio de nodos medios doblemente vinculados
  • Repositorio de tablas hash
  • Repositorio de código de procesamiento de encabezado TCP / IP
  • Lista vinculada regular con cabecera doblemente vinculada y repositorio de nodos medios doblemente vinculado
  • Registro de repositorio de la biblioteca
  • Repositorio misceláneo (confía en mí, ¡necesitas esto para cosas pequeñas y triviales o tu número de módulos diferentes será tan grande como 100!)
  • Repositorio de la biblioteca de captura de paquetes
  • Repositorio de biblioteca de interfaz de E / S de paquetes
  • Repositorio de estructura de datos de paquetes
  • Cola de bloqueo para repositorio de comunicación entre subprocesos
  • Repositorio de generadores de números aleatorios
  • Repositorio de árboles rojo-negro
  • Algún tipo de repositorio de implementación de temporizador
  • Proyecto # 1 repositorio
  • Proyecto # 2 repositorio
  • ...
  • Proyecto #N repositorio

... y el infierno de dependencia y la proliferación de números de repositorio han sido problemas genuinos.

Ahora, ¿debería usar una biblioteca de código abierto existente en lugar de escribir la suya propia? Necesitas considerar:

  • Problemas de licencia. A veces, el mero requisito de dar crédito al autor en la documentación proporcionada puede ser demasiado, ya que 20 bibliotecas generalmente tendrán 20 autores distintos.
  • Soporte de versión de sistema operativo diferente
  • Dependencias de la biblioteca particular.
  • Tamaño de la biblioteca particular: ¿es demasiado grande para la funcionalidad proporcionada? ¿Proporciona demasiadas funciones?
  • ¿Es posible la vinculación estática? ¿Es deseable la vinculación dinámica?
  • ¿Es la interfaz de la biblioteca lo que quieres? Tenga en cuenta que en algunos casos escribir un contenedor para proporcionar la interfaz deseada puede ser más fácil que reescribir el componente completo usted mismo.
  • ... y muchas, muchas otras cosas que no he mencionado en esta lista

Usualmente uso la regla de que todo lo que esté por debajo de 1000 líneas de código que no requiera algo más allá de la experiencia del programador debe implementarse por su cuenta. Nota: las 1000 líneas incluyen pruebas unitarias. Por lo tanto, no recomendaré escribir 1000 líneas de código por su cuenta si requiere 10 000 líneas adicionales para las pruebas unitarias. Para mis programas de procesamiento de paquetes, esto significa que los únicos componentes externos que he usado son:

  • Todo lo que proporciona una distribución estándar de Linux, porque hay tantas líneas de código que no tiene sentido volver a implementar Linux. Parte de la reimplementación de Linux también estaría más allá de mi nivel de experiencia.
  • Bison / flex porque el análisis LALR está más allá de mi nivel de experiencia y más de 1000 líneas de código. Ciertamente podría escribir un analizador de descenso recursivo por mi cuenta, pero Bison / flex son tan útiles que los veo útiles.
  • Netmap, porque tiene más de 1000 líneas y más allá de mi nivel de experiencia
  • Implementación de temporizador basada en lista de omisión de DPDK, porque está más allá de mi nivel de experiencia, aunque es inferior a 1000 líneas de código (aunque tengo implementaciones de temporizador alternativas que no usan listas de omisión)

Algunas cosas que he implementado por mi cuenta porque son simples incluyen incluso cosas como:

  • MurMurHash
  • SipHash
  • Mersenne Twister

... porque las implementaciones personalizadas de estos pueden permitir una gran alineación, lo que lleva a un rendimiento mejorado.

No hago criptografía; si lo hiciera, agregaría algún tipo de biblioteca criptográfica en la lista, ya que escribir algoritmos criptográficos por su cuenta puede ser susceptible a ataques de temporización de caché, incluso si puede con pruebas exhaustivas de la unidad, demuestre que son compatibles con los algoritmos oficiales.

juhist
fuente
1

Una biblioteca central puede ser mala cuando varios proyectos dependen de ella, no solo tiene que probar los cambios en su núcleo, sino que también tiene que hacer una prueba de regresión en cada proyecto dependiente. En segundo lugar, sus API principales nunca pueden cambiar porque tendrá que refactorizar cada proyecto dependiente. Cuantos más proyectos utilicen su biblioteca, más profunda será la trampa.

Otro problema es la tendencia a comenzar a arrojar todo lo "común" en su biblioteca central, hincharlo y hacer que sea más difícil sacar piezas pequeñas. Solo diré que una vez escuché de un lugar que temía tocar cualquiera de sus numerosas bibliotecas principales, la sobrecarga de las pruebas de regresión de control de calidad fue tan grande.

En cambio, ¿tal vez pueda crear un recurso de fragmento de código para permitir que los equipos de proyecto busquen y obtengan el código que necesitan y se separen de cualquier problema de mantenimiento o regresión? Eso es lo que hago en casa, de todos modos.

Patrick Hughes
fuente
44
Sin embargo, es mucho más difícil corregir un error en los fragmentos de código que se han copiado y pegado en varios lugares.
Alex Angas
Una cita de Donald Knuth: "También debo confesar que existe un fuerte sesgo en contra de la moda del código reutilizable. Para mí, el" código reeditable "es mucho, mucho mejor que una caja negra o kit de herramientas intocables. Podría seguir y seguir. sobre esto. Si estás totalmente convencido de que el código reutilizable es maravilloso, probablemente no podré influir de todos modos, pero nunca me convencerás de que el código reutilizable no es principalmente una amenaza ".
Patrick Hughes
@AlexAngas: Eso es cierto, pero puede haber casos en que una biblioteca tenga errores, pero funciona correctamente solo porque alguna otra biblioteca tiene errores sutiles que compensan los errores en el primero. Si bien ambos conjuntos de errores deben corregirse cuando sea práctico, tener una copia del código fuente de la segunda biblioteca como parte del proyecto con el primero significaría que una corrección de errores aplicada para ese código sería un cambio reconocible en el proyecto, que podría retroceder temporalmente si rompe cosas (permitiendo así que se identifique como la causa de la rotura).
supercat
@AlexAngas: Por supuesto, identificar la solución a la segunda rutina como la causa de la ruptura no significa que el remedio no sea arreglar la segunda, sino que apunta al hecho de que algún código depende erróneamente del comportamiento errante de esa rutina ; ese descubrimiento será la clave para resolver eficientemente los problemas reales. Por el contrario, si todo lo que uno sabe es que el código que solía funcionar de manera espontánea dejó de funcionar, será muy difícil localizar qué hacer al respecto.
supercat
1

Un punto aún no mencionado es que cualquier código tendrá dependencias de algo , incluso si es literalmente lo único que se ejecuta en la ROM de un microcontrolador incorporado; Si el fabricante del controlador cambia algún comportamiento en el que se basa el código, el código tendrá que modificarse para que funcione en los chips fabricados después del cambio, o de lo contrario los fabricantes del dispositivo que usa el código tendrán que adquirir de alguna manera los chips que sí lo hacen. no incorpore el cambio, posiblemente pagando una prima de precio por ellos.

El uso de una biblioteca para realizar varias funciones de hardware puede significar que el código ahora depende de una biblioteca, mientras que no lo había sido anteriormente, pero también puede eliminar las dependencias entre el código y el hardware. Por ejemplo, un fabricante de chips podría prometer suministrar una biblioteca para todos los chips presentes y futuros que siempre realizarán ciertas funciones de E / S de cierta manera. El código que usa esa biblioteca para realizar esas funciones de E / S dependería del fabricante para proporcionar versiones apropiadas de esa biblioteca, pero ya no dependería del fabricante para usar la misma implementación de hardware de esas funciones.

Desafortunadamente, a menudo es difícil saber cuál es el enfoque correcto para el código a prueba de futuro. He visto casos en los que un proveedor de chips cambió la forma en que funcionaba una biblioteca (para acomodar nuevos chips), incluso cuando se usaba para acceder a un chip que había cambiado. También he visto casos en los que un fabricante de chips cambió la forma en que funcionaba su hardware, pero las bibliotecas suministradas se ajustaron adecuadamente, por lo que el código que usaba rutinas de biblioteca continuaría funcionando sin cambios, mientras que el código que accedía al hardware directamente tenía que ajustarse.

Situaciones similares existen con las aplicaciones de Windows. A veces, a Microsoft le encanta cambiar la forma en que las aplicaciones deben hacer las cosas; el código que usa ciertas bibliotecas para tales cosas puede actualizarse simplemente actualizando la biblioteca, mientras que el código que no usa bibliotecas que se actualizan para ellas debe actualizarse manualmente.

Super gato
fuente
1

Quería contribuir con una versión ligeramente diferente de esto, aunque me encanta la Denis de Bernardyrespuesta y el artículo vinculado sobre minimizar las dependencias frente a minimizar las redundancias (reflejan altamente mis propios pensamientos sobre este tema en el que creo que la reutilización del código es un acto de equilibrio).

El mayor problema que tengo con una corebiblioteca es este:

¿Cuándo está completo? ¿Cuándo llegará a un punto de estabilidad donde hará todo lo que necesita hacer y efectivamente se "hará"?

Y creo que es muy probable que la respuesta sea " nunca ". Es posible que la gente siempre tenga la tentación de agregarlo, ya que modela una idea tan nebulosa, especialmente si esta biblioteca está evolucionando durante el desarrollo del software en lugar de tener objetivos anticipados por adelantado. Y tal vez agregar a la biblioteca no sea lo peor del mundo, ya que no romperá las dependencias existentes de la biblioteca, pero con objetivos tan nebulosos, la biblioteca podría volverse cada vez más ecléctica y fea, proporcionando una funcionalidad dispareja en la que alguien interesado en el uso de la biblioteca solo puede encontrar una pequeña porción de ella aplicable a sus necesidades.

Las dependencias en su base de código idealmente deberían fluir hacia paquetes muy estables. Un corepaquete podría encontrarse fácilmente muy inestable, mientras que grandes porciones de su base de código tienen dependencias que fluyen hacia él.

Así que creo que vale la pena dividir la biblioteca en bibliotecas más uniformes dedicadas a hacer algo más específico que simplemente, "biblioteca central de cualquier cosa que la gente pueda necesitar con frecuencia" para que pueda crecer en una dirección más uniforme con una mejor coordinación entre sus compañeros de equipo sobre exactamente lo que debería y, lo que es más importante, no debería hacer, y potencialmente alcanzar un punto de estabilidad en el que esté bien probado y no sientas que hay que agregarle nada más para que sea relativamente " completo "y estable (como en, inmutable).


fuente
0

Escribir bibliotecas para cosas básicas como cadenas y listas vinculadas es bastante tonto en este milenio. Use un lenguaje de programación incluido con baterías que ya tenga la funcionalidad principal.

Si le gusta escribir bibliotecas centrales de soporte en tiempo de ejecución solo por diversión, diseñe un nuevo lenguaje de programación. Si haces eso en una aplicación, esencialmente estás desarrollando un lenguaje fuera de su lado.

Además, ¿alguien no ha escrito N bibliotecas principales diferentes en el idioma que está utilizando? Investigar los marcos existentes y elegir el más adecuado puede ser un mejor uso del tiempo que hacerlo desde cero.

Kaz
fuente
En mi campo, el procesamiento de paquetes de alto rendimiento, ciertamente usando un lenguaje de programación incluido con baterías no es una opción. C es la elección obvia. Y no, las N bibliotecas principales diferentes disponibles para, por ejemplo, tablas hash son peores que la implementación del kernel de Linux. La implementación del kernel de Linux, siendo GPL, requiere que usted implemente manualmente una implementación similar sin tener que mirar el código fuente del kernel de Linux, pero al conocer las características avanzadas de la tabla hash que utiliza la implementación del kernel de Linux. Sin embargo, esto puede variar en el campo.
juhist