¿La programación orientada a objetos ha cumplido sus promesas de reutilización de código superior?

Actualización: creo que una forma de decirlo es eso: sí, cumplió las promesas, pero cuando obtuvimos lo que queríamos descubrimos que también teníamos problemas de orden superior. Así que ahora tenemos que resolverlos para que podamos tener un buen gemido por no haber resuelto aún más problemas de alto orden 🙂

La respuesta original sigue sin cambios.

Parcialmente, en el sentido de que los mecanismos y conceptos del lenguaje están ciertamente allí, pero no anticipamos el hecho de que para que el código sea reutilizable necesitamos abordar una serie de nociones más abstractas, como la calidad y vocabularios comunes.

La calidad es complicada porque abarca la corrección, la simplicidad, la belleza, la disciplina y la claridad semántica, todo lo cual es escaso y difícil de medir objetivamente.

Se han establecido vocabularios comunes hasta cierto punto. Por ejemplo, el libro Software Patterns de GOF hizo mucho bien para popularizar la noción de patrones de diseño. UML hizo esto en menor grado: parte de la notación fue útil para proporcionar un lenguaje descriptivo, pero uno debe ignorar todas las tonterías tediosas e inviables de la metodología que los no profesionales tratarían de venderle.

También existe el vocabulario común de ciertas API e implementaciones. Partes de las bibliotecas estándar de Java se han diseñado de manera reflexiva y coherente, y los profesionales harían bien en comprenderlas y adoptar convenciones. Otras partes … no tanto.

Por supuesto, hay muchas tecnologías que intentan abordar estos problemas, pero la esencia del problema es que la mayoría de los programadores no son muy buenos en lo que hacen la mayor parte del tiempo. Y, por supuesto, me incluyo en esa observación.

Los programadores también tienen la mala costumbre de reutilizar cosas que no merecen ser reutilizadas. Hay * lotes * de bibliotecas y programas que se reutilizan a pesar de que su diseño y / o implementación es una basura absoluta.

Creo que esperábamos más de OOP de lo que teníamos razones para esperar. Creo que la conclusión clave es que no debemos esperar que ninguna tecnología resuelva todos nuestros problemas. En el mejor de los casos, podemos esperar mejorar un poco para abordar algunos tipos de problemas (y debemos esperar regresiones ocasionales a medida que se prueban nuevas ideas y se descubre que son callejones sin salida).

No, realmente no. Si miras hacia atrás a las afirmaciones de cuando la orientación a objetos se estaba convirtiendo en la corriente principal, por ejemplo, el byte de agosto de 1986 que probablemente fue mi primera exposición a estas ideas, dos cosas se destacan. En primer lugar, se alegó que las clases arbitrarias escritas en un estilo orientado a objetos podrían reutilizarse. En segundo lugar, se afirmó que el mecanismo principal a través del cual sucedería la reutilización era la herencia. Ninguna de estas afirmaciones ha resistido la prueba del tiempo, y aunque probablemente tengamos más reutilización de software ahora que la que teníamos entonces, podría decirse que la orientación a los objetos es un obstáculo y no una ayuda para la reutilización que realmente tenemos.

En general, no es posible reutilizar código orientado a objetos que no fue diseñado explícitamente para ser reutilizado, porque los grupos de clases escritos para trabajar juntos generalmente tendrán dependencias circulares. Algunas personas le dirán que esto es una mala práctica, pero las mismas personas generalmente recomendarán técnicas de análisis que resultan en descomposiciones de problemas con este tipo de dependencias. Lo más trivial es que si toma relaciones similares a las de una base de datos y las representa en un lenguaje OO, resultan en dependencias bidireccionales que hacen que ambas clases dependan de la otra. Esto se puede evitar, por supuesto, mediante la inyección de dependencias y las técnicas de diseño del marco, pero eso requiere esfuerzo. El código OO típico no es reutilizable fuera de la aplicación para la que fue escrito y otros se derivan explícitamente de él.

Cuando vemos código que está explícitamente diseñado para ser reutilizable, el mecanismo a través del cual se reutiliza no es la herencia. Normalmente, si el código que se reutiliza es del todo complejo, define los requisitos del código que se reutiliza, por ejemplo, a través de interfaces o mecanismos menos formales, pero en realidad no proporciona ningún comportamiento diseñado para integrarse en subclases. Los lenguajes con mecanismos de herencia múltiple limpios como Eiffel o Scala pueden hacer que algunos comportamientos estén disponibles para las subclases, pero generalmente depende del código de reutilización en lugar del código reutilizado para proporcionar la mayor parte de la implementación detallada. Cuando observa estos mecanismos, el único elemento de orientación a objetos realmente en juego para promover la reutilización es el polimorfismo. Podría decirse que los beneficios del polimorfismo podrían realizarse en cualquier lenguaje con algunas abstracciones funcionales, incluso sin el mecanismo formal real. La herencia no ayuda a reutilizar mucho, y la encapsulación realmente se interpone en el camino,

La encapsulación hace que cada proyecto orientado a objetos sea su propio lenguaje de dominio privado. Al hacerlo, evita activamente la reutilización. Si pensamos en un ejemplo trivial, si creo un paquete de gráficos con las clases Edge y Vertex, y un algoritmo de primera búsqueda de profundidad y creas otro con clases de Arc y Node y un algoritmo de búsqueda de primera respiración, los dos algoritmos de búsqueda no se pueden volver a utilizar. -utilizado con el otro paquete, porque tienen nombres de clase (y función y variable) incrustados en ellos. En lenguajes puramente tipados, tal vez podría salirse con la suya creando algunas clases de contenedor, pero incluso eso es un ejercicio no trivial. El problema clave aquí es que la encapsulación alienta activamente a los desarrolladores a crear abstracciones muy especializadas que solo permiten la aplicación de funcionalidades muy especializadas. Esto evita usos indebidos que rompen invariantes, pero también evita usos inesperados que son válidos.

Los sistemas que se basan en abstracciones menos especializadas y muy potentes fomentan la reutilización mejor. Los lenguajes funcionales nos permiten escribir funciones que operan sobre un dominio general de funciones y valores. SQL nos permite escribir consultas que manipular valores de datos arbitrarios. En ambos casos, las poderosas abstracciones generales nos permiten reutilizar el código donde las abstracciones encapsuladas altamente especializadas no lo harían.