Guia de estilo do Kotlin

Este documento serve como a definição completa dos padrões de codificação do Google Android para o código-fonte na linguagem de programação Kotlin. Um arquivo de origem Kotlin é descrito como estando no Estilo do Google Android somente se ele segue as regras contidas aqui.

Assim como outros guias de estilo de programação, os problemas abordados incluem não apenas questões estéticas de formatação, mas também outros tipos de convenções ou padrões de codificação. No entanto, este documento se concentra principalmente nas regras rígidas que seguimos universalmente e evita dar conselhos que não sejam claramente aplicáveis (seja por humanos ou ferramentas).

Última atualização: 06/09/2023

Arquivos de origem

Todos os arquivos de origem devem ser codificados como UTF-8.

Nomeação

Se um arquivo de origem contiver somente uma única classe de nível superior, o nome do arquivo precisará refletir o nome que diferencia maiúsculas de minúsculas e a extensão .kt. Caso contrário, se um arquivo de origem tiver várias declarações de nível superior, escolha um nome que descreva o conteúdo do arquivo, aplique o PascalCase (o camelCase será aceitável se o nome do arquivo estiver no plural) e anexe a extensão .kt.

// MyClass.kt
class MyClass { }
// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …
// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …
// extensions.kt
fun MyClass.process() = // …
fun MyResult.print() = // …

Caracteres especiais

Caracteres de espaço em branco

Além da sequência de terminador de linha, o caractere de espaço horizontal ASCII (0x20) é o único caractere de espaço em branco que aparece em qualquer lugar em um arquivo de origem. Isso significa que:

  • todos os outros caracteres de espaços em branco em literais de string e de caracteres têm escape;
  • os caracteres de tabulação não são usados para recuo.

Sequências especiais de escape

