Solía codificar mucho en Python. Ahora, por razones de trabajo, codifico en Java. Los proyectos que hago son bastante pequeños, y posiblemente Python funcionaría mejor, pero hay razones válidas que no son de ingeniería para usar Java (no puedo entrar en detalles).
La sintaxis de Java no es un problema; Es solo otro idioma. Pero aparte de la sintaxis, Java tiene una cultura, un conjunto de métodos de desarrollo y prácticas que se consideran "correctas". Y por ahora estoy fallando completamente en "asimilar" esa cultura. Así que realmente agradecería explicaciones o punteros en la dirección correcta.
Un ejemplo completo mínimo está disponible en una pregunta de desbordamiento de pila que comencé: https://stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339
Tengo una tarea: analizar (desde una sola cadena) y manejar un conjunto de tres valores. En Python es una línea única (tupla), en Pascal o C un registro / estructura de 5 líneas.
Según las respuestas, el equivalente de una estructura está disponible en la sintaxis de Java y un triple está disponible en una biblioteca Apache ampliamente utilizada, aunque la forma "correcta" de hacerlo es en realidad creando una clase separada para el valor, completa con captadores y colocadores. Alguien fue muy amable al proporcionar un ejemplo completo. Tenía 47 líneas de código (bueno, algunas de estas líneas eran espacios en blanco).
Entiendo que una gran comunidad de desarrollo probablemente no esté "equivocada". Así que este es un problema con mi comprensión.
Las prácticas de Python optimizan la legibilidad (que, en esa filosofía, conduce a la capacidad de mantenimiento) y después de eso, la velocidad de desarrollo. Las prácticas de C se optimizan para el uso de recursos. ¿Para qué se optimizan las prácticas Java? Mi mejor suposición es la escalabilidad (todo debería estar en un estado listo para un proyecto de millones de LOC), pero es una suposición muy débil.
fuente
Respuestas:
El lenguaje Java
Creo que todas estas respuestas están perdiendo el punto al intentar atribuir la intención a la forma en que funciona Java. La verbosidad de Java no se deriva de que esté orientada a objetos, ya que Python y muchos otros lenguajes todavía tienen una sintaxis terser. La verbosidad de Java tampoco proviene de su soporte de modificadores de acceso. En cambio, es simplemente cómo se diseñó y evolucionó Java.
Java se creó originalmente como una C ligeramente mejorada con OO. Como tal, Java tiene una sintaxis de la era de los 70. Además, Java es muy conservador acerca de agregar características para mantener la compatibilidad con versiones anteriores y permitirle resistir el paso del tiempo. Si Java hubiera agregado características modernas como los literales XML en 2005, cuando XML estaba de moda, el lenguaje habría estado repleto de características fantasmas que a nadie le importan y que limitan su evolución 10 años después. Por lo tanto, Java simplemente carece de mucha sintaxis moderna para expresar conceptos concisamente.
Sin embargo, no hay nada fundamental que evite que Java adopte esa sintaxis. Por ejemplo, Java 8 agregó lambdas y referencias de métodos, reduciendo en gran medida la verbosidad en muchas situaciones. Java podría agregar de manera similar soporte para declaraciones de tipo de datos compactas como las clases de caso de Scala. Pero Java simplemente no lo ha hecho. Tenga en cuenta que los tipos de valores personalizados están en el horizonte y esta característica puede introducir una nueva sintaxis para declararlos. Supongo que ya veremos.
La cultura de Java
La historia del desarrollo empresarial de Java nos ha llevado en gran medida a la cultura que vemos hoy. A finales de los 90 / principios de los 00, Java se convirtió en un lenguaje extremadamente popular para las aplicaciones comerciales del lado del servidor. En aquel entonces, esas aplicaciones se escribieron en gran medida ad-hoc e incorporaron muchas preocupaciones complejas, como las API HTTP, las bases de datos y el procesamiento de fuentes XML.
En los años 00 quedó claro que muchas de estas aplicaciones tenían mucho en común y los marcos para gestionar estas preocupaciones, como Hibernate ORM, el analizador Xerces XML, JSP y la API de servlet, y EJB, se hicieron populares. Sin embargo, si bien estos marcos redujeron el esfuerzo para trabajar en el dominio particular que configuraron para automatizar, requirieron configuración y coordinación. En ese momento, por cualquier razón, era popular escribir marcos para atender el caso de uso más complejo y, por lo tanto, estas bibliotecas eran complicadas de configurar e integrar. Y con el tiempo se volvieron cada vez más complejos a medida que acumulaban características. El desarrollo empresarial de Java gradualmente se convirtió cada vez más en unir bibliotecas de terceros y menos en escribir algoritmos.
Finalmente, la tediosa configuración y administración de las herramientas empresariales se volvió lo suficientemente dolorosa como para que los marcos, especialmente el marco de Spring, aparecieran para administrar la administración. Podría poner toda su configuración en un solo lugar, según la teoría, y la herramienta de configuración luego configuraría las piezas y las uniría. Desafortunadamente, estos "marcos de trabajo" agregaron más abstracción y complejidad a toda la bola de cera.
En los últimos años, las bibliotecas más livianas han crecido en popularidad. No obstante, toda una generación de programadores de Java alcanzó la mayoría de edad durante el crecimiento de los marcos empresariales pesados. Sus modelos a seguir, los que desarrollan los marcos, escribieron fábricas de fábrica y cargadores de beans de configuración proxy. Tenían que configurar e integrar estas monstruosidades día a día. Y como resultado, la cultura de la comunidad en su conjunto siguió el ejemplo de estos marcos y tendió a un exceso de ingeniería.
fuente
for
bucle cuandomap
sería más apropiado (y legible). Hay un medio feliz.Creo que tengo una respuesta para uno de los puntos que plantea que otros no han planteado.
Java se optimiza para la detección de errores de programador en tiempo de compilación al nunca hacer suposiciones
En general, Java tiende a inferir solo hechos sobre el código fuente después de que el programador ya ha expresado explícitamente su intención. El compilador de Java nunca hace suposiciones sobre el código y solo usará inferencia para reducir el código redundante.
La razón detrás de esta filosofía es que el programador es solo humano. Lo que escribimos no siempre es lo que realmente pretendemos que haga el programa. El lenguaje Java intenta mitigar algunos de esos problemas al obligar al desarrollador a declarar siempre explícitamente sus tipos. Esa es solo una forma de verificar que el código que se escribió realmente cumpla lo que se pretendía.
Algunos otros idiomas impulsan esa lógica aún más al verificar las condiciones previas, las condiciones posteriores y las invariantes (aunque no estoy seguro de que lo hagan en tiempo de compilación). Estas son formas aún más extremas para que el programador haga que el compilador verifique su propio trabajo.
En su caso, esto significa que para que el compilador garantice que realmente está devolviendo los tipos que cree que está devolviendo, debe proporcionar esa información al compilador.
En Java hay dos formas de hacerlo:
Utilizo
Triplet<A, B, C>
como tipo de retorno (que en realidad debería estar enjava.util
, y realmente no puedo explicar por qué no lo es. Sobre todo porque JDK8 introducirFunction
,BiFunction
,Consumer
,BiConsumer
, etc ... pero parece quePair
yTriplet
al menos tendría sentido. Pero estoy divagando )Cree su propio tipo de valor para ese propósito, donde cada campo se nombra y se escribe correctamente.
En ambos casos, el compilador puede garantizar que su función devuelva los tipos que declaró, y que la persona que llama se dé cuenta de cuál es el tipo de cada campo devuelto y los use en consecuencia.
Algunos idiomas proporcionan verificación de tipo estático Y inferencia de tipo al mismo tiempo, pero eso deja la puerta abierta para una clase sutil de problemas de desajuste de tipo. Cuando el desarrollador tenía la intención de devolver un valor de cierto tipo, pero en realidad devuelve otro y el compilador TODAVÍA acepta el código porque sucede que, por coincidencia, tanto la función como la persona que llama solo usan métodos que se pueden aplicar a ambos y los tipos reales
Considere algo como esto en Typecript (o tipo de flujo) donde se usa la inferencia de tipos en lugar de la escritura explícita.
Este es un caso trivial tonto, por supuesto, pero puede ser mucho más sutil y conducir a problemas difíciles de depurar, porque el código parece correcto. Este tipo de problemas se evita por completo en Java, y en eso está diseñado todo el lenguaje.
Tenga en cuenta que siguiendo los principios orientados a objetos adecuados y el modelado basado en dominio, el caso de análisis de duración en la pregunta vinculada también podría devolverse como un
java.time.Duration
objeto, que sería mucho más explícito que los dos casos anteriores.fuente
Java y Python son los dos idiomas que más uso, pero vengo de la otra dirección. Es decir, estaba inmerso en el mundo de Java antes de comenzar a usar Python, por lo que podría ayudarlo. Creo que la respuesta a la pregunta más amplia de "por qué las cosas son tan pesadas" se reduce a 2 cosas:
yield
.Java 'optimiza' para software de alto valor que será mantenido por muchos años por grandes equipos de personas. He tenido la experiencia de escribir cosas en Python y mirarlas un año después y estar desconcertado por mi propio código. En Java puedo ver pequeños fragmentos de código de otras personas e instantáneamente saber lo que hace. En Python realmente no puedes hacer eso. No es que uno sea mejor como parece darse cuenta, solo tienen diferentes costos.
En el caso específico que mencionas, no hay tuplas. La solución fácil es crear una clase con valores públicos. Cuando salió Java por primera vez, la gente hacía esto con bastante regularidad. El primer problema al hacerlo es que es un dolor de cabeza de mantenimiento. Si necesita agregar algo de lógica o seguridad de hilo o si desea usar polimorfismo, al menos necesitará tocar cada clase que interactúa con ese objeto 'tuple-esque'. En Python hay soluciones para esto, como
__getattr__
etc., por lo que no es tan grave.Sin embargo, hay algunos malos hábitos (IMO) en torno a esto. En este caso, si quieres una tupla, me pregunto por qué lo convertirías en un objeto mutable. Solo debería necesitar getters (en una nota al margen, odio la convención get / set pero es lo que es). Creo que una clase básica (mutable o no) puede ser útil en un contexto privado o paquete privado en Java . Es decir, al limitar las referencias en el proyecto a la clase, puede refactorizar más tarde según sea necesario sin cambiar la interfaz pública de la clase. Aquí hay un ejemplo de cómo puede crear un objeto inmutable simple:
Este es un tipo de patrón que uso. Si no está utilizando un IDE, debe comenzar. Generará los captadores (y los setters si los necesita) para usted, por lo que esto no es tan doloroso.
Me sentiría negligente si no señalara que ya hay un tipo que parece satisfacer la mayoría de sus necesidades aquí . Dejando eso de lado, el enfoque que está utilizando no se adapta bien a Java, ya que juega con sus debilidades, no con sus fortalezas. Aquí hay una mejora simple:
fuente
Antes de enfadarse demasiado con Java, lea mi respuesta en su otra publicación .
Una de sus quejas es la necesidad de crear una clase solo para devolver un conjunto de valores como respuesta. ¡Esta es una preocupación válida que creo que muestra que sus intuiciones de programación están en camino! Sin embargo, creo que otras respuestas están perdiendo la marca al apegarse al antipatrón de obsesión primitiva al que te has comprometido. Y Java no tiene la misma facilidad de trabajo con múltiples primitivas que Python, donde puede devolver múltiples valores de forma nativa y asignarlos a múltiples variables fácilmente.
Pero una vez que empiezas a pensar en lo que un
ApproximateDuration
tipo hace por ti, te das cuenta de que no tiene un alcance tan limitado como "solo una clase aparentemente innecesaria para devolver tres valores". El concepto representado por esta clase es en realidad uno de los conceptos de negocio de su dominio central: la necesidad de poder representar los tiempos de manera aproximada y compararlos. Esto debe ser parte del lenguaje ubicuo del núcleo de su aplicación, con un buen soporte de objetos y dominios para que pueda ser probado, modular, reutilizable y útil.¿Su código que suma duraciones aproximadas juntas (o duraciones con un margen de error, sin embargo usted lo representa) es totalmente procesal o tiene algún objeto? Yo propondría que un buen diseño en torno a la suma de duraciones aproximadas dictaría hacerlo fuera de cualquier código de consumo, dentro de una clase que se pueda probar. Creo que usar este tipo de objeto de dominio tendrá un efecto dominó positivo en su código que lo ayudará a alejarse de los pasos del procedimiento línea por línea para lograr una sola tarea de alto nivel (aunque con muchas responsabilidades), hacia clases de responsabilidad única que están libres de los conflictos de diferentes preocupaciones.
Por ejemplo, supongamos que aprende más acerca de qué precisión o escala se requiere realmente para que la suma y la duración de su duración funcionen correctamente, y descubre que necesita un indicador intermedio para indicar "aproximadamente 32 milisegundos de error" (cerca del cuadrado raíz de 1000, así que a medio camino logarítmicamente entre 1 y 1000). Si se ha vinculado a un código que usa primitivas para representar esto, tendrá que encontrar cada lugar del código donde lo tenga
is_in_seconds,is_under_1ms
y cambiarlo ais_in_seconds,is_about_32_ms,is_under_1ms
. ¡Todo tendría que cambiar por todas partes! Hacer una clase cuya responsabilidad es registrar el margen de error para que pueda consumirse en otro lugar libera a sus consumidores de conocer detalles de qué márgenes de error importan o algo sobre cómo se combinan, y les permite especificar el margen de error que es relevante En el momento. (Es decir, ningún código de consumo cuyo margen de error es correcto se ve obligado a cambiar cuando agrega un nuevo nivel de margen de error en la clase, ya que todos los márgenes de error anteriores siguen siendo válidos).Frase de cierre
La queja sobre la pesadez de Java parece desaparecer a medida que te acercas a los principios de SOLID y GRASP, y a una ingeniería de software más avanzada.
Apéndice
Agregaré de manera completamente gratuita e injusta que las propiedades automáticas de C # y la capacidad de asignar propiedades de solo obtención en los constructores ayudan a limpiar aún más el código algo desordenado que requerirá "la forma Java" (con campos de respaldo privados explícitos y funciones de captador / configurador) :
Aquí hay una implementación de Java de lo anterior:
Ahora que está bastante limpio. Tenga en cuenta el uso muy importante e intencional de la inmutabilidad: esto parece crucial para este tipo particular de clase de valor.
Para el caso, esta clase también es un candidato decente para ser un
struct
, un tipo de valor. Algunas pruebas mostrarán si cambiar a una estructura tiene un beneficio de rendimiento en tiempo de ejecución (podría).fuente
Tanto Python como Java están optimizados para la mantenibilidad de acuerdo con la filosofía de sus diseñadores, pero tienen ideas muy diferentes sobre cómo lograrlo.
Python es un lenguaje multi-paradigmático que optimiza la claridad y simplicidad del código (fácil de leer y escribir).
Java es (tradicionalmente) un lenguaje OO de clase de paradigma único que optimiza la explicidad y la coherencia , incluso a costa de un código más detallado.
Una tupla de Python es una estructura de datos con un número fijo de campos. La misma funcionalidad se puede lograr con una clase regular con campos declarados explícitamente. En Python es natural proporcionar tuplas como alternativa a las clases porque le permite simplificar enormemente el código, especialmente debido al soporte de sintaxis incorporado para las tuplas.
Pero esto realmente no coincide con la cultura Java para proporcionar estos accesos directos, ya que ya puede usar clases explícitas declaradas. No es necesario introducir un tipo diferente de estructura de datos solo para guardar algunas líneas de código y evitar algunas declaraciones.
Java prefiere un solo concepto (clases) aplicado consistentemente con un mínimo de azúcar sintáctico para casos especiales, mientras que Python proporciona múltiples herramientas y mucho azúcar sintáctico para permitirle elegir el más conveniente para cualquier propósito en particular.
fuente
No busques prácticas; Por lo general, es una mala idea, como se dice en Mejores prácticas MALO, patrones ¿BUENO? . Sé que no estás pidiendo mejores prácticas, pero sigo pensando que encontrarás algunos elementos relevantes allí.
Buscar una solución a su problema es mejor que una práctica, y su problema no es una tupla para devolver tres valores en Java rápidamente:
Aquí tiene un objeto inmutable que contiene solo sus datos y público, por lo que no hay necesidad de captadores. Sin embargo, tenga en cuenta que si usa algunas herramientas de serialización o una capa de persistencia como un ORM, comúnmente usan getter / setter (y pueden aceptar un parámetro para usar campos en lugar de getter / setter). Y es por eso que estas prácticas se usan mucho. Entonces, si desea conocer las prácticas, es mejor entender por qué están aquí para un mejor uso de ellas.
Finalmente: uso getters porque uso muchas herramientas de serialización, pero tampoco las escribo; Uso lombok: uso los accesos directos proporcionados por mi IDE.
fuente
Sobre modismos de Java en general:
Hay varias razones por las cuales Java tiene clases para todo. Hasta donde yo sé, la razón principal es:
Java debería ser fácil de aprender para principiantes. Cuanto más explícitas son las cosas, más difícil es perder detalles importantes. Ocurre menos magia que sería difícil de comprender para los principiantes.
En cuanto a su ejemplo específico: la línea de argumento para una clase separada es esta: si esas tres cosas están suficientemente relacionadas entre sí que se devuelven como un valor, vale la pena nombrar esa "cosa". Y la introducción de un nombre para un grupo de cosas que están estructuradas de una manera común significa definir una clase.
Puede reducir el repetitivo con herramientas como Lombok:
fuente
Hay muchas cosas que se podrían decir sobre la cultura Java, pero creo que en el caso de que te enfrentes ahora, hay algunos aspectos importantes:
Clases de "estructura" con campos
Como han mencionado otras respuestas, puede usar una clase con campos públicos. Si haces estos finales, obtienes una clase inmutable y los inicializas con el constructor:
Por supuesto, esto significa que está atado a una clase en particular, y cualquier cosa que necesite producir o consumir un resultado de análisis debe usar esta clase. Para algunas aplicaciones, está bien. Para otros, eso puede causar algo de dolor. Gran parte del código Java se trata de definir contratos, y eso generalmente lo llevará a las interfaces.
Otro escollo es que con un enfoque basado en clases, está exponiendo campos y todos esos campos deben tener valores. Por ejemplo, isSeconds y millis siempre tienen que tener algún valor, incluso si isLessThanOneMilli es verdadero. ¿Cuál debería ser la interpretación del valor del campo millis cuando isLessThanOneMilli es verdadero?
"Estructuras" como interfaces
Con los métodos estáticos permitidos en las interfaces, en realidad es relativamente fácil crear tipos inmutables sin una gran sobrecarga sintáctica. Por ejemplo, podría implementar el tipo de estructura de resultados del que estás hablando como algo así:
Eso sigue siendo un montón de repeticiones, estoy totalmente de acuerdo, pero también hay un par de beneficios, y creo que esos comienzan a responder algunas de sus preguntas principales.
Con una estructura como este resultado de análisis, el contrato de su analizador está muy claramente definido. En Python, una tupla no es realmente distinta de otra tupla. En Java, la escritura estática está disponible, por lo que ya descartamos ciertas clases de errores. Por ejemplo, si devuelve una tupla en Python y desea devolver la tupla (millis, isSeconds, isLessThanOneMilli), puede hacer accidentalmente:
cuando quisiste decir:
Con este tipo de interfaz Java, no puede compilar:
en absoluto. Tu tienes que hacer:
Eso es un beneficio de los idiomas tipados estáticamente en general.
Este enfoque también comienza a darle la capacidad de restringir los valores que puede obtener. Por ejemplo, al llamar a getMillis (), puede verificar si isLessThanOneMilli () es verdadero y, si lo es, lanzar una IllegalStateException (por ejemplo), ya que no hay un valor significativo de millis en ese caso.
Haciendo difícil hacer lo incorrecto
Sin embargo, en el ejemplo de interfaz anterior, aún tiene el problema de que podría intercambiar accidentalmente los argumentos isSeconds e isLessThanOneMilli, ya que tienen el mismo tipo.
En la práctica, es posible que desee utilizar TimeUnit y la duración, para obtener un resultado como:
Eso va a ser mucho más código, pero solo necesita escribirlo una vez, y (suponiendo que haya documentado correctamente las cosas), las personas que terminan usando su código no tienen que adivinar qué significa el resultado, y no puede hacer accidentalmente cosas como
result[0]
cuando quieren decirresult[1]
. Todavía puede crear instancias de manera bastante sucinta, y obtener datos de ellas tampoco es tan difícil:Tenga en cuenta que también podría hacer algo como esto con el enfoque basado en la clase. Simplemente especifique constructores para los diferentes casos. Sin embargo, todavía tiene el problema de qué inicializar los otros campos y no puede evitar el acceso a ellos.
Otra respuesta mencionó que la naturaleza de tipo empresarial de Java significa que la mayor parte del tiempo, está componiendo otras bibliotecas que ya existen o escribiendo bibliotecas para que otras personas las usen. Su API pública no debería requerir mucho tiempo consultando la documentación para descifrar los tipos de resultados si se puede evitar.
Solo escribe estas estructuras una vez, pero las crea muchas veces, por lo que aún desea esa creación concisa (que obtiene). La escritura estática asegura que los datos que está obteniendo de ellos sean lo que espera.
Ahora, dicho todo esto, todavía hay lugares donde las tuplas o listas simples pueden tener mucho sentido. Puede haber menos sobrecarga al devolver una matriz de algo, y si ese es el caso (y esa sobrecarga es significativa, lo que determinaría con la creación de perfiles), entonces usar una simple matriz de valores internamente puede tener mucho sentido. Su API pública aún debería tener tipos claramente definidos.
fuente
originalTimeUnit=DurationTimeUnit.UNDER1MS
, la persona que llama intenta leer el valor de milisegundos.El problema es que comparas manzanas con naranjas . Preguntó cómo simular la devolución de más de un valor único, dando un ejemplo de Python rápido y sucio con tupla sin tipo y, de hecho, recibió la respuesta prácticamente unica .
La respuesta aceptada proporciona una solución comercial correcta. No hay una solución temporal rápida que deba tirar e implementar correctamente la primera vez que necesite hacer algo práctico con el valor devuelto, sino una clase POJO que sea compatible con un gran conjunto de bibliotecas, incluida la persistencia, la serialización / deserialización, instrumentación y todo lo posible.
Esto tampoco es largo en absoluto. Lo único que necesita escribir son las definiciones de campo. Se pueden generar setters, getters, hashCode y equals. Entonces, su pregunta real debería ser, por qué los captadores y establecedores no se generan automáticamente, sino que es un problema de sintaxis (el problema del azúcar sintáctico, dirían algunos) y no el problema cultural.
Y finalmente, estás pensando demasiado tratando de acelerar algo que no es importante en absoluto. El tiempo dedicado a escribir clases DTO es insignificante en comparación con el tiempo dedicado a mantener y depurar el sistema. Por lo tanto, nadie optimiza para menos verbosidad.
fuente
Hay tres factores diferentes que contribuyen a lo que estás observando.
Tuplas versus campos nombrados
Quizás el más trivial: en los otros idiomas usaste una tupla. Debatir si las tuplas es una buena idea no es realmente el punto, pero en Java usaste una estructura más pesada, por lo que es una comparación un poco injusta: podrías haber usado una variedad de objetos y algún tipo de conversión.
Sintaxis del lenguaje
¿Podría ser más fácil declarar la clase? No estoy hablando de hacer públicos los campos o usar un mapa, sino algo así como las clases de caso de Scala, que proporcionan todos los beneficios de la configuración que describiste, pero mucho más conciso:
Podríamos tener eso, pero hay un costo: la sintaxis se vuelve más complicada. Por supuesto, puede valer la pena para algunos casos, o incluso para la mayoría de los casos, o incluso para la mayoría de los casos durante los próximos 5 años, pero debe ser juzgado. Por cierto, esta es una de las cosas buenas con los lenguajes que puede modificar usted mismo (es decir, lisp), y observe cómo esto es posible debido a la simplicidad de la sintaxis. Incluso si en realidad no modifica el idioma, una sintaxis simple permite herramientas más potentes; por ejemplo, muchas veces extraño algunas opciones de refactorización disponibles para Java pero no para Scala.
Filosofía del lenguaje
Pero el factor más importante es que un idioma debe permitir una cierta forma de pensar. A veces puede parecer opresivo (a menudo he deseado soporte para una determinada función), pero eliminar funciones es tan importante como tenerlas. ¿Podrías apoyar todo? Claro, pero también podrías escribir un compilador que compila todos los idiomas. En otras palabras, no tendrá un idioma: tendrá un superconjunto de idiomas y cada proyecto básicamente adoptará un subconjunto.
Por supuesto, es posible escribir código que vaya en contra de la filosofía del lenguaje y, como observó, los resultados a menudo son feos. Tener una clase con solo un par de campos en Java es similar a usar un var en Scala, degenerar predicados de prólogo a funciones, hacer un inseguroPerformIO en haskell, etc. Las clases de Java no están destinadas a ser ligeras, no están ahí para pasar datos. . Cuando algo parece difícil, a menudo es fructífero retroceder y ver si hay otra forma. En tu ejemplo:
¿Por qué la duración se separa de las unidades? Hay muchas bibliotecas de tiempo que le permiten declarar una duración, algo como Duración (5, segundos) (la sintaxis variará), que luego le permitirá hacer lo que quiera de una manera mucho más sólida. Quizás desee convertirlo. ¿Por qué verificar si el resultado [1] (o [2]?) Es una 'hora' y se multiplica por 3600? Y para el tercer argumento: ¿cuál es su propósito? Supongo que en algún momento tendrá que imprimir "menos de 1 ms" o el tiempo real, esa es una lógica que naturalmente pertenece a los datos de tiempo. Es decir, deberías tener una clase como esta:
}
o lo que sea que realmente quieras hacer con los datos, por lo tanto, encapsulando la lógica.
Por supuesto, puede haber un caso en el que esta forma no funcione: ¡no estoy diciendo que este sea el algoritmo mágico para convertir los resultados de tuplas en un código Java idiomático! Y puede haber casos en los que es muy feo y malo, y quizás debiste haber usado un idioma diferente, ¡por eso hay tantos después de todo!
Pero mi punto de vista sobre por qué las clases son "estructuras pesadas" en Java es que no está destinado a usarlas como contenedores de datos sino como celdas lógicas autónomas.
fuente
A mi entender, las razones principales son
Esto le da el "Debe escribir una clase para devolver cualquier resultado no trivial" que a su vez es bastante pesado. Si usó una clase en lugar de la interfaz, podría tener campos y usarlos directamente, pero eso lo vincula a una implementación específica.
fuente
def f(...): (Int, Int)
es una funciónf
que devuelve un valor que resulta ser una tupla de enteros). No estoy seguro de que los genéricos en Java sean el problema; tenga en cuenta que Haskell también borra los tipos, por ejemplo. No creo que haya una razón técnica por la que Java no tenga tuplas.Estoy de acuerdo con JacquesB responde que
Pero la explicidad y la coherencia no son los objetivos finales para optimizar. Cuando dice 'Python está optimizado para facilitar la lectura', menciona de inmediato que el objetivo final es 'mantenibilidad' y 'velocidad de desarrollo'.
¿Qué logras cuando tienes la explicidad y la coherencia, hecho de manera Java? Mi opinión es que ha evolucionado como un lenguaje que pretende proporcionar una forma predecible, consistente y uniforme para resolver cualquier problema de software.
En otras palabras, la cultura Java está optimizada para hacer que los gerentes crean que entienden el desarrollo de software.
O, como dijo un sabio hace mucho tiempo ,
fuente
(Esta respuesta no es una explicación para Java en particular, sino que aborda la pregunta general de "¿Para qué podrían optimizar las prácticas [pesadas]?")
Considere estos dos principios:
Intentar optimizar uno de estos objetivos a veces puede interferir con el otro (es decir, hacer que sea más difícil hacer lo incorrecto también puede hacer que sea más difícil hacer lo correcto , o viceversa).
Las compensaciones que se realicen en un caso particular dependen de la aplicación, las decisiones de los programadores o del equipo en cuestión y la cultura (de la organización o comunidad lingüística).
Por ejemplo, si un error o una interrupción de algunas horas en su programa puede resultar en la pérdida de vidas (sistemas médicos, aeronáutica) o incluso simplemente dinero (como millones de dólares en los sistemas de anuncios de Google), usted haría diferentes compensaciones (no solo en su idioma pero también en otros aspectos de la cultura de ingeniería) de lo que lo haría para un guión único: es probable que se incline hacia el lado "pesado".
Otros ejemplos que tienden a hacer que su sistema sea más "pesado":
Estos son solo algunos ejemplos, para darle una idea de los casos en los que hacer que las cosas sean "pesadas" (y dificultarle escribir un código rápidamente) puede ser realmente intencional. (¡Incluso se podría argumentar que si escribir código requiere mucho esfuerzo, puede llevarlo a pensar más cuidadosamente antes de escribir código! Por supuesto, esta línea de argumento rápidamente se vuelve ridícula).
Un ejemplo: el sistema Python interno de Google tiende a hacer las cosas "pesadas", de modo que no puede simplemente importar el código de otra persona, debe declarar la dependencia en un archivo BUILD , el equipo cuyo código desea importar debe tener su biblioteca declarada como visible para su código, etc.
Nota : Todo lo anterior es solo cuando las cosas tienden a ponerse "pesadas". Absolutamente no pretendo que Java o Python (ya sea los propios lenguajes o sus culturas) hagan compensaciones óptimas para un caso particular; eso es para que lo pienses. Dos enlaces relacionados en tales compensaciones:
fuente
La cultura de Java ha evolucionado con el tiempo con fuertes influencias tanto de código abierto como de fondos de software empresarial, lo cual es una mezcla extraña si realmente lo piensas. Las soluciones empresariales exigen herramientas pesadas, y el código abierto exige simplicidad. El resultado final es que Java está en algún lugar en el medio.
Parte de lo que influye en la recomendación es que lo que se considera legible y mantenible en Python y Java es muy diferente.
Solo menciono C # porque la biblioteca estándar tiene un conjunto de clases Tuple <A, B, C, .. n>, y es un ejemplo perfecto de cuán difíciles de manejar son las tuplas si el lenguaje no las admite directamente. En casi todas las instancias, su código se vuelve más legible y mantenible si tiene clases bien elegidas para manejar el problema. En el ejemplo específico en su pregunta de desbordamiento de pila vinculada, los otros valores se expresarían fácilmente como captadores calculados en el objeto devuelto.
Una solución interesante que hizo la plataforma C # que proporciona un término medio feliz es la idea de objetos anónimos (publicados en C # 3.0 ) que rascan esta picazón bastante bien. Desafortunadamente, Java aún no tiene un equivalente.
Hasta que se modifiquen las características del lenguaje Java, la solución más fácil de leer y mantener es tener un objeto dedicado. Esto se debe a las restricciones en el lenguaje que se remontan a sus inicios en 1995. Los autores originales tenían muchas más características de lenguaje planificadas que nunca lo hicieron, y la compatibilidad con versiones anteriores es una de las principales limitaciones que rodean la evolución de Java a lo largo del tiempo.
fuente
Creo que una de las cosas centrales sobre el uso de una clase en este caso es que lo que se combina debe mantenerse unido.
He tenido esta discusión al revés, sobre los argumentos del método: considere un método simple que calcula el IMC:
En este caso, argumentaría en contra de este estilo porque el peso y la altura están relacionados. El método "comunica" esos son dos valores separados cuando no lo son. ¿Cuándo calcularías un IMC con el peso de una persona y la altura de otra? No tendría sentido.
Tiene mucho más sentido porque ahora comunicas claramente que la altura y el peso provienen de la misma fuente.
Lo mismo vale para devolver múltiples valores. Si están claramente conectados, devuelva un pequeño paquete ordenado y use un objeto, si no devuelven múltiples valores.
fuente
Para ser sincero, la cultura es que los programadores de Java originalmente tendían a salir de las universidades donde se enseñaban principios orientados a objetos y principios de diseño de software sostenible.
Como ErikE dice en más palabras en su respuesta, parece que no estás escribiendo código sostenible. Lo que veo de su ejemplo es que hay un enredo muy incómodo de preocupaciones.
En la cultura Java, tenderá a conocer qué bibliotecas están disponibles, y eso le permitirá lograr mucho más que su programación fuera de lo común. Por lo tanto, estaría intercambiando sus idiosincrasias por los patrones y estilos de diseño que habían sido probados en entornos industriales duros.
Pero como usted dice, esto no tiene inconvenientes: hoy, después de haber usado Java durante más de 10 años, tiendo a usar Node / Javascript o Go para nuevos proyectos, porque ambos permiten un desarrollo más rápido, y con arquitecturas de estilo de microservicio estas son a menudo es suficiente A juzgar por el hecho de que Google primero estaba usando mucho Java, pero fue el creador de Go, creo que podrían estar haciendo lo mismo. Pero a pesar de que ahora uso Go y Javascript, sigo usando muchas de las habilidades de diseño que obtuve de años de uso y comprensión de Java.
fuente