JavaRush /Blog Java /Random-FR /Guide du style de programmation général
pandaFromMinsk
Niveau 39
Минск

Guide du style de programmation général

Publié dans le groupe Random-FR
Cet article fait partie du cours académique « Java avancé. » Ce cours est conçu pour vous aider à apprendre à utiliser efficacement les fonctionnalités Java. Le matériel couvre des sujets « avancés » tels que la création d'objets, la compétition, la sérialisation, la réflexion, etc. Le cours vous apprendra à maîtriser efficacement les techniques Java. Détails ici .
Contenu
1. Introduction 2. Portée des variables 3. Champs de classe et variables locales 4. Arguments de méthode et variables locales 5. Boxing et Unboxing 6. Interfaces 7. Chaînes 8. Conventions de dénomination 9. Bibliothèques standard 10. Immuabilité 11. Tests 12. Suivant. .. 13. Télécharger le code source
1. Introduction
Dans cette partie du didacticiel, nous poursuivrons notre discussion sur les principes généraux d'un bon style de programmation et d'un design réactif en Java. Nous avons déjà vu certains de ces principes dans les chapitres précédents du guide, mais de nombreux conseils pratiques seront donnés dans le but d'améliorer les compétences d'un développeur Java.
2. Portée variable
Dans la troisième partie (« Comment concevoir des classes et des interfaces »), nous avons expliqué comment la visibilité et l'accessibilité peuvent être appliquées aux membres des classes et des interfaces, compte tenu des contraintes de portée. Cependant, nous n’avons pas encore discuté des variables locales utilisées dans les implémentations de méthodes. Dans le langage Java, chaque variable locale, une fois déclarée, a une portée. Cette variable devient visible depuis l'endroit où elle est déclarée jusqu'au point où l'exécution de la méthode (ou du bloc de code) se termine. Généralement, la seule règle à suivre est de déclarer une variable locale au plus près de l'endroit où elle sera utilisée. Permettez-moi de regarder un exemple typique : for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } dans les deux fragments de code, la portée des variables est limitée aux blocs d'exécution dans lesquels ces variables sont déclarées. Une fois le bloc terminé, la portée se termine et la variable devient invisible. Cela semble plus clair, mais avec la sortie de Java 8 et l'introduction des lambdas, de nombreux idiomes bien connus du langage utilisant des variables locales deviennent obsolètes. Permettez-moi de donner un exemple de l'exemple précédent utilisant des lambdas au lieu d'une boucle : Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); on peut voir que la variable locale est devenue un argument de la fonction, qui à son tour est passé comme argument à la méthode forEach .
3. Champs de classe et variables locales
Chaque méthode en Java appartient à une classe spécifique (ou, dans le cas de Java8, à une interface où la méthode est déclarée comme méthode par défaut). Entre les variables locales qui sont des champs d'une classe ou des méthodes utilisées dans l'implémentation, il existe donc une possibilité de conflit de noms. Le compilateur Java sait comment sélectionner la variable correcte parmi celles disponibles, même si plusieurs développeurs ont l'intention d'utiliser cette variable. Les IDE Java modernes font un excellent travail en indiquant au développeur quand de tels conflits sont sur le point de se produire, via des avertissements du compilateur et la mise en évidence des variables. Mais il est quand même préférable de penser à de telles choses en écrivant du code. Je suggère de regarder un exemple : public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } l'exemple semble assez simple, mais c'est un piège. La méthode calculateValue introduit une valeur de variable locale et, en agissant sur celle-ci, masque le champ de classe portant le même nom. La ligne value += value; doit être la somme de la valeur du champ de classe et de la variable locale, mais à la place, autre chose est en cours. Une implémentation appropriée ressemblerait à ceci (en utilisant le mot-clé this) : public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } Bien que cet exemple soit naïf à certains égards, il démontre un point important qui, dans certains cas, peut prendre des heures pour déboguer et corriger.
4. Arguments de méthode et variables locales
Un autre piège dans lequel tombent souvent les développeurs Java inexpérimentés est l'utilisation d'arguments de méthode comme variables locales. Java vous permet de réaffecter des valeurs à des arguments non constants (cependant, cela n'a aucun effet sur la valeur d'origine) : public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } l'extrait de code ci-dessus n'est pas élégant, mais il fait un bon travail pour découvrir le problème : str se voit attribuer un autre value (et est essentiellement utilisé comme variable locale) . Dans tous les cas (sans aucune exception), vous pouvez et devez vous passer de cet exemple (par exemple en déclarant les arguments comme constantes). Par exemple : public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } En suivant cette règle simple, il est plus facile de retracer le code donné et de trouver la source du problème, même en introduisant des variables locales.
5. Emballage et déballage
Boxing et unboxing sont le nom d'une technique utilisée en Java pour convertir les types primitifs ( int, long, double, etc. ) en wrappers de type correspondants ( Integer, Long, Double , etc.). Dans la partie 4 du didacticiel Comment et quand utiliser les génériques, vous avez déjà vu cela en action lorsque j'ai parlé de l'encapsulation des types primitifs en tant que paramètres de type des génériques. Bien que le compilateur Java fasse de son mieux pour masquer ces conversions en effectuant un autoboxing, cela est parfois moins que prévu et produit des résultats inattendus. Regardons un exemple : public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); l'extrait de code ci-dessus se compile correctement. Cependant, il lancera une NullPointerException sur la ligne // блок où il effectue la conversion entre Long et long . Un conseil dans un tel cas est qu'il est conseillé d'utiliser des types primitifs (cependant, nous savons déjà que cela n'est pas toujours possible).
6.Interfaces
Dans la troisième partie du didacticiel, « Comment concevoir des classes et des interfaces », nous avons discuté des interfaces et de la programmation contractuelle, en soulignant que les interfaces doivent être préférées aux classes concrètes dans la mesure du possible. Le but de cette section est de vous encourager à considérer d'abord les interfaces en le démontrant avec des exemples concrets. Les interfaces ne sont pas liées à une implémentation spécifique (sauf pour les méthodes par défaut). Ce ne sont que des contrats et, à titre d’exemple, ils offrent beaucoup de liberté et de flexibilité dans la manière dont les contrats peuvent être exécutés. Cette flexibilité devient plus importante lorsque la mise en œuvre implique des systèmes ou des services externes. Examinons un exemple d'interface simple et son implémentation possible : public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } L'extrait de code ci-dessus montre un modèle d'interface typique et son implémentation. Cette implémentation utilise un service HTTP externe ( http://api.geonames.org/ ) pour récupérer le fuseau horaire d'un emplacement spécifique. Cependant, parce que le contrat dépend de l'interface, il est très simple d'introduire une autre implémentation de l'interface, en utilisant, par exemple, une base de données ou même un fichier plat classique. Avec eux, les interfaces sont très utiles pour concevoir du code testable. Par exemple, il n'est pas toujours pratique d'appeler des services externes à chaque test, il est donc logique d'implémenter une implémentation alternative et la plus simple (comme un stub) : public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } cette implémentation peut être utilisée partout où l' interface TimezoneService est requise, isolant le tester le script de la dépendance aux composants externes. De nombreux excellents exemples d’utilisation efficace de telles interfaces sont encapsulés dans la bibliothèque standard Java. Collections, listes, ensembles : ces interfaces ont plusieurs implémentations qui peuvent être facilement échangées et échangées lorsque les contrats en profitent. Par exemple: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. Cordes
Les chaînes sont l’un des types les plus utilisés en Java et dans d’autres langages de programmation. Le langage Java simplifie de nombreuses manipulations de chaînes de routine en prenant en charge les opérations de concaténation et de comparaison dès le départ. De plus, la bibliothèque standard contient de nombreuses classes qui rendent les opérations sur les chaînes efficaces. C’est exactement ce dont nous allons discuter dans cette section. En Java, les chaînes sont des objets immuables représentés en codage UTF-16. Chaque fois que vous concaténez des chaînes (ou effectuez une opération modifiant la chaîne d'origine), une nouvelle instance de la classe String est créée . Pour cette raison, l'opération de concaténation peut devenir très inefficace, entraînant la création de nombreuses instances intermédiaires de la classe String (créant des déchets en général). Mais la bibliothèque standard Java contient deux classes très utiles dont le but est de faciliter la manipulation des chaînes. Il s'agit de StringBuilder et StringBuffer (la seule différence entre eux est que StringBuffer est thread-safe alors que StringBuilder est l'opposé). Examinons quelques exemples d'utilisation de l'une de ces classes : final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); Bien que l'utilisation de StringBuilder/StringBuffer soit la méthode recommandée pour manipuler des chaînes, cela peut sembler excessif dans le scénario le plus simple de concaténation de deux ou trois chaînes, de sorte que l'opérateur d'addition normal ( ("+"), par exemple : String userId = "user:" + new Random().nextInt( 100 ); la meilleure alternative pour simplifier la concaténation est souvent d'utiliser le formatage de chaîne ainsi que la bibliothèque standard Java pour fournir une méthode d'assistance statique String.format . Cela prend en charge un riche ensemble de spécificateurs de format, notamment des nombres, des symboles, la date/heure, etc. (Reportez-vous à la documentation de référence pour plus de détails) La String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12% méthode String.format fournit une approche propre et légère pour générer des chaînes à partir de différents types de données. Il convient de noter que les IDE Java modernes peuvent analyser la spécification de format à partir des arguments transmis à la méthode String.format et avertir les développeurs si des incohérences sont détectées.
8. Conventions de dénomination
Java est un langage qui n'oblige pas les développeurs à suivre strictement une convention de dénomination, mais la communauté a développé un ensemble de règles simples qui rendent le code Java cohérent à la fois dans la bibliothèque standard et dans tout autre projet Java :
  • les noms des packages sont en minuscules : org.junit, com.fasterxml.jackson, javax.json
  • les noms de classes, énumérations, interfaces, annotations sont écrits avec une majuscule : StringBuilder, Runnable, @Override
  • les noms de champs ou de méthodes (sauf pour static final ) sont spécifiés en notation camel : isEmpty, format, addAll
  • Les noms des champs finaux statiques ou des constantes d'énumération sont en majuscules, séparés par des traits de soulignement ("_") : LOG, MIN_RADIX, INSTANCE.
  • les variables locales ou les arguments de méthode sont saisis en notation camel : str, newLength, minimumCapacity
  • les noms de types de paramètres pour les génériques sont représentés par une seule lettre en majuscule : T, U, E
