Comment écrire du code que tout le monde comprendra?


D'un traducteur: Nous avons publié un article de Camil Lelonek pour vous sur la signification de la lisibilité du code et de «l'empathie des programmeurs».

Vous êtes-vous déjà demandé qui verrait votre code? Cela peut-il être difficile pour les autres? J'ai essayé de déterminer sa lisibilité ??
«Tout imbécile peut écrire du code que la machine comprend. Mais seuls les bons programmeurs écrivent du code que les gens comprennent aussi », explique Martin Fowler.
De temps en temps, quand je vois des extraits de code, je perds confiance en l'existence de l'empathie chez les programmeurs. Vous devez comprendre de quoi je parle - parce que chacun de nous est tombé sur du code qui a été écrit terriblement et qui était pratiquement illisible.
Skillbox recommande: Cours pratique de deux ans "Je suis un développeur Web PRO . "

Nous vous rappelons: pour tous les lecteurs de «Habr» - une remise de 10 000 roubles lors de l'inscription à un cours Skillbox en utilisant le code promo «Habr».
J'ai récemment vu quelque chose comme ça:

defmodule Util.Combinators do def then(a, b) do fn data -> b.(a.(data)) end end def a ~> b, do: a |> then(b) end 

En principe, tout va bien ici: peut-être que quelqu'un a juste un fantasme ou que l'auteur du code a une solide formation mathématique. Je ne voulais pas réécrire ce code, mais inconsciemment, il me semblait que quelque chose n'allait pas ici. «Il doit y avoir un moyen de l'améliorer, de le formuler différemment. Je vais voir comment tout fonctionne. "Je le pensais. Assez rapidement, j'ai trouvé ceci:

 import Util.{Reset, Combinators} # ... conn = conn!() Benchee.run( # ... time: 40, warmup: 10, inputs: inputs, before_scenario: do_reset!(conn) ~> init, formatter_options: %{console: %{extended_statistics: true}} ) 

