Des lacunes ont gagné. Traduction de la documentation des conventions de codage Kotlin à partir de JetBrains

Bonjour, Habr! J'attire votre attention sur la traduction par l'auteur de la page de documentation des conventions de codage Kotlin de JetBrains.


Documentation originale


Contenu:



Utilisation du guide de style sur Intellij Idea


Pour appliquer le formatage dans Intellij Idea conformément au manuel actuel, vous devez installer le plugin Kotlin version 1.2.20 ou ultérieure, allez dans Paramètres | Editeur | Style de code | Kotlin, cliquez sur le lien "Définir à partir de ..." dans le coin supérieur droit et sélectionnez "Style prédéfini" / Guide de style Kotlin "dans le menu déroulant.


Pour vérifier que votre code est formaté selon le style recommandé, accédez au paramètre d'inspection et activez la case à cocher "Kotlin | Problèmes de style | Le fichier n'est pas formaté selon les paramètres du projet". D'autres règles de validation, telles que les conventions de dénomination, sont activées par défaut.


Structure du projet


Structure des dossiers


Dans les projets utilisant différentes langues, les fichiers avec le code Kotlin doivent se trouver dans le même dossier que le code dans les autres langues et utiliser la même structure de fichiers acceptée pour la langue principale. Par exemple, pour Java, les fichiers doivent se trouver dans la structure de dossiers en fonction du nom du package.


Dans les projets utilisant uniquement Kotlin, la structure de dossiers recommandée est la suivante: utilisez des dossiers pour organiser les packages avec le répertoire racine ignoré, c'est-à-dire si tout le code du projet se trouve dans le package "org.example.kotlin" et ses packages, les fichiers source appartenant au package "org.example.kotlin" doivent se trouver dans le répertoire racine du projet et les fichiers avec le code source du package "org. example.kotlin.foo.bar "doit se trouver dans le sous-répertoire" foo / bar "par rapport à la racine du projet.


Nom des fichiers source


Si le fichier Kotlin ne contient qu'une seule classe (éventuellement liée à la déclaration de niveau supérieur), il doit être nommé, ainsi qu'une classe avec l'extension .kt . Si le fichier contient plusieurs classes ou n'a que des déclarations de niveau supérieur, choisissez un nom qui décrit ce que le fichier contient et nommez le fichier en conséquence. Utilisez une bosse de chameau avec une première lettre majuscule pour nommer les fichiers (par exemple ProcessDeclarations.kt ).


Le nom du fichier doit décrire ce que fait le code dans le fichier. Autrement dit, vous devez éviter les mots dénués de sens comme «Util» pour nommer les fichiers.


Organisation des fichiers source


Placer plusieurs déclarations (classes, fonctions ou propriétés de niveau supérieur) dans le même fichier source Kotlin est bienvenu si ces déclarations sont étroitement liées sémantiquement et que la taille du fichier reste raisonnable (pas plus de plusieurs centaines de lignes).


En particulier, lorsque vous définissez des fonctions d'extension pour une classe qui s'appliquent à tous les aspects de l'application de cette classe, placez-les dans le même fichier où la classe elle-même est définie. Lorsque vous définissez des fonctions d'extension qui n'ont de sens que pour le contexte spécifique d'utilisation de cette classe, placez-les à côté du code qui utilise la fonction d'extension. Ne créez pas de fichiers uniquement pour stocker "toutes les extensions Foo".


Structure des classes


En règle générale, le contenu d'une classe est trié dans l'ordre suivant:


  • Déclarations de propriétés et blocs d'initialisation
  • Constructeurs secondaires
  • Déclarations de méthode
  • Objets compagnons

Ne triez pas les déclarations de méthode par ordre alphabétique ou visuel et ne séparez pas les méthodes ordinaires des méthodes d'extension. Au lieu de cela, assemblez le code connecté de manière logique afin que quelqu'un qui lit la classe de haut en bas puisse suivre la logique de ce qui se passe. Choisissez un ordre de tri (code de niveau supérieur en premier [éléments de niveau supérieur en premier] et détails plus tard ou vice versa) et respectez-le.


