Insights | Engenharia e Arquitetura de Software
Padrões idiomáticos em Kotlin que todo dev sênior deveria dominar
A diferença entre um dev comum e um dev sênior aparece no jeito de escrever código: menos ruído, mais intenção, mais poder expressivo.
Os padrões idiomáticos abaixo mostram como substituir verbosidade por clareza. Em cada construção, você verá o antes e o depois, evidenciando como pequenas decisões elevam a qualidade e a legibilidade do código.
1. let
Antes
1
2
3
4
5
val email = if (user.email != null) {
user.email.lowercase()
} else {
null
}
Depois
1
2
val email = user.email?.let { it.lowercase() }
Código focado no que importa.
2. apply
Antes
1
2
3
4
val client = OkHttpClient()
client.followRedirects(true)
client.retryOnConnectionFailure(true)
Depois
1
2
3
4
5
val client = OkHttpClient().apply {
followRedirects(true)
retryOnConnectionFailure(true)
}
Configuração fluida e direta.
3. also
Antes
1
2
3
val user = createUser()
audit.log("created ${user.id}")
Depois
1
2
3
4
val user = createUser().also {
audit.log("created ${it.id}")
}
Efeito colateral no lugar certo.
4. run
Antes
1
2
3
val prefix = "Bearer"
val token = prefix + " " + generateToken()
Depois
1
2
3
4
5
val token = run {
val prefix = "Bearer"
"$prefix ${generateToken()}"
}
Melhor encapsulamento, menos poluição no escopo.
5. with
Antes
1
2
3
4
println(response.code)
println(response.headers)
println(response.body)
Depois
1
2
3
4
5
6
with(response) {
println(code)
println(headers)
println(body)
}
Menos repetição, mais clareza.
6. Coleções: filter, map, sorted
Antes
1
2
3
4
5
6
7
8
val names = mutableListOf<String>()
for (user in users) {
if (user.active) {
names.add(user.name)
}
}
names.sort()
Depois
1
2
3
4
5
val names = users
.filter { it.active }
.map { it.name }
.sorted()
Imutabilidade e expressividade.
7. takeIf
Antes
1
2
val adult = if (user.age >= 18) user else null
Depois
1
2
val adult = user.takeIf { it.age >= 18 }
Concisão sem perder intenção.
8. when + sealed classes
Antes
1
2
3
4
5
6
if (event is Login) {
handleLogin(event)
} else if (event is Logout) {
handleLogout(event)
}
Depois
1
2
3
4
5
when (event) {
is Login -> handleLogin(event)
is Logout -> handleLogout(event)
}
Tipado, seguro e explícito.
9. Extension Functions
Construindo APIs internas elegantes
Antes
1
2
val slug = StringUtils.toSlug(title)
Depois
1
2
val slug = title.toSlug()
O domínio ganha uma linguagem própria.
10. copy() para imutabilidade
Antes
1
2
user.name = "Danilo"
Depois
1
2
val updated = user.copy(name = "Danilo")
Imutabilidade clara e previsível.
11. Destructuring
Antes
1
2
3
val lat = location.lat
val lng = location.lng
Depois
1
2
val (lat, lng) = location
Redução de ruído direto ao ponto.
12. runCatching + Result
Antes
1
2
3
4
5
6
7
try {
val data = repository.load()
process(data)
} catch (e: Exception) {
handleError(e)
}
Depois
1
2
3
4
repository.load()
.onSuccess { process(it) }
.onFailure { handleError(it) }
Fluxo consistente e centralizado.
13. require e check
Antes
1
2
3
4
if (age < 18) {
throw IllegalArgumentException("Must be adult")
}
Depois
1
2
require(age >= 18) { "Must be adult" }
Contratos explícitos e mais legíveis.
14. Coroutines idiomáticas
Antes
1
2
3
4
val user = loadUser()
val order = loadOrders()
val result = user to order
Depois
1
2
3
4
5
6
val result = coroutineScope {
val user = async { loadUser() }
val orders = async { loadOrders() }
user.await() to orders.await()
}
Paralelismo limpo e seguro.
15. Extension Function prática: toUUID()
Extensões são um Kotlin partner de altíssimo impacto. Elas criam uma mini linguagem alinhada ao domínio, eliminam helpers estáticos e deixam chamadas mais naturais.
Antes (util estático)
1
2
val uuid = StringUtils.toUUID(user.id)
Depois (idiomático e elegante)
Primeiro criamos a extensão:
1
2
3
4
5
fun String.toUUID(): UUID {
val normalized = trim()
return UUID.nameUUIDFromBytes(normalized.toByteArray())
}
Uso:
1
2
val uuid = user.id.toUUID()
Outros exemplos seguindo a mesma ideia:
1
2
3
4
5
6
7
8
9
10
fun String.toSlug() = lowercase().replace(" ", "-")
fun Long.toHumanId() = "ID-$this"
fun String.maskEmail(): String {
val parts = split("@")
val username = parts[0].take(3) + "***"
return "$username@${parts[1]}"
}
Cria-se uma API interna rica, expressiva e coesa.
Código limpo não nasce por acaso.
Ele é resultado de decisões pequenas, consistentes e inteligentes.
Padrões idiomáticos fazem parte dessas decisões.