En suivant ces conventions simples, le code que vous écrivez aura l'air concis et ne se distinguera pas d'une autre bibliothèque ou d'un autre framework, et donnera l'impression qu'il a été développé par la même personne (l'un de ces rares cas où les conventions fonctionnent réellement).
9. Bibliothèques standards
Quel que soit le type de projet Java sur lequel vous travaillez, les bibliothèques standard Java sont vos meilleures amies. Oui, il est difficile de nier qu’ils présentent des aspérités et des décisions de conception étranges, cependant, 99 % du temps, il s’agit d’un code de haute qualité écrit par des experts. Cela vaut la peine d'être exploré. Chaque version de Java apporte de nombreuses nouvelles fonctionnalités aux bibliothèques existantes (avec quelques problèmes possibles avec les anciennes fonctionnalités) et ajoute également de nombreuses nouvelles bibliothèques. Java 5 a apporté une nouvelle bibliothèque de concurrence dans le cadre du package java.util.concurrent . Java 6 fournissait une prise en charge (moins connue) des scripts ( le package javax.script ) et une API de compilateur Java (dans le cadre du package javax.tools ). Java 7 a apporté de nombreuses améliorations à java.util.concurrent , introduisant une nouvelle bibliothèque d'E/S dans le package java.nio.file et la prise en charge des langages dynamiques dans java.lang.invoke . Et enfin, Java 8 a ajouté la date/heure tant attendue dans le package java.time . Java en tant que plate-forme évolue et il est très important qu'il progresse avec les changements ci-dessus. Chaque fois que vous envisagez d'inclure une bibliothèque ou un framework tiers dans votre projet, assurez-vous que la fonctionnalité requise n'est pas déjà contenue dans les bibliothèques Java standard (bien sûr, il existe de nombreuses implémentations spécialisées et performantes d'algorithmes qui sont en avance sur le algorithmes dans les bibliothèques standards, mais dans la plupart des cas, ils ne sont vraiment pas nécessaires).
10. Immuabilité
L'immuabilité tout au long du guide et dans cette partie reste pour rappel : veuillez la prendre au sérieux. Si une classe que vous concevez ou une méthode que vous implémentez peut fournir une garantie d'immuabilité, elle peut être utilisée dans la plupart des cas partout sans craindre d'être modifiée en même temps. Cela facilitera votre vie de développeur (et, espérons-le, celle des membres de votre équipe).
11. Tests
La pratique du développement piloté par les tests (TDD) est extrêmement populaire dans la communauté Java, plaçant la barre plus haut en matière de qualité du code. Avec tous les avantages qu'offre TDD, il est triste de constater que la bibliothèque standard Java n'inclut aujourd'hui aucun cadre de test ni aucun outil de support. Cependant, les tests sont devenus un élément nécessaire du développement Java moderne et dans cette section, nous examinerons quelques techniques de base utilisant le framework JUnit . Dans JUnit, chaque test est essentiellement un ensemble d'instructions sur l'état ou le comportement attendu d'un objet. Le secret pour rédiger de bons tests est de les garder simples et courts, en testant une chose à la fois. À titre d'exercice, écrivons un ensemble de tests pour vérifier que String.format est une fonction de la section string qui renvoie le résultat souhaité. package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } Les deux tests semblent très lisibles et leur exécution se fait par instances. Aujourd'hui, le projet Java moyen contient des centaines de cas de test, donnant au développeur un retour rapide pendant le processus de développement sur les régressions ou les fonctionnalités.
12. Suivant
Cette partie du guide complète une série de discussions liées à la pratique de la programmation en Java et aux manuels de ce langage de programmation. La prochaine fois, nous reviendrons sur les fonctionnalités du langage, en explorant le monde de Java concernant les exceptions, leurs types, comment et quand les utiliser.
13. Téléchargez le code source
Il s'agissait d'une leçon sur les principes généraux de développement du cours Advanced Java. Le code source de la leçon peut être téléchargé ici .
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION