Gaps ganados. Traducción de documentación de convenciones de codificación de Kotlin de JetBrains

Hola Habr! Les traigo a su atención la traducción del autor de la página de documentación de Convenciones de codificación de Kotlin de JetBrains.


Documentación original


Contenido:



Usando la guía de estilo en Intellij Idea


Para aplicar el formato en Intellij Idea de acuerdo con el manual actual, debe instalar el complemento Kotlin versión 1.2.20 o posterior, vaya a Configuración | Editor | Código de estilo | Kotlin, haga clic en el enlace "Establecer desde ..." en la esquina superior derecha y seleccione "Estilo predefinido" / Guía de estilo Kotlin "en el menú desplegable.


Para verificar que su código esté formateado de acuerdo con el estilo recomendado, vaya a la configuración de inspección y active la casilla "Kotlin | Problemas de estilo | El archivo no está formateado de acuerdo con la configuración del proyecto". Otras reglas de validación, como las convenciones de nomenclatura, están habilitadas de forma predeterminada.


Estructura del proyecto


Estructura de carpetas


En proyectos que utilizan diferentes idiomas, los archivos con código Kotlin deben estar en la misma carpeta que el código en otros idiomas y usar la misma estructura de archivos que se acepta para el idioma principal. Por ejemplo, para Java, los archivos deben estar en la estructura de carpetas de acuerdo con el nombre del paquete.


En proyectos que usan solo Kotlin, la estructura de carpetas recomendada es: usar carpetas para organizar paquetes con el directorio raíz omitido, es decir si todo el código del proyecto está en el paquete "org.example.kotlin" y sus paquetes, entonces los archivos fuente que pertenecen al paquete "org.example.kotlin" deben estar en el directorio raíz del proyecto, y los archivos con el código fuente del paquete "org. example.kotlin.foo.bar "debería estar en el subdirectorio" foo / bar "relativo a la raíz del proyecto.


Nombre de los archivos de origen.


Si el archivo Kotlin contiene solo una clase (posiblemente relacionada con la declaración de nivel superior), entonces debe nombrarse, así como una clase con la extensión .kt . Si el archivo contiene varias clases o solo tiene declaraciones de nivel superior, elija un nombre que describa lo que contiene el archivo y asígnele el nombre correspondiente. Use la joroba de camello con una primera letra mayúscula para nombrar los archivos (por ejemplo, ProcessDeclarations.kt ).


El nombre del archivo debe describir lo que hace el código en el archivo. Es decir, debe evitar palabras sin sentido como "Util" para nombrar archivos.


Organizar archivos fuente


Colocar varias declaraciones (clases, funciones o propiedades de nivel superior) en el mismo archivo fuente de Kotlin es bienvenido si estas declaraciones están estrechamente relacionadas entre sí semánticamente y el tamaño del archivo sigue siendo razonable (no más de varios cientos de líneas).


En particular, al definir funciones de extensión para una clase que se aplican a todos los aspectos de la aplicación de esta clase, colóquelas en el mismo archivo donde se define la clase en sí. Al definir funciones de extensión que sean significativas solo para el contexto específico del uso de esta clase, colóquelas junto al código que usa la función de extensión. No cree archivos solo para almacenar "todas las extensiones de Foo".


Estructura de clase


Por lo general, el contenido de una clase se ordena en el siguiente orden:


  • Declaraciones de propiedad y bloques de inicializador
  • Constructores Secundarios
  • Declaraciones de métodos
  • Objetos acompañantes

No ordene las declaraciones de métodos alfabéticamente o visualmente, y no separe los métodos ordinarios de los métodos de extensión. En cambio, junte el código conectado lógicamente para que alguien que lea la clase de arriba a abajo pueda seguir la lógica de lo que sucede. Elija un orden de clasificación (código de nivel superior primero [cosas de nivel superior primero] y detalles más tarde o viceversa) y manténgalo.


Coloque las clases anidadas al lado del código que usa estas clases. Si las clases están destinadas para uso externo y no están referenciadas dentro de la clase, colóquelas al final después del objeto complementario.


Marco de implementación de interfaz


Al implementar una interfaz, mantenga la misma estructura que la interfaz que se está implementando (si es necesario, alternando con métodos privados adicionales utilizados para la implementación)


Anula estructura


Redefiniciones siempre juntas, una tras otra.


Reglas de nomenclatura


Kotlin sigue las mismas convenciones de nomenclatura que Java. En particular:


Nombres de paquete en minúsculas y no utilice guiones bajos (org.example.myproject). Por lo general, no se recomienda el uso de nombres de varias palabras, pero si necesita usar varias palabras, simplemente puede combinarlas o usar camello (org.examle.myProject).


Los nombres de mayúsculas y objetos comienzan con una letra mayúscula y usan joroba de camello:


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

Nombre de la función


Los nombres de funciones, propiedades y variables locales comienzan con una letra minúscula y no contienen guiones bajos:


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

Excepción: las funciones de fábrica utilizadas para crear instancias de clases pueden tener el mismo nombre que la clase que se está creando:


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

Nombre de los métodos de prueba.


En las pruebas (y solo en las pruebas) está permitido usar nombres de métodos con espacios encerrados entre comillas. (Tenga en cuenta que dichos nombres de métodos no son compatibles actualmente con el tiempo de ejecución de Android). Los caracteres de subrayado en los nombres de métodos también están permitidos en el código de prueba.


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

Nombramiento de propiedad


Los nombres constantes (propiedades etiquetadas const , o propiedades de nivel superior o un objeto val sin una función de get personalizada que contenga datos inmutables) deben escribirse en mayúsculas, separadas por guiones bajos:


 const val MAX_COUNT = 8 val USER_NAME_FIELD = "UserName" 

Los nombres de nivel superior o las propiedades de objetos que contienen objetos con comportamiento o datos mutables deben usar nombres comunes en camello:


 val mutableCollection: MutableSet<String> = HashSet() 

Los nombres de propiedad que hacen referencia a objetos Singleton pueden usar el mismo estilo de denominación que las declaraciones de clase:


 val PersonComparator: Comparator<Person> = ... 

Para las enumeraciones, puede usar nombres escritos en mayúsculas separados por guiones bajos o en el estilo de joroba de camello, comenzando con una letra mayúscula, dependiendo del uso.


 enum class Color { RED, GREEN } 

 enum class Color { RedColor, GreenColor } 

Nota del traductor: simplemente no mezcle diferentes estilos. Elija un estilo y manténgalo en su diseño.


Nombramiento de propiedades ocultas


Si una clase tiene dos propiedades que son conceptualmente iguales, pero una es parte de la API pública y la otra es parte de la implementación, use el guión bajo como prefijo para el nombre de la propiedad oculta:


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

Elegir los nombres correctos


El nombre de la clase suele ser un sustantivo o una frase que explica qué es la clase:


 List, PersonReader 

El nombre del método suele ser una acción de verbo o frase que explica lo que hace el método:


 close, readPersons 

El nombre también debe indicar si el método cambia el objeto o devuelve uno nuevo. Por ejemplo, sort es un tipo que cambia la colección, y sorted es el retorno de una nueva copia ordenada de la colección.


Los nombres deben indicar claramente el propósito de la entidad, por lo que es mejor evitar el uso de palabras sin sentido ( Manager , Wrapper , etc.) en los nombres.


Cuando use el acrónimo como parte del nombre del anuncio, use letras mayúsculas si consta de dos letras ( IOStream ); o solo en mayúscula la primera letra, si es más larga ( XmlFormatter , HttpInputStream ).


Formateo


En la mayoría de los casos, Kotlin sigue las convenciones de formato de Java.


Use 4 espacios para sangrar. No use pestañas.


Para llaves, coloque la llave de apertura al final de la línea donde comienza la estructura y la llave de cierre en una línea separada, alineada horizontalmente con la estructura de apertura.


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

(Nota: en Kotlin, un punto y coma es opcional, por lo que el ajuste de línea es importante. El diseño del lenguaje asume llaves rizadas estilo Java, y puede encontrar un comportamiento inesperado de ejecución de código si intenta usar un estilo de formato diferente).


Espacios horizontales


Coloque espacios alrededor de operadores binarios (a + b) . Excepción: no coloque espacios alrededor del operador "rango a" (0..i)


No coloque espacios alrededor de operadores unarios (a++)


Coloque espacios entre las palabras clave de control ( if , when , for y while ) y los corchetes de apertura correspondientes.


No ponga un espacio antes del paréntesis de apertura en la declaración primaria de un constructor, método o método.


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

Nunca ponga un espacio después de ( , [ o antes ] , ) .


Nunca coloque espacio alrededor de un punto . u operador ?. :


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

Pon un espacio después de la doble barra para el comentario // :


 //   

No ponga espacios alrededor de los corchetes angulares utilizados para indicar los parámetros de tipo:


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

No ponga espacios alrededor de los dos puntos dobles para indicar una referencia al método :: class:


 Foo::class String::length 

No poner un espacio antes ? usado para marcar un null :


 String? 

En general, evite cualquier tipo de alineación horizontal. Cambiar el nombre de un identificador a un nombre de una longitud diferente no debería afectar el formato del código.


Colon


Pon un espacio antes del colon : en los siguientes casos:


  • cuando se usa para separar el tipo del supertipo;

 abstract class Foo<out T : Any> 

  • al delegar a un constructor de superclase u otro constructor de la misma clase;

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

  • después del objeto de palabra clave.

 val x = object : IFoo { ... } 

No ponga un espacio antes : cuando separa un anuncio y su tipo.


 abstract fun foo(a: Int): T 

Siempre ponga un espacio después de :


 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 { ... } } 

Formato de declaración de clase


Las clases con varios parámetros básicos de constructor y nombres cortos se pueden escribir en una línea:


 class Person(id: Int, name: String) 

Las clases con nombres más largos o el número de parámetros deben formatearse de modo que cada parámetro principal del constructor esté en una línea separada con sangría. Además, el soporte de cierre debe estar en una nueva línea. Si usamos la herencia, la llamada al constructor de la superclase o la lista de interfaces implementadas debe ubicarse en la misma línea que el paréntesis:


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

Al especificar la interfaz y llamar al constructor de la superclase, primero debe ubicarse el constructor de la superclase, luego el nombre de la interfaz en una nueva línea se justifica a la izquierda:


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

Para las clases con una larga lista de supertipos, debe poner un salto de línea después de los dos puntos y alinear todos los nombres de supertipos horizontalmente a la izquierda:


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

Para separar claramente el encabezado de clase y su cuerpo cuando el encabezado de clase es largo, coloque una línea vacía después del encabezado de clase (como en el ejemplo anterior) o coloque la llave de apertura en una línea separada:


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

Use sangría regular (4 espacios) para los parámetros del constructor.


Justificación: esto asegura que las propiedades declaradas en el constructor principal tengan la misma sangría que las propiedades declaradas en el cuerpo de la clase.


Modificadores


Si un anuncio contiene múltiples modificadores, organícelos siempre en el siguiente orden:


 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 

Ponga todas las anotaciones antes de los modificadores:


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

Si no está trabajando en una biblioteca, omita los modificadores redundantes (por ejemplo, público).


Formato de anotaciones


Las anotaciones generalmente se colocan en líneas separadas antes de la declaración a la que están adjuntas, y con el mismo guión:


 @Target(AnnotationTarget.PROPERTY) annotation class JsonExclude 

Las anotaciones sin argumentos se pueden ubicar en una línea:


 @JsonExclude @JvmField var x: String 

Se puede colocar una anotación sin argumentos en la misma línea que la declaración correspondiente:


 @Test fun foo() { ... } 

Anotaciones de archivo


Las anotaciones a los archivos se colocan después del comentario en el archivo (si corresponde), antes de la declaración del paquete y están separadas del paquete por una línea vacía (para enfatizar el hecho de que están dirigidas al archivo, no al paquete).


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

Formateo de funciones


Si la firma del método no cabe en una línea, use la siguiente sintaxis:


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

Use sangría regular (4 espacios) para los parámetros de la función.


Justificación: coherencia con los parámetros del constructor.

Es preferible usar una expresión sin llaves para funciones que consisten en una línea.


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

Formato de expresión de una línea


