Code Smell é um indicador superficial no código que sinaliza um problema potencial no design ou na arquitetura de uma aplicação. O termo foi cunhado por Kent Beck e popularizado por Martin Fowler no livro “Refactoring: Improving the Design of Existing Code”. De acordo com Martin Fowler, um cheiro no código não significa necessariamente um bug, mas quase sempre indica a necessidade de refatoração para melhorar a manutenibilidade.
Principais Pontos
Code Smell é uma metáfora para sintomas no código-fonte que com alta probabilidade indicam problemas mais profundos. O termo em si não tem uma definição formal — é uma heurística baseada na experiência dos desenvolvedores. Martin Fowler e Kent Beck sistematizaram pela primeira vez 22 cheiros em 1999 no livro “Refactoring”, e a maioria deles permanece relevante décadas depois.
É importante entender a diferença entre Code Smell e um bug. Um cheiro não é um erro: o código compila, funciona e produz resultados corretos. O problema é que esse código é difícil de ler, modificar e testar. Com o tempo, o custo de cada mudança aumenta e a confiança na correção da refatoração diminui. Ferramentas de análise estática (SonarQube, Detekt, SwiftLint) detetam automaticamente muitos cheiros.
A natureza heurística do Code Smell significa que nem todo método longo precisa ser dividido, e nem toda classe grande requer refatoração. A decisão é tomada pelo desenvolvedor avaliando o contexto: frequência de mudanças, criticidade do módulo, planos de desenvolvimento. Engenheiros experientes sentem um cheiro intuitivamente — o código “cheira mal” mesmo que todas as regras formais sejam seguidas.
Fowler identificou 22 cheiros agrupados em várias categorias. Para o desenvolvimento mobile, os mais relevantes são os cheiros estruturais, os cheiros de design orientado a objetos e problemas específicos relacionados com limitações da plataforma. Vamos examinar cada grupo com exemplos reais.
Long Method é o cheiro mais comum em aplicações mobile. Um ecrã de formulário de registo frequentemente contém um único método setupUI com 200+ linhas que cria todas as Views, configura constraints, subscreve eventos e trata erros. Solução: dividir em métodos por blocos lógicos — configureEmailField, configurePasswordField, setupConstraints, bindViewModel.
Large Class — uma Activity ou ViewController responsável pela exibição, navegação, lógica de negócio e interações de rede tudo ao mesmo tempo. Essa classe viola o Princípio da Responsabilidade Única e contém dezenas de campos e métodos. No Android, é frequentemente um Fragment com 1000+ linhas contendo lógica de diferentes ecrãs. Solução: extrair um presenter/ViewModel, mover o código de rede para um repositório e a navegação para um coordenador.
Duplicate Code — copiar blocos idênticos em diferentes partes da aplicação. Um exemplo típico: dois ecrãs exibindo um cartão de produto — no catálogo e nos favoritos. Se a lógica de exibição foi copiada, corrigir um bug num lugar não o corrigirá no outro. Solução: extrair a lógica comum para um componente reutilizável ou extensão.
Feature Envy — um método de uma classe usa intensivamente dados de outra classe. No Android, isso manifesta-se quando uma ViewModel acede diretamente aos campos de um modelo User em vez de chamar um método do modelo. Sinal: se um método pode ser movido para a classe cujos dados usa — mova-o. Switch Statements (cadeias de condições) — uma construção switch ou cadeia if-else que verifica o tipo do objeto. Em vez disso, use polimorfismo ou o padrão strategy.
Data Class — uma classe que apenas armazena dados mas não contém comportamento. As data classes em Kotlin ou estruturas em Swift não são inerentemente um cheiro. O problema surge quando a lógica de negócio que trabalha com esses dados está espalhada pela base de código em vez de encapsulada. Refused Bequest — uma subclasse não usa a maioria dos métodos do pai e sobrepõe-nos com stubs vazios. Sinal de herança incorreta: substitua herança por composição.
God Activity / God Fragment — uma Activity ou Fragment que sabe tudo: ciclo de vida, dados, navegação, Permissões, DI. É a classe mais cara de manter numa aplicação. Solução: padrões arquiteturais MVVM, MVI ou Clean Architecture separam responsabilidades. Giant ViewController — o equivalente no iOS, onde um UIViewController contém toda a lógica do ecrã e frequentemente excede 500 linhas.
Hardcoded Resources — strings, cores, tamanhos, URLs de API embutidos diretamente no código. No Android, isso viola o sistema de recursos R; no iOS, NSLocalizedString e Asset Catalog. Correção: mover todas as strings para strings.xml ou Localizable.strings, URLs para um ficheiro de configuração, tamanhos para dimens. Leaking Context — manter uma referência a uma Activity ou ViewController por mais tempo que o próprio componente vive. Causa fugas de memória e crashes. Solução: referências fracas, Jetpack Lifecycle, RxSwift DisposeBag.
| Cheiro | Onde ocorre | Solução |
|---|---|---|
| Long Method | Android/iOS | Extract Method, dividir |
| Large Class | Activity, ViewController | MVVM, VIPER, Clean Arch |
| Duplicate Code | Qualquer ecrã | Componente partilhado, DRY |
| Feature Envy | ViewModel, Presenter | Move Method |
| Leaking Context | Android | Componentes Lifecycle-aware |
Code review é a forma mais fiável de detetar cheiros. O olho humano nota construções não naturais que os analisadores automáticos perdem. A eficácia do code review melhora quando a equipa usa uma lista de verificação de cheiros típicos. Recomenda-se rever não mais de 200–400 linhas de código por sessão — após este limite, a atenção diminui e os cheiros começam a escapar.
Análise estática automatiza a procura de cheiros estruturais. Para Android, as ferramentas padrão são Detekt (Kotlin) e Android Lint; para iOS, SwiftLint e SonarQube. Essas ferramentas encontram métodos longos, classes grandes, código duplicado e muitos outros problemas. É importante ajustar as regras ao projeto — configurações predefinidas são frequentemente demasiado rigorosas ou, pelo contrário, perdem cheiros críticos.
Métricas de código fornecem critérios objetivos: Complexidade Ciclomática (limiar >10 requer atenção), Linhas de Código por Método (limiar >30), Profundidade de Herança (>3 — motivo para pensar). Ferramentas como CodeMetrics (Xcode) e Gradle Metrics Plugin constroem gráficos de mudanças de métricas ao longo do tempo. Se a complexidade de um método cresceu de 5 para 15 após o último commit — é um sinal para refatorar.
// Exemplo: método com Complexidade Ciclomática = 7 (acima do limiar 5)
fun processOrder(order: Order) {
if (order.status == Status.NEW) { /* 10 linhas */ }
else if (order.status == Status.PAID) { /* 15 linhas */ }
else if (order.status == Status.SHIPPED) { /* 20 linhas */ }
else if (order.status == Status.DELIVERED) { /* 8 linhas */ }
else if (order.status == Status.CANCELLED) { /* 5 linhas */ }
else { throw IllegalStateException() }
}
// Correção: polimorfismo em vez de switch
interface OrderHandler {
fun handle(order: Order)
}
A deteção automatizada de cheiros não substitui o code review: os analisadores estáticos apenas encontram problemas estruturais, mas não capturam cheiros semânticos (Feature Envy, Inappropriate Intimacy). A combinação de ferramentas automáticas e revisão humana dá os melhores resultados. Configure o pipeline CI/CD para que as compilações falhem quando os limiares de complexidade ou comprimento de método forem excedidos.
Refatoração é o método principal para eliminar cheiros de código. Fowler descreve dezenas de técnicas de refatoração, cada uma aplicável a um cheiro específico. Extract Method — para métodos longos, Extract Class — para classes grandes, Move Method — para Feature Envy. É importante realizar a refatoração em pequenos passos, mantendo o código funcional após cada alteração.
Testes antes de refatorar são obrigatórios. Se o código não estiver coberto por testes unitários, a refatoração transforma-se em reescrita com resultados desconhecidos. Para código legado sem testes, use Characterization Tests — escreva testes que capturem o comportamento atual, depois refatore. Os testes dão confiança de que a lógica de negócio não foi quebrada após a refatoração.
Gradualidade é a chave para corrigir com sucesso cheiros no desenvolvimento mobile. Não tente reescrever uma God Activity por completo. Primeiro extraia a camada de navegação, depois a camada de dados, depois a lógica de exibição. Cada passo deve ser acompanhado por um commit e execução de testes. Use feature toggles para ativar a refatoração para um subconjunto de utilizadores e reverter se surgirem problemas.
As ferramentas do IDE automatizam muitas técnicas de refatoração. Android Studio e IntelliJ IDEA oferecem refatorações integradas: Extract Method, Extract Interface, Pull Members Up, Encapsulate Fields. Xcode (a partir da versão 14) melhorou o suporte de refatoração para Swift. O uso de refatorações automáticas reduz o risco de erros em comparação com a cópia manual de código.
O desenvolvimento mobile adiciona os seus próprios cheiros específicos relacionados com limitações da plataforma. No Android, isto inclui fugas de Context, cursores não fechados e uso incorreto do Lifecycle. No iOS, ciclos de retenção através de closures, manipulação incorreta de Auto Layout e ViewControllers gigantes. Estes cheiros não só prejudicam a manutenibilidade, como também afetam diretamente o desempenho e a estabilidade da aplicação.
Callback Hell é um cheiro característico para código que trabalha com operações assíncronas. Callbacks aninhados tornam o código ilegível e difícil de depurar. Solução: corrotinas (Kotlin), async/await (Swift 5.5+), RxJava/RxSwift ou Combine. De acordo com Google I/O 2023, projetos que migraram do estilo callback para corrotinas reduziram o número de bugs em 30% e aceleraram a adição de novas funcionalidades.
Platform Coupling — acoplamento forte da lógica de negócio a componentes da plataforma. Testar essa lógica requer iniciar um emulador, o que atrasa o ciclo de feedback. Correção: Clean Architecture separa o código em camadas Domain (Kotlin/Swift puro sem dependências de plataforma) e Data/UI (com dependências de plataforma). A lógica de negócio é testada na JVM sem emulador.
Perguntas Frequentes
Não — Code Smell não é um erro. O código com cheiro funciona corretamente, mas é difícil de manter, modificar e testar. Um bug é um comportamento incorreto; um cheiro é um aviso sobre potenciais problemas futuros.
22 cheiros na segunda edição de “Refactoring” (2019). Entre eles estão Long Method, Large Class, Primitive Obsession, Data Clumps, Switch Statements, Speculative Generality e outros. A comunidade adicionou dezenas de novos cheiros para paradigmas e plataformas modernas.
Uma combinação dá o melhor resultado: Detekt (Android/Kotlin), SwiftLint (iOS), SonarQube (ambos) para análise automática e code review para cheiros semânticos. Nenhuma ferramenta encontra 100% dos problemas — a experiência humana continua a ser decisiva.
Sim, se o código muda raramente ou será completamente reescrito em breve. No entanto, a acumulação de cheiros transforma-se em dívida técnica: cada nova mudança torna-se mais difícil e o custo de correção cresce exponencialmente.
Sim — os frameworks declarativos geraram novos cheiros: blocos @State gigantes, manipulação incorreta de renderizações repetidas, recomposição excessiva e falta de Extração em Views separadas. Para SwiftUI, um cheiro típico é Massive View com dezenas de variáveis @State.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também