Java >> Tutoriel Java >  >> Java

Lisibilité et style en Java

À ce stade de la série, nous avons couvert la majorité de la syntaxe Java de base. Il y a une poignée de structures que nous examinerons à l'avenir, mais cela semble maintenant être le moment idéal pour aborder la lisibilité. Dans ce tutoriel, nous allons plonger dans ce qu'il faut pour produire du code qui peut être compris par d'autres personnes. Certaines de ces notes sont subjectives, il pourrait donc y avoir une petite controverse. Quoi qu'il en soit, allons-y !

Concepts de base en matière de style et de lisibilité

Le codage est un sport d'équipe. Pourquoi pensez-vous que j'utilise le terme « nous » tout le temps ? Nous devons nous habituer à travailler ensemble sur des systèmes complexes. Ce qui signifie que nous devons trouver des moyens de mieux communiquer nos idées.

L'un des meilleurs moyens d'y parvenir est d'améliorer la lisibilité globale de notre code. La lisibilité est une question complexe qui est souvent subjective. Avant d'en arriver à mon guide de style personnel, nous devons définir quelques bonnes pratiques.

Il existe deux pratiques principales que nous pouvons suivre pour éviter les ennuis :cohérence et clarté .

Cohérence

Avant même d'entrer dans le code, il y a quelques règles universelles que nous devons toujours nous assurer de suivre lors de l'écriture du code. La première règle est de garder le code cohérent. Les équipes s'appuient sur la cohérence, car cela élimine la majeure partie de la difficulté d'essayer de déchiffrer le code de quelqu'un d'autre.

Souvent, les équipes élaborent un guide de style pour aider à rendre le code cohérent dans toute la bibliothèque. Vivre sans guide de style, c'est un peu comme lire un article qui a manifestement été écrit par plus d'une personne. Le manque de fluidité rend difficile la lecture du texte.

Les mêmes règles s'appliquent pour le code; un contraste de style peut parfois donner l'impression de lire une autre langue.

Clarté

Parallèlement à la cohérence, l'autre principe de style majeur est la clarté - soyez clair dans l'intention. Si une méthode est censée additionner deux nombres, assurez-vous que le nom de la méthode reflète exactement cela.

De même, assurez-vous que la méthode n'ajoute que deux nombres ensemble. En d'autres termes, les méthodes doivent suivre le principe de la responsabilité unique. Bien que cette règle soit vraiment un principe pour les classes, elle s'applique également aux méthodes.

Un exemple peu clair

À l'aide de notre exemple d'ajout, essayons d'implémenter la solution dans le code :

public void addTwoNumbers(int a, int b) {
    result = a + b;
    System.out.println("The result is " + result);
    return result;
}

Remarquez comment notre méthode ajoute deux nombres comme prévu. Cependant, comme effet secondaire, la méthode imprime également le résultat à l'utilisateur. Que se passe-t-il si l'utilisateur ne souhaite pas que la méthode s'imprime ? Et si nous avions plusieurs méthodes similaires comme subtractTwoNumbers et divideTwoNumbers ? La déclaration imprimée devra être copiée tout au long.

Par souci de clarté, nous devrions probablement refactoriser la méthode pour n'ajouter que deux nombres. En d'autres termes, l'appelant sera responsable d'imprimer le résultat s'il le souhaite.

Améliorer la clarté

Au lieu de cela, séparons l'addition et l'instruction d'impression en deux méthodes :

public void addTwoNumbers(int a, int b) {
    result = a + b;
    return result;
}

public void printResults(results) {
    System.out.println("The result is " + results);
}

Maintenant, si nous ajoutons quelques méthodes supplémentaires qui simulent des opérateurs, nous pouvons facilement imprimer leurs résultats sans coller la même ligne dans chaque méthode. Mieux encore, les utilisateurs ne sont pas obligés d'imprimer avec leurs méthodes.

Par conséquent, ces fonctionnalités d'ajout et d'impression sont désormais découplées, et nous pourrions même écrire une méthode wrapper pour recombiner les fonctionnalités ultérieurement si nécessaire.

