Habilidade de Refatoração
Use esta habilidade ao refatorar funcionalidades para alinhar com padrões estabelecidos, remover duplicação e melhorar o reuso de código.
Para saber se a UI pertence à biblioteca de padrões (pattern-library) ou à composição local, use a habilidade design-system.
Análise Pré-Refatoração
1. Identificar Funcionalidades Semelhantes
- Encontre funcionalidades comparáveis no código com funcionalidade semelhante
- Compare estruturas de diretórios, padrões de componentes e uso de utilitários
- Procure por funcionalidades com funcionalidade semelhante, mas implementações diferentes
- Use busca semântica para encontrar funcionalidades relacionadas: "funcionalidades com padrões semelhantes de tabela/grid de dados"
2. Encontrar Oportunidades de Duplicação
Pesquise por padrões de código duplicados:
- Componentes: Procure por arquivos de componentes semelhantes em diferentes funcionalidades (mesmo nome ou funcionalidade semelhante)
- Hooks: Verifique lógica de hook duplicada (hooks de tabela, hooks de filtro, hooks de agregação)
- Utilitários: Encontre funções utilitárias duplicadas (agregações, formatadores, validadores, transformadores)
- Estilos: Identifique padrões de prop
sxduplicados ou estilos inline - Constantes: Verifique constantes ou objetos de configuração duplicados
- Tipos: Procure por definições de tipo ou interfaces duplicadas
3. Verificar Utilitários Compartilhados
Antes de criar novos utilitários, verifique os existentes:
- Pesquise nos diretórios
@/util/por utilitários específicos de domínio - Verifique
@/util/datespara funções de formatação de data - Verifique
@/util/numberspara funções de formatação de número - Verifique
@/components/para componentes reutilizáveis - Verifique
pattern-librarypara componentes do sistema de design - Use grep/busca para encontrar implementações existentes antes de criar novas
Processo de Refatoração
Etapa 1: Extrair Código Compartilhado
Ao encontrar duplicatas:
-
Crie utilitários compartilhados nos diretórios
@/util/apropriados:// Exemplo: util/[dominio]/[categoria].ts export const funcaoCompartilhada = () => { ... }; export const constanteCompartilhada = { ... }; -
Atualize todas as funcionalidades para importar do local compartilhado:
// Arquivo específico da funcionalidade - reexportar compartilhado export { funcaoCompartilhada, constanteCompartilhada } from "@/util/[dominio]/[categoria]"; -
Remova implementações duplicadas dos diretórios de funcionalidades
-
Atualize todas as importações em todo o código
Etapa 2: Consolidar Componentes
Ao encontrar componentes duplicados:
- Identifique a versão mais completa/rica em recursos (verifique props, funcionalidade, testes)
- Extraia para local compartilhado (
@/components/ou local independente de funcionalidade) - Torne as props flexíveis para suportar todos os casos de uso (use tipos união, props opcionais ou genéricos)
- Atualize importações em todas as funcionalidades que usam o componente
- Exclua arquivos de componente duplicados
- Verifique se todos os usos ainda funcionam corretamente
Etapa 3: Alinhar Padrões de Carregamento
Para estados de carregamento:
- Verifique arquivos
loading.tsx- devem ser skeletons simples, não layouts de página completos - Remova loaders skeleton no nível do componente - confie no
loading.tsxda página para carregamento inicial - Durante refetches: Mostre dados existentes, não skeletons (a menos que explicitamente necessário para UX)
- Combine padrões de funcionalidades semelhantes no código
- Use convenções do Next.js App Router -
loading.tsxlida com carregamento inicial, componentes lidam com estados de refetch
Etapa 4: Consolidar Estilos
Para estilização:
- Extraia padrões de estilo reutilizáveis para componentes compartilhados ou utilitários de estilo
- Remova props
sxinline quando um componente compartilhado existir - Use componentes do sistema de design de
pattern-libraryem vez de estilos personalizados - Verifique objetos de estilo duplicados que poderiam ser utilitários compartilhados ou extensões de tema
- Siga padrões do Material-UI - use valores do tema, evite números mágicos
Etapa 5: Verificar Alinhamento de Padrões
Garanta consistência com funcionalidades estabelecidas:
- Compare estruturas de diretórios - devem seguir padrões semelhantes (
_components/,_hooks/,_utils/) - Verifique padrões de hook - hooks mais simples são aceitáveis se os requisitos da funcionalidade diferirem
- Verifique fluxo de dados - deve corresponder a funcionalidades semelhantes quando possível
- Revise composição de componentes - deve usar padrões semelhantes (wrappers, composição, etc.)
- Verifique convenções de nomenclatura - siga padrões estabelecidos (PascalCase para componentes, camelCase para hooks)
Etapa 6: Consolidação de Funções Utilitárias
Para funções utilitárias:
- Verifique se a função existe em utilitários compartilhados primeiro (pesquise no código)
- Compare implementações - use a versão mais completa/robusta
- Consolide duplicatas - mova para local compartilhado, atualize todas as importações
- Documente diferenças - se as implementações diferem intencionalmente, documente o porquê
- Preserve funcionalidade - garanta que a versão consolidada lida com todos os casos de uso
Etapa 7: Limpar Comentários
Após a refatoração, remova comentários desnecessários:
- Remova notas de migração - Comentários explicando de onde o código foi movido (ex.: "isCollecting agora é exportado de Table/utils.ts")
- Remova comentários óbvios - Comentários que apenas reiteram o que o código faz (ex.: "// Reexportar agregações compartilhadas" quando o código mostra claramente as reexportações)
- Mantenha comentários explicativos - Comentários que explicam POR QUE algo é feito (ex.: "// Usar setTimeout para garantir que o estado do formulário seja atualizado")
- Mantenha comentários de lógica complexa - Comentários que explicam lógica de negócios não óbvia ou casos de borda
- Remova comentários TODO/FIXME - A menos que sejam itens de trabalho futuro legítimos
Exemplos de comentários desnecessários:
// ❌ Desnecessário - óbvio pelo código
// Reexportar agregações compartilhadas de util
export { latest, totalRange } from "@/util/keywords/gridAggregations";
// ❌ Desnecessário - nota de migração, não é mais necessária
// isCollecting agora é exportado de Table/utils.ts para evitar duplicação
// Importe de lá: import { isCollecting } from "../KeywordListDetail/Table/utils";
Exemplos de bons comentários:
// ✅ Bom - explica POR QUE
// Usar setTimeout para garantir que o estado do formulário seja atualizado após processamento CSV
setTimeout(() => {
void handleSubmit(onSubmit)();
}, 100);
// ✅ Bom - explica lógica complexa
// Uma palavra-chave é atualizada se:
// 1. Seu valor lastUpdated mudou (indicador mais confiável), OU
// 2. Não está mais em estado de coleta E já pesquisamos vezes suficientes (fallback)
Etapa 8: Melhorias de Qualidade de Código
Aplique padrões consistentes de qualidade de código em todo o código refatorado:
Verificações de Nulo/Indefinido
Simplifique verificações de nulo e indefinido usando igualdade flexível:
// ❌ Verboso
if (value !== undefined && value !== null) { ... }
if (value !== undefined && value !== null && value !== "") { ... }
// ✅ Simplificado
if (value != null) { ... }
if (value != null && value !== "") { ... }
Nota: Use != null (igualdade flexível) para verificar tanto null quanto undefined em uma única verificação. Isso é idiomático em TypeScript/JavaScript e mais conciso.
Importações Apenas de Tipo
Use import type para importações apenas de tipo para melhorar o desempenho da build e tornar a intenção clara:
// ❌ Importação em tempo de execução para uso apenas de tipo
import { SomeType } from "app-api/types/domain";
// ✅ Importação apenas de tipo
import type { SomeType } from "app-api/types/domain";
Aplique a:
- Definições de tipo de
app-api - Importações de interface
- Tipos utilitários apenas de tipo
- Qualquer importação usada apenas em anotações de tipo
Remover Asserções de Tipo Desnecessárias
Remova asserções de tipo quando o TypeScript puder inferir o tipo corretamente:
// ❌ Asserção desnecessária
const items = data.map((item) => item as SomeType);
// ✅ Deixe o TypeScript inferir
const items = data.map((item) => item);
Quando manter asserções: Apenas quando o TypeScript genuinamente não puder inferir o tipo, ou ao restringir tipos de uma forma que o TypeScript não possa verificar.
Remover Coalescência Nula Desnecessária
Remova ?? null quando o valor já for anulável ou o TypeScript inferir o tipo correto:
// ❌ Desnecessário - valueGetter já retorna anulável
valueGetter: (value, row) => {
return row.someField ?? null;
};
// ✅ Simplificado
valueGetter: (value, row) => {
return row.someField;
};
Quando manter: Quando convertendo explicitamente undefined para null para compatibilidade de API ou quando o sistema de tipos exigir.
Remover Props Não Utilizadas
Remova props de interfaces de componente e implementações quando não forem usadas:
// ❌ Prop não utilizada
interface ComponentProps {
item: Item;
unusedField: string; // Nunca usado no componente
}
// ✅ Interface limpa
interface ComponentProps {
item: Item;
}
Adicionar Tipos Explícitos a Callbacks
Adicione anotações de tipo explícitas a parâmetros de callback quando o TypeScript não puder inferi-los:
// ❌ Any implícito
const items = ids.map((id) => { ... });
// ✅ Tipo explícito
const items = ids.map((id: string | number) => { ... });
Aplique a: Métodos de array (map, filter, forEach), manipuladores de eventos e outros callbacks onde os tipos não são claros pelo contexto.
Extrair Padrões JSX Repetidos
Extraia padrões JSX repetidos em componentes reutilizáveis:
// ❌ Padrão repetido
{
isAggregation ? (
<Typography
variant="body2"
fontWeight="medium"
fontStyle="italic"
color="text.secondary"
>
{formattedValue}
</Typography>
) : (
<Typography variant="body2" fontWeight="medium">
{formattedValue}
</Typography>
);
}
// ✅ Componente extraído
<AggregationTypography
formattedValue={formattedValue}
isAggregation={isAggregation}
/>;
Usar Funções Fábrica para Implementações Variadas
Quando a lógica é semelhante, mas precisa lidar com diferentes estruturas de dados, use funções fábrica:
// ✅ Padrão de função fábrica
export const createIntentOperators = <T extends { intent?: Intent | Intent[] }>(
normalizeToArray: (value: T) => Intent[]
) => {
return {
// Operadores que trabalham com array normalizado
};
};
// Uso em diferentes funcionalidades
const operators1 = createIntentOperators<Feature1Type>((item) => [item.intent]);
const operators2 = createIntentOperators<Feature2Type>(
(item) => item.intents || []
);
Benefícios: Reutiliza lógica central enquanto permite que diferentes estruturas de dados sejam tratadas adequadamente.
Reforço de Revisão de Código (feedback recorrente)
Aplique estes ao refatorar grids, dashboards, fetches ou formas compartilhadas "linha + métricas" (alinha com feedback repetido de PRs de UI Moz Pro).
DataGrid: alternância de painel de detalhes
- Prefira
DetailPanelToggledepattern-librarypara expandir/recolher em uma coluna de painel de detalhes em vez deIconButton+@mui/iconsad hoc em uma célula personalizada. - Configure acessibilidade: passe
buttonTabIndex={0}quando a célula deve ser focável por tab (vejaapp/(default)/competitive-research/top-competing-content/_module/components/Table/columns.tsx,SerpFeaturesCell). - Layout / centralização: use
sxemBox/Stack(oudirection/spacingdoStackquando esse for o padrão estabelecido). Não substituasxpor props de estilo do sistema MUI legadas emBox(ex.:display,alignItemscomo props de nível superior)—o MUI está descontinuando essa abordagem em favor desx.
Tipagem: evitar object / formas de linha anônimas
- Não use
readonly object[]parabrand_definitions, fragmentos de linha ou listas adjacentes à API quando um tipo estrutural existir ou puder ser definido uma vez. - Defina uma interface "like" compartilhada (ex.:
BrandDefinitionLike) no móduloutilsda funcionalidade; exporte-a e um único auxiliar de linha de exibição (ex.:getBrandDefinitionDisplayLine) em vez de duplicargetBrandNamecom casts dentro de células. - Remova casts redundantes (
as BrandDefinitionLike[]) após os parâmetros serem tipados. - Reutilize auxiliares de domínio de
*_module/utils/dataHelpersquando a forma for umBrandDefinitionreal; use o auxiliar mais flexível apenas onde as cargas de linha de prompt diferirem.
Typography vs Box (pattern-library)
- Para texto (estados vazios, "—" inline, spans enfatizados), prefira
Typographydepattern-librarycomcomponent="span"quando apropriado—nãoBoxfingindo ser cópia. - API
Typography: usevariant,color,fontWeight,componentonde eles expressam semântica (estes não são descontinuados). Box/ wrappers de layout: prefirasxpara espaçamento, layout flex, dimensionamento e valores de tema. Evite refatorações impensadas que movam estilização desxpara props shorthand de estiloBox; isso entra em conflito com as diretrizes atuais do MUI.
Limites de componentes: props vs estado interno
- Derive booleanos de
datadentro do componente quando o pai não precisar deles para layout ou estado de URL (ex.: "tem termos de marca" debrand_termsda marca primária). - Mantenha alternâncias apenas de UI internas (ex.: alternar entre dois modos de gráfico em um card) a menos que outro irmão ou rota precise controlar o mesmo estado—então documente por que a elevação é necessária.
Fluxo de controle e JSX
- Prefira
returnantecipado para branches de carregamento/vazio em vez de um grande ternário atribuído a uma variável imediatamente antes de um únicoreturn, a menos que a memoização genuinamente precise de uma expressão. - Comparadores de ordenação: nunca codifique um sentinela mágico (ex.:
5) atrelado a um máximo de domínio assumido; useNumber.POSITIVE_INFINITY/ "ordenar por último" explícito para nulo/indefinido para que o comportamento permaneça correto se os limites mudarem.
Fetches e paginação
- Evite um
limitfixo pequeno (ex.: 100) para listas em escala de dashboard quando o backend for paginado e os projetos puderem exceder uma página. - Faça loop com um tamanho de página estável até que uma página retorne menos linhas que o limite ou zero linhas, ou siga o contrato documentado da ação (veja docs da ação
moz-pro-service/ Bruno paralimitmáximo). - Exiba
totalou totais derivados de forma consistente se os chamadores precisarem de contagens.
Wrappers de apresentação: não repita o óbvio
- Remova campos de estado alternativo redundantes quando o
Cardpai já definir o título visível (ex.: evite duplicarcardContentTitleque corresponde ao título do card). - Verificações de nulo: se uma prop for tipada como
string, não faça verificação de nulo; confie no sistema de tipos.
Fetches: coluna de painel de detalhes vs dados da linha
- Use
rowou um fetch simples (ex.:fetchCompetitors) para dados do painel de detalhes—não um hook de fetch complexo que exija gerenciamento de estado adicional. - Evite derivar um novo fetch de dados de fetch existentes (ex.: pegar
idde uma linha de grid, depois buscar detalhes completos quando a linha já contiver a maioria dos dados).
View-model vs override-prop
- View-model (componente estatelss + hook separado): prefira quando a funcionalidade principal envolver fetch, transformação de dados ou estados de carregamento, e o componente puder ser um apresentacional puro.
- Override-prop (props do componente que substituem lógica interna): prefira quando o componente for predominantemente UI que pode ser personalizada pelo chamador (ex.: um
MetricCardcomoverridespara valor, cor, tooltip). - Não force view-model em componentes simples que só renderizam props.
Reducers: casos de ação ausentes
- Em um reducer (ex.:
useReducerda funcionalidade), se uma ação retornarstateinalterado, ainda liste-a explicitamente noswitchcomreturn state;ou um comentário:// Ação X não altera estado, apenas Y. Isso torna casos ausentes rastreáveis na revisão e impede que o TypeScript perca cobertura.
Migração MUI v4 → v5 (sx vs props shorthand)
- No MUI v5+, props shorthand de sistema (ex.:
display="flex",alignItems="center") emBox/Stack/Gridestão sendo descontinuados em favor da propsx. - Correção: mova essas props para dentro de
sx:
// ❌ MUI v4 legado (descontinuado)
<Box display="flex" alignItems="center" justifyContent="space-between">
// ✅ MUI v5+ (sx-first)
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
- Exceções:
Stackusadirection/spacing/dividercomo props de nível superior (não descontinuadas).Typographyusavariant/color/fontWeight(não descontinuados). - Quando refatorar: corrija à medida que tocar no código—não faça uma migração em massa. Se uma seção estiver sendo refatorada por outros motivos, mova o estilo para
sx. - Evite regressões: se uma prop shorthand for amplamente usada em um arquivo, refatore-a como parte de uma alteração maior, não como uma correção isolada que adicione ruído ao diff.
Padrões de Fetch
Paginação de Fetch
- Nunca codifique um
limitestático pequeno (ex.: 100) para endpoints paginados—projetos podem ter milhares de linhas. - Use paginação baseada em cursor ou deslocamento conforme o endpoint exigir.
- Página com tamanho de página estável (ex.: 1000 se a API suportar) até uma página vazia ou última página.
- Para dados de painel: faça um único fetch se o total for pequeno (< limite máximo).
- Para listas em escala: sempre página.
- Considere memoização: use
useCallbackpara funções de fetch que são passadas como props ou dependências de hook.
Tratamento de Erros de Fetch
- Sempre trate erros em fetches—não deixe promessas rejeitadas não tratadas.
- Mostre feedback ao usuário: toast, alerta inline ou estado de erro no componente.
- Use blocos try/catch ou operações
.catch(). - Para fetches de fundo (ex.: refetch periódico), suprime erros silenciosamente ou registre-os, a menos que o usuário precise saber.
- Evite mostrar erros de fetch em componentes que não iniciaram o fetch (ex.: um componente filho que recebe dados por prop não deve exibir seu próprio toast de erro para o fetch do pai).