Conversor automático de Delphi, VB6 ou .NET para C# funciona? Por que não convertemos código
A promessa é tentadora: aponte a ferramenta para o código antigo e receba um projeto em C#. A conversão automática de código até entrega algo que compila. O problema é o que ela carrega junto — e é por isso que, na NasteSoft, a migração de software legado começa por entender o sistema, não por traduzi-lo.
A promessa do conversor
Quem tem um ERP em Delphi 7 ou um sistema comercial em Visual Basic 6 já ouviu a oferta, ou já procurou por ela: um conversor Delphi para C#, uma ferramenta para converter VB6 para C# ou VB.NET, um utilitário que "atualiza" o .NET Framework para o .NET moderno. Em poucas semanas, milhares de linhas traduzidas. Sem reescrever regra, sem reentrevistar ninguém.
A conta parece óbvia. Reescrever é caro e arriscado; traduzir é rápido. Se a sintaxe muda e a lógica fica, por que não automatizar?
Porque a lógica não fica sozinha. Junto com ela vêm a arquitetura, os vícios, os componentes, as dependências implícitas e as decisões que ninguém lembra de ter tomado. Um conversor traduz como o código está escrito, não o que o sistema faz. E é exatamente o "como está escrito" que tornou o sistema difícil de manter.
Um exemplo concreto: o botão que faz tudo
O trecho abaixo é ilustrativo — escrito para este artigo, não tirado de nenhum cliente —, mas qualquer pessoa que já abriu um sistema Delphi dos anos 2000 vai reconhecer o padrão. Imagine uma distribuidora com um ERP em Delphi 7. Na tela de pedidos, o botão "Calcular" faz isto:
// Delphi 7 — exemplo ilustrativo
procedure TFrmPedido.Button1Click(Sender: TObject);
var
Total, Desc: Double;
begin
Query1.Close;
Query1.SQL.Text := 'SELECT SUM(QTD * PRECO) FROM ITENS WHERE PEDIDO = ' + Edit3.Text;
Query1.Open;
Total := Query1.Fields[0].AsFloat;
if (ComboBox2.ItemIndex = 1) and (Total > 5000) then
Desc := Total * 0.05
else
Desc := 0;
if DM.TabClientesUF.AsString = 'PR' then
Total := Total - Desc + (Total * 0.012)
else
Total := Total - Desc;
Label7.Caption := FormatFloat('#,##0.00', Total);
Query2.SQL.Text := 'UPDATE PEDIDOS SET TOTAL = ' + FloatToStr(Total) +
' WHERE NUM = ' + Edit3.Text;
Query2.ExecSQL;
end;
Em vinte linhas há uma consulta ao banco montada por concatenação, uma regra comercial (5% de
desconto quando a opção de índice 1 do combo está selecionada e o pedido passa de 5.000), uma regra regional
que depende de um registro posicionado num DataModule em outra unidade, formatação de
tela e uma gravação no banco. Tudo dentro de um evento de clique.
Agora veja o que um conversor de sintaxe tipicamente produz em C# (também ilustrativo):
// C# gerado por conversão automática — exemplo ilustrativo
private void Button1_Click(object sender, EventArgs e)
{
double Total, Desc;
Query1.Close();
Query1.SQL.Text = "SELECT SUM(QTD * PRECO) FROM ITENS WHERE PEDIDO = " + Edit3.Text;
Query1.Open();
Total = Query1.Fields[0].AsFloat;
if ((ComboBox2.ItemIndex == 1) && (Total > 5000))
Desc = Total * 0.05;
else
Desc = 0;
if (DM.TabClientesUF.AsString == "PR")
Total = Total - Desc + (Total * 0.012);
else
Total = Total - Desc;
Label7.Caption = DelphiCompat.FormatFloat("#,##0.00", Total);
Query2.SQL.Text = "UPDATE PEDIDOS SET TOTAL = " + DelphiCompat.FloatToStr(Total) +
" WHERE NUM = " + Edit3.Text;
Query2.ExecSQL();
}
Compila. Provavelmente roda. E é o mesmo código. Query1 não é um objeto do .NET: é
uma classe da biblioteca de compatibilidade que a ferramenta entrega para imitar o
TQuery do Delphi — é comum que conversores tragam uma camada assim, porque sem ela o
código traduzido não teria onde se apoiar. O SQL continua concatenado e aberto a injeção. O
dinheiro continua em double, com os erros de arredondamento de ponto flutuante. A
regra de desconto continua escondida atrás de ComboBox2.ItemIndex == 1, e ninguém
sabe dizer, olhando o código, que o item 1 significa "pagamento à vista".
E há um detalhe mais sutil: o acréscimo de 1,2% é calculado sobre o total antes do desconto, não depois. Intencional ou bug de quinze anos atrás? O conversor não pergunta. Ele copia.
O que o conversor carrega junto
O exemplo acima é um caso pequeno de um problema estrutural. Numa migração de sistemas legados de verdade, o que vem junto na tradução automática costuma ser isto:
- Arquitetura acoplada. Regra de negócio dentro de evento de tela, acesso a banco espalhado por centenas de formulários, estado global em
DataModuleou variáveis públicas de módulo. Traduzir mantém tudo grudado — e é justamente esse acoplamento que impede testar, integrar e evoluir. - Componentes sem equivalente. Grades, geradores de relatório e controles de terceiros (em Delphi, suítes de grid e de relatório; em VB6, controles ActiveX/OCX e relatórios em Crystal Reports) não têm tradução direta. Ou o conversor emula, criando dependência de uma biblioteca que só ele mantém, ou deixa um buraco para o time preencher à mão.
- Semântica que não traduz. No VB6,
Variante conversões implícitas fazem"10" * "2,5"funcionar de acordo com a configuração regional do Windows, eOn Error Resume Nextfaz o código seguir em frente depois de um erro, linha após linha. Para reproduzir isso fielmente em C#, a ferramenta precisa envolver cada instrução num tratamento de erro — e o código fica ilegível — ou simplificar, e aí o comportamento muda sem ninguém perceber. - Configuração global invisível. No exemplo Delphi,
FloatToStrnuma máquina em português gera vírgula decimal, e oUPDATEquebraria — a menos que, em algum lugar da inicialização do programa, alguém tenha trocado o separador decimal global. Esse tipo de dependência não aparece no trecho convertido. - Nomes que não dizem nada.
Button1,Edit3,Query2,Label7. O conversor preserva os nomes porque não tem como saber o que eles significam. O time herda um código onde nenhum identificador explica a intenção. - Falta de testes. O sistema original quase nunca tem testes automatizados. O convertido também não terá. Ou seja: não existe como provar que o C# faz o mesmo que o Delphi fazia, a não ser usando o sistema em produção.
"Compila" não é "funciona"
O relatório final de muitas conversões é um número: tantos por cento do código convertido sem erro de compilação. É uma métrica fácil de medir e quase irrelevante. Compilar prova que a sintaxe está correta. Não prova que o desconto continua sendo aplicado no caso certo, que o arredondamento bate com o fiscal, que a ordem dos eventos de tela continua disparando a validação antes da gravação.
Em sistemas desktop antigos, boa parte do comportamento vive em lugares que um tradutor linha a
linha não enxerga bem: a ordem em que eventos como OnExit, OnChange e
BeforePost disparam, campos calculados de TDataSet, triggers no banco,
o efeito colateral de um formulário que fica aberto escondido. A diferença aparece em produção —
no fechamento do mês, numa nota fiscal, num cliente específico.
Não é "quanto do código foi convertido?", e sim "como sabemos que o sistema novo calcula igual ao antigo?". Se a resposta for "vamos ver quando os usuários usarem", o risco foi só adiado.
O custo escondido: manter um código que ninguém escreveu
O motivo pelo qual uma empresa quer sair de Delphi ou VB6 raramente é a linguagem em si. É que cada mudança demora, ninguém tem segurança para mexer e contratar quem conhece ficou difícil. A conversão automática resolve o último ponto pela metade — agora o código está em C#, e há muito mais gente que sabe C# — e piora os outros dois.
O desenvolvedor C# que recebe o projeto convertido não reconhece nada ali como C# idiomático. Não há camadas, não há injeção de dependência, não há separação entre tela e regra, há uma biblioteca de emulação que ele nunca viu. Ele não escreveu esse código, o autor original também não (foi uma ferramenta), e quem entendia o sistema antigo entendia em Delphi. É um legado novo, numa linguagem nova — com a diferença de que agora nem o histórico do código ajuda.
É comum, nesse ponto, a empresa concluir que precisa refatorar o resultado da conversão. Refatorar código convertido é, na prática, fazer a migração de verdade — só que partindo de um material pior que o original e já tendo gastado o orçamento da conversão.
Quando uma ferramenta automática ajuda
Nada disso quer dizer que ferramenta automática seja inútil. Uma boa ferramenta de migração de sistema legado é valiosa como instrumento de leitura, não como estratégia. Usamos automação, nossa e de terceiros, para:
- Inventário. Listar formulários, units, módulos, componentes usados, consultas SQL embutidas (em Delphi, muitas ficam nos arquivos
.dfm, na propriedadeSQLdos componentes de consulta), referências e controles ActiveX declarados no projeto VB6. - Mapa de dependências. Quem chama quem, quais telas tocam quais tabelas, onde está o código morto que ninguém mais executa.
- Pedaços isolados de cálculo. Uma função pura de cálculo — juros, rateio, dígito verificador — pode ser traduzida automaticamente como ponto de partida, desde que venha acompanhada de testes que comparem a saída nova com a antiga.
- Atualização mecânica. No .NET Framework, há ferramentas da própria Microsoft que ajudam a converter arquivos de projeto e referências de pacote para o .NET moderno. Isso vale para bibliotecas e código sem interface; WebForms e WCF no lado do servidor não existem no .NET moderno, então essas partes precisam ser reescritas (em ASP.NET Core, Web API ou gRPC, ou com CoreWCF, que é mantido pela comunidade).
A diferença é o papel: a ferramenta produz insumo para quem vai entender e decidir, não o produto final.
E a IA? Ela converte?
É a pergunta que mais ouvimos hoje. Modelos de linguagem traduzem trechos de Delphi ou VB6 para C#
muito melhor que os conversores baseados em regras: dão nomes melhores, usam decimal
para dinheiro, parametrizam o SQL. Para um trecho isolado, o resultado costuma ser bom.
Mas o problema arquitetural continua igual. Se você pedir para a IA traduzir o
Button1Click acima, ela vai devolver um Button1Click mais bonito — ainda
com a regra dentro do evento, ainda dependendo de um registro posicionado em outro lugar. Traduzir
tela por tela, com ou sem IA, reproduz a estrutura da tela. E a IA tem um risco a mais: ela
corrige coisas por conta própria. Se o acréscimo sobre o total bruto era intencional e
ela "arrumar" para o total líquido, o código fica mais limpo e o cálculo fica errado.
Por isso usamos IA onde ela é forte: ler. Descrever o que uma rotina faz, mapear dependências, listar candidatos a regra de negócio, rascunhar documentação, sugerir casos de teste. Isso transforma semanas de leitura em dias. A validação — "é isso mesmo que o sistema deve fazer?" — é feita por gente, com quem opera o processo. IA é acelerador da leitura, não conversor.
A alternativa: entender, documentar, reescrever
O método da NasteSoft é o oposto da conversão: primeiro entender como o sistema funciona de verdade, depois documentar, só então escrever código novo.
- Entender. Código, banco (tabelas, triggers, procedures), telas efetivamente usadas, relatórios, logs e conversa com quem opera. A pergunta não é "o que este código diz", é "o que este sistema faz pela empresa".
- Documentar. Cada regra vira uma ficha com identificador, onde foi encontrada e um exemplo numérico validado com o usuário. O "acréscimo sobre o bruto" do exemplo vira uma pergunta explícita, com resposta registrada. Detalhamos como fazemos isso em como documentar um sistema legado que ninguém documentou.
- Reescrever na arquitetura certa. A regra sai da tela e vira código de domínio testável; a tela vira só tela.
Para o mesmo cálculo do exemplo, o resultado fica mais ou menos assim (ilustrativo):
// C# reescrito — exemplo ilustrativo
// R-012: desconto de 5% para pagamento à vista acima de R$ 5.000
// R-013: acréscimo regional de 1,2% para clientes do PR, sobre o total bruto
// (confirmado com o financeiro: é sobre o bruto, não sobre o líquido)
public sealed class CalculadoraTotalPedido
{
public decimal Calcular(decimal subtotal, CondicaoPagamento condicao, string uf)
{
var desconto = condicao == CondicaoPagamento.AVista && subtotal > 5000m
? subtotal * 0.05m
: 0m;
var acrescimo = uf == "PR" ? subtotal * 0.012m : 0m;
return subtotal - desconto + acrescimo;
}
}
[Fact]
public void Pedido_a_vista_acima_de_5000_no_PR()
{
var total = new CalculadoraTotalPedido().Calcular(6000m, CondicaoPagamento.AVista, "PR");
Assert.Equal(5772.00m, total); // 6.000 − 300 + 72, igual ao sistema antigo
}
A regra tem nome, tem rastreabilidade até a ficha da documentação, tem teste que prova que bate com o sistema antigo e pode ser chamada por uma tela web, um aplicativo ou uma integração. É isso que se compra numa migração — não a troca de linguagem.
E nada disso acontece numa virada única. O sistema novo assume uma parte por vez, rodando ao lado do legado (o padrão de estrangulamento), com cada etapa reversível. Para cada tecnologia há particularidades: veja como tratamos a migração de sistemas Delphi, a migração de sistemas VB6 e a migração do .NET Framework para o .NET moderno. A NasteSoft e seus parceiros já migraram bancos de dados e aplicações em Delphi, VB6 e .NET — e em nenhum desses casos o caminho foi traduzir linha a linha.
Conclusão
O conversor automático responde à pergunta errada. Ele resolve "como colocar este código em outra linguagem", quando o problema da empresa é "como ter um sistema que dá para manter, testar e evoluir". Código traduzido compila e carrega junto tudo o que tornava o original difícil.
A migração de software legado que funciona começa antes do código: entendendo o sistema, documentando as regras e só então reescrevendo, por partes. Leva mais pensamento no começo e muito menos dor depois — e deixa com a empresa uma documentação que continua valendo mesmo que a tecnologia mude de novo.
Recebeu uma proposta de conversão automática?
Mande o cenário. Fazemos uma leitura honesta do que a conversão resolveria, do que ela deixaria para trás e de qual seria o caminho alternativo.