Original web-page: https://www.garfieldtech.com/blog/language-tradeoffs
Enviado por Larry el 30 de septiembre de 2010 – 7:04 de la tarde
Se ha discutido mucho en Drupal acerca de la programación orientada a objetos. Eso no es realmente sorprendente, dado que Drupal 7 es la primera versión que realmente ha intentado usar los objetos de alguna manera significativa (vis, como algo que no sean matrices que pasan de forma extraña). Sin embargo, gran parte de la discusión se ha reducido a “¡Los objetos OMG son inflexibles, por lo que son malvados!” vs. “¡Los objetos OMG son geniales, yay!” Ambas posiciones son inocentemente inocentes.
Es importante para nosotros dar un paso atrás y examinar por qué es útil un paradigma de programación en particular, y para hacerlo debemos entender lo que entendemos por “útil”.
Programación de paradigmas, como la arquitectura de software, tienen ventajas y desventajas. De hecho, muchos de los mismos métodos para comparar los diseños arquitectónicos se aplican igual de bien que el diseño de lenguajes. Para hacer eso, sin embargo, tenemos que dar un paso atrás y mirar a algo más que objetos de estilo PHP.
Advertencia: la acción de la ciencia informática de núcleo duro sigue. Si eres un programador, te recomiendo que tomes una taza de $ bebida antes de continuar, ya que podría tardar un poco en digerir, aunque he tratado de simplificarlo lo más posible. Aquí hay bastante cosas específicas de Drupal, así que espero que sea útil para cualquier desarrollador de PHP.
Aproximaciones a la programación.
Cada lenguaje de programación es, fundamentalmente, una forma de codificar la lógica. Hay diferentes fue para codificar la lógica de tal manera que un ordenador puede procesar, y cada lengua tiene un giro ligeramente diferente sobre el tema. En el resumen, sin embargo, podemos identificar varios paradigmas generales, algunos de los cuales son relevantes para nosotros como desarrolladores de PHP. (Los enlaces de Wikipedia a continuación tienen mucho más detalle de lo que puedo entrar aquí.)
El procedimiento es frecuentemente el primer estilo de programación enseñado. Un programa está organizado en “procedimientos” (también conocido como funciones o subrutinas) que luego operan sobre datos. El código generalmente se escribe de manera imperativa: haz X, luego S, luego Z. Esas subrutinas frecuentemente tienen efectos secundarios; es decir, algún estado del programa ha cambiado permanentemente de una manera que perdura más allá de la vida de la subrutina.
La programación funcional es tan antigua como la programación de procedimientos. De hecho, los dos enfoques fueron el cisma original en la conceptualización de la programación. En la programación puramente funcional, uno no escribe un conjunto de pasos para que el programa siga. En su lugar, uno escribe funciones matemáticas que se relacionan entre sí. Eso tiene una serie de atributos importantes: más notablemente, los programas puramente funcionales son incapaces de tener efectos secundarios. Las funciones no tienen un estado propio y, de hecho, una vez que una variable tiene un valor, no puede volver a cambiarse. Siempre. La salida de una función depende exclusivamente de sus entradas explícitas. En general, los lenguajes funcionales también tratan a las funciones como objetos de primera clase junto con otras variables más familiares, como ints, cadenas, etc.
Los lenguajes declarativos evitan especificar cómo la computadora debe hacer algo a favor de decir lo que debería hacer, de alguna manera. Esto es extremadamente poderoso para simplificar tareas comunes, pero puede dificultar el desarrollo de capacidades nuevas e innovadoras; todavía necesitan ser traducidos de la forma declarativa a un enfoque ejecutable en algún lugar. El propio SQL es el ejemplo más obvio para los desarrolladores web, pero hay muchos más. Incluso algunos lenguajes de marcado pueden, posiblemente, considerarse programación declarativa. (Ver también: HTML5 o SVG)
Programación clásica orientada a objetos
En OO basado en clases (o “Clásico”, como tiendo a llamarlo), en lugar de encapsular la lógica en subrutinas simples que operan en datos arbitrarios pasados a ellos, la lógica y los datos se agrupan en “objetos”. Estos objetos son tratados por el mundo exterior como una sola caja negra. La manipulación de datos no ocurre directamente, sino que se realiza a través de la lógica vinculada a esos datos. Es decir, a través de métodos sobre el objeto. Los ejemplos populares aquí incluyen C++, Java y PHP.
Esta variante sin clases de OO está, de alguna manera, más cercana a la programación funcional. Javascript es el ejemplo más conocido de este estilo. Como no es realmente posible en PHP, lo dejaremos por el momento. Lo separo del OO “clásico” principalmente para señalar que el enfoque de PHP para OO no es de ninguna manera universal.
Programación Orientada a Aspectos
Como un recién llegado relativo en la escena, AOP se basa en crear puntos de unión entre diferentes partes de un sistema. Es decir, lugares donde una rutina lógica puede inyectarse en otra sin tener que modificar ninguna de las dos directamente.
(Nota: estoy seguro de que algunos puristas dirán que estoy simplificando excesivamente uno o más de los estilos anteriores. Probablemente tengan razón, pero por el simple hecho de discutir solo estoy considerando algunos aspectos de esos enfoques Si quieres un tratamiento más completo, para eso son los enlaces. Recomiendo leerlos con una mente abierta.
Hay un hecho muy importante acerca de los diseños anteriores que es importante tener en cuenta: son equivalentes. Se ha demostrado matemáticamente que los lenguajes de procedimiento y funcionales son igualmente expresivos; es decir, cualquier algoritmo que pueda implementar en uno puede implementarse en el otro. OOP y AOP son esencialmente producto de la programación de procedimientos y con frecuencia se implementan en lenguajes de paradigma múltiple, por lo que cualquier cosa que pueda hacer en lenguaje de procedimientos se puede hacer en un lenguaje de POO o AOP, o viceversa. La programación declarativa es el hombre extraño ya que muchos lenguajes declarativos no están completamente completos, aunque muchos lo son si lo intentas lo suficiente.
Entonces, si todos estos paradigmas de programación son equivalentes, ¿por qué molestarse en usar uno sobre otro?
Enfoque de lenguaje
Simple: cada enfoque tiene diferentes ventajas y desventajas que hacen que sea más fácil o más difícil escribir ciertos tipos de algoritmos. Imposible”; Cualquier funcionalidad que pueda implementar en lenguajes funcionales puede implementarse en un lenguaje orientado a aspectos, y viceversa. Lo hacen “más fácil”. La cantidad de código y la complejidad incomprensible implicada variarán enormemente. ¿Qué hace que diferentes paradigmas sean más fáciles de aplicar a ciertos algoritmos? Lo que no te dejan hacer.
Está bien. Lo que hace que un estilo de programación sea más fácil es lo que no te permite hacer. Porque si sabe a ciencia cierta que ciertas cosas no son posibles, puede hacer suposiciones basadas en esa imposibilidad que facilitan otras tareas.
Funcional: Lógica centrada.
Por ejemplo, en los lenguajes puramente funcionales que sabe a ciencia cierta que el mismo conjunto de entradas a una función dada será siempre el mismo resultado. Eso significa que el propio compilador puede optimizar la distancia varias llamadas a la misma función con los mismos parámetros. Seguir adelante y llamar a una función varias veces. No se moleste almacenamiento en caché el resultado. La semántica del lenguaje sí lo hará por usted sin ningún pensamiento de su parte. Haskell, creo, hace exactamente eso. También hace fácil verificabilidad; se puede demostrar matemáticamente la corrección de una función independiente particular del resto del sistema. Eso puede ser muy útil en un sistema intolerantes a la culpa, tales como, por ejemplo, reactores nucleares o de control del tráfico aéreo donde los insectos se vuelven muy, muy peligroso.
Puesto que usted sabe, a ciencia cierta, que una función no afectará a cualquier código fuera de sí mismo (aparte de su valor de retorno), no hay ningún requisito de que una función tiene acceso a todos los datos, excepto sus propios parámetros. Ni siquiera tiene que estar en el mismo espacio de memoria que el resto del programa… o incluso en el mismo equipo. Sea testigo de Erlang, donde cada función puede ejecutarse en su propio hilo, o su propio proceso, o incluso en un proceso en un equipo diferente sin cambios sintácticos. Eso hace que los programas escritos en Erlang extremadamente distribuible y escalable, precisamente porque las funciones son incapaces de producir efectos secundarios.
Por supuesto, para algunos casos de uso, la estructura del programa requiere que los lenguajes funcionales se vuelvan terriblemente desagradables. Esto es especialmente cierto para los programas que se basan en manipular el estado a largo plazo. Esa es la desventaja: una estructura clara y lógicamente simple que hace que los algoritmos complejos sean fáciles de compilar y se adapte bien, pero hace que los sistemas con estado sean más difíciles de construir.
Tramitación: centrada en la instrucción.
Los programas de procedimiento fueron la otra bifurcación importante en la teoría del lenguaje de programación. La programación de procedimientos puede comenzar de manera muy simple como solo una lista de instrucciones, dividida en partes (subrutinas o funciones). La mayoría también incluye el estado global de una forma u otra además del estado de ámbito local.
Lo que los lenguajes de procedimiento no le permiten hacer es segmentar en gran medida su programa. Hay un gran grupo de subrutinas que se pueden llamar prácticamente en cualquier momento. No puede vincular una subrutina determinada a solo ciertos datos o viceversa. Eso hace que el código sea altamente impredecible, ya que su función se puede llamar desde cualquier lugar y en cualquier momento. No puede hacer suposiciones sobre el entorno en el que se está ejecutando, especialmente si su sistema utiliza variables globales (o sus primas cercanas, variables estáticas). No hay forma de ocultar datos, no hay forma de controlar cuándo una rutina determinada puede o no puede ejecutarse, no hay manera de protegerse contra otro desarrollador que se abre camino en una subrutina que no fue diseñada para ser pirateada.
Que por supuesto es también su poder. Debido a que es de tan bajo nivel y no tiene salvaguardas, puede abrirse camino (dentro o fuera de) la mayoría de las situaciones con suficiente esfuerzo. Como no puede ocultar datos, obtiene una gran flexibilidad. Eso puede ser muy bueno en algunos casos. Por otro lado, eso significa que, en general, la programación de procedimientos no le permite hacer suposiciones sobre el contexto de su sistema o su estado.
Debido a que tiene un control tan limitado, es extremadamente difícil realizar cualquier forma significativa de prueba unitaria. Puede realizar pruebas funcionales (es decir, pruebas de funcionalidad a un alto nivel) o pruebas de integración, pero no tiene unidades claramente separables para trabajar.
Orientado a objetos: centrada en el comportamiento
Hay muchas variaciones en los lenguajes orientados a objetos, cada uno con sus propias sutilezas. Por el momento, solo nos interesan los lenguajes de clase e interfaz, como PHP. La parte de la interfaz es importante: en un lenguaje OO clásico, los valores primitivos individuales son irrelevantes. Lo que importa es la interfaz con un objeto, según lo definido por su clase e interfaces. La clase forma un tipo de datos completamente nuevo con su propia semántica.
Así como una cadena primitiva tiene su semántica (por ejemplo, longitud) y posibles operaciones (Split, concatenar, etc.), también lo hace un objeto. La implementación interna de la cadena es irrelevante: puede ser una tabla hash, puede ser una matriz de caracteres recta. Como alguien que hace uso de ella no conoce, ni qué te importa (excepto en C, por supuesto). Del mismo modo con un objeto, que tiene comportamientos como se define por sus métodos. La implementación subyacente es irrelevante.
Los datos dentro de la clase está estrechamente unida a la clase; la clase en sí es (si se hace correctamente) débilmente acoplado a cualquier otra cosa. Los datos dentro de la clase es irrelevante para cualquier cosa menos esa clase. Debido a que está escondido (“encapsulado” en la jerga académica), usted sabe, a ciencia cierta, que sólo selecciona fragmentos de código (en la misma clase) son capaces de modificarlo. Al contrario que en el código de procedimiento, que puede confiar en los datos no cambia por debajo suyo. Incluso puede reestructurar por completo el código. Siempre y cuando la interfaz no cambia, es decir, el comportamiento, estás bien.
Esa es una distinción importante. En OO, no estás codificando los datos. Estás codificando el comportamiento. Los datos son secundarios al comportamiento de un objeto.
Debido a que usted ha aislado de datos detrás de las paredes de comportamiento, puede verificar y prueba de unidad de cada clase de forma independiente. Es decir, suponiendo que hemos aislado adecuadamente su objeto. Una gran cantidad de código no correctamente hacerlo, lo que contradice el objetivo. (Ver mis diatribas anteriores sobre la inyección de dependencia).
Orientada a los aspectos: centrada en los efectos secundarios.
Y, finalmente, llegamos a la programación orientada a aspectos de los chicos nuevos en el bloque. De alguna manera, AOP es el opuesto diametral de la programación funcional. Cuando la programación funcional intenta eliminar los efectos secundarios, AOP se basa en ellos. Cada punto de unión en AOP es una gran bandera roja que dice “haga efectos secundarios aquí”. Esos efectos secundarios pueden ser todo tipo de cosas. Podrían modificar los datos, podrían cambiar el flujo del programa, podrían iniciar otra lógica de banda lateral e incluso desencadenar otros efectos secundarios.
Lo que ofrece AOP es exactamente eso: la capacidad de modificar un programa sin modificarlo. Una vez que se establece un punto de unión, puede alterar los datos o la lógica del programa en ese punto sin cambiar ningún código existente. Eso proporciona una gran flexibilidad y extensibilidad, pero a expensas del control.
Una vez que introduce una manera de permitir que un código de terceros modifique su flujo lógico o sus datos, renuncia a cualquier capacidad para controlar ese flujo lógico o datos. Ya no puede hacer suposiciones sobre su estado, porque ha incorporado un mecanismo para permitir que su estado cambie desde debajo de usted. La forma en que compartimenta su código es hacer que sea imposible compartimentar completamente su código. (Reflexiona sobre eso por un momento…)
Compensaciones
Los enfoques funcionales enfatizan la verificabilidad, la capacidad de prueba y la escalabilidad a expensas de la modificabilidad, la extensibilidad y, en algunos casos, la comprensibilidad.
Los enfoques de procedimiento enfatizan la modificabilidad, la comprensibilidad y la conveniencia a expensas de la verificabilidad, la verificabilidad y, si no se tiene cuidado, la capacidad de mantenimiento.
Los enfoques orientados a objetos enfatizan la capacidad de prueba, la modificabilidad y la escalabilidad a expensas de la extensibilidad, la conveniencia y, si tiene un diseño pobre, comprensible.
Los enfoques orientados a los aspectos enfatizan la modificabilidad, la extensibilidad y la conveniencia a expensas de la verificabilidad, la verificabilidad y, posiblemente, la comprensibilidad.
Oh genial, ¿cuál queremos usar? ¿Cuál es el mejor enfoque? El que mejor se adapte a su caso de uso y prioridades, por supuesto.
Lenguajes multi-paradigmas
Debido a que todos estos enfoques son perfectamente viables dependiendo de su caso de uso, la mayoría de los lenguajes de programación más importantes en la actualidad son de múltiples paradigmas. Es decir, admiten, al menos en cierta medida, múltiples enfoques y formas de pensar acerca de la lógica del programa.
PHP comenzó la vida como un lenguaje enteramente procedural. Con PHP 4 comenzó a agregar capacidades orientadas a objetos, aunque esas realmente no se consideraron una alternativa viable hasta que PHP 5. PHP 5.3 introdujo funciones anónimas de primera clase, que no son programación funcional pura, ya que aún permiten que las variables ser cambiado permite a los programadores que están tan inclinados a escribir de una manera más funcional.
Aunque la mayoría de las implementaciones orientadas a aspectos se construyen sobre modelos orientados a objetos, PHP es compatible con AOP basado en procedimientos. En Drupal, lo llamamos ganchos. module_invoke_all() se convierte en un punto conjunto, y una implementación de gancho se convierte en un punto de corte.
(No soy de ninguna manera el primero en llamar sistema de gancho de Drupal una forma de AOP. Creo que es una forma particularmente buena de describirlos).
Para ser justos, sin ganchos de soporte sintáctico nativos son un AOP de tipo pobre, bastante torpe y pirateado, pero conceptualmente todavía es AOP. Tienen las mismas ventajas y desventajas implícitas: extremadamente flexibles cuando se usan de manera adecuada, pero destruyen totalmente cualquier esperanza de aislar un sistema para probarlo por unidad o hacer un desarrollo basado en interfaces.
El hecho de que también están atornillados en la parte superior de un lenguaje no AOP pero no documentados como AOP, o aplicarse de manera coherente, es también un obstáculo importante para los nuevos desarrolladores, especialmente los que se han criado en un mundo predominantemente OO.
Así como es posible emular AOP en el código de procedimiento, es posible en código orientado a objetos también. Hay muchos patrones de programación orientada a objetos que le dan todos la misma flexibilidad que AOP, por ejemplo, a veces de manera más detallada. Observadores y visitantes patrones vienen a la mente, en particular. Una vez más, no es una cuestión de lo pueden realizar un diseño dado, pero la facilidad con que puede hacerlo, ya qué costo.
Nada prohíbe la mezcla y combinación de diferentes enfoques, ya sea. Tome capa de base de datos de Drupal 7. Es sobre todo hacia arriba OO – inyectado dependencia modular, autónomo-impulsado por la interfaz – pero lanza en algunos AOP en forma de hook_query_alter () y tiene envolturas de conveniencia de procedimiento, como db_query (). Desde luego, no pretendo que se trata de un equilibrio perfecto, pero sí muestra cómo los enfoques múltiples se puede aprovechar juntos.
Decisiones decisiones
Al considerar cómo abordar un problema determinado o cómo utilizar una función de idioma en particular, no es suficiente decir “bueno, me gusta X” o “acercarse a Y es estúpido”. Ese es un enfoque ingenuo, y tiende a conducir a un código de espagueti. (La pasta existe en todos los idiomas). En cambio, deberíamos preguntarnos cuáles son nuestras prioridades, a qué estamos dispuestos a renunciar y qué estamos dispuestos a hacer para mitigarla. Siempre tenemos que renunciar a algo. Siempre.
El costo que desea pagar no siempre es un saldo fácil de alcanzar. ¿Prefiere la robustez (capacidad de prueba, verificabilidad, escalabilidad, ocultación de datos, encapsulación, etc.), flexibilidad (modificabilidad, extensibilidad, datos simples, etc.) o simplicidad (conveniencia, mantenibilidad, posiblemente rendimiento, etc.)?
Elige dos.
Mi agradecimiento a Bec White y Matt Farina por sus aportaciones en este artículo.
Recent Comments