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

SinalSintoma
God Function1 funcao com 2000+ linhas, 50+ Locals
Magic numbersIf nValor > 10000 sem explicacao
Commented dead code30% do codigo comentado por algum motivo perdido
Nested ifs profundosIf dentro de If dentro de If — 6+ niveis
Public/Private spaghettiVariaveis Public usadas em qualquer lugar
DBSelectArea sem RestArea"Vazamento" de estado entre chamadas
SQL inline gigantecQuery com 80 linhas concatenadas
Function nuaFunction Calc() sem User/Static — RPO Token rejeita

Estrategia: refactor incremental + safety nets

Etapa 1: Estabelecer safety net

  1. Capture comportamento atual com testes de "characterization" — pegue inputs reais e outputs, salve.
  2. Use /advpl-find-issues pra mapear problemas que ja existem.
  3. 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

Ferramentas que ajudam

Quando refatorar vs reescrever

SituacaoAcao
Codigo de 1000 linhas, com testes parciaisRefatorar incremental
Codigo de 3000+ linhas, sem documentacaoReescrever modulo a modulo
Regra de negocio mudou completamenteReescrever
Performance ruim mas estrutura okOtimizar, nao refatorar

Sinais de sucesso

  1. Cada funcao < 50 linhas
  2. Nomes auto-explicativos (sem need de comentario)
  3. Testes unitarios cobrindo paths principais
  4. /advpl-find-issues retorna limpo
  5. Novo dev consegue entender em 1 hora (nao 1 semana)

Veja também