JavaRush /Blog Java /Random-ES /Pausa para el café #64. Cómo escribir código limpio. Por ...

Pausa para el café #64. Cómo escribir código limpio. Por qué Java es mejor que C++ para sistemas de baja latencia

Publicado en el grupo Random-ES

Cómo escribir código limpio

Fuente: Dev.to Escribir código limpio es como escribir poesía. Esta es una poesía que debe ser concisa, comprensible y accesible al cambio. Un código limpio implica una organización escalable. Esto significa que hacer cambios no causa caos. La capacidad de escribir dicho código es una de las cualidades clave de un desarrollador experimentado. Después de que varias personas me recomendaron leer el libro Código Limpio, finalmente me armé de valor para leerlo. Resultó que este es uno de esos libros donde la portada está completamente a la altura de las expectativas. Las recomendaciones del libro son claras, específicas, prácticas e incluso presentadas con humor. Hoy quiero compartir con ustedes las principales conclusiones de este libro.Pausa para el café #64.  Cómo escribir código limpio.  Por qué Java es mejor que C++ para sistemas de baja latencia - 1

1. El código no sólo debe funcionar, sino también ser legible.

La mayor parte del costo del software está ligado al soporte a largo plazo. Por lo tanto, el código que escribas debe expresar claramente tus intenciones. Debe ser tal que los nuevos desarrolladores que se unan al equipo puedan comprender fácilmente qué está sucediendo exactamente en el código y por qué. Cuanto más comprensible sea el código que escriba el autor, menos tiempo les llevará a otros desarrolladores comprenderlo. Esto reduce los defectos y los costes de mantenimiento. ¿Cómo lograr esto? Buen naming + clases y funciones con responsabilidad única + pruebas de escritura.

2. Más tarde significa nunca

Seamos honestos: todos nos prometemos a veces que volveremos y limpiaremos el código más tarde, pero terminamos olvidándonos de ello. No dejes fragmentos de código inútiles que ya no sean necesarios. Confunden a otros desarrolladores y no tienen ningún valor. Por lo tanto, cuando realice cambios en la funcionalidad, elimine siempre el código antiguo. Si algo se rompe en alguna parte, las pruebas lo mostrarán de inmediato. ¿Cómo lograr esto? Eliminar código puede dar miedo, especialmente en arquitecturas grandes. Entonces las pruebas son clave aquí. Le permiten eliminar código con confianza.

3. Las funciones deben ser pequeñas

La primera regla para escribir funciones es que deben ser pequeñas, de hasta unas 20 líneas . Cuanto más pequeña sea la función y más centrada esté en una tarea, más fácil será encontrarle un buen nombre. En cuanto a los argumentos de las funciones, su número ideal es 0. Luego viene el 1, 2, pero debes intentar que no tenga más de 3 argumentos, ¿ cómo lograrlo? Las funciones deben escribirse de acuerdo con los principios de responsabilidad única y abierto/cerrado.

4. La duplicación de código es mala

La duplicación es el enemigo de un sistema bien organizado. Es trabajo extra, riesgo extra y complejidad extra innecesaria. ¿Qué hacer al respecto? Asegúrese de que su código esté escrito según el principio DRY, aislado y modular.

5. El único comentario bueno es aquel que encontraste la manera de no escribir.

“No hay nada más útil que un buen comentario en el lugar indicado. Pero los comentarios, incluso en el mejor de los casos, son un mal necesario”. Los comentarios tienen como objetivo compensar nuestra incapacidad para expresar nuestros pensamientos en código. Es decir, esto es inicialmente una admisión de derrota. Sí, tenemos que usarlos porque no siempre podemos dejar claras nuestras intenciones con el código, pero eso no es motivo para celebrar. La cuestión es que los comentarios suelen mentir. No siempre ni a propósito, pero sí con demasiada frecuencia. Cuanto más antiguo sea el comentario y más alejado esté del código que describe, más probabilidades habrá de que sea incorrecto. La razón de esto es simple: los programadores no pueden mantener muy bien tanto el código como todos los comentarios. Por lo tanto, muy a menudo los comentarios se separan del código al que hacen referencia y se convierten en anotaciones huérfanas con una precisión mínima. ¿Qué hacer al respecto? Se deben utilizar métodos de denominación descriptivos. Cuando lees el nombre de una variable, debes comprender inmediatamente de qué se trata. También se necesitan pruebas para que otros desarrolladores comprendan qué funcionalidad es más importante.

