DEV Community

Cover image for De Kotlin e Go para Clojure: uma jornada de 8 meses no Nubank.
Fernando Rosa
Fernando Rosa

Posted on

De Kotlin e Go para Clojure: uma jornada de 8 meses no Nubank.

Olá, pessoal!

Há 8 meses, embarquei na jornada de ser um Lead Software Engineer no Nubank. Vindo de um mundo onde Kotlin e Go eram minhas principais ferramentas, mergulhar em Clojure foi uma mudança de paradigma. Hoje, quero compartilhar um pouco dessa experiência, mostrando com código as diferenças e o que torna Clojure uma linguagem tão fascinante de se trabalhar.

Vamos explorar três problemas simples, resolvidos em cada uma das três linguagens.

Problema 1: O Clássico "Olá, Mundo!"

Tudo começa aqui. Ver a sintaxe mais básica já nos dá uma pista da filosofia de cada linguagem.

Go:
Focado em simplicidade e um ferramental robusto. Tudo é explícito.

package main import "fmt" func main() { fmt.Println("Olá, Mundo!") } 
Enter fullscreen mode Exit fullscreen mode

Kotlin:
Moderno, conciso e interoperável com Java. A sintaxe é familiar para quem vem do mundo OO.

fun main() { println("Olá, Mundo!") } 
Enter fullscreen mode Exit fullscreen mode

Clojure:
Aqui a primeira "estranheza" que vira um encanto. A sintaxe LISP, com parênteses e prefixos (função argumento), trata código como dados. É simples e incrivelmente poderosa.

(println "Olá, Mundo!") 
Enter fullscreen mode Exit fullscreen mode

Análise Rápida: De cara, a concisão de Clojure se destaca. A ausência de cerimônias como declaração de package ou main para um script simples já mostra o foco em ir direto ao ponto.

Problema 2: Transformação de Dados – Agrupar e Somar Vendas

Este é um cenário do dia a dia: temos uma lista de vendas e queremos calcular o total por produto. É aqui que a abordagem funcional de Clojure realmente brilha.

Digamos que temos estes dados:
[{"produto": "A", "valor": 10}, {"produto": "B", "valor": 20}, {"produto": "A", "valor": 5}]

Go:
Em Go, faríamos isso de forma imperativa, inicializando um mapa e iterando sobre a lista para acumular os valores. É eficiente, mas verboso.

package main import "fmt" type Venda struct { Produto string Valor int } func main() { vendas := []Venda{ {"A", 10}, {"B", 20}, {"A", 5}, } totalPorProduto := make(map[string]int) for _, v := range vendas { totalPorProduto[v.Produto] += v.Valor } fmt.Println(totalPorProduto) // Output: map[A:15 B:20] } 
Enter fullscreen mode Exit fullscreen mode

Kotlin:
Kotlin oferece uma API de coleções rica e funcional, tornando o código mais expressivo e menos propenso a erros.

data class Venda(val produto: String, val valor: Int) fun main() { val vendas = listOf( Venda("A", 10), Venda("B", 20), Venda("A", 5) ) val totalPorProduto = vendas .groupBy { it.produto } .mapValues { entry -> entry.value.sumOf { it.valor } } println(totalPorProduto) // Output: {A=15, B=20} } 
Enter fullscreen mode Exit fullscreen mode

Clojure:
Em Clojure, a transformação de dados é o coração da linguagem. O código é uma composição de funções, resultando em uma "pipeline" de dados clara e elegante.

(def vendas [{:produto "A" :valor 10} {:produto "B" :valor 20} {:produto "A" :valor 5}]) (def total-por-produto (->> vendas (group-by :produto) (map (fn [[produto lista-vendas]] [produto (reduce + (map :valor lista-vendas))])) (into {}))) (println total-por-produto) ; Output: {"A" 15, "B" 20} 
Enter fullscreen mode Exit fullscreen mode

Análise Rápida: Enquanto Go é explícito e manual, Kotlin e Clojure mostram o poder das abstrações funcionais. A solução em Clojure, com o macro ->> (thread-last), descreve perfeitamente o fluxo: pegue as vendas, agrupe por :produto, depois mapeie cada grupo para calcular a soma e, por fim, transforme tudo em um mapa. É como ler uma receita.

Problema 3: Concorrência – Incrementando um Contador com Segurança

Como lidar com estado compartilhado é um desafio central em sistemas concorrentes. Cada linguagem tem sua abordagem. Vamos simular 1.000 "processos" incrementando um contador.

Go:
Goroutines e Channels são os cidadãos de primeira classe para concorrência em Go. Para estado mutável compartilhado, usamos mutex para garantir a segurança.

package main import ( "fmt" "sync" ) func main() { var contador int var wg sync.WaitGroup var mu sync.Mutex for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() mu.Lock() contador++ mu.Unlock() }() } wg.Wait() fmt.Println("Contador final:", contador) // Output: Contador final: 1000 } 
Enter fullscreen mode Exit fullscreen mode

Kotlin:
Coroutines são a resposta de Kotlin para concorrência leve. Para estado compartilhado, podemos usar tipos atômicos do Java ou um Mutex específico para coroutines.

import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock val mutex = Mutex() var contador = 0 fun main() = runBlocking { val jobs = List(1000) { launch(Dispatchers.Default) { mutex.withLock { contador++ } } } jobs.forEach { it.join() } println("Contador final: $contador") // Output: Contador final: 1000 } 
Enter fullscreen mode Exit fullscreen mode

Clojure:
Clojure abraça a imutabilidade e fornece construções simples e poderosas para gerenciar estado quando ele é inevitável. O atom é perfeito para estado compartilhado e não coordenado. A função swap! garante atualizações atômicas.

(def contador (atom 0)) (defn incrementar [] (swap! contador inc)) (let [processos (repeatedly 1000 #(future (incrementar)))] (doseq [p processos] (deref p))) ; Espera todos terminarem (println "Contador final:" @contador) ; Output: Contador final: 1000 
Enter fullscreen mode Exit fullscreen mode

Análise Rápida: As três linguagens resolvem o problema com segurança, mas a abordagem de Clojure é notavelmente mais limpa. Não há locks manuais visíveis no nosso código de negócio. A complexidade da concorrência é abstraída pelo atom e pela função swap!, tornando o código mais simples de ler e escrever.

Conclusão

Trabalhar com Go e Kotlin me deu uma base sólida em sistemas eficientes e bem tipados. Mas a imersão em Clojure no Nubank me ensinou a amar a simplicidade, a imutabilidade e o poder da programação funcional.

A capacidade de moldar o código como uma sequência de transformações de dados e de lidar com concorrência de forma tão elegante não só torna o desenvolvimento mais rápido, mas também mais prazeroso. É uma linguagem que nos convida a pensar no problema de forma diferente e, na minha opinião, de um jeito muito mais direto e poderoso.

E você, já teve uma experiência parecida ao aprender uma nova linguagem que mudou sua forma de pensar? Adoraria saber!

Top comments (1)

Collapse
 
borba profile image
André Borba

Muito bom!