2008-09-07 17 views
23

Estoy investigando y experimentando más con Groovy y estoy tratando de entender las ventajas y desventajas de implementar cosas en Groovy que no puedo/debo hacer en Java. La programación dinámica sigue siendo solo un concepto para mí, ya que he estado profundamente inmerso en lenguajes estáticos y fuertemente tipados.¿Cuáles son algunas de las ventajas de la tipificación de patos frente a la tipificación estática?

Groovy me da la capacidad de duck-type, pero realmente no puedo ver el valor. ¿Cómo es más productivo el tipado de patos que el tipado estático? ¿Qué tipo de cosas puedo hacer en mi práctica de código para ayudarme a comprender los beneficios?

Hago esta pregunta teniendo en cuenta a Groovy, pero entiendo que no es necesariamente una pregunta de Groovy así que doy la bienvenida a las respuestas de cada campo de código.

+1

escribiendo pato y tipos estáticos son conceptos ortogonales. –

Respuesta

4

Siguiente, que es mejor: EMACS o vi? Esta es una de las guerras religiosas corrientes.

Piénselo de esta manera: cualquier programa que sea correcto, será correcto si el lenguaje está estático. Lo que hace el tipado estático es dejar que el compilador tenga suficiente información para detectar desajustes de tipos en tiempo de compilación en lugar de tiempo de ejecución. Esto puede ser una molestia si haces un tipo incremental de programación, aunque (lo mantengo) si estás pensando claramente sobre tu programa, no importa mucho; por otro lado, si está construyendo un programa realmente grande, como un sistema operativo o un conmutador de teléfono, con docenas o cientos o miles de personas trabajando en él, o con requisitos de confiabilidad realmente altos, entonces tener el compilador puede detectar una gran clase de problemas para usted sin necesidad de un caso de prueba para ejercer solo la ruta correcta del código.