6. El objeto revela comportamiento, pero no datos.

Un módulo no debería conocer el interior de los objetos que manipula. Los objetos ocultan sus datos y revelan sus operaciones. Esto significa que un objeto no debe exponer su estructura interna a través de métodos de acceso. No es necesario que todos te vean desnudo. ¿Qué hacer al respecto? El alcance de las variables debe ser lo más local posible para no exponer más de lo necesario.

7. Pruebas

El código de prueba es tan importante como lo que pasa a producción. Por lo tanto, debe cambiar y crecer a medida que se desarrolla el proyecto. Las pruebas mantienen su código flexible, mantenible y reutilizable. Sin ellos, cualquier cambio puede generar errores. Las pruebas te permiten limpiar tu código sin temor a que algo se rompa. Por tanto, mantener la pureza de las pruebas es de gran importancia. La limpieza de las pruebas garantiza su legibilidad. Las pruebas son una oportunidad para explicar a otros desarrolladores en un lenguaje sencillo las intenciones del autor del código. Por lo tanto, probamos solo un concepto en cada función de prueba. Esto hace que la prueba sea descriptiva, más fácil de leer y, si falla, es más fácil rastrear el motivo. ¿Cómo lograr esto? Hay que seguir los principios de las PRIMERAS pruebas limpias . Las pruebas deben ser:
  • Rápido. Las pruebas deben ejecutarse rápidamente. Si tiene que esperar demasiado para que se ejecute una prueba, es menos probable que la ejecute con más frecuencia.
  • Independiente/aislado (Independiente). Las pruebas deben ser lo más aisladas e independientes entre sí posible.
  • Repetible. Las pruebas deben ser repetibles en cualquier entorno: desarrollo, puesta en escena y producción.
  • Autovalidación. El resultado de la prueba debe ser un valor booleano. La prueba debe pasar o fallar.
  • Exhaustivo. Debemos esforzarnos por cubrir todos los casos extremos, todos los problemas de seguridad, cada caso de uso (caso de uso) y el camino feliz (el escenario más favorable para el código) con pruebas.

8. Manejo de errores y excepciones

Cada excepción que lance debe proporcionar suficiente contexto para determinar el origen y la ubicación del error. Normalmente, tiene un seguimiento de la pila de cualquier excepción, pero un seguimiento de la pila no le indicará el propósito de la operación que falló. Si es posible, evite pasar nulo en su código. Si tiene la tentación de devolver un valor nulo desde un método, considere lanzar una excepción. Haga que el manejo de errores sea una tarea separada que se pueda ver independientemente de la lógica principal. ¿Cómo lograr esto? Cree mensajes de error informativos y páselos junto con sus excepciones. Especifique la operación que falló y el tipo de error.

9. clases

Las clases deben ser pequeñas. Pero no son las líneas de código las que hay que contar, sino la responsabilidad. Los nombres de las clases son clave para describir de qué son responsables. Nuestros sistemas deberían constar de muchas clases pequeñas, no de unas pocas grandes. Cada una de estas pequeñas clases debe encapsular una única responsabilidad. Debe haber sólo una razón específica para que exista cada clase, y cada clase debe "cooperar" con varias otras clases para lograr el comportamiento deseado del sistema. Rara vez existe una buena razón para crear una variable pública. Debilitar la encapsulación es siempre un último recurso. Además, debería haber pocas variables de instancia. Un buen diseño de software permite realizar cambios sin grandes inversiones ni reelaboraciones. Reducir el rango de variables facilita esta tarea. ¿Cómo lograr esto? La separación de preocupaciones es una de las técnicas de diseño más antiguas e importantes. Las clases deben estar abiertas para extensión, pero cerradas para modificación. En un sistema ideal, habilitamos nuevas funciones ampliando el sistema en lugar de realizar cambios en el código existente.

10. Formateo

