Refatorar User Function legada — checklist
Como modernizar codigo AdvPL antigo (anos 2000) sem quebrar comportamento. Identificar code smells, dividir, simplificar, testar.
Toda customizacao Protheus madura tem um "monstro": User Function de 2000-3000 linhas, 15 anos de mudancas incrementais, ninguem mais entende. Refatorar e arriscado mas necessario. Esse guia mostra como fazer sem quebrar.
Code smells classicos em AdvPL
| Sinal | Sintoma |
|---|---|
| God Function | 1 funcao com 2000+ linhas, 50+ Locals |
| Magic numbers | If nValor > 10000 sem explicacao |
| Commented dead code | 30% do codigo comentado por algum motivo perdido |
| Nested ifs profundos | If dentro de If dentro de If — 6+ niveis |
| Public/Private spaghetti | Variaveis Public usadas em qualquer lugar |
| DBSelectArea sem RestArea | "Vazamento" de estado entre chamadas |
| SQL inline gigante | cQuery com 80 linhas concatenadas |
| Function nua | Function Calc() sem User/Static — RPO Token rejeita |
Estrategia: refactor incremental + safety nets
Etapa 1: Estabelecer safety net
- Capture comportamento atual com testes de "characterization" — pegue inputs reais e outputs, salve.
- Use /advpl-find-issues pra mapear problemas que ja existem.
- Snapshot do banco antes de mexer em codigo critico.
Etapa 2: Extract Function (extrair sub-funcoes)
// ANTES
User Function Calcular()
Local nDesc := 0
// 50 linhas calculando desconto
Local nJuros := 0
// 80 linhas calculando juros
Local nImp := 0
// 100 linhas calculando impostos
Return
// DEPOIS
User Function Calcular()
Local nDesc := _CalcDesc()
Local nJuros := _CalcJuros()
Local nImp := _CalcImpostos()
Return
Static Function _CalcDesc()
// 50 linhas isoladas — testavel
Return nDesc
Etapa 3: Constantes nomeadas
// ANTES
If nValor > 10000
nDesc := nValor * 0.10
EndIf
// DEPOIS
#define LIMIAR_DESCONTO_VIP 10000
#define ALIQUOTA_DESC_VIP 0.10
If nValor > LIMIAR_DESCONTO_VIP
nDesc := nValor * ALIQUOTA_DESC_VIP
EndIf
Etapa 4: Substituir Private por Local + retorno
// ANTES — variavel Private "espalhada"
User Function A()
Private nTotal := 0
U_B()
ConOut(nTotal) // espera U_B ter modificado
Return
Static Function B()
nTotal := 100 // modifica Private alheia — magico e fragil
Return
// DEPOIS — funcao retorna explicitamente
User Function A()
Local nTotal := U_B()
ConOut(nTotal)
Return
Static Function B()
Return 100
Etapa 5: Modernizar SQL
// ANTES — concat string verbose
cQry := "SELECT "
cQry += " A1_COD, "
cQry += " A1_NOME, "
cQry += " A1_CGC "
cQry += " FROM " + RetSqlName("SA1") + " SA1 "
cQry += " WHERE A1_FILIAL = '" + xFilial("SA1") + "'"
cQry += " AND D_E_L_E_T_ = ' '"
// DEPOIS — TLPP modern
cQry := "SELECT A1_COD, A1_NOME, A1_CGC " + ;
" FROM " + RetSqlName("SA1") + ;
" WHERE A1_FILIAL = '" + xFilial("SA1") + "'" + ;
" AND D_E_L_E_T_ = ' '"
// EVEN BETTER — TLPP com prepared statement (release moderna)
// cQry com placeholders + parametros
Etapa 6: Adicionar /advpl-doc
Antes de mover codigo, gere header Protheus.doc com a skill /advpl-doc. Documenta parametros e retorno automaticamente.
Anti-padroes de refactor
- Big bang: refazer tudo de uma vez. NUNCA — uma mudanca por vez, testar, deploy.
- Mudar API publica sem aviso: outros codigos podem chamar — mantenha assinatura compativel.
- Renomear sem buscar dependentes: grep global obrigatorio antes.
- Refactor + nova feature no mesmo PR: separe. Bug em refactor + feature confunde.
Ferramentas que ajudam
- /advpl-find-issues --fix — auto-corrige 4 regras safe (GetMV default, MsgYesNo legado, Function nua, MemoWrite)
- git diff — antes/depois pra revisao
- git bisect — encontrar quando comportamento mudou
- TDS-VSCode Outline — visualizar estrutura da funcao gigante
Quando refatorar vs reescrever
| Situacao | Acao |
|---|---|
| Codigo de 1000 linhas, com testes parciais | Refatorar incremental |
| Codigo de 3000+ linhas, sem documentacao | Reescrever modulo a modulo |
| Regra de negocio mudou completamente | Reescrever |
| Performance ruim mas estrutura ok | Otimizar, nao refatorar |
Sinais de sucesso
- Cada funcao < 50 linhas
- Nomes auto-explicativos (sem need de comentario)
- Testes unitarios cobrindo paths principais
/advpl-find-issuesretorna limpo- Novo dev consegue entender em 1 hora (nao 1 semana)