No es que la tipificación dinámica sea algo nuevo y diferente: C, por ejemplo, se tipea de forma dinámica, ya que siempre puedo convertir foo* en bar*. Simplemente significa que es mi responsabilidad como programador en C nunca utilizar el código que es apropiado en un bar* cuando la dirección realmente apunta a un foo*. Pero como resultado de los problemas con los programas grandes, C desarrolló herramientas como lint (1), fortaleció su sistema de tipos con typedef y finalmente desarrolló una variante fuertemente tipada en C++. (Y, por supuesto, C++ a su vez desarrolló formas en torno a la tipificación fuerte, con todas las variedades de moldes y genéricos/plantillas y con RTTI.

Sin embargo, otra cosa --- no confunda la "programación ágil" con los "lenguajes dinámicos". Agile programming se trata de la forma en que las personas trabajan juntas en un proyecto: ¿puede el proyecto adaptarse a los requisitos cambiantes para satisfacer las necesidades de los clientes mientras se mantiene un entorno humano para los programadores? Se puede hacer con lenguajes de tipado dinámico, y a menudo lo es, porque pueden ser más productivos (por ejemplo, Ruby, Smalltalk), pero se puede hacer, se ha realizado con éxito, en C e incluso en ensamblador. De hecho, Rally Development incluso usa métodos ágiles (SCRUM en particular) para hacer marketing y documentación.

+9

No es cierto que cualquier programa que sea correcto será correcto en tipeo estático. La tipificación estática es una aproximación conservadora al comportamiento en tiempo de ejecución. Esta es la razón por la que algunos programas con moldes aún pueden ser de tipo correcto (preservando un invariante que el verificador de tipos no puede probar). –

+4

Lo sentimos, el sistema de tipo débil de C no es lo mismo que el tipado dinámico. Ni siquiera está cerca. No hay un enlace tardío. Lanzar punteros como su ejemplo hará que el programa asuma una estructura subyacente diferente que conduce a errores, no a la funcionalidad. – postfuturist

+0

Doug, creo que es un teorema. Supongamos lo contrario: luego tiene un programa que tiene un programa que es correcto, es decir, cumple con la condición posterior, pero donde existe una declaración para la cual no es correcto el tipado estático. Pero eso es equivalente a que exista una declaración sin semántica definida en el prg correcto, Contradictos –

1

No es que la mecanografía de pato sea más productiva que la mecanografía estática tanto como simplemente es diferente. Con el tipado estático, siempre debe preocuparse de que sus datos sean del tipo correcto, y en Java se muestra a través del envío al tipo correcto. Con el tipado de pato, el tipo no importa, siempre que tenga el método correcto, por lo que realmente elimina la molestia de la fundición y las conversiones entre los tipos.

+0

Hay mucha evidencia anecdótica que sugiere que la tipificación de pato es significativamente más productiva que la tipificación estática. – postfuturist

+1

@postfuturist: solo para sistemas de tipo estático nominal. –

6

Es un poco difícil ver el valor de escribir patos hasta que lo haya usado por un tiempo. Una vez que te hayas acostumbrado, te darás cuenta de lo recargado que es no tener que lidiar con interfaces o tener que preocuparte por qué tipo de objeto es exactamente.

1

@ Chris Manojo

No es que los tipos estáticos es más productivo que la tipificación de pato tanto como lo es simplemente diferente. Con el tipado de pato siempre debe preocuparse de que sus datos tengan el método correcto y en Javascript o Ruby se muestren a través de muchas pruebas de métodos. Con el tipado estático no importa, siempre y cuando sea la interfaz correcta, por lo que realmente elimina la molestia de las pruebas y las conversiones entre los tipos.

Lo siento, pero tenía que hacerlo ...

+0

Hehe, por supuesto, ¡es todo cierto! ¡Es por eso que ninguno de ellos es realmente más productivo y simplemente diferente! –

9

No hay nada malo con tipos estáticos si está utilizando Haskell, que tiene un sistema de tipo estático increíble. Sin embargo, si está utilizando lenguajes como Java y C++ que tienen sistemas de tipo terriblemente paralizantes, el tipado de patos es definitivamente una mejora.

Imagine que intenta usar algo tan simple como "map" en Java (y no, no me refiero a the data structure). Incluso los genéricos son bastante poco compatibles.

+1

Haskell tiene un gran sistema de tipos; sería increíble que otros idiomas utilizaran un mecanismo similar. – mipadi

+0

No hay nada de malo con el tipo de mapa de Java. El problema es que Java no tiene una sintaxis fluida para tratar con Maps. Eso es algo que Groovy aborda, pero en realidad no está relacionado con el tipado de patos de Groovy. – slim

+0

@slim: no estoy hablando del tipo 'mapa', estoy hablando de la función de orden superior 'mapa'. Añadiré un enlace a mi respuesta. –

7

El tipado de pato anula la comprobación estática más moderna de IDE, que puede señalar errores a medida que escribe. Algunos lo consideran una ventaja. Quiero que IDE/Compiler me diga que he hecho un estúpido truco de programador tan pronto como sea posible.

Mi más reciente argumento favorito contra tipificación de pato viene de un DTO proyecto Grails:

class SimpleResults { 
    def results 
    def total 
    def categories 
} 

donde results resulta ser algo así como Map<String, List<ComplexType>>, que puede ser descubierta sólo siguiendo un rastro de llamadas a métodos en diferentes clases hasta que encuentres dónde fue creado. Para los curiosos terminal, total es la suma de los tamaños de los List<ComplexType> s y categories es el tamaño de la Map

Puede haber sido claro para el desarrollador original, pero el pobre chico de mantenimiento (ME) perdido mucho cabello siguiendo este.

+1

Aunque las declaraciones explícitas de tipo pueden ser instructivas, la reflexión es a menudo un sustituto decente. Abra una sesión interactiva y pregunte a esos objetos de qué tipo son. –

+0

-1: Esos no son problemas con el tipado de patos. –

+0

Su ejemplo señala los problemas con documentación deficiente, no con tipeo dinámico (y aquí no hay tipado de pato). En tipeo estático, sabría (porque está declarado) que 'results' es un' Map > ', y que' total' es un 'int' y' categories' es un 'int'. Eso no me dice mucho acerca de cómo usar esta clase. – Ben

3

En mi humilde opinión, la ventaja de la mecanografía de pato se magnifica cuando se cumplen algunas convenciones, como nombrar variables y métodos de manera consistente. Tomando el ejemplo de Ken G, creo que sería leer mejor:

class SimpleResults { 
    def mapOfListResults 
    def total 
    def categories 
} 

Digamos que se define un contrato en alguna operación denominada 'calculateRating (A, B)' donde A y B se adhieren a otro contrato. En pseudocódigo, se leería:

Long calculateRating(A someObj, B, otherObj) { 

    //some fake algorithm here: 
    if(someObj.doStuff('foo') > otherObj.doStuff('bar')) return someObj.calcRating()); 
    else return otherObj.calcRating(); 

} 

Si desea implementar esto en Java, tanto A como B deben implementar algún tipo de interfaz que dice algo como esto:

public interface MyService { 
    public int doStuff(String input); 
} 

Además, si quiera que generalizar el contrato para el cálculo de puntuaciones (digamos que usted tiene otro algoritmo para el cálculo de calificación), también se tiene que crear una interfaz:

public long calculateRating(MyService A, MyServiceB); 

con la tipificación de pato, se puede d Pica tus interfaces y solo confía en que en tiempo de ejecución, tanto A como B responderán correctamente a tus llamadas doStuff(). No hay necesidad de una definición de contrato específica. Esto puede funcionar para usted, pero también puede funcionar en su contra.

El inconveniente es que debe tener mucho cuidado para garantizar que su código no se rompa cuando otras personas lo cambian (es decir, la otra persona debe conocer el contrato implícito sobre el nombre y los argumentos del método) .

