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.
Direitos autorais/licença
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
eimport
. - 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 (
::
).
- O separador de pontos (
- 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 tipo enumerado 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
oucatch
em um parêntese aberto ((
) que a segue nessa linha.// WRONG! for(i in 0..1) { }
// Okay for (i in 0..1) { }
- Separando qualquer palavra reservada, como
else
oucatch
, de um chave de fechamento (}
) que precede essa linha.// WRONG! }else { }
// Okay } else { }
-
Antes de qualquer chave aberta (
{
).// 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":- a seta em uma expressão lambda (
->
).// WRONG! ints.map { value->value.toString() }
// Okay ints.map { value -> value.toString() }
-
os dois dois pontos (
::
) de uma referência de membro.// WRONG! val toString = Any :: toString
// Okay val toString = Any::toString
-
o 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) }
- a seta em uma expressão lambda (
-
Antes de dois-pontos (
:
) somente se usado em uma declaração de classe para especificar uma classe de base ou interfaces, ou quando usada em uma cláusulawhere
para restrições genéricas.// 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>
-
Após 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 tipo enumerado 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 um tipo enumerado 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 tipo enumerado são classes, todas as outras regras para classes de formatação são válidas.
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
(comoRequestT
,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:
- Converta a frase em ASCII simples e remova as apóstrofes. Por exemplo, o "Müller’s algorithm" pode se tornar o "Muellers algorithm".
- 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.
- Agora mude tudo para letras minúsculas (incluindo acrônimos) 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.
- 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.