Vas a odiar esto o una historia sobre lo bien que debería verse el código

Cuántas copias están rotas y seguirán estando rotas en busca del código perfecto. Aparentemente ha llegado el momento y debería participar en esto :)


Buen dia a todos. Hace algún tiempo hablé con los estudiantes sobre el tema "¿Qué esperamos de un buen código" y decidí duplicarlo aquí. En el proceso de traducción, el texto cambió un poco, pero la esencia siguió siendo la misma. El artículo resultó ser simple (y ciertamente no completo), pero aquí hay un grano racional.


El código debería funcionar


Las cosas obvias no traen menos problemas por el hecho de que son obvias. Muchos proyectos colapsan solo porque el desarrollo se apartó por completo de resolver problemas reales del usuario


Hablemos de lo que esperamos del código. Bueno, para empezar, debería funcionar.


Parece obvio, por supuesto, pero cada uno de nosotros alguna vez probamos o lanzamos con éxito un código que ni siquiera iba a hacerlo, así que no se rían. El segundo punto: el código debería funcionar correctamente con situaciones incorrectas. Eso es atrapar errores. Pero volvamos al primer punto y hablemos un poco al respecto.


Periódicamente, recibo una tarea que no tengo idea de cómo hacer. Es decir, en general (trato de mirar alrededor y constantemente intento algo nuevo). Y aquí me sentí inmediatamente atraído por escribir algunas abstracciones, algún tipo de infraestructura, para retrasar el momento del trabajo real. Entonces, esto está mal. El código debería funcionar. Sé que me estoy repitiendo, pero este es un punto importante. Si no sabe cómo resolver el problema, no se apresure a crear interfaces, módulos y eso es todo. Esta es una mala idea, que terminará al final de su tiempo, y no irá a ningún lado. Recuerde, un código que funciona mal es muchas veces mejor que un código bueno, pero no funciona.


Hay una vieja parábola sobre dos compañías de software que hicieron el mismo producto. El primero lo hizo de todos modos, pero el primero entró al mercado, y el segundo hizo todo perfectamente y llegó tarde. Como resultado, la primera campaña logró conquistar el mercado y compró la segunda compañía. Es un poco sobre otro, pero la idea principal sigue siendo la misma. Primero resolvemos el problema, luego hacemos que el código sea hermoso.


En general, primero haga un prototipo que funcione. Deje que sea cojo, torcido e infeliz, pero cuando se le pregunta, puede decir que la solución ya está allí, queda por integrarla. Y reescríbalo como debería. Puede intentar expresar esto con tal máxima, si sabe cómo hacer la tarea, hágalo bien. Si no lo sabe, primero resuélvalo de alguna manera.


Y hay un punto importante. Me gustaría que lo entendieras. Esta no es una llamada para escribir código incorrecto. El código debería ser bueno. Esta es una llamada a First Thing First: primero el código funciona, luego se refactoriza.


Ahora hablemos de que sucede la mierda. Entonces, tenemos el código, incluso funciona. Más bien, "funciona". Veamos un ejemplo simple:


public string Do(int x) { using (WebClient xx = new WebClient()) { return xx.DownloadString("https://some.super.url"); } } 

Este es un gran ejemplo de código "funcional". Por qué Debido a que no tiene en cuenta que tarde o temprano, nuestro punto final se caerá. Este ejemplo no tiene en cuenta el llamado caso límite: límite, "casos incorrectos". Cuando comience a escribir código, piense en lo que podría salir mal. De hecho, no solo estoy hablando de llamadas remotas, sino de todos los recursos que están fuera de su control: entrada del usuario, archivos, conexiones de red, incluso la base de datos. Todo lo que puede romperse se romperá en el momento más inoportuno y lo único que puede hacer con él es estar preparado para ello tanto como sea posible.


Desafortunadamente, no todos los problemas son tan obvios. Hay una serie de áreas problemáticas que están casi garantizadas para generar errores. Por ejemplo, trabajar con configuración regional, con zonas horarias. Es dolor y gritos "todo funciona en mi máquina". Solo necesitan saber y trabajar con ellos cuidadosamente.


Por cierto sobre la entrada del usuario. Hay un principio muy bueno, que dice que cualquier entrada del usuario se considera incorrecta hasta que se demuestre lo contrario. En otras palabras, siempre valide lo que ingresó el usuario. Y sí, también en el servidor.


Total:


  • Primero haz que el código funcione,
  • Entonces hazlo bueno
  • No se olvide de los casos límite y el manejo de errores.

Ahora hablemos sobre el soporte de código


El soporte es un concepto complejo, pero incluiría tres componentes aquí: el código debe ser fácil de leer, fácil de cambiar y ser coherente.


