Práticas Recomendadas de Revisão de Código Flutter/Dart
Lista de verificação abrangente e independente de bibliotecas para revisar aplicativos Flutter/Dart. Esses princípios se aplicam independentemente da solução de gerenciamento de estado, biblioteca de roteamento ou estrutura de injeção de dependência usada.
1. Saúde Geral do Projeto
- O projeto segue uma estrutura de pastas consistente (primeiro recurso ou primeira camada)
- Separação adequada de responsabilidades: UI, lógica de negócios, camadas de dados
- Nenhuma lógica de negócios em widgets; widgets são puramente de apresentação
-
pubspec.yamlestá limpo — sem dependências não utilizadas, versões fixadas adequadamente -
analysis_options.yamlinclui um conjunto de lints estrito com configurações de analisador estritas ativadas - Nenhuma declaração
print()no código de produção — usedart:developerlog()ou um pacote de logging - Arquivos gerados (
.g.dart,.freezed.dart,.gr.dart) estão atualizados ou no.gitignore - Código específico de plataforma isolado atrás de abstrações
2. Armadilhas da Linguagem Dart
- Dinâmico implícito: Anotações de tipo ausentes levando a
dynamic— ativestrict-casts,strict-inference,strict-raw-types - Mau uso da segurança de nulo: Uso excessivo de
!(operador de explosão) em vez de verificações de nulo adequadas ou correspondência de padrões do Dart 3 (if (value case var v?)) - Falhas de promoção de tipo: Usar
this.fieldonde a promoção de variável local funcionaria - Capturar muito amplamente:
catch (e)sem cláusulaon; sempre especifique os tipos de exceção - Capturar
Error: Subtipos deErrorindicam bugs e não devem ser capturados -
asyncnão utilizado: Funções marcadas comoasyncque nunca usamawait— sobrecarga desnecessária - Uso excessivo de
late:lateusado onde anulável ou inicialização de construtor seria mais seguro; adia erros para o tempo de execução - Concatenação de strings em loops: Use
StringBufferem vez de+para construção iterativa de strings - Estado mutável em contextos
const: Campos em classes de construtorconstnão devem ser mutáveis - Ignorar valores de retorno de
Future: Useawaitou chame explicitamenteunawaited()para sinalizar intenção -
varondefinalfunciona: Prefirafinalpara variáveis locais econstpara constantes de tempo de compilação - Importações relativas: Use importações
package:para consistência - Coleções mutáveis expostas: APIs públicas devem retornar visualizações não modificáveis, não
List/Mapbrutos - Correspondência de padrões do Dart 3 ausente: Prefira expressões switch e
if-caseem vez de verificaçõesisverbosas e casting manual - Classes descartáveis para múltiplos retornos: Use registros Dart 3
(String, int)em vez de DTOs de uso único -
print()no código de produção: Usedart:developerlog()ou o pacote de logging do projeto;print()não tem níveis de log e não pode ser filtrado
3. Práticas Recomendadas de Widget
Decomposição de widget:
- Nenhum widget único com um método
build()excedendo ~80-100 linhas - Widgets divididos por encapsulamento E por como eles mudam (limites de reconstrução)
- Métodos auxiliares privados
_build*()que retornam widgets são extraídos para classes de widget separadas (permite reutilização de elemento, propagação const e otimizações do framework) - Widgets sem estado preferidos em vez de com estado onde nenhum estado local mutável é necessário
- Widgets extraídos estão em arquivos separados quando reutilizáveis
Uso de const:
- Construtores
constusados sempre que possível — evita reconstruções desnecessárias - Literais
constpara coleções que não mudam (const [],const {}) - Construtor é declarado
constquando todos os campos são finais
Uso de chave:
-
ValueKeyusado em listas/grades para preservar o estado em reordenações -
GlobalKeyusado com moderação — apenas quando acessar o estado na árvore for realmente necessário -
UniqueKeyevitado embuild()— força a reconstrução a cada quadro -
ObjectKeyusado quando a identidade é baseada em um objeto de dados em vez de um único valor
Temas & sistema de design:
- As cores vêm de
Theme.of(context).colorScheme— semColors.redou valores hexadecimais codificados - Os estilos de texto vêm de
Theme.of(context).textTheme— semTextStyleembutido com tamanhos de fonte brutos - Compatibilidade com modo escuro verificada — sem suposições sobre fundo claro
- Espaçamento e dimensionamento usam tokens de design consistentes ou constantes, não números mágicos
Complexidade do método build:
- Nenhuma chamada de rede, E/S de arquivo ou computação pesada em
build() - Nenhum
Future.then()ou trabalhoasyncembuild() - Nenhuma criação de assinatura (
.listen()) embuild() -
setState()localizado na menor subárvore possível
4. Gerenciamento de Estado (Independente de Biblioteca)
Esses princípios se aplicam a todas as soluções de gerenciamento de estado do Flutter (BLoC, Riverpod, Provider, GetX, MobX, Signals, ValueNotifier, etc.).
Arquitetura:
- A lógica de negócios vive fora da camada de widget — em um componente de gerenciamento de estado (BLoC, Notifier, Controller, Store, ViewModel, etc.)
- Os gerenciadores de estado recebem dependências via injeção, não as construindo internamente
- Uma camada de serviço ou repositório abstrai fontes de dados — widgets e gerenciadores de estado não devem chamar APIs ou bancos de dados diretamente
- Os gerenciadores de estado têm uma única responsabilidade — nenhum gerenciador "deus" lidando com preocupações não relacionadas
- As dependências entre componentes seguem as convenções da solução:
- Em Riverpod: provedores dependendo de provedores via
ref.watché esperado — sinalize apenas cadeias circulares ou excessivamente emaranhadas - Em BLoC: blocs não devem depender diretamente de outros blocs — prefira repositórios compartilhados ou coordenação na camada de apresentação
- Em outras soluções: siga as convenções documentadas para comunicação entre componentes
- Em Riverpod: provedores dependendo de provedores via
Imutabilidade & igualdade de valor (para soluções de estado imutável: BLoC, Riverpod, Redux):
- Os objetos de estado são imutáveis — novas instâncias criadas via
copyWith()ou construtores, nunca mutadas no local - As classes de estado implementam
==ehashCodeadequadamente (todos os campos incluídos na comparação) - O mecanismo é consistente em todo o projeto — override manual,
Equatable,freezed, registros Dart ou outros - Coleções dentro de objetos de estado não são expostas como
List/Mapbrutos mutáveis
Disciplina de reatividade (para soluções de mutação reativa: MobX, GetX, Signals):
- O estado é mutado apenas através da API reativa da solução (
@actionno MobX,.valueem signals,.obsno GetX) — a mutação direta de campo ignora o rastreamento de alterações - Valores derivados usam o mecanismo computado da solução em vez de serem armazenados redundantemente
- Reações e descartadores são limpos adequadamente (
ReactionDisposerno MobX, limpeza de efeito no Signals)
Design da forma do estado:
- Estados mutuamente exclusivos usam tipos selados, variantes de união ou o tipo de estado assíncrono embutido da solução (por exemplo,
AsyncValuedo Riverpod) — não sinalizadores booleanos (isLoading,isError,hasData) - Cada operação assíncrona modela carregamento, sucesso e erro como estados distintos
- Todas as variantes de estado são tratadas exaustivamente na UI — nenhum caso ignorado silenciosamente
- Estados de erro carregam informações de erro para exibição; estados de carregamento não carregam dados obsoletos
- Dados anuláveis não são usados como indicador de carregamento — os estados são explícitos
// RUIM — sopa de sinalizadores booleanos permite estados impossíveis
class UserState {
bool isLoading = false;
bool hasError = false; // isLoading && hasError é representável!
User? user;
}
// BOM (abordagem imutável) — tipos selados tornam estados impossíveis não representáveis
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
const UserError(this.message);
}
// BOM (abordagem reativa) — enum observável + dados, mutações via API de reatividade
// enum UserStatus { initial, loading, loaded, error }
// Use o observable/signal da sua solução para encapsular status e dados separadamente
Otimização de reconstrução:
- Widgets consumidores de estado (Builder, Consumer, Observer, Obx, Watch, etc.) com escopo o mais estreito possível
- Seletores usados para reconstruir apenas quando campos específicos mudam — não em cada emissão de estado
- Widgets
constusados para interromper a propagação de reconstrução na árvore - Estado computado/derivado é calculado reativamente, não armazenado redundantemente
Assinaturas & descarte:
- Todas as assinaturas manuais (
.listen()) são canceladas emdispose()/close() - Stream controllers são fechados quando não são mais necessários
- Timers são cancelados no ciclo de vida de descarte
- O ciclo de vida gerenciado pelo framework é preferido em vez de assinatura manual (construtores declarativos em vez de
.listen()) - Verificação
mountedantes desetStateem callbacks assíncronos -
BuildContextnão usado apósawaitsem verificarcontext.mounted(Flutter 3.7+) — context obsoleto causa travamentos - Nenhuma navegação, diálogos ou mensagens de scaffold após lacunas assíncronas sem verificar se o widget ainda está montado
-
BuildContextnunca armazenado em singletons, gerenciadores de estado ou campos estáticos
Estado local vs. global:
- Estado de UI efêmero (caixa de seleção, controle deslizante, animação) usa estado local (
setState,ValueNotifier) - O estado compartilhado é elevado apenas o mais alto necessário — não globalizado demais
- O estado com escopo de recurso é descartado adequadamente quando o recurso não está mais ativo
5. Desempenho
Reconstruções desnecessárias:
-
setState()não chamado no nível do widget raiz — localize as alterações de estado - Widgets
constusados para interromper a propagação de reconstrução -
RepaintBoundaryusado em torno de subárvores complexas que repintam independentemente - Parâmetro filho de
AnimatedBuilderusado para subárvores independentes da animação
Operações caras em build():
- Nenhuma ordenação, filtragem ou mapeamento de grandes coleções em
build()— compute na camada de gerenciamento de estado - Nenhuma compilação de regex em
build() - O uso de
MediaQuery.of(context)é específico (por exemplo,MediaQuery.sizeOf(context))
Otimização de imagem:
- Imagens de rede usam cache (qualquer solução de cache apropriada para o projeto)
- Resolução de imagem apropriada para o dispositivo de destino (sem carregar imagens 4K para miniaturas)
-
Image.assetcomcacheWidth/cacheHeightpara decodificar no tamanho de exibição - Widgets de espaço reservado e de erro fornecidos para imagens de rede
Carregamento preguiçoso:
-
ListView.builder/GridView.builderusado em vez deListView(children: [...])para listas grandes ou dinâmicas (construtores concretos são adequados para listas pequenas e estáticas) - Paginação implementada para grandes conjuntos de dados
- Carregamento adiado (
deferred as) usado para bibliotecas pesadas em compilações web
Outros:
- Widget
Opacityevitado em animações — useAnimatedOpacityouFadeTransition - Recorte evitado em animações — pré-recorte imagens
-
operator ==não sobrescrito em widgets — use construtoresconstem vez disso - Widgets de dimensão intrínseca (
IntrinsicHeight,IntrinsicWidth) usados com moderação (passo de layout extra)
6. Testes
Tipos de teste e expectativas:
- Testes unitários: Cobrem toda a lógica de negócios (gerenciadores de estado, repositórios, funções utilitárias)
- Testes de widget: Cobrem comportamento de widget individual, interações e saída visual
- Testes de integração: Cobrem fluxos de usuário críticos de ponta a ponta
- Testes dourados: Comparações perfeitas de pixel para componentes de UI críticos para o design
Metas de cobertura:
- Buscar 80%+ de cobertura de linha na lógica de negócios
- Todas as transições de estado têm testes correspondentes (carregamento → sucesso, carregamento → erro, repetição, etc.)
- Casos de borda testados: estados vazios, estados de erro, estados de carregamento, valores de contorno
Isolamento de teste:
- Dependências externas (clientes de API, bancos de dados, serviços) são simuladas ou falsificadas
- Cada arquivo de teste testa exatamente uma classe/unidade
- Os testes verificam o comportamento, não detalhes de implementação
- Stubs definem apenas o comportamento necessário para cada teste (stubbing mínimo)
- Nenhum estado mutável compartilhado entre casos de teste
Qualidade do teste de widget:
-
pumpWidgetepumpusados corretamente para operações assíncronas -
find.byType,find.text,find.byKeyusados apropriadamente - Nenhum teste instável dependendo de tempo — use
pumpAndSettleoupump(Duration)explícito - Testes executados em CI e falhas bloqueiam merges
7. Acessibilidade
Widgets semânticos:
- Widget
Semanticsusado para fornecer rótulos de leitor de tela onde os rótulos automáticos são insuficientes -
ExcludeSemanticsusado para elementos puramente decorativos -
MergeSemanticsusado para combinar widgets relacionados em um único elemento acessível - Imagens têm a propriedade
semanticLabeldefinida
Suporte a leitor de tela:
- Todos os elementos interativos são focalizáveis e têm descrições significativas
- A ordem de foco é lógica (segue a ordem de leitura visual)
Acessibilidade visual:
- Relação de contraste >= 4.5:1 para texto contra fundo
- Alvos tocáveis têm pelo menos 48x48 pixels
- A cor não é o único indicador de estado (use ícones/texto junto)
- O texto escala com as configurações de tamanho da fonte do sistema
Acessibilidade de interação:
- Nenhum callback
onPressedsem operação — todo botão faz algo ou está desabilitado - Campos de erro sugerem correções
- O contexto não muda inesperadamente enquanto o usuário está inserindo dados
8. Preocupações Específicas de Plataforma
Diferenças iOS/Android:
- Widgets adaptativos de plataforma usados onde apropriado
- Navegação de volta tratada corretamente (botão voltar Android, gesto de deslizar para voltar iOS)
- Barra de status e área segura tratadas via widget
SafeArea - Permissões específicas de plataforma declaradas em
AndroidManifest.xmleInfo.plist
Design responsivo:
-
LayoutBuilderouMediaQueryusado para layouts responsivos - Pontos de interrupção definidos consistentemente (telefone, tablet, desktop)
- O texto não transborda em telas pequenas — use
Flexible,Expanded,FittedBox - Orientação paisagem testada ou explicitamente bloqueada
- Específico para web: interações de mouse/teclado suportadas, estados de hover presentes
9. Segurança
Armazenamento seguro:
- Dados confidenciais (tokens, senhas, chaves de API) armazenados em armazenamento seguro do dispositivo (
flutter_secure_storage) - Nenhum segredo codificado no código — segredos injetados via variáveis de ambiente ou serviços de configuração
- Chaves de API de terceiros nunca armazenadas em repositórios de código (use git-crypt ou semelhante para arquivos de configuração)
-
SharedPreferencesnão usado para dados confidenciais
Comunicação de rede:
- Todas as chamadas de API usam HTTPS com certificados SSL/TLS válidos
- Certificados SSL/TLS fixados (pinados) em aplicativos de produção para prevenir ataques MITM
- Nenhum cabeçalho de autorização exposto em logs ou mensagens de erro
Validação de entrada:
- Todas as entradas do usuário são validadas e sanitizadas antes do uso
- Nenhuma injeção de código ou SQL possível através de entradas do usuário
- Validação de entrada do lado do servidor também presente para chamadas de API
Outros:
- Verificações de integridade de deep links realizadas
- Nenhuma comunicação insegura com plataformas nativas (canais de método seguros)
- Dependências de terceiros atualizadas para evitar vulnerabilidades conhecidas
10. Gerenciamento de Dependências
Dependências de pacotes:
- Nenhuma dependência não utilizada em
pubspec.yaml - Dependências são fixadas com intervalo ou trava (lock) para builds reproduzíveis
- Dependências são atualizadas regularmente para versões compatíveis
- Dependências depreciadas ou com vulnerabilidades conhecidas são evitadas ou substituídas
Dependências de desenvolvimento:
- Dependências de desenvolvimento (testes, linting) não são incluídas na compilação de produção
- Builders de código (freezed, json_serializable) são especificados como dependências de desenvolvimento
Dependências transitivas:
- Nenhuma dependência transitiva indesejada é arrastada (use
dependency_overridescom moderação) - A árvore de dependências é verificada por pacotes duplicados ou conflitantes
11. Tratamento de Erros e Logging
Tratamento de erros:
- Toda operação assíncrona tem tratamento de erro (try/catch em torno de
awaitou tratamento de erro em streams) - Erros não são engolidos — cada erro é registrado ou exibido ao usuário
- Erros são mapeados para mensagens amigáveis ao usuário antes da exibição
- Erros inesperados são capturados em um nível superior (por exemplo, em um gerenciador de estado global)
Logging:
- Logging estruturado usado (em vez de
print) - Níveis de log apropriados: debug, info, warn, error
- Logging não expõe informações confidenciais do usuário
- Logging é configurável por ambiente (desenvolvimento, homologação, produção)
12. Roteamento e Navegação
Roteamento declarativo:
- Roteamento declarativo preferido (Navigator 2.0 ou bibliotecas de terceiros como go_router)
- Navegação baseada em URL usada para web e deep links
- Roteamento profundo (nested routing) implementado corretamente
Navegação segura:
- Nenhuma navegação executada após o contexto ser destruído (verifique
context.mounted) - Argumentos de rota são tipados e validados
- Nenhuma navegação ocorre em callbacks de build ou initState sem verificação de montagem
13. Otimizações de Compilação
Compilação para web:
- Estruturas de árvore de renderização otimizadas para web (canvas vs DOM)
- Imagens otimizadas para web (WebP, carregamento preguiçoso)
- Divisão de código usada para bibliotecas grandes
- Pacotes JavaScript otimizados em tamanho (tree shaking, minificação)
Compilação para mobile:
- Binários otimizados para tamanho (remover código morto, minimizar ativos não utilizados)
- Ativos (imagens, fontes, arquivos) são compactados para o tamanho mínimo
- Estratégias de cache de ativos implementadas
14. Configuração e Ambiente
Variáveis de ambiente:
- Configurações específicas de ambiente (API URLs, chaves) são injetadas via variáveis de ambiente ou serviços de configuração
- Nenhuma configuração codificada no código
- Diferentes ambientes (desenvolvimento, homologação, produção) têm configurações separadas
Configuração de build:
- Utilizar flavors de build para diferentes ambientes
- Nomes de aplicativos, ícones e nomes de pacotes diferem entre ambientes para evitar conflitos
15. Documentação e Comentários
Documentação de código:
- Comentários de documentação (///) presentes para APIs públicas
- Comentários explicam o "porquê", não apenas o "o quê" (código já mostra o quê)
- Nenhum comentário desatualizado ou enganoso
- README do projeto explica como configurar, construir e testar
Resumo
Esta lista de verificação é projetada para ser usada durante revisões de código para garantir qualidade, consistência e aderência às melhores práticas em aplicativos Flutter/Dart. Adapte-a às necessidades específicas do seu projeto eliminando ou adicionando itens conforme apropriado.