279 pessoas já começaram a estudar hoje
279279pessoasjá começaram a estudar hoje
já começaram a estudar hoje

Enquanto você avalia,

279279pessoas já começaram a estudar hoje

Dê o próximo passo e inicie sua carreira

Dê o próximo passo e inicie sua carreira agora mesmo

Comece agora
  • Home

    Escolha seu Plano

    • Plus 2430% OFF
    • Pro 2430% OFF
    • Ultra Lab 2430% OFF

    Carreiras Alura

    Evolua com profundidade técnica, direção clara e aplicação prática.

    Jornadas guiadas do básico ao avançado com checkpoints práticos, desenhadas para quem busca profundidade e protagonismo técnico.

    Iniciante

    Comece do zero com uma trilha clara e progressiva.

    Fundamentos práticos para dar os primeiros passos com segurança e construir uma base sólida.

    Intermediário

    Continue sua evolução com cursos que conectam fundamentos, ferramentas e prática profissional. Ganhe autonomia, e avance para projetos mais completos.

    Avançado

    Aprofunde temas técnicos e estratégicos para enfrentar desafios mais complexos. Explore boas práticas, arquitetura, performance com visão profissional.

    CURSOS

    Ver mais cursos de Back-end
    • Pensamento computacional: fundamentos da computação e lógica de programação
    • Lógica de programação: mergulhe em programação com JavaScript
    • DDD: fundamentos do design orientado a domínio
    • ASP.NET: autenticação e autorização em APIs e aplicações web
    • Microsserviços: migração de monólitos e modularização

    CURSOS

    Ver mais cursos de Dados
    • Python para Dados: primeiros passos
    • Excel: domine o editor de planilhas
    • Engenharia de Dados: Orquestração de Pipelines com Apache Airflow
    • Power BI Desktop: construindo meu primeiro dashboard
    • Governança e Arquitetura de Dados

    CURSOS

    Ver mais cursos de Inteligência Artificial
    • IA: Explorando o Potencial da Inteligência Artificial Generativa
    • Engenharia de Prompt: Criando Prompts Eficazes para IA Generativa
    • Claude para Análise de Dados: prompts, integrações e automações
    • Agno: criando agentes e sistemas multiagente
    • Engenharia de software na era da IA: segurança de aplicações com agentes, MCPs e código gerado por IA

    CURSOS

    Ver mais cursos de Front-end
    • HTML e CSS: ambiente, estrutura e estilo
    • React: realizando testes avançados com Jest e Testing Library
    • React: integrando TypeScript em projetos
    • JavaScript: aprendendo a programar
    • React: aplicando arquiteturas de Micro frontends

    CURSOS

    Ver mais cursos de DevOps
    • Sistema Operacional Linux: fundamentos e administração prática
    • Containers e Docker: empacotamento, isolamento e gestão
    • Redes: dos conceitos iniciais à criação de uma intranet
    • Linux: gerenciando diretórios, arquivos, permissões e processos
    • Cibersegurança: Fundamentos e práticas integradas

    CURSOS

    Ver mais cursos de UX & Design
    • UX Research: mapeando a experiência da pessoa usuária
    • UX/UI Design: Entregando produtos digitais com IA
    • UI Design: Prototipação e animações interativas
    • Figma: Conhecendo o programa
    • Product Design: métricas e ciclo de vida do produto

    CURSOS

    Ver mais cursos de Inovação e Gestão
    • Canva: Criação de landing pages e e-mail marketing
    • Agilidade: como ela pode ajudar a criar um time de alta performance
    • Google Analytics 4: Extrair insights e configurar eventos
    • Management 3.0: gerencie o ambiente, não as pessoas
    • Automação de processos com n8n: Inteligência de dados para Marketing

    CURSOS

    Ver mais cursos de Mobile
    • React Native: Estilização e Layouts com Flexbox e StyleSheet
    • Flutter: Graphql e suporte offline
    • Android com Gemini: Trabalhando com textos e imagens na IA
    • Dart: trabalhando com orientação a objetos
    • React Native: Dominando Listas com FlatList e ScrollView

    CARREIRAS

    Ver mais carreiras
    • Especialista em IA
    • Engenharia de IA
    • Ai native software engineering
    • Arquitetura de Soluções com IA
    • AI Product Design
    • Engenharia de Machine Learning
    • Desenvolvimento Front-End React
    • Engenharia de Dados
    • Cloud Security
    • Social Media Marketing
    • Engenharia de Agentes de IA

    CURSOS

    • Engenharia de Prompt: Prompts Eficazes para IA Generativa
    • Claude Code: Criando sua Primeira Aplicação
    • Python: Crie sua Primeira Aplicação
    • Git e GitHub: Compartilhando e Colaborando
    • N8N: Fluxos de Trabalho Avançados

    CURSOS

    • Copilot Studio: Solução Multiagentes
    • Padrões de API HTTP e Modelagem de APIs
    • Python para Análise de Dados com SQL
    • Docker: Criando e Gerenciando Containers
    • Design com IA: Otimizando o Processo Criativo

    CURSOS

    • Model Context Protocol (MCP)
    • Spec-Driven Development: Dev Assistido por Agentes
    • Arquitetura de Sistemas Distribuídos com Java
    • Governança de Modelos e Reprodutibilidade
    • Pentest: Vulnerabilidades em Aplicações Web

    Escolha seu Plano

    • Plus 2430% OFF
    • Pro 2430% OFF
    • Ultra Lab 2430% OFF

    Skills & Go

    O Skills & Go é para Tech Leads, Product Managers, estrategistas e early adopters que transformam ideias em soluções de alto impacto. Em cursos ao vivo e 100% online, você domina a nova era da IA em tempo real.

    Aulas ao vivo

    Saiba mais
    • Agentic Engineering: orquestre multiplos agentes com confiabilidade
    • Building AI Products: transforme ideias em produtos reais com IA
    • AI Data Strategy: transforme dados em decisões estratégicas com IA

    Escolha seu Plano

    • Plus 2430% OFF
    • Pro 2430% OFF
    • Ultra Lab 2430% OFF

    Eventos Alura

    Nossos eventos são pensados para quem quer estar à frente das mudanças em tecnologia, IA e inovação. Em experiências presenciais, você acompanha tendências e se conecta com especialistas que estão moldando o futuro.

    Próximos eventos

    • IA Conference
    • Alura Signals
  • Talent Lab
  • IA
  • Artigos
  • Para Empresas