Si el cuerpo de una función de una sola línea no cabe en la misma línea que la declaración, coloque el signo = en la primera línea. Sangra el cuerpo de la expresión por 4 espacios.


 fun f(x: String) = x.length 

Formato de propiedad


Para propiedades simples de solo lectura, es preferible utilizar el formato de una sola línea:


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

Para propiedades más complejas, use siempre get y set en líneas separadas:


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

Para las propiedades con inicialización, si el inicializador es demasiado largo, agregue un salto de línea después del signo igual y una sangría de cuatro espacios para la cadena de inicialización:


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

Instrucciones de control de formato


Si la condición en la instrucción de control if o when es de varias líneas, use siempre llaves alrededor del cuerpo de la instrucción. Sangra cada línea subsiguiente de la condición por 4 espacios con relación al inicio de la declaración. Coloque los corchetes de cierre de la condición junto con la llave de apertura en una línea separada:


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

Justificación: alineación ordenada y separación clara de la condición corporal y la condición corporal

Coloque las palabras clave else , catch , finally , así como la palabra clave while del bucle do / while en la misma línea que el corchete de cierre anterior:


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

Si las condiciones de las instrucciones consisten en varios bloques, se recomienda separarlas entre sí con una línea vacía:


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

Coloque los bloques cortos de las declaraciones when en la misma línea sin llaves.


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

Método de formateo de llamadas


Cuando use una larga lista de parámetros, coloque el salto de línea después del paréntesis. Sangra 4 espacios y agrupa los argumentos relacionados lógicamente en una línea.


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

Use espacios alrededor del signo igual entre el nombre del parámetro y su valor.


Formatear una llamada de función en cadena


Cuando use llamadas encadenadas, ponga . o operadores en una nueva línea con una sangría en 4 espacios:


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

La primera llamada en la cadena generalmente debería tener un salto de línea en frente, pero es normal no hacerlo si el código se lee mejor y tiene sentido.


Formatear expresiones lambda


En las expresiones lambda, se deben usar espacios alrededor de llaves y alrededor de la flecha que separa los parámetros del cuerpo. Si una llamada acepta un solo carácter lambda, debe usarse fuera del paréntesis siempre que sea posible.


 list.filter { it > 10 } 

Al asignar una etiqueta a una expresión lambda, no coloque un espacio entre la etiqueta y la llave de apertura:


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

Al declarar nombres de parámetros en una expresión lambda de varias líneas, coloque los nombres en la primera línea, luego en la flecha y en la nueva línea al comienzo del cuerpo de la función:


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

Si la lista de parámetros no cabe en una línea, coloque la flecha en una línea separada:


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

Papeleo


Cuando use documentación de varias líneas, coloque /** en una línea separada y comience cada línea subsiguiente con un asterisco:


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

Se puede colocar una breve documentación en una línea:


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

En general, evite usar las etiquetas param y return . En su lugar, incluya una descripción de los parámetros y valores de retorno directamente en el comentario de la documentación y agregue referencias de parámetros donde sea que se mencionen. Use param y return solo cuando se requiera una descripción larga que no se ajuste al significado del texto 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) = ... 

Evitar construcciones innecesarias


Muchas construcciones sintácticas en Kotlin son opcionales y el entorno de desarrollo las resalta como innecesarias; no debe usarlas en su código solo para que su código sea "claro".


Use la palabra clave Unidad


En funciones, el uso de la palabra clave Unit no debe usarse:


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

Punto y coma


Evite usar un punto y coma en cada oportunidad.


Patrones de cuerda


No use llaves para insertar una variable simple en una cadena de plantilla. Use llaves entre comillas solo para expresiones largas.


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

Uso idiomático de las características del lenguaje.


Inmutabilidad


Es preferible utilizar datos inmutables antes que datos mutables. Siempre declare las variables y propiedades locales como val , no var , a menos que realmente cambien.


Utilice siempre interfaces de colección inmutables ( Collection , List , Set , Map ) para declarar colecciones que no cambian. En cada oportunidad, cuando use métodos de fábrica para crear una colección, use una implementación que devuelva colecciones inmutables:


 // 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/es431990/


All Articles