Tenga en cuenta que esto se agrava especialmente en Java, donde la sintaxis no es tan estricta como podría ser (en comparación con Scala, por ejemplo). Un contraejemplo de esto es el Lift framework, donde dicen que el recuento SLOC del marco es similar a Rails, pero el código de prueba tiene menos líneas porque no necesitan implementar verificaciones de tipo dentro de las pruebas.

13

Muchos de los comentarios sobre el tipado de patos realmente no corroboran las afirmaciones. No "tener que preocuparse" por un tipo no es sostenible para el mantenimiento o para hacer una aplicación extensible. Realmente tuve una buena oportunidad para ver a Grails en acción en mi último contrato y es realmente divertido de ver. Todo el mundo está contento con las ganancias de poder "crear-aplicación" y ponerse en marcha, lamentablemente todo te alcanza en la parte de atrás.

Groovy me parece lo mismo. Claro que puedes escribir un código muy sucinto y definitivamente hay algo de azúcar en cómo trabajamos con propiedades, colecciones, etc. Pero el costo de no saber qué diablos pasa de un lado a otro simplemente empeora cada vez más. En algún punto, se rasca la cabeza preguntándose por qué el proyecto se ha convertido en 80% de prueba y 20% de trabajo. La lección aquí es que "más pequeño" no significa código "más legible". Lo siento amigos, es una lógica simple: cuanto más se tiene que saber intuitivamente, más complejo es el proceso de comprensión de ese código. Es por eso que las GUI se han vuelto cada vez más icónicas a lo largo de los años. Seguro que se ve bien, pero WTH no siempre es obvio.

Las personas en ese proyecto parecían tener problemas para "clavar" las lecciones aprendidas, pero cuando tienes métodos que devuelven un solo elemento de tipo T, una matriz de T, un ErrorResult o un nulo ... se convierte en algo aparente.

Una cosa que trabajar con Groovy ha sido para mí, sin embargo, ¡horas increíbles facturables!

2

Aquí hay un caso en el que la escritura de pato ahorra trabajo.

Aquí es una clase muy trivial

class BookFinder { 
    def searchEngine 

    def findBookByTitle(String title) { 
     return searchEngine.find([ "Title" : title ]) 
    } 
} 

Ahora para la prueba de unidad:

void bookFinderTest() { 
    // with Expando we can 'fake' any object at runtime. 
    // alternatively you could write a MockSearchEngine class. 
    def mockSearchEngine = new Expando() 
    mockSearchEngine.find = { 
     return new Book("Heart of Darkness","Joseph Conrad") 
    } 

    def bf = new BookFinder() 
    bf.searchEngine = mockSearchEngine 
    def book = bf.findBookByTitle("Heart of Darkness") 
    assert(book.author == "Joseph Conrad" 
} 

Nos eran capaces de sustituir un Expando para la SearchEngine, debido a la ausencia de comprobación de tipo estático. Con la comprobación del tipo estático, deberíamos asegurarnos de que SearchEngine fuera una interfaz, o al menos una clase abstracta, y crear una implementación simulada completa de la misma. Eso requiere mucha mano de obra, o puede usar un sofisticado marco de burla de un solo propósito. Pero el tipado de pato es de uso general y nos ha ayudado.

Debido a la tipificación de pato, nuestra prueba de unidad puede proporcionar cualquier objeto antiguo en lugar de la dependencia, siempre que implemente los métodos que se llaman.

Para destacar: puede hacerlo en un lenguaje estáticamente tipado, con un uso cuidadoso de las interfaces y las jerarquías de clases. Pero con el tipado de patos puedes hacerlo con menos pensamiento y menos teclas.

Esa es una ventaja de la tipificación de pato. No significa que el tipado dinámico sea el paradigma correcto para usar en todas las situaciones. En mis proyectos Groovy, me gusta volver a Java en circunstancias en las que siento que las advertencias del compilador sobre los tipos me van a ayudar.

+1

-1: Está hablando de un tipo específico de sistema de tipo estático * nominal * y suponiendo (incorrectamente) que sus observaciones se apliquen a todos los sistemas de tipo estático. Ellos no –

2

Mi opinión:

tipos dinámicos o de pato mecanografiado lenguas son juguetes. No puede obtener Intellisense y pierde tiempo de compilación (o tiempo de edición, al usar un IDE REAL como VS, no es que la basura los demás piensen que son IDE) validación de código.

Manténgase alejado de todos los idiomas que no están tipados estáticamente, todo lo demás es simplemente masoquismo.

2

Con, TDD + Cobertura de código 100% + Herramientas IDE para ejecutar constantemente mis pruebas, ya no siento la necesidad de escribir estática. Sin tipos fuertes, mi prueba de unidad se ha vuelto tan fácil (simplemente use Maps para crear objetos simulados). Especialmente, cuando se utiliza genéricos, se puede ver la diferencia:

//Static typing 
Map<String,List<Class1<Class2>>> someMap = [:] as HashMap<String,List<Class1<Class2>>> 

vs

//Dynamic typing 
def someMap = [:] 
Cuestiones relacionadas