Placez les classes imbriquées à côté du code qui utilise ces classes. Si les classes sont destinées à un usage externe et ne sont pas référencées dans la classe, placez-les à la fin après l'objet compagnon.


Cadre de mise en œuvre de l'interface


Lors de la mise en œuvre d'une interface, conservez la même structure que l'interface en cours de mise en œuvre (si nécessaire, en l'alternant avec des méthodes privées supplémentaires utilisées pour la mise en œuvre)


Remplace la structure


Des redéfinitions toujours mises en place, l'une après l'autre.


Règles de dénomination


Kotlin suit les mêmes conventions de dénomination que Java. En particulier:


Noms de packages en minuscules et n'utilisez pas de tirets bas (org.example.myproject). L'utilisation de noms à plusieurs mots n'est généralement pas recommandée, mais si vous devez utiliser plusieurs mots, vous pouvez simplement les combiner ensemble ou utiliser une bosse de chameau (org.examle.myProject).


Les majuscules et les noms d'objet commencent par une majuscule et utilisent une bosse de chameau:


 open class DeclarationProcessor { ... } object EmptyDeclarationProcessor : DeclarationProcessor() { ... } 

Nom de la fonction


Les noms des fonctions, des propriétés et des variables locales commencent par une lettre minuscule et ne contiennent pas de soulignement:


 fun processDeclarations() { ... } var declarationCount = ... 

Exception: les fonctions d'usine utilisées pour instancier des classes peuvent avoir le même nom que la classe en cours de création:


 abstract class Foo { ... } class FooImpl : Foo { ... } fun Foo(): Foo { return FooImpl(...) } 

Nom des méthodes d'essai


Dans les tests (et uniquement dans les tests), il est permis d'utiliser des noms de méthode avec des espaces entre virgules inversées. (Notez que ces noms de méthode ne sont actuellement pas pris en charge par le runtime Android.) Les soulignements dans les noms de méthode sont également autorisés dans le code de test.


 class MyTestCase { @Test fun `ensure everything works`() { ... } @Test fun ensureEverythingWorks_onAndroid() { ... } } 

Dénomination des propriétés


Les noms de constantes (propriétés étiquetées const , ou propriétés de niveau supérieur ou un objet val sans fonction get personnalisée qui contient des données immuables) doivent être capitalisés, séparés par des traits de soulignement:


 const val MAX_COUNT = 8 val USER_NAME_FIELD = "UserName" 

Les noms de haut niveau ou les propriétés d'objet qui contiennent des objets avec un comportement ou des données modifiables doivent utiliser des noms communs dans la bosse du chameau:


 val mutableCollection: MutableSet<String> = HashSet() 

Les noms de propriété qui font référence aux objets Singleton peuvent utiliser le même style de dénomination que les déclarations de classe:


 val PersonComparator: Comparator<Person> = ... 

Pour les énumérations, vous pouvez utiliser des noms écrits en lettres majuscules séparées par des traits de soulignement ou dans le style bosse de chameau, en commençant par une lettre majuscule, selon l'utilisation.


 enum class Color { RED, GREEN } 

 enum class Color { RedColor, GreenColor } 

Note du traducteur: ne mélangez pas différents styles. Choisissez un style et respectez-le dans votre conception.


Attribution d'un nom aux propriétés masquées


Si la classe possède deux propriétés conceptuellement identiques, mais que l'une fait partie de l'API publique et l'autre fait partie de l'implémentation, utilisez le trait de soulignement comme préfixe pour le nom de la propriété masquée:


 class C { private val _elementList = mutableListOf<Element>() val elementList: List<Element> get() = _elementList } 

Choisir les bons noms


Le nom de la classe est généralement un nom ou une phrase expliquant ce qu'est la classe:


 List, PersonReader 

Le nom de la méthode est généralement un verbe ou une expression qui explique ce que fait la méthode:


 close, readPersons 

Le nom doit également indiquer si la méthode modifie l'objet ou en renvoie un nouveau. Par exemple, le sort est un tri qui modifie la collection et sorted est le retour d'une nouvelle copie triée de la collection.


Les noms doivent clairement indiquer le but de l'entité, il est donc préférable d'éviter l'utilisation de mots dénués de sens ( Manager , Wrapper , etc.) dans les noms.


Lorsque vous utilisez l'acronyme dans le nom de l'annonce, utilisez des majuscules s'il se compose de deux lettres ( IOStream ); ou en majuscule uniquement la première lettre, si elle est plus longue ( XmlFormatter , HttpInputStream ).


Formatage


Dans la plupart des cas, Kotlin suit les conventions de formatage Java.


Utilisez 4 espaces pour mettre en retrait. N'utilisez pas d'onglets.


Pour les croisillons, placez le croisillon d'ouverture à la fin de la ligne où commence la structure et le croisillon de fermeture sur une ligne distincte, aligné horizontalement avec la structure d'ouverture.


 if (elements != null) { for (element in elements) { // ... } } 

(Remarque: dans Kotlin, un point-virgule est facultatif, donc l'habillage de ligne est important. La conception du langage suppose des accolades de style Java et vous pouvez rencontrer un comportement d'exécution de code inattendu si vous essayez d'utiliser un style de mise en forme différent.)


Espaces horizontaux


Placez des espaces autour des opérateurs binaires (a + b) . Exception: ne placez pas d'espaces autour de l'opérateur "range to" (0..i)


Ne placez pas d'espaces autour des opérateurs unaires (a++)


Placez des espaces entre les mots clés de contrôle ( if , when , for et while ) et les crochets d'ouverture correspondants.


Ne placez pas d'espace avant la parenthèse ouvrante dans la déclaration principale d'un constructeur, d'une méthode ou d'une méthode.


 class A(val x: Int) fun foo(x: Int) { ... } fun bar() { foo(1) } 

Ne placez jamais d'espace après ( , [ ou avant ] , ) .


Ne placez jamais d'espace autour d'un point . ou opérateur ?. :


 foo.bar().filter { it > 2 }.joinToString() foo?.() 

Mettez un espace après la double barre oblique pour le commentaire // :


 //   

Ne placez pas d'espaces autour des parenthèses angulaires utilisées pour indiquer les paramètres de type:


 Class Map<K, V> { ... } 

Ne placez pas d'espaces autour des deux points pour indiquer une référence à la méthode :: class:


 Foo::class String::length 

Ne mettez pas d'espace avant ? utilisé pour marquer un null :


 String? 

Évitez généralement tout type d'alignement horizontal. Renommer un identifiant en un nom d'une longueur différente ne doit pas affecter la mise en forme du code.


Colon


Mettez un espace avant les deux points : dans les cas suivants:


  • quand il est utilisé pour séparer le type du super type;

 abstract class Foo<out T : Any> 

  • lors de la délégation à un constructeur de superclasse ou à un autre constructeur de la même classe;

 constructor(x: String) : super(x) { ... } constructor(x: String) : this(x) { ... } 

  • après l'objet mot clé.

 val x = object : IFoo { ... } 

Ne mettez pas d'espace avant : lorsqu'il sépare une annonce et son type.


 abstract fun foo(a: Int): T 

Mettez toujours un espace après :


 abstract class Foo<out T : Any> : IFoo { abstract fun foo(a: Int): T } class FooImpl : Foo() { constructor(x: String) : this(x) { ... } val x = object : IFoo { ... } } 

Formatage de la déclaration de classe


Les classes avec plusieurs paramètres constructeurs de base et noms abrégés peuvent être écrites sur une seule ligne:


 class Person(id: Int, name: String) 

Les classes avec des noms plus longs ou le nombre de paramètres doivent être formatées de manière à ce que chaque paramètre principal du constructeur soit sur une ligne distincte avec indentation. De plus, le support de fermeture doit se trouver sur une nouvelle ligne. Si nous utilisons l'héritage, alors l'appel au constructeur de la superclasse ou la liste des interfaces implémentées devrait être situé sur la même ligne que le support:


 class Person( id: Int, name: String, surname: String ) : Human(id, name) { ... } 

Lorsque vous spécifiez l'interface et appelez le constructeur de la superclasse, le constructeur de la superclasse doit d'abord être localisé, puis le nom de l'interface sur une nouvelle ligne est justifié à gauche:


 class Person( id: Int, name: String, surname: String ) : Human(id, name), KotlinMaker { ... } 

Pour les classes avec une longue liste de super types, vous devez mettre un saut de ligne après les deux points et aligner tous les noms de super types horizontalement vers la gauche:


 class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { fun foo() { ... } } 

Pour séparer clairement l'en-tête de classe et son corps lorsque l'en-tête de classe est long, placez une ligne vide après l'en-tête de classe (comme dans l'exemple ci-dessus), ou placez l'accolade ouvrante sur une ligne distincte:


 class MyFavouriteVeryLongClassHolder : MyLongHolder<MyFavouriteVeryLongClass>(), SomeOtherInterface, AndAnotherOne { fun foo() { ... } } 

Utilisez une indentation régulière (4 espaces) pour les paramètres du constructeur.


Justification: cela garantit que les propriétés déclarées dans le constructeur principal ont la même indentation que les propriétés déclarées dans le corps de la classe.


Modificateurs


Si une annonce contient plusieurs modificateurs, organisez-les toujours dans l'ordre suivant:


 public / protected / private / internal expect / actual final / open / abstract / sealed / const external override lateinit tailrec vararg suspend inner enum / annotation companion inline infix operator data 

Mettez toutes les annotations avant les modificateurs:


 @Named("Foo") private val foo: Foo 

Si vous ne travaillez pas sur une bibliothèque, omettez les modificateurs redondants (par exemple publics).


Mettre en forme les annotations


Les annotations sont généralement placées sur des lignes distinctes avant la déclaration à laquelle elles sont jointes, et avec le même tiret:


 @Target(AnnotationTarget.PROPERTY) annotation class JsonExclude 

Les annotations sans arguments peuvent être situées sur une seule ligne:


 @JsonExclude @JvmField var x: String 

Une annotation sans arguments peut être placée sur la même ligne que la déclaration correspondante:


 @Test fun foo() { ... } 

Annotations de fichier


Les annotations aux fichiers sont placées après le commentaire sur le fichier (le cas échéant), avant l'instruction package et sont séparées du package par une ligne vide (pour souligner le fait qu'elles visent le fichier, pas le package).


 /** License, copyright and whatever */ @file:JvmName("FooBar") package foo.bar 

Formatage des fonctions


Si la signature de la méthode ne tient pas sur une seule ligne, utilisez la syntaxe suivante:


 fun longMethodName( argument: ArgumentType = defaultValue, argument2: AnotherArgumentType ): ReturnType { // body } 

Utilisez une indentation régulière (4 espaces) pour les paramètres de fonction.


Justification: cohérence avec les paramètres du constructeur

Il est préférable d'utiliser une expression sans accolades pour les fonctions constituées d'une seule ligne.


 fun foo(): Int { // bad return 1 } fun foo() = 1 // good 

Formatage d'expression sur une seule ligne


Si le corps d'une fonction sur une seule ligne ne tient pas sur la même ligne que la déclaration, mettez le signe = dans la première ligne. Indentation du corps de l'expression par 4 espaces.


 fun f(x: String) = x.length 

Formatage des propriétés


Pour les propriétés simples en lecture seule, il est préférable d'utiliser un formatage sur une seule ligne:


 val isEmpty: Boolean get() = size == 0 

Pour des propriétés plus complexes, utilisez toujours get et set sur des lignes distinctes:


 val foo: String get() { ... } 

Pour les propriétés avec initialisation, si l'initialiseur est trop long, ajoutez un saut de ligne après le signe égal et un retrait de quatre espaces pour la chaîne d'initialisation:


 private val defaultCharset: Charset? = EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file) 

Formatage des instructions de contrôle


Si la condition dans l'instruction if ou when est multiligne, utilisez toujours des accolades autour du corps de l'instruction. Indenter chaque ligne suivante de la condition de 4 espaces par rapport au début de l'instruction. Placez les crochets de fermeture de la condition ainsi que l'accolade ouvrante sur une ligne distincte:


 if (!component.isSyncing && !hasAnyKotlinRuntimeInScope(module) ) { return createKotlinNotConfiguredPanel(module) } 

Justification: alignement net et séparation claire du corps de condition et du corps de condition

Placez les mots-clés else , catch , finally , ainsi que le mot-clé while de la boucle do / while sur la même ligne que le crochet précédent.


 if (condition) { // body } else { // else part } try { // body } finally { // cleanup } 

Si les conditions when les instructions sont constituées de plusieurs blocs, il est recommandé de les séparer les uns des autres par une ligne vide:


 private fun parsePropertyValue(propName: String, token: Token) { when (token) { is Token.ValueToken -> callback.visitValue(propName, token.value) Token.LBRACE -> { // ... } } } 

Placez les blocs courts des instructions when sur la même ligne sans accolades.


 when (foo) { true -> bar() // good false -> { baz() } // bad } 

Formatage des appels de méthode


Lorsque vous utilisez une longue liste de paramètres, placez le saut de ligne après la parenthèse. Mettez en retrait 4 espaces et regroupez les arguments liés logiquement sur une seule ligne.


 drawSquare( x = 10, y = 10, width = 100, height = 100, fill = true ) 

Utilisez des espaces autour du signe égal entre le nom du paramètre et sa valeur.


Formatage d'un appel de fonction de chaîne


Lorsque vous utilisez des appels en chaîne, mettez . ou ?. opérateurs sur une nouvelle ligne avec une indentation dans 4 espaces:


 val anchor = owner ?.firstChild!! .siblings(forward = true) .dropWhile { it is PsiComment || it is PsiWhiteSpace } 

Le premier appel dans la chaîne devrait généralement avoir un saut de ligne devant lui, mais il est normal de ne pas le faire si le code est mieux lu et si cela a du sens.


Formatage des expressions lambda


Dans les expressions lambda, les espaces doivent être utilisés autour des accolades et autour de la flèche qui sépare les paramètres du corps. Si un appel accepte un seul caractère lambda, il doit être utilisé en dehors des parenthèses chaque fois que possible.


 list.filter { it > 10 } 

Lors de l'attribution d'une étiquette à une expression lambda, ne mettez pas d'espace entre l'étiquette et l'accolade ouvrante:


 fun foo() { ints.forEach lit@{ // ... } } 

Lorsque vous déclarez des noms de paramètres dans une expression lambda sur plusieurs lignes, placez les noms sur la première ligne, puis sur la flèche et sur la nouvelle ligne, le début du corps de la fonction:


 appendCommaSeparated(properties) { prop -> val propertyValue = prop.get(obj) // ... } 

Si la liste des paramètres ne tient pas sur une seule ligne, placez la flèche sur une ligne distincte:


 foo { context: Context, environment: Env -> context.configureEnv(environment) } 

Paperasse


Lorsque vous utilisez la documentation multiligne, mettez /** sur une ligne distincte et démarrez chaque ligne suivante avec un astérisque:


 /** * This is a documentation comment * on multiple lines. */ 

Une courte documentation peut être placée sur une seule ligne:


 /** This is a short documentation comment. */ 

En général, évitez d'utiliser les balises param et return . Au lieu de cela, incluez une description des paramètres et renvoyez des valeurs directement dans le commentaire de la documentation et ajoutez des références de paramètres partout où ils sont mentionnés. Utilisez param et return uniquement lorsqu'une longue description est requise qui ne correspond pas à la signification du texte principal.


 // Avoid doing this: /** * Returns the absolute value of the given number. * @param number The number to return the absolute value for. * @return The absolute value. */ fun abs(number: Int) = ... // Do this instead: /** * Returns the absolute value of the given [number]. */ fun abs(number: Int) = ... 

Éviter les constructions inutiles


De nombreuses constructions syntaxiques dans Kotlin sont facultatives et mises en évidence par l'environnement de développement comme inutiles, vous ne devez pas les utiliser dans votre code juste pour rendre votre code «clair».


Utilisez le mot-clé Unité


Dans les fonctions, l'utilisation du mot-clé Unit ne doit pas être utilisée:


 fun foo() { // ": Unit" is omitted here } 

Point-virgule


Évitez d'utiliser un point-virgule à chaque occasion.


Modèles de chaînes


N'utilisez pas d'accolades lorsque vous insérez une variable simple dans une chaîne de modèle. Utilisez des accolades uniquement pour les expressions longues.


 println("$name has ${children.size} children") 

Utilisation idiomatique des fonctionnalités linguistiques


Immuabilité


Il est préférable d'utiliser des données immuables avant les données mutables. Déclarez toujours les variables et propriétés locales comme val , pas var , à moins qu'elles ne changent vraiment.


Utilisez toujours des interfaces de collection immuables ( Collection , List , Set , Map ) pour déclarer les collections qui ne changent pas. À chaque occasion, lorsque vous utilisez des méthodes d'usine pour créer une collection, utilisez une implémentation qui renvoie des collections immuables:


 // Bad: use of mutable collection type for value which will not be mutated fun validateValue(actualValue: String, allowedValues: HashSet<String>) { ... } // Good: immutable collection type used instead fun validateValue(actualValue: String, allowedValues: Set<String>) { ... } // Bad: arrayListOf() returns ArrayList<T>, which is a mutable collection type val allowedValues = arrayListOf("a", "b", "c") // Good: listOf() returns List<T> val allowedValues = listOf("a", "b", "c") 

: .



.


 // Bad fun foo() = foo("a") fun foo(a: String) { ... } // Good fun foo(a: String = "a") { ... } 

[Type alias]


, , :


 typealias MouseClickHandler = (Any, MouseEvent) -> Unit typealias PersonIndex = Map<String, Person> 

-


-, , it . - .


-


. - , . , - .


( @ ) .



, , boolean , .


 drawSquare(x = 10, y = 10, width = 100, height = 100, fill = true) 


try , if when , return :


 return if (x) foo() else bar() //   ,    if (x) return foo() else return bar() // return when(x) { 0 -> "zero" else -> "nonzero" } //   ,    when(x) { 0 -> return "zero" else -> return "nonzero" } 

if when


if when


 when (x) { null -> ... else -> ... } if (x == null) ... else ... //      

, when .


Boolean?


Boolean? , if (value == true) if (value == false) , if (value ?: false) if (value != null && value) .



filtet , map .. . : forEach ( for null forEach )


, , , .



until ( ):


 for (i in 0..n - 1) { ... } // bad for (i in 0 until n) { ... } // good 


.


\n escape- .


, trimIndent , , trimMargin , :


 assertEquals( """ Foo Bar """.trimIndent(), value ) val a = """if(a > 1) { | return a |}""".trimMargin() 


. , , .


:


  • ( )
  • ,


. , , , , . API, , . , .



infix , , . : and , to , zip . : add .


infix , .



, , . , , . , , .


 class Point(val x: Double, val y: Double) { companion object { fun fromPolar(angle: Double, radius: Double) = Point(...) } } 

, , .



: , , Kotlin null , null

public /, , Kotlin:


 fun apiCall(): String = MyJavaApi.getProperty("name") 

(package-level class-level) Kotlin:


 class Person { val name: String = MyJavaApi.getProperty("name") } 

, , Kotlin :


 fun main() { val name = MyJavaApi.getProperty("name") println(name) } 

apply / with / run / also / let


Kotlin . , :


  • ? , , it , this ( also let ). also , .

 // Context object is 'it' class Baz { var currentBar: Bar? val observable: Observable val foo = createBar().also { currentBar = it // Accessing property of Baz observable.registerCallback(it) // Passing context object as argument } } // Receiver not used in the block val foo = createBar().also { LOG.info("Bar created") } // Context object is 'this' class Baz { val foo: Bar = createBar().apply { color = RED // Accessing only properties of Bar text = "Foo" } } 

  • ? , apply also . , with , let run .

 // Return value is context object class Baz { val foo: Bar = createBar().apply { color = RED // Accessing only properties of Bar text = "Foo" } } // Return value is block result class Baz { val foo: Bar = createNetworkConnection().let { loadBar() } } 

  • null ? , apply , let run . , with also .

 // Context object is nullable person.email?.let { sendEmail(it) } // Context object is non-null and accessible directly with(person) { println("First name: $firstName, last name: $lastName") } 


API:


  • ( API)
  • ( )
  • KDoc public api, , /

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


All Articles