¿Quién escribe los comentarios en ruso? Nadie esta escribiendo? Genial En general, uno de los problemas es el código que no está en inglés. No hagas eso. Tenía un código con clases en noruego, y simplemente no podía pronunciar sus nombres. Fue triste Obviamente, apoyar dicho código (para los no noruegos) no será una tarea trivial. Pero esto es raro.


En general, la facilidad de lectura se trata de nombrar y estructurar. Los nombres de las entidades (clases, métodos, variables) deben ser simples, legibles y tener significado. Toma nuestro ejemplo anterior.


 public string Do(int x) { using (WebClient xx = new WebClient()) { return xx.DownloadString("https://some.super.url"); } } 

¿Puedes entender lo que hace el método Do a pesar de la implementación? Apenas Del mismo modo con nombres de variables. Para entender qué tipo de objeto xx necesita buscar su declaración. Esto lleva nuestro tiempo, nos impide comprender lo que, en términos generales, sucede en el código. Por lo tanto, los nombres deben reflejar la esencia de la acción o el significado. Por ejemplo, si cambia el nombre del método Do a GetUserName, el código se vuelve un poco más claro y, en algunos casos, ya no tenemos que analizar su implementación. Del mismo modo con nombres de variables en la forma x y xx. Es cierto que hay excepciones generalmente aceptadas en forma de e para errores, i, k para contadores de ciclos, n para dimensiones y algunas más.


Nuevamente, por ejemplo, tome su código que escribió hace un mes e intente leerlo con fluidez. ¿Entiendes lo que está pasando allí? Si es así, te felicito. De lo contrario, tiene un problema con la legibilidad del código.


En general, hay una cita tan interesante:


"Solo hay dos cosas difíciles en Ciencias de la Computación: invalidación de caché y nombrar cosas". © Phil Karlton

Solo hay dos cosas complejas en informática: invalidación de caché y nomenclatura.


Recuérdala cuando le des nombres a tus entidades.


El segundo componente del código legible es su complejidad o estructura. Estoy hablando de aquellos a quienes les gusta escribir seis ifas anidados, o escribir una devolución de llamada en una devolución de llamada dentro de la devolución de llamada. JavaScript incluso tiene un término llamado Callback Hell .


Hablar sobre el código perfecto es fácil, pero escribirlo es un poco más difícil. Lo más importante aquí es al menos no mentirte a ti mismo. Si su código es malo, no lo llame dulce, tómalo y termínelo


Ten piedad de tus colegas y de ti mismo. Después de una semana, tendrás que literalmente leer este código para arreglarlo o agregarle algo. Evitar esto no es tan difícil:


  • Escribe funciones cortas,
  • Evite muchas ramificaciones o anidamientos,
  • Separe los bloques lógicos de código en funciones separadas incluso si no va a reutilizarlos,
  • Use polimorfismo en lugar de if

Ahora hablemos de una cosa más complicada: que un buen código es fácil de cambiar. ¿Quién conoce el término gran bola de barro? Si alguien no está familiarizado, mire la imagen.


En general, a este respecto, me gusta mucho el código abierto. Cuando su código está abierto a todo el mundo, de alguna manera quiero que sea al menos normal.


Cada módulo depende de cada módulo y es probable que los cambios en el contrato en un lugar conduzcan al advenimiento del zorro polar o, al menos, a una depuración muy larga. Teóricamente, lidiar con esto es bastante simple: reduzca la dependencia de su código de su propio código. Cuanto menos conozca su código acerca de los detalles de implementación, mejor será para él. Pero en la práctica, es mucho más complicado y conduce a una nueva complicación del código.


En forma de consejos, lo pondría de esta manera:


  • Oculta tu código lo más profundo posible. Imagine que mañana tendrá que eliminarlo manualmente del proyecto. ¿Cuántos lugares tendrás que arreglar y qué tan difícil será? Intenta minimizar esta cantidad.
  • Evitar dependencias circulares. Separe el código en capas (lógica, interfaz, acceso a datos) y asegúrese de que las capas del nivel "inferior" no dependan de las capas del nivel "superior". Por ejemplo, el acceso a los datos no debe depender de la interfaz de usuario.
  • Agrupe la funcionalidad en módulos (proyectos, carpetas) y oculte las clases dentro de ellos, dejando solo la fachada y las interfaces.

Y dibujar. Simplemente dibuje en una hoja de papel cómo la aplicación procesa sus datos y qué clases se utilizan para esto. Esto lo ayudará a comprender los lugares demasiado complicados antes de que todo se vuelva irreparable.


Y finalmente sobre la uniformidad. Siempre trate de adherirse al estilo uniforme adoptado por el equipo, incluso si le parece incorrecto. Esto se aplica al formato y los enfoques para resolver el problema. No use ~~ para redondear, incluso si es más rápido. El equipo no lo apreciará. Y cuando comience a escribir código nuevo, siempre mire el proyecto, tal vez algo que necesita ya se haya implementado y pueda reutilizarlo.