Bien qu'il s'agisse d'une leçon sur la lisibilité, l'argument de vente consistant à forcer une seule responsabilité par méthode est le test. C'est difficile à dire à partir de l'exemple stupide ci-dessus, mais les tests deviennent difficiles lorsque les méthodes ont trop de dépendances.

Par exemple, imaginez qu'au lieu d'imprimer, nous poussions en fait les résultats vers une base de données. C'est probablement un défaut de conception plus important, mais vous voyez l'idée. Il serait presque impossible d'écrire des tests portables sans se moquer de la connexion à la base de données. Au lieu de cela, nous extrayons simplement la logique métier dans sa propre méthode, afin qu'elle soit facile à isoler et à tester.

Styles de codage

En fin de compte, un bon style est subjectif. Chaque équipe a une culture différente qui peut conduire à différents styles de codage. En conséquence, nous devrions prendre certaines des stratégies suivantes avec un grain de sel. En d'autres termes, la section suivante est ma vision personnelle d'un guide de style de codage - n'hésitez pas à donner votre avis dans les commentaires.

Conventions de dénomination Java

Parmi tout ce qui est discuté dans la section des styles de codage, les conventions de nommage sont probablement les moins discutables. Chaque langue a un ensemble standardisé de conventions de dénomination que nous devrions tous suivre. Voici une bonne répartition de certaines de ces normes pour Java :

Les constantes doivent être nommées en utilisant toutes les lettres majuscules avec des traits de soulignement pour indiquer les espaces :

public static final int SIZE_OF_WINDOW = 100;

Les classes doivent être nommées avec la première lettre de chaque mot en majuscule :

public class FourDimensionalMatrix { ... }

Les méthodes doivent être nommées en utilisant la convention camelCase :

public void printAllWordsInDictionary() { ... }

Les variables doivent être nommées en utilisant la convention camelCase :

String myName = "Jeremy";

Pour tout le reste, reportez-vous à une sorte de guide de style.

Commentaires

Les commentaires sont bénéfiques car ils ajoutent un élément de langage naturel au code. Ici, nous pouvons déclarer explicitement l'intention d'une méthode ou d'un morceau de logique.

Cependant, les commentaires peuvent être délicats. Idéalement, le code est explicite. Nous devrions nous efforcer de donner des noms explicites aux variables, aux méthodes et aux classes. Lorsque cela n'est pas possible, les commentaires peuvent jouer un rôle important.

Pour les besoins de cette série de didacticiels, nous devons nous efforcer d'utiliser les commentaires JavaDoc dans la mesure du possible. Les commentaires JavaDoc nous permettent de documenter clairement chaque classe et méthode. La beauté de cela est que nous pouvons compiler les résultats dans une page Web qui peut être parcourue comme une API Java classique.

Tout ce qui va au-delà de JavaDoc doit être limité. Nous devons éviter d'ajouter trop de commentaires car ils peuvent rendre le code tout aussi difficile à naviguer. Cela est particulièrement vrai lorsque les commentaires ne sont pas conservés. Par exemple, imaginez que nous avons commenté notre premier addTwoNumbers méthode pour dire quelque chose comme ceci :

/**
 * Adds two numbers then prints the result.
 *
 * @param a the first number
 * @param b the second number
 * @return the result of adding a and b
 */

Si nous refactorisions notre code en deux méthodes comme avant mais oubliions de modifier ce commentaire, les utilisateurs commenceraient à signaler des bogues dans notre API.

La prochaine personne à lire ce commentaire serait probablement assez intelligente pour remarquer que la méthode n'imprime plus le résultat. Espérons qu'ils nettoieraient simplement le commentaire. Cependant, il est possible qu'ils incorporent à nouveau l'impression. Après tout, l'intention de la méthode semble être d'ajouter deux nombres et d'imprimer le résultat.

Comme on peut le voir, les commentaires peuvent parfois être dangereux. Cela dit, cela ne devrait pas nous empêcher de les utiliser. Si quoi que ce soit, cela devrait simplement nous pousser à écrire un meilleur code. Après tout, le code est lu beaucoup plus souvent qu'il n'est écrit.

Bretelles

À ce stade, nous allons entrer dans un territoire dangereux car tout le monde semble avoir une opinion différente en ce qui concerne les broches.

Bretelles sont ces symboles d'accolades que nous utilisons pour désigner un bloc de code. Certains langages comme Python les ont complètement supprimés pour forcer la sensibilité aux espaces blancs. C'est parce que les accolades ne sont qu'une autre forme de code passe-partout. En conséquence, de nombreux développeurs essaient de les éviter comme la peste.

La vie sans bretelles

De retour dans le didacticiel JUnit Testing, nous avons abordé une section de code qui a peut-être été écrite par l'un de ces développeurs. Dans cet exemple, le développeur a écrit une instruction if sans accolades.

Bien sûr, le problème était que le code semblait tout à fait correct à première vue. En fait, les développeurs Python n'ont peut-être même jamais remarqué le problème. Malheureusement, le problème était que les instructions if sans accolades n'exécutent que du code jusqu'au prochain point-virgule. Par exemple :

if (x < 5)
    System.out.println("How Now");
    System.out.println("Brown Cow");

Dans le cas où x est inférieur à 5, les deux chaînes s'impriment comme prévu. Sinon, "Brown Cow" s'imprime presque mystérieusement.

À mon avis, c'est une raison suffisante pour être cohérent et mettre tous les blocs de code entre accolades. Peu importe si le bloc de code est une doublure rapide. Les accolades nous aident à être explicites dans l'intention du code.

Heureusement, la plupart des IDE le feront pour nous, donc cela ne devrait même pas être un problème. Il y a des arguments selon lesquels les accolades peuvent gaspiller un espace précieux sur l'écran vertical, mais cela ne semble tout simplement pas vrai. Dans le cas où une méthode est si volumineuse qu'elle ne tient pas à l'écran, il est peut-être temps de procéder à une refactorisation.

Placement de l'attelle

Après avoir décidé de toujours utiliser des accolades, nous devrions probablement trouver où les mettre. L'emplacement réel de nos accolades est un peu plus controversé. En réalité, c'est une préférence personnelle. Cependant, nous devrions probablement choisir un style et nous y tenir si nous sommes en équipe. Sinon, nous sommes libres d'expérimenter.

Les deux styles principaux sont les suivants :

public void max(int a, int b) {
  if (a > b) {
    return a;
  } else {
    return b;
  }
}
public void max(int a, int b)
{
  if (a > b)
  {
    return a;
  }
  else
  {
    return b;
  }
}

Personnellement, je préfère la première option comme vous l'avez sans doute remarqué dans ces tutoriels. J'aime la façon dont le code apparaît compact, et je pense que c'est le style accepté pour Java dans la plupart des endroits, Google et Sun en particulier.

Cependant, la deuxième option a un peu plus de mérite. Tout d'abord, le contreventement est beaucoup plus cohérent. En d'autres termes, les accolades n'apparaissent que sur deux colonnes au lieu de cinq. De même, il est plus facile de commenter. Des commentaires peuvent être ajoutés au-dessus des lignes 3 et 7.

Malheureusement, la première option n'est pas aussi explicite, donc dans un souci de clarté et de cohérence, il est logique de suivre la deuxième option. En fait, je crois que l'option 2 est la syntaxe préférée en C#.

Interrompre, continuer et revenir

Comme nous en avons discuté, la clarté est essentielle. Cependant, parfois, convention ne rime pas toujours avec clarté.

Par exemple, les boucles se terminent lorsque leur condition n'est plus satisfaite. Java fournit un contrôle supplémentaire sur les boucles via le break , continue , et return déclarations. Nous avons spécifiquement évité les deux premiers mots-clés car ils sont très faciles à utiliser à mauvais escient. Cela dit, ils ont leur place lorsqu'il s'agit d'améliorer l'intention d'une boucle.

Un exemple de sortie anticipée

Pour les besoins de l'argumentation, une boucle for each - un type spécial de boucle for - ne nous permet pas de spécifier une condition de fin. Au lieu de cela, il se termine une fois qu'il a itéré sur son ensemble.