Para qualquer caractere que tenha uma sequência de escape especial (\b, \n, \r, \t, \', \", \\ e \$), essa sequência é usada em vez do escape Unicode correspondente (por exemplo, \u000a).

Caracteres não ASCII

Para os caracteres não ASCII restantes, será usado o caractere Unicode real (por exemplo, ) ou o escape Unicode equivalente (por exemplo, \u221e). Será escolhido aquele que torna o código mais fácil de ler e entender. Escapes Unicode não são recomendados para caracteres de impressão em nenhum local e fora de literais de string e comentários.

Exemplo Discussão
val unitAbbrev = "μs" Melhor: perfeitamente claro, mesmo sem um comentário.
val unitAbbrev = "\u03bcs" // μs Ruim: não há motivo para usar um escape com um caractere de impressão.
val unitAbbrev = "\u03bcs" Ruim: o leitor não tem ideia do que seja isso.
return "\ufeff" + content Bom: use escapes para caracteres não imprimíveis e comente, se necessário.

Estrutura

Um arquivo .kt compreende o seguinte, em ordem:

  • Cabeçalho de direitos autorais e/ou licença (opcional)
  • Anotações no nível do arquivo
  • Declaração do pacote
  • Declarações de importação
  • Declarações de nível superior

Exatamente uma linha em branco separa cada uma dessas seções.

Se um cabeçalho de direitos autorais ou licença pertencer ao arquivo, ele precisará ser colocado na parte superior imediata de um comentário de várias linhas.

/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
 

Não use um estilo KDoc ou um comentário em estilo de linha única.

/**
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
// Copyright 2017 Google, Inc.
//
// ...

Anotações no nível do arquivo

Anotações com o "arquivo" de destino de uso do site são colocadas entre qualquer comentário de cabeçalho e a declaração do pacote.

Declaração do pacote

A declaração do pacote não está sujeita a nenhum limite de colunas e nunca é preenchida com linhas.

Declarações de importação

As declarações de importação para classes, funções e propriedades são agrupadas em uma única lista e são classificadas como ASCII.

Importações de caracteres curinga (de qualquer tipo) não são permitidas.

Assim como a declaração do pacote, as declarações de importação não estão sujeitas a um limite de colunas e nunca são preenchidas com linhas.

Declarações de nível superior

Um arquivo .kt pode declarar um ou mais tipos, funções, propriedades ou aliases de tipo no nível superior.

O conteúdo de um arquivo precisa ser focado em um único tema. Exemplos disso seriam um único tipo público ou um conjunto de funções de extensão realizando a mesma operação em vários tipos de receptor. Declarações não relacionadas precisam ser separadas nos próprios arquivos, e declarações públicas em um único arquivo precisam ser minimizadas.

Nenhuma restrição explícita é imposta em relação ao número ou à ordem do conteúdo de um arquivo.

Os arquivos de origem geralmente são lidos de cima para baixo, o que significa que a ordem, em geral, precisa refletir que as declarações mais acima informarão a compreensão das que estão mais abaixo. Arquivos diferentes podem optar por ordenar os conteúdos de forma diferente. Da mesma forma, um arquivo pode conter 100 propriedades, outras 10 funções e ainda uma única classe.

O importante é que cada classe use alguma ordem lógica, que o mantenedor poderá explicar, se solicitado. Por exemplo, novas funções não são adicionadas habitualmente ao fim da classe, já que resultariam em ordenação "cronológica por data de adição", o que não é uma ordem lógica.

Ordenação de membros da classe

A ordem dos membros em uma classe segue as mesmas regras das declarações de nível superior.

Formatação

Chaves

As chaves não são obrigatórias para ramificações when e expressões if que não têm mais de uma ramificação else e cabem em uma única linha.

if (string.isEmpty()) return

val result =
    if (string.isEmpty()) DEFAULT_VALUE else string

when (value) {
    0 -> return
    // …
}

As chaves são obrigatórias para todas as declarações e expressões if, for, ramificação when, do e while, mesmo quando o corpo está vazio ou contém apenas uma declaração.

if (string.isEmpty())
    return  // WRONG!

if (string.isEmpty()) {
    return  // Okay
}

if (string.isEmpty()) return  // WRONG
else doLotsOfProcessingOn(string, otherParametersHere)

if (string.isEmpty()) {
    return  // Okay
} else {
    doLotsOfProcessingOn(string, otherParametersHere)
}

Blocos não vazios

As chaves seguem o estilo de Kernighan e Ritchie (colchetes egípcios) para blocos não vazios e construções semelhantes a blocos:

  • Nenhuma quebra de linha antes da chave de abertura.
  • Quebra de linha após a chave de abertura.
  • Quebra de linha antes da chave de fechamento.
  • Quebra de linha após a chave de fechamento, somente se essa chave terminar uma declaração ou encerrar o corpo de uma função, construtor ou classe nomeada. Por exemplo, não haverá nenhuma quebra de linha depois da chave, se ela for seguida por else ou por uma vírgula.
return Runnable {
    while (condition()) {
        foo()
    }
}

return object : MyClass() {
    override fun foo() {
        if (condition()) {
            try {
                something()
            } catch (e: ProblemException) {
                recover()
            }
        } else if (otherCondition()) {
            somethingElse()
        } else {
            lastThing()
        }
    }
}

Veja abaixo algumas exceções para classes de enumeração.

Blocos vazios

Um bloco vazio ou uma construção parecida com um bloco precisa estar no estilo K&R.

try {
    doSomething()
} catch (e: Exception) {} // WRONG!
try {
    doSomething()
} catch (e: Exception) {
} // Okay

Expressões

Uma condicional if/else que é usada como uma expressão poderá omitir chaves somente se toda a expressão couber em uma linha.

val value = if (string.isEmpty()) 0 else 1  // Okay
val value = if (string.isEmpty())  // WRONG!
    0
else
    1
val value = if (string.isEmpty()) { // Okay
    0
} else {
    1
}

Recuo

Sempre que um novo bloco ou construção semelhante a um bloco é aberto, o recuo aumenta em quatro espaços. Quando o bloco termina, o recuo retorna ao nível de recuo anterior. O nível do recuo se aplica ao código e aos comentários em todo o bloco.

Uma declaração por linha

Cada declaração é seguida por uma quebra de linha. O ponto e vírgula (;) não é usado.

Quebra de linha

O código tem um limite de colunas de 100 caracteres. Exceto conforme indicado abaixo, qualquer linha que exceda a esse limite precisa ter quebra de linha, conforme explicado abaixo.

Exceções:

  • Quando não é possível usar linhas que obedeçam ao limite de colunas (por exemplo, um URL longo no KDoc).
  • Em declarações package e import.
  • Em linhas de comando em um comentário que possa ser recortado e colado em um shell.

Onde quebrar

A principal diretiva de quebra de linha é: prefira quebrar em um nível sintático superior. Além disso:

  • Quando uma linha é quebrada em um nome de operador ou de função de infixo, a quebra aparece depois do nome do operador ou da função de infixo.
  • Quando uma linha é quebrada nos seguintes símbolos "semelhantes a operadores", a quebra aparece antes do símbolo:
    • O separador de pontos (., ?.).
    • Os dois-pontos duplos de uma referência de membro (::).
  • Um nome de método ou construtor permanece anexado ao parêntese de abertura (() que o segue.
  • Uma vírgula (,) permanece anexada ao token que a precede.
  • Uma seta lambda (->) permanece anexada à lista de argumentos que a precede.

Funções

Quando uma assinatura de função não se encaixar em uma única linha, divida cada declaração de parâmetro na própria linha. Os parâmetros definidos nesse formato precisam usar um único recuo (+4). O parêntese de fechamento ()) e o tipo de retorno são colocados na própria linha sem recuo adicional.

fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = ""
): String {
    // …
}
Funções de expressão

Quando uma função contém apenas uma única expressão, ela pode ser representada como uma função de expressão (link em inglês).

override fun toString(): String {
    return "Hey"
}
override fun toString(): String = "Hey"

Propriedades

Quando um inicializador de propriedade não se encaixar em uma única linha, divida após o sinal de igual (=) e use um recuo.

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

As propriedades que declaram uma função get e/ou set precisam ser colocadas cada uma na própria linha com um recuo normal (+4). Formate-as usando as mesmas regras das funções.

var directory: File? = null
    set(value) {
        // …
    }
As propriedades somente leitura podem usar uma sintaxe mais curta que caiba em uma única linha.
val defaultExtension: String get() = "kt"

Espaço em branco

Vertical

Uma única linha em branco é exibida:

  • Entre membros consecutivos de uma classe: propriedades, construtores, funções, classes aninhadas etc.
    • Exceção: uma linha em branco entre duas propriedades consecutivas (não tendo outro código entre elas) é opcional. Essas linhas em branco são usadas conforme necessário para criar agrupamentos lógicos de propriedades e associar propriedades com a propriedade de backup, se presente.
    • Exceção: linhas em branco entre constantes de enumeração são abordadas abaixo.
  • Entre declarações, conforme necessário, para organizar o código em subseções lógicas.
  • Opcionalmente antes da primeira declaração em uma função, antes do primeiro membro de uma classe ou depois do último membro de uma classe (não incentivado nem desencorajado).
  • Conforme exigido por outras seções deste documento (como a seção Estrutura).

Várias linhas em branco consecutivas são permitidas, mas não são recomendadas nem necessárias.

Horizontal

Além de onde exigido pela linguagem ou outras regras de estilo, além de literais, comentários e KDoc, um único espaço ASCII também aparece somente nos seguintes locais:

  • Separando qualquer palavra reservada, como if, for ou catch, de um parêntese de abertura (() que a segue nessa linha.
    // WRONG!
    for(i in 0..1) {
    }
    
    // Okay
    for (i in 0..1) {
    }
    
  • Separação de qualquer palavra reservada, como else ou catch, de uma chave de fechamento (}) que a precede nessa linha.
    // WRONG!
    }else {
    }
    
    // Okay
    } else {
    }
    
  • Antes de qualquer chave de abertura ({).
    // WRONG!
    if (list.isEmpty()){
    }
    
    // Okay
    if (list.isEmpty()) {
    }
    
  • Nos dois lados de qualquer operador binário.
    // WRONG!
    val two = 1+1
    
    // Okay
    val two = 1 + 1
    
    Isso também se aplica aos seguintes símbolos "semelhantes a operadores":
    • Na seta em uma expressão lambda (->).
      // WRONG!
      ints.map { value->value.toString() }
      
      // Okay
      ints.map { value -> value.toString() }
      
    Mas não:
    • Os dois pontos (::) de uma referência de membro.
      // WRONG!
      val toString = Any :: toString
      
      // Okay
      val toString = Any::toString
      
    • no separador de pontos (.).
      // WRONG
      it . toString()
      
      // Okay
      it.toString()
      
    • o operador de intervalo (..).
      // WRONG
      for (i in 1 .. 4) {
        print(i)
      }
      
      // Okay
      for (i in 1..4) {
        print(i)
      }
      
  • Antes de dois-pontos (:), somente se usado em uma declaração de classe para especificar uma classe de base ou interfaces, ou quando usado em uma cláusula where para restrições genéricas (link em inglês).
    // WRONG!
    class Foo: Runnable
    
    // Okay
    class Foo : Runnable
    
    // WRONG
    fun <T: Comparable> max(a: T, b: T)
    
    // Okay
    fun <T : Comparable> max(a: T, b: T)
    
    // WRONG
    fun <T> max(a: T, b: T) where T: Comparable<T>
    
    // Okay
    fun <T> max(a: T, b: T) where T : Comparable<T>
    
  • Depois de uma vírgula (,) ou dois-pontos (:).
    // WRONG!
    val oneAndTwo = listOf(1,2)
    
    // Okay
    val oneAndTwo = listOf(1, 2)
    
    // WRONG!
    class Foo :Runnable
    
    // Okay
    class Foo : Runnable
    
  • Em ambos os lados da barra dupla (//) que começa um comentário de fim de linha. Aqui, vários espaços são permitidos, mas não obrigatórios.
    // WRONG!
    var debugging = false//disabled by default
    
    // Okay
    var debugging = false // disabled by default
    

Essa regra nunca é interpretada como exigência ou proibição de espaço adicional no início ou no final de uma linha; ela aborda apenas o espaço interior.

Construções específicas

Classes de enumeração

Uma enumeração sem funções e sem documentação sobre as constantes pode, opcionalmente, ser formatada como uma única linha.

enum class Answer { YES, NO, MAYBE }

Quando as constantes em uma enumeração são colocadas em linhas separadas, uma linha em branco não é obrigatória entre elas, exceto no caso em que elas definem um corpo.

enum class Answer {
    YES,
    NO,

    MAYBE {
        override fun toString() = """¯\_(ツ)_/¯"""
    }
}

Como as classes de enumeração são classes, todas as outras regras para classes de formatação se aplicam.

Anotações

Anotações de membros ou tipos são colocadas em linhas separadas imediatamente antes da construção anotada.

@Retention(SOURCE)
@Target(FUNCTION, PROPERTY_SETTER, FIELD)
annotation class Global

Anotações sem argumentos podem ser colocadas em uma única linha.

@JvmField @Volatile
var disposable: Disposable? = null

Quando uma única anotação sem argumentos estiver presente, ela poderá ser colocada na mesma linha que a declaração.

@Volatile var disposable: Disposable? = null

@Test fun selectAll() {
    // …
}

A sintaxe @[...] só pode ser usada com um destino de site de uso explícito e somente para combinar duas ou mais anotações sem argumentos em uma única linha.

@field:[JvmStatic Volatile]
var disposable: Disposable? = null

Tipos implícitos de retorno/propriedade

Se um corpo de função de expressão ou um inicializador de propriedade for um valor escalar ou o tipo de retorno puder ser claramente inferido a partir do corpo, ele poderá ser omitido.

override fun toString(): String = "Hey"
// becomes
override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")
// becomes
private val ICON = IconLoader.getIcon("/icons/kotlin.png")

Ao gravar uma biblioteca, retenha a declaração de tipo explícito quando ela fizer parte da API public.

Nomeação

Os identificadores usam apenas letras e dígitos ASCII e, em um pequeno número de casos indicados abaixo, são sublinhados. Assim, cada nome de identificador válido é correspondido pela expressão regular \w+.

Prefixos ou sufixos especiais, como os exibidos nos exemplos name_, mName, s_name e kName, não são usados, exceto no caso de propriedades de backup (consulte Propriedades de backup).

Nomes de pacote

Nomes de pacotes são todos minúsculos, com palavras consecutivas simplesmente concatenadas juntas (sem sublinhados).

// Okay
package com.example.deepspace
// WRONG!
package com.example.deepSpace
// WRONG!
package com.example.deep_space

Nomes de tipo

Nomes de classes são escritos em PascalCase e normalmente são substantivos ou sintagmas nominais. Por exemplo, Character ou ImmutableList. Nomes de interface também podem ser substantivos ou sintagmas nominais (por exemplo, List), mas às vezes podem ser adjetivos ou frases adjetivas (por exemplo, Readable).

As classes de teste são nomeadas começando com o nome da classe que estão testando e terminando com Test. Por exemplo, HashTest ou HashIntegrationTest.

Nomes de função

Nomes das funções são escritos em camelCase e normalmente são verbos ou frases com verbo. Por exemplo, sendMessage ou stop.

Os sublinhados podem ser usados em nomes de funções de teste para separar componentes lógicos do nome.

@Test fun pop_emptyStack() {
    // …
}

As funções anotadas com @Composable que retornam Unit são escritas em PascalCase e nomeadas como substantivos, como se fossem tipos.

@Composable
fun NameTag(name: String) {
    // …
}

Os nomes de função não podem conter espaços porque isso não é compatível com todas as plataformas (em especial, não é totalmente compatível com o Android).

// WRONG!
fun `test every possible case`() {}
// OK
fun testEveryPossibleCase() {}

Nomes de constante

Nomes de constante usam UPPER_SNAKE_CASE: todas as letras maiúsculas, com palavras separadas por sublinhados. Mas o que é uma constante, exatamente?

Constantes são propriedades val sem nenhuma função get personalizada, cujos conteúdos são imutáveis e cujas funções não têm efeitos colaterais detectáveis. Isso inclui tipos imutáveis e coleções imutáveis de tipos imutáveis, bem como escalares e strings, se marcados como const. Se algum estado observável da instância puder ser modificado, ele não será uma constante. Apenas tentar não mudar o objeto não é suficiente.

const val NUMBER = 5
val NAMES = listOf("Alice", "Bob")
val AGES = mapOf("Alice" to 35, "Bob" to 32)
val COMMA_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

Esses nomes normalmente são substantivos ou sintagmas nominais.

Os valores constantes só podem ser definidos dentro de object ou como uma declaração de nível superior. Valores que atendam ao requisito de uma constante, mas são definidos dentro de uma class precisam usar um nome não constante.

Constantes que são valores escalares precisam usar o modificador const (link em inglês).

Nomes não constantes

Nomes não constantes são escritos em camelCase. Eles se aplicam a propriedades de instâncias, propriedades locais e nomes de parâmetros.

val variable = "var"
val nonConstScalar = "non-const"
val mutableCollection: MutableSet = HashSet()
val mutableElements = listOf(mutableInstance)
val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2)
val logger = Logger.getLogger(MyClass::class.java.name)
val nonEmptyArray = arrayOf("these", "can", "change")

Esses nomes normalmente são substantivos ou sintagmas nominais.

Propriedades de backup

Quando uma propriedade de backup é necessária, o nome tem que corresponder exatamente ao da propriedade real, exceto o prefixo com um sublinhado.

private var _table: Map? = null

val table: Map
    get() {
        if (_table == null) {
            _table = HashMap()
        }
        return _table ?: throw AssertionError()
    }

Nomes de variáveis de tipo

Cada variável de tipo é nomeada em um de dois estilos:

  • Uma única letra maiúscula, opcionalmente seguida por um único número (como E, T, X, T2)
  • Um nome no formulário usado para as classes, seguido pela letra maiúscula T (como RequestT, FooBarT)

CamelCase

Às vezes, há mais de uma maneira razoável de converter uma frase em inglês em letras concatenadas, como quando acrônimos ou construções incomuns como "IPv6" ou "iOS" estão presentes. Para melhorar a previsibilidade, use o seguinte esquema.

Começando com a forma em prosa do nome:

  1. Converta a frase em ASCII simples e remova as apóstrofes. Por exemplo, o "Müller’s algorithm" pode se tornar o "Muellers algorithm".
  2. Divida esse resultado em palavras, dividindo em espaços e qualquer pontuação restante (normalmente hifens). Recomendado: se alguma palavra já tiver uma aparência de concatenação convencional de uso comum, divida-a nas partes constituintes (por exemplo, "AdWords" se torna "ad words"). Uma palavra como "iOS" não é realmente concatenada; ela desafia qualquer convenção, então essa recomendação não se aplica.
  3. Agora coloque tudo em letras minúsculas (incluindo siglas) e siga um destes procedimentos:
    • Coloque em maiúsculas o primeiro caractere de cada palavra para alternar entre maiúsculas e minúsculas.
    • Coloque em maiúscula o primeiro caractere de cada palavra, exceto a primeira, para produzir a concatenação.
  4. Por fim, junte todas as palavras em um único identificador.

A capitalização das palavras originais é praticamente ignorada.

Forma de prosa Correta Incorreta
"XML Http Request" XmlHttpRequest XMLHTTPRequest
"new customer ID" newCustomerId newCustomerID
"inner stopwatch" innerStopwatch innerStopWatch
"supports IPv6 on iOS" supportsIpv6OnIos supportsIPv6OnIOS
"YouTube importer" YouTubeImporter YoutubeImporter*

* Aceitável, mas não recomendado.

Documentação

Formatação

A formatação básica de blocos KDoc é vista neste exemplo:

/**
 * Multiple lines of KDoc text are written here,
 * wrapped normally…
 */
fun method(arg: String) {
    // …
}

...ou neste exemplo de linha única:

/** An especially short bit of KDoc. */

A forma básica é sempre aceitável. A forma de uma única linha pode ser substituída quando a totalidade do bloco KDoc (incluindo marcadores de comentário) couber em uma única linha. Isso só se aplica quando não há tags de bloco, como @return.

Parágrafos

Uma linha em branco, ou seja, uma linha contendo apenas o asterisco inicial alinhado (*), aparece entre os parágrafos e antes do grupo de tags de bloco, se houver.

Tags de bloco

Qualquer uma das "tags de bloco" padrão que são usadas aparece na ordem @constructor, @receiver, @param, @property, @return, @throws, @see e elas nunca aparecem com uma descrição vazia. Quando uma tag de bloco não se encaixa em uma única linha, as linhas de continuação são recuadas 4 espaços a partir da posição da @.

Fragmento de resumo

Cada bloco KDoc começa com um breve fragmento de resumo. Esse fragmento é muito importante: é a única parte do texto que aparece em determinados contextos, como índices de classe e método.

Isso é um fragmento: uma frase nominal ou uma frase verbal, não uma frase completa. Não começa com "A `Foo` is a..." ou "This method returns...", nem tem que formar uma frase imperativa completa como "Save the record.". No entanto, o fragmento é capitalizado e pontuado como se fosse uma frase completa.

Uso

No mínimo, o KDoc está presente para cada tipo public e todo membro public ou protected desse tipo, com algumas exceções indicadas abaixo.

Exceção: funções autoexplicativas

O KDoc é opcional para funções “simples e óbvias” como getFoo e propriedades como foo, casos em que realmente não vale a pena dizer nada.

Não é apropriado citar essa exceção para justificar a omissão de informações relevantes que um leitor comum pode precisar. Por exemplo, para uma função denominada getCanonicalName ou propriedade denominada canonicalName, não omita a documentação (com a justificativa de que diria apenas /** Returns the canonical name. */) se um leitor comum não tiver ideia do que o termo "nome canônico" significa.

Exceção: substituições

O KDoc nem sempre está presente em um método que substitui um método de supertipo.