Cada línea vacía es una señal visual que ayuda a identificar que ha comenzado un concepto nuevo e independiente. Las variables locales deben aparecer en la parte superior de la función. Las variables de instancia deben declararse en la parte superior de la clase. Las líneas cortas son mejores que las largas. Normalmente el límite es de 100 a 120 caracteres; no deberías alargarlo. ¿Cómo lograr esto? La mayoría de los parámetros se pueden pasar a un linter en su CI o editor de texto. Utilice estas herramientas para que su código sea lo más limpio posible.

Principios de desarrollo de programas

Utilice las siguientes técnicas y su código siempre estará limpio: Nombrar variables. Elegir nombres apropiados (buenos nombres) es fundamental para que el código sea legible y, por lo tanto, mantenible. "Debes elegir el nombre de una variable con tanta responsabilidad como lo harías con tu primogénito". Elegir buenos nombres suele ser un desafío para los desarrolladores. Esto requiere buenas habilidades descriptivas y un trasfondo cultural compartido. El código limpio es código leído y mejorado por desarrolladores completamente diferentes. El nombre de una variable, función o clase debe responder a todas las preguntas básicas: por qué existe esta entidad, qué y cómo se utiliza. Si un nombre requiere comentario, significa que no revela suficientemente la esencia de lo que describe. Los nombres más largos son más importantes que los más cortos y cualquier nombre que se pueda buscar es mejor que una constante. Los nombres de una sola letra solo se pueden usar como variables locales dentro de métodos cortos: la longitud del nombre debe coincidir con el alcance. Los nombres de los métodos deben ser verbos o frases verbales; el nombre de la clase no debe ser un verbo. Las dependencias deben mantenerse al mínimo. Es mejor confiar en lo que controlas que en lo que no puedes controlar. De lo contrario, estas cosas te controlarán. Exactitud. Cada fragmento de código debe estar en un lugar donde el lector espere encontrarlo. La navegación a través del código base debe ser intuitiva y las intenciones del desarrollador deben ser claras. Limpieza. No dejes código inútil en el código base (antiguo y que ya no se usa o se crea "por si acaso"). Reduzca la duplicación y cree abstracciones simples desde el principio. Estandarización. Al escribir código, debes seguir el estilo y las prácticas establecidas para el repositorio. Autodisciplina. A medida que las tecnologías usadas se desarrollan y aparecen otras nuevas, los desarrolladores a menudo desean cambiar y mejorar algo en el código existente. No se deje llevar por las exageraciones demasiado rápido: estudie las nuevas pilas a fondo y sólo para un propósito específico. Mantener limpia su base de código es algo más que ser cortés con sus colegas actuales y futuros. Es esencial para la supervivencia a largo plazo del programa. Cuanto más limpio sea su código, más felices serán los desarrolladores, mejor será el producto y más durará.

Por qué Java es mejor que C++ para sistemas de baja latencia

Fuente: StackOverflow Como desarrolladores, todos sabemos que hay dos formas de hacer las cosas: manualmente, de forma lenta y molesta, o de forma automática, difícil y rápida. Podría usar inteligencia artificial para escribir este artículo por mí. Esto podría ahorrarme mucho tiempo: la IA puede generar miles de artículos por segundo, pero a mi editor probablemente no le alegraría saber que se necesitarían dos años para generar el primer artículo. Pausa para el café #64.  Cómo escribir código limpio.  Por qué Java es mejor que C++ para sistemas de baja latencia - 2Una situación similar surge cuando se desarrollan sistemas de software con baja latencia. La opinión generalizada es que sería una locura utilizar cualquier otra cosa que no sea C++ porque todo lo demás tiene demasiada latencia. Pero estoy aquí para convencerlos de la noción opuesta, contraria a la intuición y casi herética: cuando se trata de lograr una baja latencia en sistemas de software, Java es mejor. En este artículo, quiero tomar un ejemplo específico de software que valora la baja latencia: los sistemas comerciales. Sin embargo, los argumentos presentados aquí se pueden aplicar a casi cualquier circunstancia en la que se requiera o desee una latencia baja. Simplemente es más fácil discutir en relación con el área de desarrollo en la que tengo experiencia. Y la verdad es que la latencia es difícil de medir. Todo se reduce a lo que quieres decir con baja latencia. Resolvamos esto ahora.