Total:


  • Nombramiento apropiado
  • Buena estructura
  • Uniformidad

El código debe ser bastante productivo.


Vamos a tener un poco de resfriado. El siguiente requisito que consideraremos es que el código debe ser bastante productivo.


¿Qué quiero decir con la palabra "suficiente"? Probablemente todos hayan escuchado que las optimizaciones prematuras son malas, matan la legibilidad y complican el código. Esto es verdad Pero también es cierto que debe conocer su herramienta y no escribir en ella para que el cliente de correo web cargue Core I7 en un 60%. Debe conocer los problemas típicos que conducen a problemas de rendimiento y evitarlos incluso en la etapa de escritura de código.


Volvamos a nuestro ejemplo:


 public string GetUserName(int userId) { using (WebClient http = new WebClient()) { return http.DownloadString("https://some.super.url"); } } 

Este código tiene un problema: la descarga síncrona a través de la red. Esta es una operación de E / S que congela nuestro flujo hasta que se ejecuta. En aplicaciones de escritorio, esto conducirá a una interfaz colgante, y en aplicaciones de servidor, a la reserva inútil de memoria y al agotamiento de la cantidad de solicitudes al servidor. Solo conociendo tales problemas, ya puede escribir código más optimizado. Y en la mayoría de los casos esto será suficiente.


Pero a veces no. Por lo tanto, antes de escribir el código, debe saber de antemano qué requisitos se le establecen en términos de rendimiento.


Ahora hablemos de las pruebas.


Este no es un tema menos holivny que el anterior, y tal vez aún más. Todo es complicado con las pruebas. Comencemos con la declaración: creo que el código debe estar cubierto por un número razonable de pruebas.


¿Por qué incluso necesitamos pruebas y cobertura de código? En un mundo ideal, no son necesarios. En un mundo ideal, el código se escribe sin errores y los requisitos nunca cambian. Pero vivimos en un mundo lejos del ideal, por lo que necesitamos pruebas para asegurarnos de que el código funciona correctamente (no hay errores) y que el código funciona correctamente después de cambiar algo. Este es el beneficio que nos brindan las pruebas. Por otro lado, incluso el 100% (debido a los detalles de cálculo de las métricas) cubierto por las pruebas no garantiza que haya cubierto absolutamente todo. Además, cada prueba adicional ralentiza el desarrollo ya que después de cambiar el funcional también tendrá que actualizar las pruebas. Por lo tanto, el número de pruebas debe ser razonable y la principal dificultad consiste en encontrar un compromiso entre la cantidad de código y la estabilidad del sistema. Encontrar esta faceta es bastante difícil y no existe una receta universal sobre cómo hacerlo. Pero hay algunos consejos que pueden ayudarlo a hacer esto.


  • Cubrir la lógica de la aplicación empresarial. La lógica de negocios es todo para lo que se crea una aplicación, y debe ser lo más estable posible.
  • Cubre cosas complejas y calculadas. Cálculos, transformaciones, fusiones de datos complejos. Uno donde es fácil cometer un error.
  • Cubra los errores. Un error es una bandera que nos dice que el código era vulnerable aquí. Y este es un buen lugar para escribir una prueba aquí.
  • Cubra el código frecuentemente reutilizado. Es muy probable que se actualice con frecuencia y debemos asegurarnos de que agregar una cosa no rompa la otra.

No cubra sin mucha necesidad


  • Bibliotecas extranjeras: busque bibliotecas cuyo código ya esté cubierto por pruebas.
  • Infraestructura: DI, mapeo automático (si no hay mapeo complicado), etc. Hay e2e o pruebas de integración para esto.
  • Cosas triviales: asignar datos a campos, reenviar llamadas, etc. Seguramente encontrará lugares mucho más útiles para cubrirlos con pruebas.

Bueno, eso es básicamente todo.


Para resumir. Buen código es


  • Código de trabajo
  • Fácil de leer
  • Fácil de cambiar
  • Lo suficientemente rápido
  • Y cubierto en pruebas en la cantidad correcta.

Buena suerte de esta manera difícil. Y lo más probable, vas a odiar esto. Bueno, si aún no, bienvenido.


De hecho, este es un mundo increíble de descubrimientos, oportunidades y creatividad. Aquí solo en el vecindario existe el aburrimiento de los azotes de forma, la penumbra de más de 20 años de código heredado y un campo de muletas para términos duraderos. Así que preferiría llamarlo el puente capilar (C). Busca proyectos que quemarás. Intenta hacer algo bueno. Simplemente, haga del mundo un lugar mejor y todo estará bien.

Source: https://habr.com/ru/post/es429732/


All Articles