Hmmm, il semble que non seulement ~> soit importé, mais aussi les fonctions conn! / 0 et do_reset! / 1. Ok, jetons un œil au module de réinitialisation:

 defmodule Util.Reset do alias EventStore.{Config, Storage.Initializer} def conn! do {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() conn end def do_reset!(conn) do fn data -> Initializer.reset!(conn) data end end end 

En ce qui concerne conn!, Il existe plusieurs façons de rendre ce site plus facile. Néanmoins, l'arrêt à ce stade n'a aucun sens. Je préfère me concentrer sur do_reset! / 1. Cette fonction renvoie une fonction qui renvoie un argument et effectue une réinitialisation pour l'initialiseur; et le nom lui-même est assez compliqué.



J'ai décidé de désosser le code. Selon la documentation de Benchee, before_scenario prend l'entrée de script comme argument. La valeur de retour devient l'entrée pour les étapes suivantes. Voici ce que l'auteur a probablement voulu dire:

  • Initialisation d'une connexion Postgrex.
  • Réinitialisez EventStore.
  • Utiliser des valeurs d'entrée comme éléments de configuration (parler du nombre de comptes).
  • Préparer les données pour les tests (c'est-à-dire créer des utilisateurs et entrer dans l'application).
  • Utilisation de repères.

En général, tout est clair, un tel code est facile à écrire. Je note qu'en refactorisant je ne montrerai ni ne modifierai la fonction init, ce n'est pas très important ici.

La première étape consiste à utiliser explicitement l'alias au lieu d'importations implicites. Je n'ai jamais aimé les fonctionnalités «magiques» qui apparaissent dans mon code, même si Ecto.Query rend les requêtes élégantes. Maintenant, notre module de connexion ressemble à ceci:

 defmodule Benchmarks.Util.Connection do alias EventStore.{Config, Storage.Initializer} def init! do with {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() do conn end end def reset!(conn), do: Initializer.reset!(conn) end 

Ensuite, j'ai décidé d'écrire un «crochet», comme suggéré dans la documentation:

before_scenario: fn entrées -> entrées fin

Il ne reste plus qu'à préparer les données. Le résultat final est le suivant:

 alias Benchmarks.Util.Connection conn = Connection.init!() # ... Benchee.run( inputs: inputs, before_scenario: fn inputs -> Connection.reset!(conn) init.(inputs) end, formatter_options: %{console: %{extended_statistics: true}} ) Connection.reset!(conn) 

Ce code est-il parfait? Probablement pas encore. Mais est-ce plus facile à comprendre? Je l'espère. Cela pourrait-il être fait tout de suite? Certainement oui.

Quel est le problème?


Lorsque j'ai proposé une solution à l'auteur, j'ai entendu: «Cool». Cependant, je ne m'attendais pas à plus.

Le problème est que le code principal a fonctionné. La seule chose qui m'a amené à penser au besoin de refactoring était la structure trop complexe du code et sa faible lisibilité.

Pour convaincre d'autres développeurs de rendre leur code lisible, vous avez besoin de quelque chose de convaincant. Et l'argument "J'ai décidé de refaire votre code, car il est obscur", ne sera pas accepté, la réponse est "signifie que vous êtes juste un mauvais développeur, que puis-je faire ¯ \ _ (ツ) _ / ¯."



Il s'agit d'un problème de (non) gestion


Personne n'est surpris que l'entreprise attende des résultats des employés. Et le plus tôt ils seront reçus, mieux ce sera. Les gestionnaires évaluent généralement le logiciel et sa rédaction en termes de délais, de budget, de vitesse. Je ne dis pas que c'est mauvais, j'essaie juste d'expliquer pourquoi il n'y a pas de code de trop haute qualité qui "fonctionne juste". Le fait est que les managers ne sont pas très intéressés par la beauté et la lisibilité, ils ont besoin de ventes, de faibles coûts et de résultats rapides.

Lorsque les programmeurs sont pressés, ils cherchent une issue. Le plus souvent, la solution est de créer un «code de travail» dans lequel il peut y avoir un tas de «béquilles». Il est créé sans penser que le code doit être maintenu à l'avenir. Et un code élégant est très difficile à écrire rapidement. Peu importe l'expérience du programmeur, lorsque le travail est effectué sous la pression du temps, personne ne pense à la beauté.

Cette situation ne peut être résolue qu'en convaincant les gestionnaires qu'un mauvais code (bien que fonctionnel) menace d'augmenter le coût de sa maintenance dans un avenir proche. Correction de bugs et ajout de nouvelles fonctionnalités, révision, rédaction de documentation technique - dans le cas d'un code de mauvaise qualité, tout cela prend beaucoup plus de temps que dans une situation où il est élégant et rationnel.



Le rôle important de l'empathie


Si vous développez un logiciel, vous comprenez qu'il est destiné à d'autres personnes et que le développement s'effectue en équipe. Et l'empathie est très importante ici.

Votre code est une forme de communication particulière. Dans le processus de développement de l'architecture des futurs logiciels, vous devez penser à ceux qui interagiront avec votre code.

«L'empathie des programmeurs» aide à créer un code plus propre et plus rationnel, même lorsque les délais sont épuisés, et que le gestionnaire «écrase» constamment. Cela aide à comprendre ce que c'est que d'analyser le code illisible de quelqu'un d'autre, ce qui est extrêmement difficile à comprendre.



En conclusion


J'ai récemment écrit du code sur Elixir:

 result = calculate_results() Connection.close(conn) result 

J'ai ensuite pensé à une méthode de tap Ruby qui vous permet de réécrire ce code:

 calculate_result().tap do Connection.close(conn) end 

À mon humble avis, il serait préférable de le faire sans le résultat variable intermédiaire. J'ai réfléchi à la façon dont cela pourrait être fait et suis arrivé à la conclusion suivante:

 with result = calculate_results(), Connection.close(conn), do: result 

Le résultat sera le même. Mais l'utilisation de avec peut causer des problèmes à quelqu'un qui étudie ce code, car dans le cas habituel, avec est utilisé différemment.

J'ai donc décidé de tout laisser tel quel, pour ne pas m'améliorer au détriment des autres. Je vous demande de faire de même, sans compliquer le code au-delà de toute mesure. Avant d'introduire une fonction ou une variable exotique, n'oubliez pas que vos collègues peuvent tout simplement ne pas les comprendre dans une revue.

En général, je recommande d'utiliser le principe suivant: "Lorsque vous écrivez VOTRE code, pensez à AUTRE."

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


All Articles