Sabiduría adquirida

Dado que C++ está mucho más cerca del hardware, la mayoría de los desarrolladores le dirán que codificar en este lenguaje ofrece una ventaja en velocidad. En situaciones de baja latencia, como el comercio de alta velocidad, donde milisegundos pueden marcar la diferencia entre una pieza de software viable y un desperdicio heredado de espacio en disco, C++ se considera el estándar de oro. Al menos así era antes. Pero la realidad es que muchos grandes bancos y corredores utilizan ahora sistemas escritos en Java. Y me refiero a escrito de forma nativa en Java, no escrito en Java y luego interpretado en C++ para reducir la latencia. Estos sistemas se están convirtiendo en estándar incluso para los bancos de inversión de primer nivel, a pesar de que son (supuestamente) más lentos. Entonces, ¿qué está pasando? Sí, C++ puede tener una "latencia baja" cuando se trata de ejecutar código, pero definitivamente no es una latencia baja cuando se trata de implementar nuevas funciones o incluso encontrar desarrolladores que puedan escribirlas.

Diferencias (reales) entre Java y C++

La cuestión del tiempo de desarrollo es sólo el comienzo cuando se trata de las diferencias entre Java y C++ en sistemas del mundo real. Para comprender el verdadero valor de cada idioma en este contexto, profundicemos un poco más. Primero, es importante recordar la verdadera razón por la que C++ es más rápido que Java en la mayoría de las situaciones: un puntero de C++ es la dirección de una variable en la memoria. Esto significa que el software puede acceder a variables individuales directamente y no tiene que rastrear tablas computacionalmente intensivas para buscarlas. O al menos se puede solucionar especificando dónde están, porque con C++ a menudo hay que gestionar explícitamente la vida útil y la propiedad de los objetos. Como resultado, a menos que seas realmente bueno escribiendo código (una habilidad que puede llevar décadas dominar), C++ requerirá horas (o semanas) de depuración. Y como le dirá cualquiera que haya intentado depurar un motor Monte Carlo o una herramienta de prueba PDE, intentar depurar el acceso a la memoria en un nivel fundamental puede llevar mucho tiempo. Un solo puntero defectuoso puede fácilmente hacer caer un sistema completo, por lo que lanzar una nueva versión escrita en C++ puede ser realmente aterrador. Por supuesto, eso no es todo. Las personas que disfrutan de la programación en C++ señalarán que el recolector de basura de Java sufre picos de latencia no lineales. Esto es especialmente cierto cuando se trabaja con sistemas heredados, por lo que enviar actualizaciones al código Java sin dañar los sistemas cliente puede hacerlos tan lentos que resulten inutilizables. En respuesta, me gustaría señalar que durante la última década se ha trabajado mucho para reducir la latencia creada por Java GC. Disruptor LMAX, por ejemplo, es una plataforma comercial de baja latencia escrita en Java, también construida como un marco que tiene una "interacción mecánica" con el hardware en el que se ejecuta y no requiere bloqueo. Los problemas se pueden mitigar aún más si construye un sistema que utiliza un proceso de integración y entrega continua (CI/CD), ya que CI/CD permite la implementación automatizada de cambios de código probados. Esto se debe a que CI/CD proporciona un enfoque iterativo para reducir la latencia de la recolección de basura, donde Java puede mejorar y adaptarse gradualmente a entornos de hardware específicos sin el proceso intensivo de recursos de preparar el código para diferentes especificaciones de hardware antes de enviarlo. Dado que el soporte de Java del IDE es mucho más amplio que el de C++, la mayoría de los marcos (Eclipse, IntelliJ IDEA) le permiten refactorizar Java. Esto significa que los IDE pueden optimizar el código para un rendimiento de baja latencia, aunque esta capacidad aún es limitada cuando se trabaja con C++. Incluso si el código Java no iguala la velocidad de C++, a la mayoría de los desarrolladores todavía les resulta más fácil lograr un rendimiento aceptable en Java que en C++.

¿Qué queremos decir con "más rápido"?

