Blog

  • Considerações básicas de hardware para modelos de linguagem em código aberto:  Memória, Desempenho e Viabilidade

    Considerações básicas de hardware para modelos de linguagem em código aberto: Memória, Desempenho e Viabilidade

    Modelos de linguagem em código aberto são especialmente relevantes no contexto da privacidade e da soberania digital, pois permitem execução em ambientes isolados com dados sensíveis ou sigilosos. Uma grande barreira para sua adoção em empresas de menor porte são os requisitos de hardware e, consequentemente, a capacitação de recursos humanos. Neste artigo, apresentamos alguns conceitos que podem contribuir para especificar aproximadamente qual hardware pode atender a essa necessidade, respondendo a duas perguntas: 

    1. Qual a quantidade de memória necessária para carregar o modelo?
    2. Qual parâmetro de desempenho deve ser observado para garantir que o hardware escolhido forneça respostas fluidas, semelhantes a um chat tradicional?

    Para responder à primeira pergunta, é necessário entender que a complexidade dos modelos LLMs pode, em linhas gerais, ser inferida pelo número de parâmetros. Essa informação geralmente aparece no próprio nome do modelo. Por exemplo: Llama-3B indica que se trata do modelo Llama (Meta) com 3 bilhões de parâmetros. Caso essa informação não esteja explícita, deve-se consultar a documentação do modelo. Para calcular a quantidade mínima de memória necessária, considere que cada parâmetro é armazenado em uma variável com precisão de ponto flutuante de 32 bits. Como cada byte equivale a 8 bits, uma variável de 32 bits corresponde a 4 bytes na memória. Assim:

    3.000.000.000 parâmetros x 4 bytes/parâmetro = 12×10^9 bytes = 12 Gigabytes 

    Ou seja, para carregar um modelo de 3B parâmetros, são necessários pelo menos 12 GB livres de memória de vídeo (VRAM). Observa-se que mesmo em 2025 placas de vídeo discretas com 8 GB ainda são comuns em computadores de mesa, assim como 4 GB em GPUs integradas em computadores portáteis. Uma estratégia para otimizar recursos é a quantização das variáveis do modelo, que reduz a “resolução” dos parâmetros mantendo suas características funcionais. Um exemplo de biblioteca que implementa esse processo é a bitsandbytes. Abordagens recentes também exploram o uso de SSD como alternativa parcial à VRAM.

    Os requisitos detalhados de hardware variam conforme o objetivo: treinamento, ajuste fino ou apenas inferência do modelo. O treinamento exige armazenamento e processamento de enormes volumes de dados. Para ilustrar, de acordo com o reporte técnico do DeepSeek-V3, foram necessárias 2.78 Bilhões de horas de GPU (NVIDIA H800) utilizando 14.8 trilhões de tokens de alta qualidade e diversidade, considerando que cada token equivale a aproximadamente 4 bytes [referência], conclui-se que foi utilizado aproximadamente 59 TB de dados de treinamento. É evidente que esse nível de infraestrutura está fora do alcance de pequenas empresas.

    O ajuste fino de um LLM busca introduzir novos comportamentos ou melhorar instruções específicas, ajustando apenas parte dos parâmetros com base em exemplos de prompts e respostas esperadas. Sua viabilidade depende do número de parâmetros ajustáveis, do método adotado, do tamanho do conjunto de dados e do hardware disponível. 

    Recentemente foram desenvolvidos métodos mais eficientes de ajuste fino dos quais LoRA (Low Rank Adaptation, em inglês) se destaca (Hu et al., 2022).  O método é baseado em uma camada adaptadora, cujos parâmetros ajustáveis são obtidos através de uma decomposição de matrizes. A nova camada de parâmetros pode ser anexada nas camadas de atenção (attention heads) ou na camada totalmente conectada (sinônimo de camada densa ou linear no contexto de redes neurais) [fonte]. A atualização  das matrizes dos adaptadores demanda muito menos recursos computacionais durante o procedimento de reajuste devido ao menor posto (rank) dessas matrizes em comparação com a matriz de pesos original, com maiores dimensões e posto (rank). O reajuste de parâmetros propriamente dito é realizado através da solução de um problema de otimização com um método de gradiente descendente moderno, como Adam (Kingma & Ba, 2014), por exemplo. 

    QLoRA é  uma versão mais eficiente do método, proposta por Dettmers et al., 2023, que alcança melhor desempenho computacional através da quantização dos parâmetros do modelo original. Ambos os métodos foram implementados na biblioteca PEFT disponibilizada para Python, e mais detalhes, exemplos, podem ser encontrados [aqui]. O uso dessas técnicas permite personalizar modelos de maneira viável, equilibrando eficiência e desempenho, sem necessidade de re-estimar todos os parâmetros.

    Empresas que integram LLMs em produtos completos ou serviços de inferência são chamadas por alguns de Application-Layer AI Companies [Fonte1]. Seu papel é transformar modelos de propósito geral em soluções aplicadas a problemas específicos no mundo real, trazendo valor direto aos usuários. Exemplos incluem lucidworks, Perssua, getenter.ai, requesty.ai, braine.digital, dynadok.com, etc. Nesse cenário, a principal demanda computacional recai sobre a inferência, que embora mais leve que o treinamento ou ajuste fino, pode se tornar intensiva em ambientes de grande escala.

    Para a inferência, o parâmetro-chave é tokens por segundo, que mede a capacidade do modelo de gerar ou processar tokens de forma contínua. Esse indicador determina a latência e a fluidez da interação, sendo crucial para proporcionar ao usuário a sensação de diálogo em tempo real. O desempenho depende tanto das características do modelo (arquitetura e número de parâmetros) quanto da infraestrutura que o hospeda (tipo e quantidade de GPUs, TPUs, etc.). Alguns exemplos de testes de desempenho de LLMs/hardware podem ser encontrados no Link1, ou Link2.

    Em resumo, a adoção de LLMs em código aberto oferece oportunidades estratégicas para empresas que buscam maior autonomia e segurança sobre seus dados. Entretanto, o sucesso dessa implementação depende de uma análise cuidadosa dos requisitos de hardware, do alinhamento entre métodos de otimização como quantização e LoRA, e do objetivo final: seja treinamento, ajuste fino ou apenas inferência. Compreender essas variáveis é essencial para transformar o potencial dos modelos de linguagem em resultados práticos, equilibrando custos e benefícios.

    Referências 

    Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023). Qlora: Efficient finetuning of quantized llms. Advances in neural information processing systems, 36, 10088-10115.

    [link]

    Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., … & Chen, W. (2022). Lora: Low-rank adaptation of large language models. ICLR, 1(2), p. 3. [link]

    Kingma, D. P., & Ba, J. (2014). Adam: A method for stochastic optimization. arXiv preprint arXiv:1412.6980. [link]

     

  • Gerando arquivos PDF com JavaScript

    Gerando arquivos PDF com JavaScript

    Não é novidade nenhuma que o JavaScript hoje é praticamente uma solução universal para qualquer problema tecnológico. Desde aplicações web, até IoT (Internet das Coisas) — conseguimos fazer de tudo com a linguagem. E para o problema de geração de arquivos em PDF, a situação é a mesma: o JavaScript também é a solução.

    Se procurarmos na internet como fazer isso, vamos nos deparar com uma quantidade considerável de opções. Existem boas, ruins e medianas, mas das que usamos até então, a que se apresentou a melhor foi o jsPDF.

    Vamos ver como ela funciona.

    Gerando PDFs loucamente

    jsPDF é um projeto open source disponível no GitHub por meio da licença MIT e que já tem mais de 15000 estrelas no repositório. O objetivo da biblioteca é bem simples: gerar PDFs por meio do JavaScript no client-side.

    O primeiro passo para utilizar ela é trazê-la para o seu projeto. O jeito mais fácil é por meio da tag script no HTML, apontando para a biblioteca hospedada no CloudFlare. Para isso, faça:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.4.1/jspdf.debug.js" integrity="sha384-THVO/sM0mFD9h7dfSndI6TS0PgAGavwKvB5hAxRRvc0o9cPLohB0wb/PTA7LdUHs" crossorigin="anonymous"></script>

    Caso esteja utilizando o yarn ou npm, basta executar:

    npm install jspdf --save
    yarn add jspdf

    Feito isso já podemos começar a trabalhar no nosso PDF. Primeiramente criamos uma nova instância do objeto jsPDF. Com o objeto em mãos, podemos usar o método text() para inserir um texto. Por fim, usamos o método save() para gravar e gerar o documento em PDF. Este código todo não passa de três linhas.

    var doc = new jsPDF()
    doc.text('Hello world!', 10, 10)
    doc.save('a4.pdf')

    Mas isso é só o primeiro passo. Por definição, o pdf exportado é no tamanho A4, na orientação retrato e usa milímetros como unidade de medida. Mas tudo isso é configurável. Na função construtora jsPDF() podemos passar um objetos de configuração com todos estes parâmetros:

    var doc = new jsPDF({
    orientation: 'landscape',
    unit: 'cm',
    format: 'letter'
    })

    Note que agora temos um pdf no formato paisagem usando centímetros como unidade de medida. Além disso, também configuramos para o tamanho ser no formato carta. Todas configurações disponíveis estão nesta página.

    Texto é só o começo

    Vimos que o método text() adiciona texto ao pdf., mas se a biblioteca se limitasse a isso, não seria tão interessante. O jsPDF nos oferece várias APIs para criar formas, imagens e figuras. Segue algumas delas abaixo:

    • circle
    • addImage
    • addFont
    • ellipse
    • line
    • triangle
    • rect

    Todos os métodos disponíveis estão na documentação.

    Testando os PDFs

    Para poder testar os PDFs e ver os seus resultados na prática, podemos usar este link. Do lado esquerdo adicionamos o código e do lado direito o pdf é gerado de acordo com as suas especificações, como no exemplo a seguir:

    var imgData = ""; // base-64
    var doc = new jsPDF();doc.setFontSize(40);
    doc.text(35, 25, "Artigo para o iMasters");
    doc.addImage(imgData, 'JPEG', 15, 40, 180, 180);

    O resultado é o seguinte:

    Press enter or click to view image in full size

    PDF criado usando o jsPDF

    ps: Repare que no exemplo anterior não inserimos o código da imagem em base64 porque ficaria bem extenso. Mas você pode codificar qualquer imagem para usar neste site.

    E ai, o que acharam? Estão prontos para gerar uns PDFs?

    Referências

    Versão em vídeo

    Além das instruções que daremos abaixo, você também pode acompanhar todos os passos que serão feitos na versão em vídeo!

  • Como Construí um Pipeline de Dados em Go, Kafka e Elasticsearch com Docker Compose

    Como Construí um Pipeline de Dados em Go, Kafka e Elasticsearch com Docker Compose

    Nos últimos dias eu precisava testar o Kafka Connect para um cenário de ingestão de dados e pensei: por que não montar um laboratório simples que já faça sentido para outros projetos?

    Diagrama de Arquitetura

    A ideia era clara:

    • Um producer em Go que consulta a API da Binance e envia mensagens para o Kafka.
    • Kafka Connect consumindo esse tópico e jogando as mensagens no Elasticsearch.
    • Kafka UI para inspecionar o que está rolando nos tópicos.

    Tudo isso rodando em containers com Docker Compose, porque ninguém merece configurar isso manualmente.

    Quer aprender como fazer? Bora lá!


    O que vamos construir

    Nosso fluxo de dados vai ser assim:

    Diagrama de Sequência

    E vamos adicionar o Kafka UI para facilitar a visualização.


    Passo 1 – Estrutura do projeto

    Crie uma pasta para o projeto:

    mkdir kafka-connect-lab
    cd kafka-connect-lab

    Estruture assim:

    kafka-connect-lab/
    ├── cmd/worker/        # Código Go do producer
    │   └── main.go
    ├── Dockerfile         # Dockerfile do worker
    ├── docker-compose.yml # Orquestração
    └── go.mod             # Módulo Go
    

    Inicialize o módulo Go:

    go mod init github.com/seuprojeto/kafka-connect-lab
    go get github.com/segmentio/kafka-go
    

    Passo 2 – Escrevendo o Worker em Go

    Vamos criar um producer que consulta o preço do Bitcoin na Binance e publica no Kafka.

    O código completo:

    Abra cmd/worker/main.go:

    package main
    
    import (
        "context"
        "encoding/json"
        "fmt"
        "log"
        "net/http"
        "os"
        "time"
    
        kafka "github.com/segmentio/kafka-go"
    )
    

    👉 O que temos aqui?

    • Importamos pacotes básicos (logostime, etc.).
    • Importamos a lib kafka-go para conectar no Kafka.

    type Ticker struct {
        Symbol string `json:"symbol"`
        Price  string `json:"price"`
    }
    

    👉 Por que essa struct?
    Ela representa o JSON que recebemos da API da Binance, com par de negociação e preço.


    func fetchTicker(symbol string) (*Ticker, error) {
        url := "https://api.binance.com/api/v3/ticker/price?symbol=" + symbol
        resp, err := http.Get(url)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        if resp.StatusCode != http.StatusOK {
            return nil, fmt.Errorf("erro Binance: %d", resp.StatusCode)
        }
        var t Ticker
        if err := json.NewDecoder(resp.Body).Decode(&t); err != nil {
            return nil, err
        }
        return &t, nil
    }
    

    👉 O que essa função faz?

    • Monta a URL da Binance.
    • Faz uma requisição HTTP GET.
    • Decodifica a resposta JSON direto para a struct Ticker.
    • Retorna um ponteiro para Ticker.

    Agora vem a função main():

    func main() {
        broker := os.Getenv("KAFKA_BROKER")
        topic := os.Getenv("KAFKA_TOPIC")
        symbol := os.Getenv("BINANCE_SYMBOL")
        if symbol == "" {
            symbol = "BTCUSDT"
        }
        interval := os.Getenv("FETCH_INTERVAL")
        if interval == "" {
            interval = "5s"
        }
        d, err := time.ParseDuration(interval)
        if err != nil {
            log.Fatalf("Intervalo inválido: %v", err)
        }
    

    👉 O que estamos fazendo?

    • Pegando as configs via variáveis de ambiente.
    • Se não passar nada, usamos defaults (BTCUSDT e intervalo de 5s).

        writer := kafka.NewWriter(kafka.WriterConfig{
            Brokers:  []string{broker},
            Topic:    topic,
            Balancer: &kafka.LeastBytes{},
        })
        defer writer.Close()
    
        log.Printf("Worker iniciado. Broker=%s, Tópico=%s, Ativo=%s", broker, topic, symbol)
    

    👉 Aqui inicializamos o Producer:

    • NewWriter cria um writer para enviar mensagens ao Kafka.
    • Usamos o balanceador LeastBytes para distribuir mensagens (bom mesmo em clusters).

        ticker := time.NewTicker(d)
        defer ticker.Stop()
    
        for range ticker.C {
            data, err := fetchTicker(symbol)
            if err != nil {
                log.Println("Erro ao buscar ticker:", err)
                continue
            }
            msg, _ := json.Marshal(data)
            err = writer.WriteMessages(context.Background(), kafka.Message{Value: msg})
            if err != nil {
                log.Println("Erro ao publicar:", err)
            } else {
                log.Printf("Mensagem publicada: %s = %s", data.Symbol, data.Price)
            }
        }
    }
    

    👉 O loop principal:

    • Executa a cada interval segundos.
    • Busca o preço na Binance.
    • Serializa para JSON.
    • Publica no Kafka.

    Resultado: A cada 5 segundos, temos o preço do BTC publicado no tópico.

    Tópico


    Passo 3 – Empacotando com Docker

    Crie Dockerfile na raiz:

    # build
    FROM golang:1.20-alpine AS builder
    WORKDIR /app
    COPY go.mod go.sum ./
    RUN go mod download
    COPY cmd/worker ./cmd/worker
    RUN go build -o worker ./cmd/worker
    
    # runtime
    FROM alpine:latest
    WORKDIR /app
    COPY --from=builder /app/worker ./
    RUN apk add --no-cache ca-certificates
    ENTRYPOINT ["./worker"]
    

    👉 Por que multi-stage?

    • A primeira imagem compila o binário.
    • A segunda só carrega o executável final, leve e rápida.

    Passo 4 – Orquestrando tudo com Docker Compose

    Crie docker-compose.yml:

    services:
      zookeeper:
        image: confluentinc/cp-zookeeper:7.7.4
        environment:
          ZOOKEEPER_CLIENT_PORT: 2181
    
      kafka:
        image: confluentinc/cp-kafka:7.7.4
        depends_on: [zookeeper]
        ports: ["9092:9092"]
        environment:
          KAFKA_BROKER_ID: 1
          KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
          KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
          KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    
      elasticsearch:
        image: docker.elastic.co/elasticsearch/elasticsearch:7.17.28
        environment:
          - discovery.type=single-node
          - ES_JAVA_OPTS=-Xms512m -Xmx512m
        ports: ["9200:9200"]
    
      kafka-connect:
        image: confluentinc/cp-kafka-connect:7.7.4
        depends_on: [kafka]
        ports: ["8083:8083"]
        environment:
          CONNECT_BOOTSTRAP_SERVERS: kafka:9092
          CONNECT_GROUP_ID: connect-cluster
          CONNECT_CONFIG_STORAGE_TOPIC: connect-config
          CONNECT_OFFSET_STORAGE_TOPIC: connect-offsets
          CONNECT_STATUS_STORAGE_TOPIC: connect-status
          CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
          CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
          CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1
          CONNECT_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
          CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
          CONNECT_KEY_CONVERTER_SCHEMAS_ENABLE: 'false'
          CONNECT_VALUE_CONVERTER_SCHEMAS_ENABLE: 'false'
          CONNECT_PLUGIN_PATH: /usr/share/java,/usr/share/confluent-hub-components
        command:
          - bash -c
          - |
            confluent-hub install --no-prompt confluentinc/kafka-connect-elasticsearch:13.0.0
            /etc/confluent/docker/run
    
      worker:
        build: .
        depends_on: [kafka]
        environment:
          KAFKA_BROKER: kafka:9092
          KAFKA_TOPIC: binance-ticker
          BINANCE_SYMBOL: BTCUSDT
          FETCH_INTERVAL: 5s
    
      kafka-ui:
        image: provectuslabs/kafka-ui:v0.7.2
        depends_on: [kafka, kafka-connect]
        ports: ["8080:8080"]
        environment:
          KAFKA_CLUSTERS_0_NAME: local
          KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
          KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: connect
          KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect:8083
    

    👉 O que temos aqui?

    • Kafka + Zookeeper.
    • Elasticsearch (para receber os dados).
    • Kafka Connect (já baixando o conector para ES).
    • Worker em Go.
    • Kafka UI para facilitar o debug.

    Passo 5 – Subindo tudo

    docker-compose up --build
    

    Se o Elasticsearch reclamar de memória:

    sudo sysctl -w vm.max_map_count=262144
    

    Passo 6 – Criando o tópico

    docker-compose exec kafka kafka-topics --create \
      --topic binance-ticker \
      --bootstrap-server kafka:9092 \
      --replication-factor 1 \
      --partitions 1
    

    Passo 7 – Configurando o conector

    Agora vem a estrela do show: Kafka Connect.

    curl -X POST -H "Content-Type: application/json" \
      --data '{
        "name": "es-sink",
        "config": {
          "connector.class":"io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
          "tasks.max":"1",
          "topics":"binance-ticker",
          "connection.url":"http://elasticsearch:9200",
          "type.name":"_doc",
          "key.ignore":"true",
          "schema.ignore":"true"
        }
      }' http://localhost:8083/connectors
    

    Quer saber se ele subiu?

    curl http://localhost:8083/connectors/es-sink/status
    

    Status

    Passo 8 – Testando tudo

    • Vá no Kafka UIhttp://localhost:8080 → veja o tópico binance-ticker recebendo mensagens.
    • Consulte no Elasticsearch:
      curl http://localhost:9200/binance-ticker/_search?pretty
    

    Saída esperada:

    Saida

    Pronto. Kafka Connect funcionando!


    Conclusão

    Neste artigo, construímos um laboratório completo para testar o Kafka Connect:

    • Criamos um worker em Go que consome dados da API da Binance e publica no Kafka.
    • Subimos um ambiente com Kafka, Kafka Connect, Elasticsearch e Kafka UI usando Docker Compose.
    • Configuramos um conector Elasticsearch no Kafka Connect para consumir mensagens e indexá-las automaticamente.
    • Validamos o pipeline via Kafka UI e consultas REST no Elasticsearch.

    Esse ambiente é um ótimo ponto de partida para testar outros conectores, experimentar transformações e entender o fluxo de dados em arquiteturas orientadas a eventos.

    Como próximos passos, você pode:

    • Adicionar Kibana para visualizações.
    • Criar um cluster Kafka com múltiplos brokers.
    • Habilitar TLS e autenticação, aproximando o setup de produção.

    Referências

    Apache Software Foundation. (2024). Kafka Connect documentationhttps://kafka.apache.org/documentation/#connect

    Confluent, Inc. (2024). Confluent Hubhttps://www.confluent.io/hub/

    Elastic. (2024). Elasticsearch REST API documentationhttps://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html

    LuizTools. (2023). Introdução ao Kafka e como usar com Node.jshttps://luiztools.com.br/kafka-com-nodejs

    Segment.io. (2024). kafka-go: Apache Kafka client for Gohttps://github.com/segmentio/kafka-go


    Leitura complementar

  • Desenvolvedor de games do futuro: habilidades essenciais e oportunidades em um mercado bilionário

    Desenvolvedor de games do futuro: habilidades essenciais e oportunidades em um mercado bilionário

    Sempre me perguntam quais são as habilidades essenciais para quem deseja trabalhar com games, ser desenvolvedor de games. Minha resposta é simples: não existe fórmula mágica, mas existe um ponto de partida. E o momento para se preparar é agora.

    A indústria de jogos deixou de ser um nicho e se consolidou como um dos maiores segmentos de entretenimento do mundo. Segundo a Newzoo, em 2025 o mercado global deve chegar a US$ 205,7 bilhões – com os jogos mobile representando mais da metade desse valor. Esse crescimento não é apenas sobre números: ele reflete a dimensão de impacto social, cultural e econômico que os games têm hoje.

    O que buscamos em talentos?

    Vejo diariamente como a base sólida em Engenharia de Software e Ciência da Computação faz a diferença. A linguagem de programação ou a engine usada podem mudar, mas a capacidade de resolver problemas complexos e de aprender rápido é o que realmente sustenta uma carreira de longo prazo. Adaptabilidade não é mais um diferencial – é um pré-requisito.

    Quando olho para os jovens profissionais, por exemplo, observo que o mercado valoriza muito aqueles que tiveram experiências práticas, como Game Jams, projetos independentes e portfólios consistentes. Mais do que provar conhecimento técnico, essas vivências demonstram curiosidade, paixão e resiliência.

    O Brasil forma mais de 40 mil engenheiros e cientistas da computação por ano. Ainda assim, poucos enxergam  a área de desenvolvimento de jogos como possibilidade concreta de carreira. Isso precisa mudar. Somos um país criativo, com profissionais capazes de atuar em nível global.

    A tecnologia que redefine o jogo

    Outro ponto que não posso deixar de destacar é a revolução que a Inteligência Artificial está trazendo para o setor. Profissionais que dominam fundamentos de IA têm uma vantagem competitiva inegável. No futuro, a competitividade global dependerá do aproveitamento da IA, tornando essencial investir cada vez mais no desenvolvimento dessas habilidades.

    Além do código: o fator humano

    Ainda assim, o que realmente diferencia os melhores desenvolvedores não cabe em uma linha de código. Comunicação clara, capacidade de trabalhar em times multidisciplinares e, sobretudo, paixão genuína por jogos são elementos que tornam um profissional completo. Grandes ideias surgem quando alguém olha para a experiência do jogador e se pergunta: isso seria divertido?

    No mercado atual, a formação de talentos é um pilar estratégico que se traduz em programas concretos de desenvolvimento. Isso significa que as empresas precisam investir ativamente na aquisição de conhecimentos técnicos e no aprimoramento de habilidades práticas, preparando seus colaboradores para os desafios reais da indústria. O objetivo é construir equipes qualificadas e inovadoras, capazes de impulsionar o avanço do setor em nível nacional e global.

    O futuro é agora: prepare-se para o próximo nível

    O mercado de games é um universo de possibilidades ilimitadas, impulsionado por inovação constante e uma demanda crescente por mentes brilhantes. As tendências e habilidades destacadas aqui não são apenas requisitos, mas, sim, o mapa para navegar com sucesso neste cenário dinâmico. Ao focar em fundamentos sólidos, adaptabilidade, domínio de novas tecnologias e, acima de tudo, uma paixão genuína por criar experiências que encantam, os profissionais brasileiros têm em suas mãos o poder de moldar o futuro do entretenimento global. O potencial é imenso, e a jornada para se tornar um desenvolvedor de games de sucesso começa com a preparação e a visão estratégica.

  • Novidades no Java 25 (para desenvolvedores)

    Novidades no Java 25 (para desenvolvedores)

    O Java 25 chega com uma forte mistura de qualidade de vida da linguagem, melhor inicialização, criação de perfil poderosa e APIs práticas.

    Este post é um tour prático: seções curtas, código executável e cenários concretos onde você usará cada recurso em breve. Cada trecho é intencionalmente minimalista para que você possa copiar, adaptar e executar rapidamente.

    🚀 Destaques do Java 25

    • ✅ Corpos de construção flexíveis (JEP 513)
    • 🧩 Padrões primitivos em instanceofswitch(JEP 507)
    • 📦 Programas compactos e importação de módulos (JEP 512 + JEP 511)
    • 🔄 Concorrência estruturada + valores com escopo (JEP 505 + JEP 506)
    • 🔐 API KDF e codificação PEM (JEP 510 + JEP 470)
    • 📊 Novas ferramentas de perfil JFR (JEP 520 + JEP 509)
    • ⚡ Inicialização mais rápida via cache AOT (JEP 514 + JEP 515)

    Atualizações de linguagem e sintaxe

    JEP 513: Corpos de construção flexíveis (✅ final)

    👉 PEC 513

    O que é: Você pode executar instruções antes de chamar super(...)ou this(...). Um novo construtor “prólogo” permite validar entradas e inicializar seus próprios campos com segurança antes de delegar na hierarquia.

    Quando você vai usar: Validação à prova de falhas e estabelecimento da integridade da subclasse antes que uma superclasse possa chamar métodos substituíveis.

    Antes (abordagem anterior: tinha que chamar superprimeiro, muitas vezes enviando a validação para uma fábrica ou fazendo isso depois super, arriscando um estado parcialmente inicializado se a superclasse usasse métodos substituíveis):

    // BEFORE (pre-Java 25)
    class Person {
        Person(String name) { init(); }
        void init() { /* might call overridable methods */ }
    }
    
    class Employee extends Person {
        private String payrollId; // cannot set before super
    
        Employee(String name, String payrollId) {
            super(name); // must be first line
            if (payrollId == null || !payrollId.matches("EMP-\\d{4}")) {
                throw new IllegalArgumentException("Invalid payrollId");
            }
            this.payrollId = payrollId; // set only after super
        }
    }

    Limitações antes:

    • A validação é executada somente após super, então o código da superclasse pode observar um estado de subclasse incompleto.
    • Soluções alternativas: fábricas estáticas, inicialização em duas fases ou validação duplicada anterior.

    Depois (ID da folha de pagamento do funcionário validado e inicializado antes do construtor da Pessoa):

    // src/main/java/com/loiane/people/Employee.java
    package com.loiane.people;
    
    class Person {
        final String name;
        Person(String name) { this.name = name; log("constructed: " + name); }
        void log(String msg) { System.out.println(msg); }
    }
    
    class Employee extends Person {
        final String payrollId;
    
        Employee(String name, String payrollId) {
            // Prologue: validate & set subclass state before calling super
            if (payrollId == null || !payrollId.matches("EMP-\\d{4}"))
                throw new IllegalArgumentException("Invalid payrollId");
            this.payrollId = payrollId; // allowed before super
            super(name); // delegation after establishing invariants
            log("ready: " + this.payrollId);
        }
    
        public static void main(String[] args) {
            new Employee("Ana", "EMP-1234");
        }
    }

    O que está acontecendo aqui?

    • Validação + configuração invariante acontecem no prólogo (antes super).
    • A atribuição de campo payrollIdé permitida com antecedência.
    • O estado da subclasse existe antes da execução da lógica da superclasse.

    JEP 507: Padrões primitivos em instanceof e switch (🧪 terceira prévia)

    👉 PEC 507

    O que é: A correspondência de padrões agora lida com tipos primitivos em instanceofswitch. Você pode testar e vincular valores restritos com segurança, sem verificações manuais de intervalo; switchsuporta booleanlongfloatdoubletambém.

    Quando você usará: Conversões numéricas mais seguras, análise expressiva e regras de negócios mais limpas.

    Antes (fundição manual + ramificações separadas: mais texto padronizado e risco de alargamento acidental):

    // BEFORE (pre-Java 25)
    public class TierLegacy {
        static String tierFor(Object usage) {
            if (usage instanceof Integer) {
                int i = (Integer) usage;
                if (i < 1) return "FREE";
                if (i < 1_000) return "STARTER";
                if (i < 10_000) return "PRO";
                return "ENTERPRISE";
            } else if (usage instanceof Long) {
                long l = (Long) usage;
                if (l < 10_000) return "PRO"; else return "ENTERPRISE";
            } else if (usage instanceof Double) {
                double d = (Double) usage;
                int asInt = (int) d; // silent truncation risk
                if (asInt == d) {
                    return tierFor(asInt); // re-box to Integer call
                }
                return d < 1_000.0 ? "STARTER" : "PRO";
            }
            return "UNKNOWN";
        }
    }

    Limitações antes:

    • Repetição de conversões e declarações de variáveis.
    • Possível truncamento silencioso ao restringir doublepara int.
    • Nenhum padrão de proteção em linha com switcha expressão.

    Depois (níveis de preços com estreitamento preciso e proteções):

    // src/main/java/com/loiane/billing/Tier.java
    package com.loiane.billing;
    
    public class Tier {
        static String tierFor(Object usage) {
            if (usage instanceof int i) {
                return switch (i) {
                    case int v when v < 1 -> "FREE";
                    case int v when v < 1_000 -> "STARTER";
                    case int v when v < 10_000 -> "PRO";
                    case int v -> "ENTERPRISE";
                };
            }
            if (usage instanceof long l) {
                return switch (l) {
                    case long v when v < 10_000 -> "PRO";
                    case long v -> "ENTERPRISE";
                };
            }
            if (usage instanceof double d) {
                // Bind only when d converts exactly to int (no silent truncation)
                if (d instanceof int exact) {
                    return tierFor(exact);
                }
                return d < 1_000.0 ? "STARTER" : "PRO";
            }
            return "UNKNOWN";
        }
    
        public static void main(String[] args) {
            System.out.println(tierFor(0));        // FREE
            System.out.println(tierFor(999));      // STARTER
            System.out.println(tierFor(9_999));    // PRO
            System.out.println(tierFor(10_000L));  // ENTERPRISE (long path)
            System.out.println(tierFor(42.0));     // STARTER (double exact -> int)
        }
    }

    O que está acontecendo aqui?

    • instanceof int exactvincula-se apenas à conversão exata (sem perda silenciosa).
    • switchsobre longe guardas de valores deixam clara a intenção.

    Como executar (prévia):

    javac --enable-preview --release 25 -d out src/main/java/com/loiane/billing/Tier.java
    java --enable-preview -cp out com.loiane.billing.Tier

    Programas compactos e importações mais limpas

    JEP 512: Arquivos de origem compactos + instância principal (✅ final)

    👉 PEC 512

    O que é: Você pode escrever um arquivo apenas com métodos/campos; o compilador o encapsula em uma classe implícita. void main()Pode ser um método de instância. O novo java.lang.IO facilita a E/S do console.

    Quando você usará: Katas, scripts, ensinamentos, demonstrações rápidas.

    Antes (classe explícita + main estático necessário):

    // BEFORE (pre-Java 25)
    public class HelloClassic {
    
        public static void main(String[] args) {
            System.out.println("Hello, World!");
        }
    }

    Depois (fonte compacta simplificada Hello World):

    // Name this file: Hello.java (compact source)
    void main() {
        IO.println("Hello, World!");
    }

    O que está acontecendo aqui?

    • Nenhuma declaração de classe; void main()é o ponto de entrada.
    • IO.println(java.lang.IO) evita clichês.

    Run:

    java Hello.java

    JEP 511: Declarações de importação de módulos (✅ final)

    👉 PEC 511

    O que é: import module <name>;importa todos os pacotes exportados de um módulo (e transitivos), até mesmo do código classpath.

    Quando você vai usar: Prototipagem e aprendizado; extração rápida de APIs amplas (por exemplo, java.seem código modular).

    Código (análise HTTP + JSON com menos importações):

    // src/main/java/com/loiane/http/FetchTodo.java
    package com.loiane.http;
    
    import module java.base;
    import module java.net.http;
    
    public class FetchTodo {
        public static void main(String[] args) throws Exception {
            var client = java.net.http.HttpClient.newHttpClient();
            var req = java.net.http.HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/todos/1")).build();
            var res = client.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
            System.out.println(res.body());
        }
    }

    O que está acontecendo aqui?

    • Duas importações de módulos trazem todos java.net.httpos tipos necessários; ainda qualificamos os tipos para maior clareza.
    • Caso surja alguma ambiguidade, adicione uma importação específica para resolvê-la.

    Concorrência e contexto

    JEP 506 + JEP 505: Valores com escopo (✅ final) + Concorrência estruturada (🧪 quinta prévia)

    👉 PEC 505 👉 PEC 506

    O que é: Valores com escopo são uma alternativa mais segura e herdável a ThreadLocal. A simultaneidade estruturada fornece um ciclo de vida para grupos de tarefas.

    Quando você usará: Propagação de IDs de solicitação, autenticação ou localidade em threads virtuais com cancelamento claro.

    Código (correlação-id em um grupo de tarefas):

    / src/main/java/com/loiane/observability/Trace.java
    package com.loiane.observability;
    
    import java.lang.ScopedValue;
    import java.util.concurrent.StructuredTaskScope; // preview
    
    public class Trace {
        static final ScopedValue<String> CORR_ID = ScopedValue.newInstance();
    
        static void log(String msg) { System.out.println("[" + CORR_ID.orElse("-") + "] " + msg); }
    
        public static void main(String[] args) throws Exception {
            var id = java.util.UUID.randomUUID().toString();
            ScopedValue.where(CORR_ID, id).run(() -> {
                try (var scope = new StructuredTaskScope<Void>()) { // simplified example
                    var a = scope.fork(() -> { log("load customer"); return null; });
                    var b = scope.fork(() -> { log("load orders"); return null; });
                    scope.join();
                    log("done");
                }
            });
        }
    }

    O que está acontecendo aqui?

    • ScopedValue.where(CORR_ID, id).run(...)vincula o ID de correlação para todo o escopo da tarefa.
    • Threads virtuais (se usadas) herdam o valor do escopo sem vazamentos.

    Executar (visualização para simultaneidade estruturada):

    javac --enable-preview --release 25 -d out src/main/java/com/loiane/observability/Trace.java
    java --enable-preview -cp out com.loiane.observability.Trace

    Blocos de construção de segurança

    JEP 510: API KDF (✅ final) com HKDF

    👉 PEC 510

    O que é: Padrão javax.crypto.KDFpara derivar chaves; inclui HKDF e especificações de parâmetros.

    Quando você vai usar: Chaves de sessão, fluxos HPKE/KEM, chaves de semente.

    Código (derivar material de chave AES para uma exportação):

    // src/main/java/com/loiane/crypto/HkdfDemo.java
    package com.loiane.crypto;
    
    import javax.crypto.*;
    import javax.crypto.spec.HKDFParameterSpec;
    import java.security.spec.AlgorithmParameterSpec;
    
    public class HkdfDemo {
        public static void main(String[] args) throws Exception {
            byte[] ikm = "seed-key-material".getBytes();
            byte[] salt = "salt".getBytes();
            byte[] info = "export-label".getBytes();
    
            KDF hkdf = KDF.getInstance("HKDF-SHA256");
            AlgorithmParameterSpec params = HKDFParameterSpec
                    .ofExtract().addIKM(ikm).addSalt(salt)
                    .thenExpand(info, 32);
    
            SecretKey key = hkdf.deriveKey("AES", params);
            System.out.println("Key algorithm: " + key.getAlgorithm());
            System.out.println("Key length: " + key.getEncoded().length);
        }
    }

    JEP 470: codificações PEM (🧪 visualização)

    👉 PEC 470

    O que é: APIs de primeira classe para ler/escrever PEM e DER com criptografia opcional.

    Quando você vai usá-lo: Importar/exportar chaves sem bibliotecas de terceiros; manipular envelopes PEM de forma limpa.

    Código (escreva um registro PEM simples de bytes):

    // src/main/java/com/loiane/crypto/PemWrite.java
    package com.loiane.crypto;
    
    import java.nio.file.*;
    import java.util.HexFormat;
    import jdk.security.pem.*; // preview API
    
    public class PemWrite {
        public static void main(String[] args) throws Exception {
            byte[] payload = HexFormat.of().parseHex("DEADBEEF");
            var rec = PEMEncoder.encode("TEST-DATA", payload);
            Files.writeString(Path.of("test-data.pem"), rec);
        }
    }

    Executar (prévia):

    javac --enable-preview --release 25 -d out src/main/java/com/loiane/crypto/*.java
    java --enable-preview -cp out com.loiane.crypto.PemWrite

    Criação de perfil e observabilidade

    JEP 520: Tempo e rastreamento do método JFR (✅ final)

    👉 PEC 520

    O que é: Métodos específicos de tempo e rastreamento por instrumentação de bytecode: sem necessidade de alterações no código. Configuração via CLI, arquivos de configuração ou JMX.

    Quando você vai usar: Identifique inicializadores lentos, pontos críticos em estruturas ou vazamentos em ciclos de vida de recursos.

    Experimente (rastreie HashMap::resizeenquanto executa seu aplicativo):

    java '-XX:StartFlightRecording:jdk.MethodTrace#filter=java.util.HashMap::resize,filename=recording.jfr' -jar yourapp.jar
    jfr print --events jdk.MethodTrace --stack-depth 20 recording.jfr

    O JMX programático (rastreamento remoto sob demanda) também é suportado.

    JEP 509: Criação de perfil de tempo de CPU JFR (experimental, Linux)

    👉 PEC 509

    O que é: Perfis precisos de tempo de CPU usando o temporizador de CPU do Linux; atribui tempo nativo aos quadros Java.

    Quando você vai usar: Criação de perfil de produção com baixa sobrecarga; priorize o trabalho vinculado à CPU.

    Início rápido:

    java -XX:StartFlightRecording=jdk.CPUTimeSample#enabled=true,filename=cpu.jfr -jar yourapp.jar
    jfr view cpu-time-hot-methods cpu.jfr

    Inicialização e aquecimento

    JEP 514: Criação de cache AOT em uma etapa (✅ final)

    👉 PEC 514

    O que é: -XX:AOTCacheOutput=app.aotexecuta uma invocação de treinamento e cria o cache em uma única etapa. Uso -XX:AOTCache=app.aotem produção.

    Cenário: A etapa de CI treina a inicialização (rota HTTP típica + desserialização) e publica o artefato de cache. Os pods de produção são montados app.aotpara inicializações a frio consistentemente rápidas.

    # Train and create cache (one-shot)
    java -XX:AOTCacheOutput=app.aot -jar yourapp.jar --train
    
    # Use cache in prod
    java -XX:AOTCache=app.aot -jar yourapp.jar

    JEP 515: Criação de perfil de método antecipado (✅ final)

    👉 PEC 515

    O que é: Os perfis de treinamento são armazenados no cache AOT para que o JIT seja otimizado mais cedo na inicialização.

    Dica: Para infraestrutura dividida, defina opções somente de criação JDK_AOT_VM_OPTIONSse o treinamento e a criação ocorrerem em instâncias diferentes.

    Notas

    • Os recursos de linguagem de visualização (padrões primitivos, simultaneidade estruturada) são necessários --enable-previewpara compilar e executar.
    • As adições do JFR permitem tanto amostragem (tempo de CPU) quanto tempo/rastreamento precisos; combine com o JMC para análise profunda.
    • Os fluxos de trabalho AOT de Leyden (AOTCacheOutput + criação de perfil de método) melhoram as inicializações a frio e reduzem o tempo de aquecimento.
    • Fique de olho nos JEPs de pré-visualização/incubadora; os detalhes (nomes de métodos, formatos de API secundários) ainda podem mudar um pouco até a versão final.

    Use os recursos que se adequam ao seu caso de uso hoje e mantenha as visualizações no seu radar: elas já são úteis e estão quase prontas.

    Referências

    Boa codificação!

  • Desvendando os Completers no Dart: Simplificando o Desenvolvimento Assíncrono

    Desvendando os Completers no Dart: Simplificando o Desenvolvimento Assíncrono

    A programação assíncrona é algo que certamente está presente no dia-a-dia de todo desenvolvedor e necessitamos frequentemente gerenciar Futures.

    E é bem simples, na verdade, controlar o fluxo de uma Future para garantir que não executemos a Future antes de um código que dependa de seu resultado quando executamos a partir de uma interação do usuário, entretanto as vezes essa execução foge um pouco de nosso controle. Mas calma, no Dart existe com Completers e serve justamente para esses casos.

    O Completer é uma forma de executar Futures, que podem ser completadas ou disparar um error, e gerencia-las manualmente. O Completer te dará uma maior flexibilidade no controle das Futures.

    No Completer teremos 5 propriedades importantes0

    /// INICIALIZAÇÃO	
    final Completer<T> completer = Completer<T>();
    
    /// RETORNA O FUTURO EM EXECÃO
    final Future<T> future = completer.future;
    
    /// RETORNA SE O FUTURO FOI CONCLUÍDO
    bool completer.isCompleted
    
    /// COMPLETA O FUTURO COM UM VALOR
    completer.complete(...);
    
    // COMPLETA O FUTURO COM UM ERRO
    completer.completeError(...);

    Então para usarmos o Completer podemos executar o complete(T) para quando queremos completar com sucesso e o .completeError(Object) quando queremos completar com algum error.

    Future<String> fetchData() async {
      final completer = Completer<String>();
      try {
        await Future.delayed(
            const Duration(seconds: 2)); // Simulating network latency
    
        completer.complete('Data fetched successfully');
      } on Exception {
        completer.completeError('Data fetched successfully');
      }
    
      return completer.future;
    }

    A partir dai conseguimos utilizar a Future normalmente

    Press enter or click to view image in full size

    Um exemplo pratico que podemos fazer é a de criar um Timeout para chamada de uma API, no exemplo a seguir estou simulando a chamada em uma API que demora 5 segundos e configurei um Timeout de 3 segundos.

    Press enter or click to view image in full size

    E o Resultado será

    Se eu mudar o timeout para 10 segundos obterei

    Agora imagine que necessitemos executar alguma função assíncrona no construtor de uma classe, como por exemplo, iremos utilizar o Hive e necessitamos abrir o box no construtor para que utilizemos nos outros métodos.

    class HiveRepository {
      late final Box<Map<String, dynamic>> box;
      HiveRepository(String boxName) {
        _init(boxName);
      }
    
      _init(String boxName) async {
        if (completer.isCompleted) return;
    
        try {
          final directory = await getApplicationDocumentsDirectory();
    
          Hive.init('${directory.path}/$boxName.db.hive');
    
          box = await Hive.openBox<Map<String, dynamic>>(boxName);
        } catch (e) {
          throw Exception(e);
        }
      }
    
      add(Map<String, dynamic> item) async {
        await box.add(item);
      }
    
      Future<List<Map<String, dynamic>>> getAll() async {
        return box.values.toList();
      }
    }

    O problema dessa abordagem é que não conseguimos controlar o fluxo de execução das Futures e não conseguimos garantir que o _init() seja executado antes do add() por exemplo.

    Dai que entra os completers, utilizando o Completer conseguimos facilmente garantir que o box do Hive está aberto antes de chamarmos o add() ou até mesmo o values.

    class HiveRepository {
      late final Box<Map<String, dynamic>> box;
      final completer = Completer<Box<Map<String, dynamic>>>();
    
      HiveRepository(String boxName) {
        _init(boxName);
      }
    
      _init(String boxName) async {
        if (completer.isCompleted) return;
    
        try {
          final directory = await getApplicationDocumentsDirectory();
    
          Hive.init('${directory.path}/$boxName.db.hive');
    
          var box = await Hive.openBox<Map<String, dynamic>>(boxName);
          box.clear();
          completer.complete(box);
        } catch (e) {
          completer.completeError(e);
        }
      }
    
      add(Map<String, dynamic> item) async {
        final box = await completer.future;
        await box.add(item);
      }
    
      Future<List<Map<String, dynamic>>> getAll() async {
        final box = await completer.future;
        return box.values.toList();
      }
    }

    E para utilizarmos basta criar uma instancia do repositório e chamar os métodos add() e getAl()

    Press enter or click to view image in full size

    Press enter or click to view image in full size

    Legal, ne? Essa abordagem eu utilizo bastante quando crio uma adaptação do Hive por exemplo e preciso registrar no meu gerenciador de injeção de dependência como o getIt e o modular, é uma forma muito eficaz de trabalhar

  • Depois do hype, o impacto real da IA é o que vai definir quem continua no mercado

    Depois do hype, o impacto real da IA é o que vai definir quem continua no mercado

    Nos últimos anos, a IA (inteligência artificial) se consolidou como o tema mais quente da tecnologia. Produtos, serviços e até pitches de startups passaram a usar a sigla como selo de inovação. Mas como em todo ciclo tecnológico, o entusiasmo inicial deu lugar à cobrança por resultados concretos.

    Esse movimento já se reflete em algumas pesquisas. A edição de 2025 do relatório anual “Technology Vision”, que foi produzido pela consultoria Accenture e entrevistou mais de quatro mil executivos do mundo todo, revelou que apenas 36% deles dizem que suas organizações escalaram soluções de IA generativa. Além disso, somente 13% relatam impacto empresarial significativo em nível de toda a companhia.

    Essa distância entre intenção e prática também aparece no último levantamento “The state of AI”, divulgado em março pela McKinsey & Company e que revelou as impressões de quase 1.500 respondentes sobre o tema. De acordo com o documento, só 21% dos entrevistados que trabalham em empresas que usam IA generativa observaram uma reformulação significativa em alguns fluxos de trabalho. Para completar, menos de um terço relatam que suas companhias estão seguindo a maioria das 12 práticas de adoção e escalonamento da tecnologia.

    Há uma lacuna clara entre adoção e maturidade. Muitas empresas experimentam essa ferramenta tecnológica, mas poucas conseguem transformá-la em resultados com impacto real.

    Essa realidade já demonstra sinais de saturação. As big techs que dominam o desenvolvimento de modelos de IA seguem valorizadas, porém várias outras organizações que surfaram apenas na narrativa viram sua relevância diminuir.

    Esse cenário não significa que a tecnologia perdeu força, pelo contrário. A transformação provocada por ela é irreversível. O que mudou foi o critério: não se trata mais de anunciar uma integração com IA, mas de mostrar métricas claras de ganho de eficiência, aumento de produtividade ou novos fluxos de receita.

    Projeções para o futuro

    Em suma, a lição é simples: a fase de encantamento acabou, e o mercado agora premia a consistência. Startups que usam a IA para resolver problemas específicos, com clareza de modelo de negócio, encontram hoje mais espaço do que aquelas que se apresentam como “disruptivas” apenas no discurso.

    Um exemplo recente é o da Enter, startup de IA para o direito. A empresa vem sendo reconhecida justamente por entregar resultados concretos: recebeu investimento da Sequoia Capital e do investidor Peter Thiel, e acaba de conquistar uma segunda rodada, atingindo um valuation de R$ 2 bilhões. A Enter, inclusive, foi tema do episódio 178 do nosso podcast IA Sob Controle, que contou com a participação de Michael Mac-Vicar, cofundador e CTO da empresa. Vale a pena conferir!

    As lideranças precisam trabalhar para integrar a tecnologia de forma responsável e mensurável no dia a dia dos times. Ainda existe muito terreno a ser explorado por quem souber transformar projetos-piloto em estratégias consistentes, com objetivos bem definidos e retorno comprovado.

    Nos setores onde há dores urgentes, a oportunidade de crescer com o impacto real da IA é ainda mais clara. Mercados como logística, saúde, educação e serviços financeiros que não usarem a ferramenta como modismo, e sim como diferencial competitivo capaz de reduzir custos, ampliar acesso e transformar a experiência do usuário, entrarão com o pé direito nessa nova fase.

    No fim das contas, a grande virada de chave é entender que IA não é mais uma carta de apresentação para investidores ou manchetes. Ela precisa ser, acima de tudo, uma fonte de valor. Só sobreviverão as empresas que conseguirem traduzir código em impacto.

  • Dicas para usar o Github como Portfólio

    Dicas para usar o Github como Portfólio

    Olá galerinha!

    Hoje eu vim falar pra vocês sobre um tópico que eu adoro palestrar sobre, porque se aplica a pessoas de todos os níveis técnicos e qualquer área dentro de tecnologia: Como usar o GitHub como Portfólio!

    Apesar de não ser algo mandatório, é normal que recruiters queiram ver seu GitHub e às vezes também pedem um Portfólio, então porque não unir os dois?

    O GitHub é o lugar ideal, porque lá você pode mostrar exemplos do seu código, sua evolução, seus estudos e iniciativas em que participou, e no artigo de hoje eu vou compartilhar umas dicas de como fazer isso.

    1. Seu README

    Vamos começar com a parte mais importante, a sua página inicial no GitHub: O REAME.

    Aqui você consegue usar texto, gráficos e Gifs para deixar sua mensagem. Olha o meu:

    Como faz?

    Primeiramente você tem que criar esse arquivo.

    • Vá em Novo Repositório.
    • O nome desse novo repositório tem que ser seu @ do GitHub
    • Se você digitar certinho, vai aparecer essa mensagem:

    • Cheque a caixinha de Add a README file se já não estiver checada.
    • Cria o Repositório,

    Agora você tem acesso a esse repositório 🌟especial🌟

    E o que eu coloco aqui?

    Você pode colocar várias coisas. No meu, eu tenho minha Bio, minhas Skills, Redes sociais e uma lista dos meu 5 artigos mais recentes.

    Minha recomendação é que você tenha no mínimo a bio. A minha está em inglês, mas escreva no idioma que for mais relevante pra você!

    Se você está sem criatividade, te recomendo usar esse site https://www.profileme.dev/. Aqui tem um template para preencher e no final você tem acesso ao arquivo .md, aí é só copiar e colar no seu README.

    Se você manjar de markdown e for uma pessoa criativa, dá pra deixar isso aqui bem bonitinho, tipo o perfil da GitHub Star alumm Levxyca que é simples, mas uma gracinha:

    2. Preencha a Coluna Esquerda

    Quando você entra em um perfil no GitHub, mesmo que a pessoa não tenha criado o README que falamos acima, ela tem uma coluna na esquerda com as informações básicas e é importante que você tenha esse cantinho preenchido.

    Vou usar de exemplo outro GitHub Star, o Julio Arruda:


    O perfil dele está completinho, e você pode seguir esse exemplo preenchendo:

    • Seu nome
    • O @ é automático mas agora você pode adicionar seus pronomes pessoais aqui!
    • Uma bio curtinha: Aqui é coisa rápida Seu título ou aspiração. O meu é o mesmo da Bio do Twitter.
    • Onde você trabalha (ou estuda)
    • País de residência e você pode mostrar também seu fuso horário
    • E abaixo você pode colocar todos os links que achar relevantes.
    • Se você está em busca de emprego ou está disponível para parcerias, recomendo deixar seu email aqui também.
    • Se a opção de email não aparecer aqui, você terá que ir em configurações > e-mails e desmarcar a opção Keep my email addresses private.
    • Agora você pode voltar lá no perfil que vai aparecer a opção de adicionar o seu e-mail.

    Olha que legal, agora você tem sua coluna de perfil bem completa, e seu README bonitinho! Esse é o básico, mas podemos fazer um pouco mais.

    3. Projetos “Pinados”

    Logo abaixo do seu README você tem a opção de “pinar” projetos, pinar nada mais é do que deixar em destaque.

    Você pode escolher de 1 a 6 projetos seus. Escolha os projetos dos quais você mais se orgulha! Aqui você pode ver os escolhidos da GitHub Star Erika Heidi:

    Uma dica que eu dou para todos os repositórios, mas que é especialmente importante para aqueles que você quer deixar em destaque é escrever uma pequena descrição sobre o projeto. Só o que o projeto faz e a tecnologia usada.

    No exemplo da imagem acima, para o projeto MiniCli, a descrição é: Um framework minimalista para aplicações da linha de comando em PHP.

    Isso é importante, porque essa informação é vista sem a pessoa ter que clicar no repositório para saber mais, então quando ela abrir o repo vai ser porque ele a interessa. Isso facilita a vida de recruiters olhando seu perfil.

    Desafio: Como está seu perfil?

    Bom pessoal, essas são as minhas dicas principais para que seu perfil seja não apenas atrativo, mas também funcional. Essas dicas parecem simples, mas com o mercado atual tão competitivo, pequenas coisas podem fazer a diferença na hora do processo seletivo.

    O desafio da semana é que você aplique uma (ou todas) essas dicas lá no seu perfil 

     

  • Ferramentas de IA para desenvolvedores: aprendizados práticos entre o que já funciona e o que ainda é hype

    Ferramentas de IA para desenvolvedores: aprendizados práticos entre o que já funciona e o que ainda é hype

    Nos últimos anos, testei diversas ferramentas de IA aplicadas ao desenvolvimento de software. Uma coisa ficou clara para mim: não existe solução única.

    Cada ferramenta agrega valor em um estágio diferente do fluxo de trabalho, da ideação à prototipagem, da codificação aos testes e até à implementação. O que funciona em um contexto pode não se aplicar em outro, e a utilidade depende muito do escopo do projeto e da bagagem do desenvolvedor.

    Com base nessa experiência, quero compartilhar o que já se mostrou útil, onde ainda encontramos limitações e quais recomendações considero relevantes para quem está avaliando adotar IA no dia a dia de desenvolvimento.

    Onde a IA já faz diferença no fluxo de trabalho

    Na prototipagem rápida sem código, o Hostinger Horizons reduz a barreira de entrada ao transformar descrições em linguagem natural, gerando aplicativos prontos em poucos minutos. É uma forma ágil de validar ideias, embora encontre limites quando o projeto exige engenharia mais complexa.

    Quando falamos de ambientes de codificação colaborativa, o Replit é uma das opções mais práticas. Ele suporta várias linguagens e elimina a necessidade de configurar ambientes locais, sendo ideal para experimentar e até criar aplicações completas em menor escala.

    A limitação surge em projetos grandes ou corporativos, já que rodar tudo em nuvem demanda mais recursos e os recursos avançados de IA ficam restritos a planos pagos.

    Na codificação assistida, o GitHub Copilot foi a ferramenta que mais acelerou minha produtividade. Ele sugere blocos inteiros de código diretamente no editor, liberando tempo para que eu foque na arquitetura e na resolução de problemas.

    Por outro lado, as sugestões nem sempre são corretas e exigem revisão cuidadosa. Para desenvolvedores experientes, isso é um apoio valioso. Para iniciantes, pode acabar se tornando um obstáculo.

    Nos testes e na depuração, a IA ainda não superou as ferramentas tradicionais. O Chrome DevTools continua indispensável para inspecionar elementos e monitorar desempenho, enquanto o Fiddler é útil para depurar interações complexas de API e simular condições reais de rede.

    O desafio está na curva de aprendizado. Ambas as ferramentas são poderosas, mas podem intimidar quem está começando.

    Na edição, editores como Sublime Text e Notepad++ seguem relevantes. O Sublime é leve e rápido, enquanto o Notepad é gratuito e adequado para scripts rápidos. Mesmo com todo o avanço da inteligência artificial, esses ambientes continuam sendo a espinha dorsal do desenvolvimento.

    Por fim, no controle de versão, o GitHub permanece insubstituível. Ele estrutura fluxos de trabalho, integra pipelines de CI/CD e garante colaboração segura. No entanto, exige familiaridade com Git e, em equipes grandes, os limites do plano gratuito são facilmente atingidos.

    Os desafios que ainda enfrentamos

    Apesar dos avanços, a inteligência artificial não é uma solução mágica. Os principais desafios podem ser agrupados em três pontos principais.

    O primeiro é a confiabilidade. Ferramentas como o Copilot aceleram tarefas, mas exigem supervisão constante. O código gerado pode ser útil, mas também pode conter erros que passam despercebidos.

    O segundo é a curva de aprendizado. DevTools, Fiddler e até mesmo Replit exigem conhecimentos prévios. A promessa de acessibilidade muitas vezes esbarra na necessidade de ter uma base técnica sólida.

    O terceiro é a integração com fluxos de trabalho existentes. Alinhar ferramentas de IA com sistemas legados ou pipelines já consolidados continua sendo um obstáculo. Muitas empresas ainda enfrentam dificuldades para conectar essas soluções ao que já utilizam e, em alguns casos, precisam recorrer a consultores externos.

    Além disso, existem preocupações éticas, riscos à privacidade de dados e a necessidade de requalificação das equipes. Quem está começando a adotar IA, por exemplo, geralmente cita a falta de conhecimento como barreira.

    Recomendações para desenvolvedores

    Não existe uma ferramenta universalmente melhor. O segredo está na experimentação. Testar diferentes soluções, avaliar a integração com o fluxo de trabalho e descartar o que não gera valor real é parte essencial do processo.

    Para quem está começando, ferramentas sem código e de baixo código são uma boa porta de entrada, pois permitem transformar ideias em produtos sem exigir habilidades técnicas avançadas.

    Para desenvolvedores mais experientes, assistentes de codificação e plataformas de depuração podem acelerar tarefas repetitivas e fornecer insights valiosos, desde que seus resultados sejam analisados com senso crítico.

    A recomendação mais importante é escolher ferramentas que aumentem a confiança no trabalho, em vez de criar dependência. A inteligência artificial deve ampliar as capacidades do desenvolvedor, não substituí-las.

    IA como aliada, não substituta

    Minha experiência mostra que a inteligência artificial não substitui os desenvolvedores, mas remodela a forma como trabalhamos. Ela reduz o tempo gasto em tarefas repetitivas, acelera a prototipagem e permite que equipes menores façam mais com menos recursos.

    Ainda assim, a supervisão humana, o pensamento crítico e a capacidade de resolver problemas permanecem essenciais. O que diferencia um projeto bem-sucedido não é apenas a ferramenta utilizada, mas a forma como ela é aplicada.

    O futuro do desenvolvimento não será sobre escolher entre humanos ou máquinas, mas sobre como unir as duas forças para criar soluções melhores em menos tempo, com qualidade e segurança.

  • Função recursiva em Go para acessar valores em mapas aninhados

    Função recursiva em Go para acessar valores em mapas aninhados

    GO | Recentemente, recebi um retorno de uma API que continha um JSON com uma estrutura complexa, grande e aninhada. Eu precisava acessar um valor específico dentro dessa estrutura, mas o caminho para esse valor poderia variar.

    A ideia então foi criar uma função recursiva que recebesse uma lista com o caminho (path) para o valor desejado e a estrutura de dados (um mapa aninhado) e retornasse o valor correspondente, se encontrado.

    Acredito que existem maneiras mais espertas de fazer isso, mas essa abordagem recursiva funcionou bem para o meu caso.

    package main
    
    import (
    	"fmt"
    )
    
    func GetFromMap(parts []string, current any) (any, bool) {
    	fmt.Printf("parts: %v, current: %v\n", parts, current)
    
    	if len(parts) == 0 {
    		return current, true
    	}
    
    	m, ok := current.(map[string]any)
    	if !ok { // not a map, cannot descend
    		return nil, false
    	}
    
    	key := parts[0]
    	next, exists := m[key]
    	if !exists {
    		return nil, false
    	}
    
    	return GetFromMap(parts[1:], next)
    }
    
    func MustGetFromMap(path []string, data any) any {
    	val, ok := GetFromMap(path, data)
    	if !ok {
    		panic(fmt.Sprintf("path %v not found in map", path))
    	}
    	return val
    }

    E aqui está um exemplo de como você pode testar essa função:

    package main
    
    import "testing"
    
    func TestGetFromMap(t *testing.T) {
    	data := map[string]any{
    		"not_a_map": "just a string",
    		"a": map[string]any{
    			"b": map[string]any{
    				"c": 42,
    			},
    			"d": "hello",
    		},
    		"x": 3.14,
    	}
    
    	tests := []struct {
    		path          []string
    		expectedVal   any
    		expectedFound bool
    	}{
    		{
    			path:          []string{"a", "b", "c"},
    			expectedVal:   42,
    			expectedFound: true,
    		},
    		{
    			path:          []string{"a", "d"},
    			expectedVal:   "hello",
    			expectedFound: true,
    		},
    		{
    			path:          []string{"x"},
    			expectedVal:   3.14,
    			expectedFound: true,
    		},
    		{
    			path:          []string{"a", "not_found"},
    			expectedVal:   nil,
    			expectedFound: false,
    		},
    	}
    
    	for _, tt := range tests {
    		val, found := GetFromMap(tt.path, data)
    		if found != tt.expectedFound {
    			t.Errorf("GetFromMap(%v) found = %v; want %v", tt.path, found, tt.expectedFound)
    		}
    		if val != tt.expectedVal {
    			t.Errorf("GetFromMap(%v) = %v; want %v", tt.path, val, tt.expectedVal)
    		}
    	}
    }

    E, por favor, quando for criar uma API, lembre-se do seu colega programador que vai consumir a API. Estruturas simples e planas que não mudam de formato com frequência são melhores. Aliás, simples sempre é melhor.