Ver planos
Links principais
  • Planos e Promoções
  • Carreiras

    Ver todas
    • Especialista em IA
    • Engenharia de IA
    • Ai native software engineering
    • Arquitetura de Soluções com IA
    • AI Product Design
    • Engenharia de Machine Learning
    • Desenvolvimento Front-End React
    • Engenharia de Dados
    • Cloud Security
    • Social Media Marketing
    • Engenharia de Agentes de IA
    Entrar Ver Planos
  • Área de Interesse

    Entrar Ver Planos
  • Aulas ao Vivo - Skills & Go

    • Agentic Engineering: orquestre multiplos agentes com confiabilidade
    • Building AI Products: transforme ideias em produtos reais com IA
    • AI Data Strategy: transforme dados em decisões estratégicas com IA
    Entrar Ver Planos
  • Senioridade

    Entrar Ver Planos
  • Eventos Alura

    • IA Conference
    • Alura Signals
    Entrar Ver Planos
  • Links complementares do menu mobile
  • Talent lab
  • Inteligência Artificial
  • Pós Graduações
  • Artigos
  • Para Empresas
  • Sobre a Alura
  • Grupo Alun
  • Entrar
    • Home
    • Carreiras
    • Skills & Go
    • Talent Lab
    • Inteligência Artificial
    • Pós Graduações
    • Artigos
    • Sobre a Alura
    • Grupo Alun
    LoginContato
    • Plano Pro 24
    • Plano Plus 24
    • Plano Ultra Lab 24
    • LinkedIn
    • Instagram
    • YouTube
    1. Página inicial
    2. Back-end
    3. SOLID: o que é e quais os 5 princípios da Programação Orientada a Objetos (POO)

    SOLID: o que é e quais os 5 princípios da Programação Orientada a Objetos (POO)

    26 min26 minutos de leitura

    26 min26 minutos de leitura

    Publicado: 03/01/2024

    Última atualização: 1 de Julho

    1. O que é SOLID?
    2. Origem dos princípios SOLID
    3. O que é Programação Orientada a Objetos (POO)?
    4. Princípio da Responsabilidade Única (S - Single Responsibility Principle)
    5. Princípio Aberto-Fechado (O - Open Closed Principle)
    6. Princípio de Substituição de Liskov (L - Liskov Substitution Principle)
    7. Princípio de Segregação de Interface (I - Interface Segregation Principle)
    8. Princípio da Inversão de Dependência (D - Dependency Inversion Principle)
    9. Solução
    10. Definição do Princípio da Inversão de Dependência
    11. Clean Code
    12. Vantagens de deixar o código limpo
    13. Conclusão

    Fique por dentro do que são os princípios SOLID e como podemos aplicá-los nos códigos para aumentar a qualidade dos projetos.


    A qualidade de software é um fator fundamental para o sucesso no desenvolvimento de qualquer projeto.

    Afinal, aplicações bem projetadas, com boas práticas, códigos limpos e organizados, não só reduzem a incidência de erros, como também facilitam a manutenção e expansão ao longo do tempo.

    E aí surge uma questão importante: o que podemos fazer para aumentar a qualidade do software?

    Não tem uma única resposta para essa pergunta. Mas, dentre tantas possibilidades, uma opção é que seu código siga os princípios SOLID.

    Neste artigo, vou explicar quais são esses princípios e como podemos aplicá-los para aumentar a qualidade dos projetos. Além disso, vou explorar o Clean Code e as boas práticas de programação.

    Vamos lá?

    O que é SOLID?

    O acrônimo SOLID representa os cinco princípios que facilitam o processo de desenvolvimento — o que facilita a manutenção e a expansão do software.

    Estes princípios são fundamentais na programação orientada a objetos e podem ser aplicados em qualquer linguagem que adote este paradigma.

    Os 5 princípios são:

    • S — Single Responsibility Principle (Princípio da responsabilidade única)
    • O — Open-Closed Principle (Princípio Aberto-Fechado)
    • L — Liskov Substitution Principle (Princípio da substituição de Liskov)
    • I — Interface Segregation Principle (Princípio da Segregação da Interface)
    • D — Dependency Inversion Principle (Princípio da inversão da dependência)
    Figura representando o SOLID. Há 5 colunas brancas, sustentando uma plataforma. Acima de cada coluna, na plataforma, há uma letra, representando cada princípio. Abaixo de cada coluna, há o nome em português e a sigla em inglês dos princípios, contornados por um retângulo verde. Dessa forma, acima da primeira coluna há a letra S, e abaixo dela há “Responsabilidade Única (SRP) “. Acima da segunda coluna, há a letra O e abaixo dela, “Aberto/Fechado (OCP)”. Acima da terceira coluna, a letra L, e abaixo, “Substituição de Liskov (LSP)”. Acima da quarta coluna, a letra I, e abaixo dela, “Segregação de interface (ISP)”. Por fim, acima da quinta e última coluna, há a letra D, e abaixo, “Inversão de Dependência (DIP)”.

    Banner da Alura convidando profissionais a desenvolver habilidades em inteligência artificial para acompanhar as transformações do mercado de tecnologia. A campanha destaca que quem utiliza IA produz mais, cresce na carreira e se torna mais competitivo, reforçando que a inteligência artificial deixou de ser tendência e passou a ser uma habilidade essencial. O banner incentiva a começar a aprender IA com os cursos da Alura e impulsionar a transformação digital.

    Origem dos princípios SOLID

    O primeiro indício dos princípios SOLID apareceu em 1995, no artigo “The principles of OoD” de Robert C Martin, também conhecido como “Uncle Bob”.

    Nos anos seguintes, Robert se dedicou a escrever mais sobre o tema, consolidando esses princípios de forma categórica.

    E, em 2002, lançou o livro “Agile Software Development, Principles, Patterns, and Practices” que reúne diversos artigos sobre o tema.

    Fotografia de Robert Martin. Homem branco com cabelo grisalho e louro, usando óculos. Ele olha e aponta para a câmera e se encontra sentado em uma poltrona branca estampada. Veste um casaco fechado azul marinho.

    No entanto, a sigla SOLID só foi apresentada mais tarde, por Michael Feathers.

    Fotografia de Michael Feathers. Homem de cabelo e barba branca. Pele branca. Ele está em um ambiente iluminado, ao fundo uma parede na cor verde

    Agora que você já sabe o significado da sigla e a origem dos princípios, é importante dar um passo para trás para compreender o paradigma da Programação Orientada a Objetos (POO). Afinal de contas, como você já sabe, através da POO é possível aplicar os princípios SOLID.

    O que é Programação Orientada a Objetos (POO)?

    A Programação Orientada a Objetos (POO) é um paradigma que se baseia no conceito de “objetos”, que representam instâncias concretas ou abstrações de conceitos do mundo real.

    Vamos aplicar essa ideia em um exemplo para compreender melhor. Olha só essa imagem, que representa um sistema de identificação de carros:

    Imagem representando 2 categorias: Classe, Objetos. Na parte superior, categoria Classe, onde é ilustrado um carro abstrato na cor cinza, ao lado desta imagem há as seguintes características: Tipo, Cor, Placa e Número de portas. Ambas as características com o valor de ?. Na categoria de objetos situado na parte inferior. Há dois carros. Um branco com as seguintes propriedades: Tipo: Porsche, Cor: Branco, Placa: MHZ-4345, Número de portas: 4. Em sequência há o carro vermelho, com as seguintes características: Tipo: Ferrari, Cor: Vermelho, Placa: JKL-0001,Número de portas: 2

    Perceba que a classe “carro” representa um modelo abstrato do que um carro precisa ter: o tipo, cor, placa e número de portas.

    A partir desse modelo (que é a classe), podemos criar representações mais específicas de carros, que são os objetos:

    • Tipo: Porsche
    • Cor: Branco
    • Placa: MHZ-4345
    • Número de portas: 4

    ou

    • Tipo: Ferrari
    • Cor: Vermelho
    • Placa: JKL-0001
    • Número de portas: 4

    Assim, se for necessário incluir mais informações sobre os carros, como, por exemplo, se tem ar condicionado ou não, podemos mudar a classe.

    A partir disso, todos os objetos serão obrigatoriamente atualizados com um novo campo que deve ser preenchido.

    Nesse sentido, o paradigma promove a reutilização de código e facilita a manutenção desde sistemas mais simples, tal qual o exemplo, até os mais complexos.

    Vale dizer que a Programação Orientada a Objetos é utilizada em muitas linguagens de programação populares, como Java, Python, PHP, C++, C#, entre outras.

    Tem um episódio do #HipstersPontoTube, em que o host Paulo Silveira bate um papo com a Roberta Arcoverde sobre o uso de orientação a objeto e suas aplicações.

    Agora que você já está por dentro do conceito de POO, está na hora de aprender sobre cada um dos princípios SOLID.

    Princípio da Responsabilidade Única (S - Single Responsibility Principle)

    Para entender o princípio da responsabilidade única, vamos pensar no desenvolvimento de um gerenciador de tarefas. Vamos começar com o seguinte código:

    public class GerenciadorTarefas {
        public String conectarAPI(){
            //...
        }
        public void criarTarefa(){
            //...
        }
        public void atualizarTarefa(){
            //...
        }
        public void removerTarefa(){
            //...
        }
        public void enviarNotificacao(){
            //...
        }
        public void produzirRelatorio(){
            //...
        }
        public void enviarRelatorio(){
            //...
        }
    }
    

    Problemática

    Tente enumerar todas as funções que a classe GerenciadorTarefas tem. Ela é responsável por lidar com todas as operações das tarefas em si e também está consumindo uma API, enviando notificações para pessoas usuárias e ainda gerando relatórios da aplicação.

    Pense na Orientação a Objetos. Um objeto gerenciador de tarefas deveria enviar e-mails e gerar relatórios? Não! Um gerenciador de tarefas gerencia as tarefas, não e-mails ou relatórios.

    Imagem de um canivete suíço com muitas utilidades, sendo a mistura de diversas ferramentas, como chaves e facas diferentes. Essas ferramentas se amontoam dos lados do canivete, deixando-o difícil de manusear.

    Como na imagem, não é legal que uma única ferramenta tenha 1001 utilidades, pois pode causar confusão.

    Isso se intensifica quando falamos de desenvolvimento de softwares. O ideal é criar várias ferramentas diferentes, cada uma com sua própria função.

    Solução

    Para resolver esse problema vamos criar classes diferentes, cada uma representando uma função.

    Nossa classe GerenciadorTarefas terá apenas o código relacionando a operação com tarefas. Outras operações estarão em outras classes. E cada classe será responsável por uma parte diferente da aplicação.

    Assim, teremos a classe GerenciadorTarefas refatorada:

    public class GerenciadorTarefas {
    
    public void criarTarefa(){
        //...
    }
    public void atualizarTarefa(){
        //...
    }
    public void removerTarefa(){
        //...
    }
    
    
    

    }

    Assim, vamos criar uma classe para consumir uma API externa, outra classe para enviar notificações e uma última classe para lidar com os relatórios.

    public class ConectorAPI {
        public String conectarAPI() {
            //...
        }
    }
    
    public class Notificador {
        public void enviarNotificacao() {
            //...
        }
    } 
    
    public class GeradorRelatorio {
        public void produzirRelatorio(){
            //...
        }
        public void enviarRelatorio(){
            //...
        }
    }
    

    Talvez você se pergunte se as classes não são pequenas demais. Nesse caso, não estão. Cada classe reflete exatamente a responsabilidade que ela tem.

    Se precisarmos adicionar algum método, por exemplo, relacionado ao consumo da API, vamos saber exatamente em qual parte do código devemos ir. Ou seja, fica muito mais fácil alterar o que for preciso.

    Definição do Princípio da Responsabilidade Única

    Em resumo, o princípio da responsabilidade única diz que: “Cada classe deve ter um, e somente um, motivo para mudar.”

    Se uma classe tem várias responsabilidades, mudar um requisito do projeto pode trazer várias razões para modificar a classe. Por isso, as classes devem ter responsabilidades únicas.

    Esse princípio pode ser estendido para os métodos que criamos também. Quanto mais tarefas um método executa, mais difícil é testá-lo e garantir que o programa está em ordem.

    Uma dica para aplicar o princípio na prática é tentar nomear suas classes ou métodos com tudo que eles são capazes de fazer.

    Se o nome está gigante, como GerenciadorTarefasEmailsRelatorios, temos um sinal de que o código pode ser refatorado.

    Vantagens de aplicar o Princípio da Responsabilidade Única

    Existem vários benefícios ao aplicar esse princípio, principalmente:

    • Facilidade para fazer manutenções
    • Reusabilidade das classes
    • Facilidade para realizar testes
    • Simplificação da legibilidade do código

    Princípio Aberto-Fechado (O - Open Closed Principle)

    Para entender o Princípio Aberto-Fechado (a letra O da sigla), vamos pensar que estamos trabalhando no sistema de uma clínica médica.

    Nessa clínica, existe uma classe que trata das solicitações de exames. Inicialmente, o único exame possível é o exame de sangue. Por isso, temos o código:

    public class AprovaExame {
        public void aprovarSolicitacaoExame(Exame exame){
            if(verificaCondicoesExameSangue(exame))
                System.out.println("Exame aprovado!");
        }
        public boolean verificaCondicoesExameSangue(){
            //....
        }
    }
    

    Agora, precisamos incluir uma nova funcionalidade ao sistema: a clínica vai começar a fazer exames de Raio-X. Como incluir isso no nosso código?

    Uma alternativa seria verificar qual o tipo de exame está sendo feito para poder aprová-lo:

    public class AprovaExame {
        public void aprovarSolicitacaoExame(Exame exame){
            if(exame.tipo == SANGUE){
                if(verificaCondicoesExameSangue(exame))
                    System.out.println("Exame sanguíneo aprovado!");
            } else if(exame.tipo == RAIOX) {
                if (verificaCondicoesRaioX(exame))
                    System.out.println("Raio X aprovado!");
            }
    
    }
    private boolean verificaCondicoesExameSangue(){
        //....
    }
    private boolean verificaCondicoesRaioX(){
        //....
    }
    
    
    

    }

    Problemática

    A princípio parece tudo certo, não é mesmo? Nosso código executa normalmente e conseguimos adicionar a funcionalidade corretamente.

    Mas, e se além de raio-x, a clínica passasse a fazer também ultrassons? Seguindo a lógica, iríamos adicionar mais um if no código e mais um método para olhar condições específicas do exame.

    Essa definitivamente não é uma boa estratégia. Cada vez que incluir uma função, a classe (e o projeto como um todo) vai ficar mais complexa.

    Por isso, é necessário uma estratégia para adicionar mais recursos ao projeto, sem modificar e bagunçar a classe original.

    Solução

    Nesse cenário, o projeto compreende vários tipos de aprovação de exames. Assim, podemos criar uma classe ou uma interface que representa uma aprovação de forma genérica.

    A cada tipo de exame fornecido pela clínica, é possível criar novos tipos de aprovação, mais específicos, que irão implementar a interface. Assim, podemos ter o código:

    public interface AprovaExame{
        void aprovarSolicitacaoExame(Exame exame);
        boolean verificaCondicoesExame(Exame exame);
    }
    
    public class AprovaExameSangue implements AprovaExame{
        @Override
        public void aprovarSolicitacaoExame(Exame exame){
                if(verificaCondicoesExame(exame))
                    System.out.println("Exame sanguíneo aprovado!");
        }
        @Override
        boolean verificaCondicoesExame(Exame exame){
            //....
        }
    }
    
    public class AprovaRaioX implements AprovaExame{
        @Override
        public void aprovarSolicitacaoExame(Exame exame){
            if(verificaCondicoesExame(exame))
                System.out.println("Raio-X aprovado!");
        }
        @Override
        boolean verificaCondicoesExame(Exame exame){
            //....
        }
    }
    

    Agora, como a interface representa a aprovação de um exame, para incluir mais um recurso ou mais um tipo de exame, basta criar uma nova classe que implementa a interface AprovaExame. Essa classe vai representar como o novo exame é aprovado.

    Repare que sempre será possível implementar a interface AprovaExame ao adicionarmos recursos. Essa interface, no entanto, não muda. Estamos estendendo-a, mas não alterando.

    Definição do Princípio Aberto-Fechado

    Assim, é possível definir o Princípio Aberto-Fechado como: “entidades de software (como classes e métodos) devem estar abertas para extensão, mas fechadas para modificação”.

    Ou seja, se uma classe está aberta para modificação, quanto mais recursos adicionarmos, mais complexa ela vai ficar.

    O ideal é adaptar o código não para alterar a classe, mas para estendê-la. Em geral, isso é feito quando abstraímos um código para uma interface.

    Aplicando o Open-Closed, é possível deixar o nosso código semelhante ao mundo real, praticando de maneira sólida a orientação a objetos.

    Pense em um caminhão: toda a sua implementação, como motor, bateria e cabine é fechada para modificação.

    Porém, podemos estender as tarefas que ele realiza dependendo da carroceria que anexamos, conforme mostra a figura abaixo:

    Imagem de um caminhão e suas várias carrocerias. À direita, há a cabine - parte da frente - do caminhão. à esquerda, há 3 tipos diferentes de carrocerias do caminhão.  A primeira é retangular, a segunda é em forma de trapézio e a terceira é retangular arredondada. Ao lado de cada uma das três, têm setas apontando para a cabine, mostrando que elas podem se encaixar na primeira.

    Vantagens de aplicar o Princípio Aberto-Fechado

    Ao aplicar esse princípio, é possível tornar o projeto muito mais flexível. Adicionar novas funcionalidades torna-se uma tarefa mais fácil.

    Além disso, os códigos ficam mais simples de ler. Com isso tudo, o risco de introduzir bugs diminui de forma significativa.

    Além disso, esse princípio nos faz caminhar diretamente para a aplicação de alguns padrões de projeto, como o Strategy.

    Assim, alinhamos várias boas práticas de desenvolvimento. O resultado disso é um código cada vez mais limpo e organizado.

    Princípio de Substituição de Liskov (L - Liskov Substitution Principle)

    Para entender o Princípio de Substituição de Liskov (a letra L da sigla), vamos pensar no seguinte cenário: o desenvolvimento de um sistema de uma faculdade.

    Dentro do sistema, há uma classe-mãe Estudante, que representa um estudante de graduação, e a filha dela, EstudantePosGraduacao, tendo o seguinte código:

    public class Estudante {
        String nome;
        public Estudante(String nome) {
            this.nome = nome;
        }
        public void estudar() {
            System.out.println(nome + " está estudando.");
        }
    }
    
    public class EstudanteDePosGraduacao extends Estudante {
        @Override
        public void estudar() {
            System.out.println(nome + " está estudando e pesquisando.");
        }
    }
    

    Para adicionar a funcionalidade entregarTCC() ao sistema, basta colocar esse método na classe Estudante O código fica assim:

    class Estudante {
        String nome;
        public Estudante(String nome) {
            this.nome = nome;
        }
        public void estudar() {
            System.out.println(nome + " está estudando.");
        }
        public void entregarTCC(){
    	//…
        }
    }
    

    Problemática

    Você provavelmente já percebeu algo errado no código. Normalmente, estudantes de pós-graduação não entregam TCCs.

    Só que a classe EstudanteDePosGraduacao é filha de Estudante, e portanto, deve apresentar todos os comportamentos dela.

    Uma alternativa seria sobrescrever o método entregarTCC na classe EstudanteDePosGraduacao lançando uma exceção.

    No entanto, continuaria sendo problemático: a classe EstudanteDePosGraduacao ainda não teria os comportamentos iguais aos de Estudante.

    O ideal é que, nos lugares que estiver a classe Estudante, seja possível usar uma classe EstudanteDePosGraduacao, já que, pela herança, um estudante de pós-graduação é um estudante.

    Solução

    A solução para este problema é modificar a nossa modelagem. Podemos criar uma nova classe EstudanteDeGraduacao, que também herdará de Estudante. Essa classe terá o método entregarTCC:

    public class EstudanteDeGraduacao extends Estudante {
        public void estudar() {
            System.out.println(nome + " está estudando na graduação.");
        }
        public void entregarTCC() {
    	//…
        }
    }
    

    Repare que, dessa forma, nossas classes representam melhor o mundo real. Não estamos forçando uma classe a fazer algo que ela originalmente não faz.

    Além disso, se precisarmos utilizar uma instância de Estudante, podemos passar, sem medo, uma instância de EstudanteDeGraduacao ou de EstudanteDePosGraduacao.

    Afinal de contas, essas classes conseguem executar todas as funções de Estudante — mesmo tendo funções mais específicas.

    Definição do Princípio da Substituição de Liskov

    Quem propôs o Princípio da Substituição de Liskov, de maneira formal e matemática, foi Bárbara Loskov.

    No entanto, Robert Martin deu uma definição mais simples para ele: “Classes derivadas (ou classes-filhas) devem ser capazes de substituir suas classes-base (ou classes-mães)”.

    Ou seja, uma classe-filha deve ser capaz de executar tudo que sua classe-mãe faz. Esse princípio se conecta com o polimorfismo e reforça esse pilar da POO.

    É importante notar também que, ao entendermos esse princípio, passamos a nos atentar mais para o código: caso um método de uma classe-filha tenha um retorno muito diferente do da classe-mãe, ou lance uma exceção, por exemplo, já dá para perceber que algo está errado.

    Se no seu programa você tem uma abstração que se parece com um pato, faz o som de um pato, nada como um pato, mas precisa de baterias, sua abstração está equivocada.

    Imagine que, no seu projeto, você tem uma classe Pato, e uma classe filha dela, PatoBorracha. Nessa imagem você pode perceber que um pato e um pato de borracha são bem diferentes:

    Imagem comparando dois patos. A imagem é composta por duas fotos, uma com cada pato, e as fotos estão separadas por um símbolo de diferente. Os dois patos estão sob a água, iluminados com uma luz azulada. O primeiro pato, à esquerda, é um pato real, com penas brancas e cinzas e bico laranja. O segundo, à direita, é um pato amarelo, de borracha, também com bico laranja.

    Se em uma parte do código você precisar usar um objeto Pato, mas usar um PatoBorracha no seu lugar, pode ter problemas.

    Isso fere o princípio de substituição de Liskov, já que não conseguimos substituir um pai por um filho completamente.

    Vantagens de aplicar o Princípio da Substituição de Liskov

    Aplicar esse princípio nos traz diversos benefícios, especialmente para ter uma modelagem mais fiel à realidade, reduzir erros inesperados no programa e simplificar a manutenção do código.

    Princípio de Segregação de Interface (I - Interface Segregation Principle)

    Para entender o Princípio de Segregação da Interface, imagine que estamos trabalhando com um sistema de gerenciamento de funcionários de uma empresa.

    Vamos criar uma interface, conforme o código abaixo:

    Interface Funcionário

    public interface Funcionario {
        public BigDecimal salario();
        public BigDecimal gerarComissao();
    

    }

    Repare que criamos a interface para estabelecer um “contrato” com as pessoas que são funcionárias dessa empresa. N

    esse contexto, o código a seguir descreve duas classes que fazem referências a duas profissões nessa empresa: Vendedor e Recepcionista.

    Ambas usam a interface Funcionario e, portanto, devem implementar os métodos salario() e gerarComissao().

    Classe Vendedor

    import java.math.BigDecimal;
    public class Vendedor implements Funcionario {
        @Override
        public BigDecimal salario() {
        }
        @Override
        public BigDecimal gerarComissao() {
        }
    

    }

    Classe Recepcionista

    import java.math.BigDecimal;
    public class Recepcionista implements Funcionario{
        @Override
        public BigDecimal salario() {
        }
        @Override
        public BigDecimal gerarComissao() {
        }
    

    }

    Problemática

    Analisando o código acima, faz sentido uma pessoa que possui o cargo de vendedora ou recepcionista ter salário? Sim! Afinal, todos nós temos boletos para pagar.

    Seguindo esta mesma linha, faz sentido uma pessoa com cargo de vendedor ou recepcionista ter comissão? Não!.

    Para uma pessoa que tem o cargo de vendedora, faz sentido. Mas para a pessoa que tem o cargo de recepcionista, não faz sentido.

    Ou seja, a classe Recepcionista foi forçada a implementar um método que não faz sentido para ela. Embora ela seja funcionária dessa empresa, esse cargo não recebe comissão.

    Portanto, podemos perceber que este problema foi gerado por temos uma interface genérica.

    Solução

    Para resolver isso, é possível criar Interfaces específicas. Ao invés de ter uma única interface Funcionário, podemos ter duas: Funcionario e Comissionavel.

    Interface Funcionário

    import java.math.BigDecimal;
    public interface Funcionario {
        public BigDecimal salario();
    }
    

    Repare que mantemos a interface Funcionario, mas retiramos o método gerarComissao() a qual é específico de algumas pessoas, para adicioná-lo em uma nova interface FuncionarioComissionavel:

    Interface Comissionável

    import java.math.BigDecimal;
    public interface Comissionavel{
        public BigDecimal gerarComissao();
    }
    

    Agora, a pessoa que possui o direito de ter comissão irá implementar a interface Comissionavel, um exemplo disso é a classe Vendedor:

    Vendedor

    import java.math.BigDecimal;
    public class Vendedor implements Funcionario, Comissionavel{
        @Override
        public BigDecimal salario() {
        }
        @Override
        public BigDecimal gerarComissao() {
        }
    }
    

    Agora, a classe Recepcionista pode implementar a interface Funcionario sem ter a obrigação de criar o método gerarComissao():

    Recepcionista

    import java.math.BigDecimal;
    public class Recepcionista implements Funcionario{
        @Override
        public BigDecimal salario() {
        }
    }
    

    Definição do Princípio da Segregação da Interface

    Conforme analisamos o código acima, podemos perceber que:

    Devemos criar interfaces específicas ao invés de termos uma única interface genérica.

    E é justamente isto que Princípio da Segregação da Interface diz: “Uma classe não deve ser forçada a implementar interfaces e métodos que não serão utilizados”.

    É possível que você já tenha comprado um adaptador com várias entradas, tal qual a foto. Na maioria das vezes, as pessoas não sabem a utilidade de todas as conexões.

    Imagem de um dispositivo eletrônico multifuncional em cor preta, destacando suas seis conexões, incluindo portas USB e tipo C.

    Seguindo essa analogia, se não precisamos de um conector ou de uma entrada específica, não faz sentido incluí-los — assim como comprar um conector sob medida para um aparelho específico.

    Ou seja, uma classe também não deve ser obrigada a implementar métodos que não serão utilizados.

    Vantagens de aplicar o Princípio da Segregação da Interface

    Seguir o Princípio da Segregação da Interface ajuda a promover a coesão e a flexibilidade em nossos sistemas, tornando-os fáceis de manter e estender.

    Princípio da Inversão de Dependência (D - Dependency Inversion Principle)

    Para compreender o Princípio da Inversão de Dependência (letra D da sigla) imagine que estamos trabalhando em uma startup de e-commerce e precisamos desenvolver o sistema de gerenciamento de pedidos.

    Sem conhecer o Princípio da Inversão de Dependência, é bem provável que vamos desenvolver uma classe PedidoService semelhante ao código abaixo:

    Classe PedidoService

    public class PedidoService {
        private PedidoRepository repository;
        public PedidoService() {
            this.repository = new PedidoRepository();
        }
        public void processarPedido(Pedido pedido) {
            // Lógica de processamento do pedido
            repository.salvarPedido(pedido);
        }
    }
    

    Problemática

    Aparentemente, o código parece estar certo. No entanto, se um dia precisar alterar o armazenamento deste pedido para um outro lugar (por exemplo, uma API externa), vai precisar de mais de uma classe para resolver o problema.

    Afinal, a classe PedidoService está diretamente acoplada à implementação concreta da classe PedidoRepository.

    Solução

    Para resolver este problema, podemos criar uma interface para a classe de acesso ao banco de dados e injetá-la na classe `PedidoService´.

    Dessa forma, nós estamos dependendo de abstrações e não de implementações concretas.

    Interface PedidoRepository

    public interface PedidoRepository {
        void salvarPedido(Pedido pedido);
    }
    

    Classe PedidoService

    public class PedidoService {
        private PedidoRepository repository;
        public PedidoService(PedidoRepository repository) {
            this.repository = repository;
        }
        public void processarPedido(Pedido pedido) {
            // Lógica de processamento do pedido
            repository.salvarPedido(pedido);
        }
    }
    

    Deste modo, conseguimos fazer com que a classe de alto nível (PedidoService) seja independente dos detalhes de implementação da classe de baixo nível (PedidoRepository).

    Definição do Princípio da Inversão de Dependência

    O Princípio da Inversão de Dependência diz: “dependa de abstrações e não de implementações concretas”.

    Assim, é recomendado que os módulos de alto nível não dependam diretamente dos detalhes de implementação de módulos de baixo nível.

    Em vez disso, eles devem depender de abstrações ou interfaces que definem contratos de funcionamento. Isso promove maior flexibilidade e facilita a manutenção do sistema.

    Por exemplo, a funcionalidade de um equipamento eletrônico qualquer é garantida pela conexão adequada entre o plug e a tomada, não é mesmo?

    Representação visual de uma pessoa conectando um equipamento à tomada. O ambiente é composto por uma parede branca com um interruptor visível, destacando a ação prática de plugar o dispositivo.

    Nessa analogia, os módulos de alto nível representam o plug, enquanto os módulos de baixo nível correspondem à tomada.

    Da mesma forma que um plug se conecta à tomada independentemente de seus detalhes internos, os módulos de alto nível devem se vincular a abstrações ou interfaces, estabelecendo contratos de funcionamento.

    Essa abordagem assemelha-se a usar um plug padronizado, garantindo uma conexão flexível e fácil manutenção.

    Vantagens de aplicar o Princípio da Inversão de Dependência

    A adesão ao Princípio de Inversão de Dependência promove a flexibilidade e a extensibilidade dos nossos sistemas.

    Isso faz com que seja mais fácil fazer testes de unidade e construir códigos mais robustos e duradouros.

    Agora que abordamos todos os conceitos do SOLID com exemplos práticos, vamos conhecer de que forma este princípio está ligado ao Clean Code.

    Clean Code

    No universo da programação, frequentemente nos deparamos com o termo: Clean Code ou Código Limpo.

    Mas o que exatamente é um “código limpo”? Quais características são necessárias para obtê-lo?

    Escrever um código limpo significa escrever códigos de um jeito que conseguimos entendê-lo sem complicação.

    Isso não apenas simplifica a manipulação do código, mas também facilita a colaboração entre o time. No fim das contas, todo desenvolvimento e manutenção do sistema também se torna mais fácil.

    De acordo com "Uncle Bob", em seu livro “Código Limpo: Habilidades Práticas do Software Ágil”, existem algumas boas práticas fundamentais para alcançar a clareza do código.

    Vamos conhecê-las, a seguir:

    Utilizar os princípios SOLID:

    O Clean Code e os princípios SOLID compartilham o objetivo de melhorar a qualidade do software, tornando-o legível, organizado, extensível e fácil de manter.

    Neste episódio do #HipstersPontoTube sobre Clean Code e Solid, o host Paulo Silveira bate um papo com o Alberto Sousa para discutir se é necessário seguir essas práticas à risca para desenvolver um bom projeto.

    Possuir nomes significativos

    Nomes descritivos ajudam a entender a finalidade de uma parte do código sem a necessidade de comentários explicativos.

    Para ilustrar, considere o código a seguir:

    public static double conv(double tC) {
        double tF = (tC * 9 / 5) + 32;
        return tF;
    }
    

    Temos que nos esforçar para entender o que o código acima faz. Podemos melhorar o entendimento apenas adicionando nomes significativos para as variáveis e para o método:

    public static double converterCelsiusParaFahrenheit(double temperaturaCelsius) {
        double temperaturaFahrenheit = (temperaturaCelsius * 9 / 5) + 32;
        return temperaturaFahrenheit;
    }
    

    Agora, fica claro qual é o propósito do código, sem a necessidade de se lembrar de fórmulas ou realizar pesquisas adicionais. Isso economiza tempo e evita confusões desnecessárias.

    Priorizar o uso de funções pequenas

    Escrever métodos ou funções pequenas e focadas em uma única tarefa é fundamental para manter o código claro e seguir o princípio da responsabilidade única (SRP).

    Para ilustrar, considere o código a seguir:

    public class Main {
        public static void main(String[] args) {
            int[] numeros = {1, 2, 3, 4, 5};
    
        int soma = 0;
        for (int numero : numeros) {
            soma += numero;
        }
        
        double media = (double) soma / numeros.length;
        
        if (media > 3) {
            System.out.println("A média é maior que 3");
        } else {
            System.out.println("A média é menor ou igual a 3");
        }
    }
    
    
    

    }

    Apesar do uso de nomes descritivos, a legibilidade poderia ser melhorada dividindo as tarefas em funções distintas, cada uma com sua descrição. Por exemplo:

    public class Main {
        public static void main(String[] args) {
            int[] numeros = {1, 2, 3, 4, 5};
    
        int soma = calcularSoma(numeros);
        double media = calcularMedia(numeros);
        verificarEMostrarResultado(media);
    }
    
    public static int calcularSoma(int[] numeros) {
        int soma = 0;
        for (int numero : numeros) {
            soma += numero;
        }
        return soma;
    }
    
    public static double calcularMedia(int[] numeros) {
        return (double) calcularSoma(numeros) / numeros.length;
    }
    
    public static void verificarEMostrarResultado(double media) {
        if (media > 3) {
            System.out.println("A média é maior que 3");
        } else {
            System.out.println("A média é menor ou igual a 3");
        }
    }
    
    
    

    }

    Embora o código tenha ficado maior, ganhamos em legibilidade e segmentação. Qualquer pessoa que precise alterar a maneira como a média é exibida à pessoa usuária, só precisa modificar o método verificarEMostrarResultado.

    Isso demonstra como funções pequenas podem facilitar a manutenção e a compreensão do código.

    Evitar comentários desnecessários

    O código deve ser autoexplicativo, com nomes significativos e estrutura lógica clara. Comentários excessivos podem tornar o código poluído e difícil de manter.

    Para ilustrar, considere o código a seguir:

    public class R {
        private double w;
        private double h;
        // Método para calcular a área
        public double calc() {
            return w * h;
        }
    }
    

    Os nomes curtos para as variáveis dificultam o entendimento, fazendo necessário o uso de comentários no nosso código, deixando o nosso código sujo. Então, podemos resolver isso adicionando nomes descritivos e removendo os comentários:

    public class Retangulo {
        private double largura;
        private double altura;
        public Retangulo(double largura, double altura) {
            this.largura = largura;
            this.altura = altura;
        }
        public double calcularArea() {
            return largura * altura;
        }
    }
    

    Pronto, perceba como facilitou o entendimento. Qualquer pessoa desenvolvedora que ler este código consegue assimilar o que cada parte faz.

    Evitar complexidade

    A complexidade desnecessária pode aumentar a chance de erros e tornar o código difícil de manter. Um exemplo de código complexo para fazer algo simples, como somar dois números, seria:

    public void soma() {
            Scanner scanner = new Scanner(System.in);
            System.out.print("Digite o primeiro número: ");
            String num1String = scanner.nextLine();
            System.out.print("Digite o segundo número: ");
            String num2String = scanner.nextLine();
            boolean validInput = false;
            double num1 = 0;
            double num2 = 0;
            while (!validInput) {
                num1 = Double.parseDouble(num1String);
                num2 = Double.parseDouble(num2String);
                validInput = true;
            }
            double soma = num1 + num2;
            System.out.println("A soma dos números é: " + soma);
            scanner.close();
        }
    

    Repare que é feita uma verificação da entrada, para só depois convertê-la em double.

    Poderíamos simplesmente considerar que é esperado que o usuário digite um double e fazer o tratamento de exceções relacionado a isso:

    import java.util.InputMismatchException;
    import java.util.Scanner;
    public class SimpleSumWithErrorHandling {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            try {
                System.out.print("Digite o primeiro número: ");
                double num1 = scanner.nextDouble();
                System.out.print("Digite o segundo número: ");
                double num2 = scanner.nextDouble();
                double soma = num1 + num2;
                System.out.println("A soma dos números é: " + soma);
            } catch (InputMismatchException e) {
                System.out.println("Erro: Por favor, digite números válidos.");
            } finally {
                scanner.close();
            }
        }
    }
    

    Executamos a lógica desejada de forma rápida e fácil de compreender. Imagine que precisamos somar agora 3 variáveis, ao invés de duas.

    É mais fácil modificar o segundo código do que o primeiro. O segundo código é um exemplo de código limpo.

    Fazer o mínimo de argumentos

    Funções e métodos devem ter o mínimo possível de argumentos. Isso melhora a legibilidade e a facilidade de uso.

    No exemplo a seguir, note como cadastrar uma pessoa colaboradora é complexo ao passar muitos parâmetros:

    public static void cadastrarFuncionario(String nome, int idade, String cargo, double salario, String endereco, String cidade, String cep, String telefone, String email) {
            // Lógica de cadastro do funcionário aqui...
    
    }
    
    
    

    Ao chamar esse método, é difícil entender qual parâmetro utilizar em que lugar, podendo confundi-los, por exemplo.

    Uma boa alternativa seria criar uma classe para representar o funcionário, outra para o endereço, e mais uma para o contato. Assim, faremos a divisão:

    public static void cadastrarFuncionario(Funcionario funcionario, Endereco endereco, Contato contato){
    }
    

    Dessa forma, conseguimos agrupar as informações para que seja possível usar menos argumentos. Essa é uma boa prática

    Evitar código com repetição

    A repetição torna o código difícil de manter, pois quando há mudanças necessárias elas precisam ser aplicadas em múltiplos lugares.

    Então, extraia código repetido em funções ou métodos para promover a reutilização e a manutenção eficiente.

    Um exemplo disso:

    public static void main(String[] args) {
            int numero1 = 5;
            int numero2 = 7;
            // Cálculo do fatorial para o primeiro número
            int resultado1 = 1;
            for (int i = 1; i <= numero1; i++) {
                resultado1 *= i;
            }
            System.out.println("Fatorial de " + numero1 + ": " + resultado1);
            // Cálculo do fatorial para o segundo número
            int resultado2 = 1;
            for (int i = 1; i <= numero2; i++) {
                resultado2 *= i;
            }
            System.out.println("Fatorial de " + numero2 + ": " + resultado2);
        }
    

    Como calculamos o fatorial mais de uma vez, extraímos o código para uma função, evitando repetições de cálculo no código:

    public static void main(String[] args) {
            int numero1 = 5;
            int numero2 = 7;
            // Cálculo e impressão do fatorial para o primeiro número
            calcularEImprimirFatorial(numero1);
            // Cálculo e impressão do fatorial para o segundo número
            calcularEImprimirFatorial(numero2);
        }
        // Função para calcular e imprimir o fatorial de um número
        public static void calcularEImprimirFatorial(int numero) {
            int resultado = 1;
            for (int i = 1; i <= numero; i++) {
                resultado *= i;
            }
            System.out.println("Fatorial de " + numero + ": " + resultado);
        }
    

    Dessa forma, se precisarmos calcular novamente o fatorial de outro número, não precisaremos repetir código. Basta chamar a função de calcular fatorial novamente. Isso facilita o desenvolvimento.

    Vantagens de deixar o código limpo

    Ao implementar cada um desses princípios e práticas, você não apenas irá melhorar a qualidade do seu código, mas também facilitará a compreensão, a manutenção e a colaboração no desenvolvimento de software. Tudo isso que discutimos aqui resume uma frase dita por Martin Fowler:

    “Qualquer tolo pode escrever código que um computador pode entender. Bons programadores escrevem código que humanos podem entender”.

    A programação não se trata apenas de fazer a máquina funcionar, mas também de criar soluções que sejam compreensíveis e colaborativas.

    Inclusive, neste episódio do Hipsters convidamos o Fábio Akita para saber quais são as principais boas práticas de programação, como elas mudaram ao longo tempo e como podem potencializar a rotina de trabalho de devs e equipes.

    Conclusão

    Neste artigo, conhecemos o significado da sigla SOLID e como cada um dos princípios ajudam a manter nossos projetos orientados a objetos mais coesos e limpos, atingindo o famoso clean code.

    Agora você pode começar aplicar todos esses conhecimentos em seus projetos para aprimorar sua prática como dev!

    E vamos lançar um desafio! Se você gostou desse artigo, compartilhe nas redes sociais o que você aprendeu com ele com a hashtag #AprendinoBlogdaAlura.

    Se deseja mergulhar ainda mais no assunto, recomendamos alguns cursos e livros:

    • SOLID com PHP: princípios da programação orientada a objetos
    • C#: aplique princípios SOLID;
    • Boas práticas de programação: melhore o código de uma API Java;
    • Orientação a Objetos e SOLID para Ninjas;
    • Desbravando SOLID;
    • Deixe seu código limpo e brilhante.

    Bons estudos!

    Banner da Alura convidando profissionais a desenvolver habilidades em inteligência artificial para acompanhar as transformações do mercado de tecnologia. A campanha destaca que quem utiliza IA produz mais, cresce na carreira e se torna mais competitivo, reforçando que a inteligência artificial deixou de ser tendência e passou a ser uma habilidade essencial. O banner incentiva a começar a aprender IA com os cursos da Alura e impulsionar a transformação digital.

    Avalie este artigo

    Foto de Iasmin Araújo

    Autor(a)

    Iasmin Araújo

    Curso graduação em Ciência da Computação na UFMG. Sou instrutora da Escola de Programação e aqui no fórum estarei principalmente nos tópicos de Java.

    Explore por tópico

    • Mobile
    • Back-end
    • Front-end
    • DevOps
    • UX & Design
    • Dados
    • Gestão & Negócios
    • Inteligência Artificial
    • Cibersegurança
    • Cloud

    Leia também

    • 19/06/2018

      Quais as diferenças entre Python 2 e Python 3?

      Ler mais
    • 26/12/2021

      Por que e o que é possível testar?

      Ler mais
    • Na imagem vemos uma tela com códigos hexadecimais divididos em 4 colunas, cada uma com um grupo de 8 caracteres. 
      26/09/2021

      Compilação: o que é e qual a função dos compiladores 

      Ler mais
    Ver mais conteúdos

    Inscreva-se em nossa Newsletter

    Fique por dentro de conteúdos, insights e oportunidades do universo tech. Receba novidades e lançamentos direto no seu e-mail.

    Institucional

    • Sobre nós
    • Trabalhe Conosco
    • Para Empresas
    • Para Escolas
    • Política de Privacidade
    • Compromisso de Integridade
    • Termos de Uso
    • Canal de Ética
    • Código de Ética
    • Fale Conosco
    • Documentos Institucionais
    • Status
    Institucional
    • Sobre nós
    • Trabalhe Conosco
    • Para Empresas
    • Para Escolas
    • Política de Privacidade
    • Compromisso de Integridade
    • Termos de Uso
    • Canal de Ética
    • Código de Ética
    • Fale Conosco
    • Documentos Institucionais
    • Status

    A Alura

    • Como Funciona
    • Inteligência Artificial
    • Plataforma
    • Depoimentos
    • Instrutores(as)
    • Dev em <T>
    • Luri, a Inteligência Artificial da Alura
    • IA Conference
    • Cursos Imersivos
    • Perguntas Frequentes
    A Alura
    • Como Funciona
    • Inteligência Artificial
    • Plataforma
    • Depoimentos
    • Instrutores(as)
    • Dev em <T>
    • Luri, a Inteligência Artificial da Alura
    • IA Conference
    • Cursos Imersivos
    • Perguntas Frequentes

    Conteúdos

    • Alura Cases
    • Imersões
    • Artigos
    • Podcasts
    • Artigos de educação corporativa
    Conteúdos
    • Alura Cases
    • Imersões
    • Artigos
    • Podcasts
    • Artigos de educação corporativa
    Uma Empresa do GrupoLogo Grupo Alun

    Outras empresas do Grupo Alun

    • FIAP
    • STARTSE
    • PM3
    • LUMINA

    Ao submeter seu e-mail, você concorda com a política de privacidade da Alura.

    Redes Sociais & Apps

    instagram
    youtube
    tiktok
    x
    appStore
    googlePlay
    Uma Empresa do GrupoLogo Grupo Alun

    Outras empresas do Grupo Alun

    • FIAP
    • STARTSE
    • PM3
    • LUMINA

    Ao submeter seu e-mail, você concorda com a política de privacidade da Alura.

    Redes Sociais & Apps

    instagram
    youtube
    tiktok
    x
    appStore
    googlePlay
    Alura