Peut-être voulons-nous écrire une fonction de recherche pour rechercher un caractère dans une chaîne. Lors de l'utilisation d'une boucle pour chaque, nous sommes obligés d'itérer sur toute la chaîne même si nous trouvons la lettre dont nous avons besoin. Comme solution potentielle, nous pouvons break hors de la boucle lorsque nous trouvons notre lettre. Sinon, nous pourrions return immédiatement :

public static boolean findChar(char toFind, char[] charList) {
    for (char test : charList) {
        if (test == toFind) {
            return true;
        }
    }
    return false;
}

Dans cet exemple, nous pouvons prétendre que charList est une chaîne. Avec ce spécial pour chaque boucle, nous pouvons examiner chaque caractère de la chaîne et déterminer s'il s'agit du caractère que nous voulons. Dès que nous le trouvons, nous retournons vrai. Sinon, la boucle se termine et nous renvoyons faux.

Un autre exemple

Certains diront qu'il s'agit d'une méthode mal conçue car nous avons deux déclarations de retour. Personnellement, je trouve cela bien meilleur en termes de lisibilité car il ne contient aucune variable locale superflue pour stocker les booléens. De plus, cela permet de gagner du temps en revenant au plus tard.

L'alternative pourrait ressembler à ceci :

public static boolean findChar(char toFind, String myString) {
    boolean found = false;
    char test = 0;
    for (int i = 0; !found && i < myString.length(); i++) {
        test = myString.charAt(i);
        if (test == toFind) {
            found = true;
        }
    }
    return found;
}

Dans ce cas, la complexité du code a considérablement augmenté. Nous devons maintenant maintenir deux variables locales et évaluer une condition composée juste pour correspondre au comportement de la première implémentation. Certes, cette fois j'ai utilisé une chaîne plutôt qu'un tableau. À mon avis, nous devrions probablement nous en tenir à la première implémentation.

Personnellement, je n'ai pas trouvé de bon usage pour continue . Cependant, il a probablement sa place un peu comme break . Nous devrions toujours nous concentrer sur la création de conditions de boucle soigneusement conçues dans la plupart des cas, mais nous ne devrions pas forcer ces conditions dans les situations où un break ou return pourrait rendre l'intention plus explicite.

Le style et la lisibilité sont-ils vraiment importants ?

Très bien, nous avons étudié quelques domaines de style différents et nous avons appris quelques principes stylistiques importants. Cependant, devrions-nous vraiment nous soucier de l'apparence de notre code ? Après tout, cela ne fait aucune différence si le code échoue au test.

En fin de compte, nous devrions nous en soucier. Un style de codage cohérent et clair est essentiel pour travailler en équipe. C'est même important quand on travaille seul. Parfois, nous devrons fouiller dans nos anciennes bibliothèques et nous réaliserons que nous n'avons aucune idée de ce que nous avons écrit. S'en tenir à un style nous aide à revenir à la vitesse supérieure, afin que nous puissions commencer à apporter des changements importants.

Cela dit, ne me croyez pas sur parole. Jetez un œil à certains des articles suivants :

  • Débattons du style de codage :une vue des différents styles de codage avec les préférences de l'auteur (principalement C++, mais toujours pertinent)
  • Pourquoi le style de codage est important :un commentaire sur l'importance des styles de codage
  • Pourquoi j'ai abandonné les styles de codage :un commentaire sur les inconvénients des styles de codage

Assurez-vous de jeter un œil aux commentaires lorsque vous aurez fini de lire ces articles. Il y a toujours un petit débat sympa autour du sujet.

Maintenant que nous avons creusé dans trois domaines de style différents qui font souvent l'objet de débats, nous devrions nous sentir plus confiants lors de l'écriture de code. En fin de compte, nous pouvons prendre la décision d'utiliser un style ou un autre. Cependant, nous devons garder à l'esprit ces deux principes fondamentaux :la cohérence et la clarté. Plus nous suivrons ces principes, plus il sera facile pour les autres de travailler avec notre code.

Comme toujours, merci d'être passé. Si vous avez des questions ou des commentaires, n'hésitez pas à utiliser les commentaires ci-dessous. Si vous souhaitez suivre les derniers articles sur The Renegade Coder, n'oubliez pas de vous abonner !


Balise Java