De hecho, hay buenas razones para dudar de que C++ sea realmente "más rápido" o incluso tenga una "latencia más baja" que Java. Me doy cuenta de que me estoy metiendo en aguas bastante turbias y que muchos desarrolladores comenzarán a cuestionar mi cordura. Pero escúchame. Imaginemos esta situación: tienes dos desarrolladores, uno escribe en C++ y el otro en Java, y les pides que escriban una plataforma comercial de alta velocidad desde cero. Como resultado, un sistema escrito en Java tardará más en completar transacciones comerciales que un sistema escrito en C++. Sin embargo, Java tiene muchos menos casos de comportamiento indefinido que C++. Por poner sólo un ejemplo, la indexación fuera de una matriz es un error tanto en Java como en C++. Si accidentalmente haces esto en C++, es posible que obtengas un error de segmentación o (más a menudo) terminarás con algún número aleatorio. En Java, salirse de los límites siempre genera un error ArrayIndexOutOfBoundsException . Esto significa que la depuración en Java es mucho más fácil porque los errores generalmente se identifican inmediatamente y la ubicación del error es más fácil de rastrear. Además, al menos en mi experiencia, Java reconoce mejor qué fragmentos de código no es necesario ejecutar y cuáles son fundamentales para el funcionamiento de su software. Por supuesto, puedes pasar días modificando tu código C++ para que no contenga ningún código extraño, pero en el mundo real, cada pieza de software contiene algo de exceso, y Java es mejor para reconocerlo automáticamente. Esto significa que en el mundo real, Java suele ser más rápido que C++, incluso según las métricas de latencia estándar. E incluso cuando este no es el caso, la diferencia en la latencia entre idiomas a menudo se ve superada por otros factores que no son lo suficientemente grandes como para importar incluso en el comercio de alta velocidad.

Beneficios de Java para sistemas de baja latencia

Todos estos factores, en mi opinión, constituyen un argumento bastante convincente a favor del uso de Java para escribir plataformas comerciales de alta velocidad (y sistemas de baja latencia en general, hablaremos de eso en un momento). Sin embargo, para convencer un poco a los entusiastas de C++, veamos algunas razones adicionales para usar Java:
  • En primer lugar, cualquier exceso de latencia que Java introduzca en su software probablemente será mucho menor que otros factores que afectan la latencia, como los problemas de Internet. Esto significa que cualquier código Java (bien escrito) puede funcionar tan bien como C++ en la mayoría de situaciones comerciales.

  • El menor tiempo de desarrollo de Java también significa que, en el mundo real, el software escrito en Java puede adaptarse a los cambios de hardware (o incluso a nuevas estrategias comerciales) más rápidamente que C++.

  • Si profundiza en esto, verá que incluso optimizar el software Java puede ser más rápido (cuando se considera todo el software) que una tarea similar en C++.

En otras palabras, es muy posible escribir código Java para reducir la latencia. Sólo necesitas escribirlo como C++, teniendo en cuenta la gestión de la memoria en cada etapa del desarrollo. La ventaja de no escribir en C++ es que la depuración, el desarrollo ágil y la adaptación a múltiples entornos es más fácil y rápido en Java.

conclusiones

A menos que esté desarrollando sistemas comerciales de baja latencia, probablemente se esté preguntando si algo de lo anterior se aplica a usted. La respuesta, salvo contadas excepciones, es sí. El debate sobre cómo lograr una baja latencia no es nuevo ni exclusivo del mundo de las finanzas. Por este motivo, de él se pueden aprender valiosas lecciones para otras situaciones. En particular, el argumento anterior de que Java es "mejor" porque es más flexible, más tolerante a fallos y, en última instancia, más rápido de desarrollar y mantener se puede aplicar a muchas áreas del desarrollo de software. Las razones por las que (personalmente) prefiero escribir sistemas de baja latencia en Java son las mismas razones que han hecho que el lenguaje tenga tanto éxito durante los últimos 25 años. Java es fácil de escribir, compilar, depurar y aprender. Esto significa que puede dedicar menos tiempo a escribir código y más tiempo a optimizarlo. En la práctica, esto conduce a sistemas comerciales más fiables y rápidos. Y eso es todo lo que importa para el comercio de alta velocidad.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION