Sebenta · Programar com código generativo (UC04372)
- Introdução
- 1. Programação genérica
- 2. Código generativo e meta linguagens
- 3. Ferramentas e técnicas de geração de código
- 4. Frameworks e templates de desenvolvimento
- 5. Design patterns
- 6. Código genérico: herança e polimorfismo
- 7. Metaprogramação
- 8. Tipagem dinâmica para código genérico
- 9. Frameworks e bibliotecas flexíveis
- 10. Adaptar código para automatizar tarefas
- 11. Testar código em diferentes plataformas
- 12. Segurança, normas e boas práticas
- Erros comuns
- Glossário
- Síntese
- Exercícios resolvidos
Introdução
Esta sebenta ensina a escrever código que escreve código: em vez de repetir a mesma lógica ficheiro após ficheiro, aprendes a generalizar, a gerar e a automatizar. É uma competência central para quem produz conteúdos interativos, porque este trabalho repete-se muito, um quiz atrás de outro, um cartão de produto atrás de outro, uma galeria atrás de outra, e a diferença entre um técnico júnior e um técnico sénior está em saber quando e como automatizar essa repetição em vez de a copiar e colar.
O percurso começa nos princípios abstratos (programação genérica, tipos de linguagens), passa pelas técnicas concretas de geração de código (templates, scaffolding, meta linguagens, IA generativa), consolida com design patterns e metaprogramação, e termina no que torna o código gerado seguro e testável em produção: tipagem dinâmica, frameworks flexíveis, testes em diferentes plataformas e segurança no código gerado. Fecha com as normas de segurança, saúde e ambiente que acompanham qualquer trabalho técnico.
Objetivos de aprendizagem (referencial): criar código com código generativo; adaptar código para automatizar tarefas; desenvolver código com frameworks; testar código em diferentes plataformas; evitando estrangulamentos no processo de geração, garantindo escalabilidade, evitando duplicação excessiva, facilitando a integração e validando entradas para evitar falhas de segurança.
1. Programação genérica
A programação genérica é a técnica de escrever algoritmos e estruturas de dados que funcionam com qualquer tipo, em vez de escrever uma versão diferente para cada tipo de dado.
Porque interessa
Sem programação genérica, escrever uma função ordenar para números obriga a escrever outra quase igual para texto, e outra para objetos. Com genéricos, escreve-se uma vez:
function primeiro<T>(lista: T[]): T {
return lista[0];
}
primeiro<number>([1, 2, 3]); // T = number
primeiro<string>(["a", "b"]); // T = string
O <T> é um parâmetro de tipo: só se define concretamente quando a função é usada. O compilador continua a verificar que o tipo é usado de forma consistente.
Tipos de linguagens e como resolvem a genericidade
| Categoria | Linguagens | Como funciona |
|---|---|---|
| Estaticamente tipadas com genéricos | TypeScript, Java, C#, Kotlin | tipo verificado em compilação, com <T> |
| Templates (compilação) | C++ | o compilador gera uma versão do código por cada tipo usado |
| Dinamicamente tipadas | JavaScript, Python, Ruby | qualquer valor serve; o tipo só se conhece em execução |
Nesta UC o exemplo prático é TypeScript: junta a segurança dos tipos à familiaridade do JavaScript, muito usado na produção de conteúdos interativos para a web.
Exemplo resolvido: um Stack genérico
Precisamos de uma pilha (stack) que funcione tanto para números de pontuação como para nomes de jogadores.
class Pilha<T> {
private itens: T[] = [];
empilhar(item: T): void { this.itens.push(item); }
desempilhar(): T | undefined { return this.itens.pop(); }
}
const pontuacoes = new Pilha<number>();
pontuacoes.empilhar(10);
const nomes = new Pilha<string>();
nomes.empilhar("Ana");
Uma só classe Pilha<T> serve os dois casos. Sem genéricos, seria preciso PilhaDeNumeros e PilhaDeNomes, praticamente iguais.
2. Código generativo e meta linguagens
Código generativo é código que gera outro código, poupando trabalho manual repetitivo e garantindo consistência entre ficheiros parecidos.
Três níveis de código generativo
- Geradores clássicos: um script lê dados ou uma especificação e produz ficheiros de código, sem intervenção humana em cada um.
- Metaprogramação: código que examina ou modifica código, o seu ou de outro, em tempo de execução ou compilação (ver capítulo 7).
- IA generativa de código: assistentes (GitHub Copilot, ChatGPT, Claude) que geram excertos a partir de linguagem natural ou do contexto do ficheiro.
O que é uma meta linguagem
Uma meta linguagem é uma linguagem usada para descrever ou gerar outra linguagem. É o "molde" a partir do qual sai o código final.
- Templates de texto:
Handlebars,EJS,Nunjucks, misturam marcação fixa com marcadores ({{nome}}) que um motor substitui por dados reais. - DSL (linguagem de domínio específico): uma mini linguagem feita para um problema concreto, por exemplo um
.yamlque descreve os ecrãs de um jogo educativo. - Schemas:
JSON SchemaouGraphQL SDLdescrevem a forma dos dados e, a partir daí, podem gerar automaticamente tipos e formulários.
Exemplo resolvido: template de texto
Um .hbs (Handlebars) que gera 50 cartões de produto a partir de um único ficheiro de dados:
<div class="cartao">
<h3>{{nome}}</h3>
<p>{{preco}} EUR</p>
</div>
const dados = [{ nome: "Cabaz", preco: 12 }, { nome: "Azeite", preco: 8 }];
dados.forEach(item => gerarCartao(template, item));
Um só template + um ficheiro de dados substitui 50 blocos de HTML repetidos e reduz drasticamente o risco de erro de cópia.
3. Ferramentas e técnicas de geração de código
Scaffolding
Scaffolding é gerar a estrutura inicial de um projeto, ou de uma peça de código, em vez de a construir do zero.
| Ferramenta | O que gera |
|---|---|
npm create vite@latest |
um projeto novo completo, com configuração e exemplo |
Plop, Hygen |
ficheiros a partir de um template e de perguntas ao utilizador |
Geradores a partir de schema |
tipos, validações e até formulários, a partir de uma definição de dados |
Assistentes de IA generativa
Ferramentas como GitHub Copilot, ChatGPT ou Claude geram código a partir de um pedido em linguagem natural ou do contexto do ficheiro aberto. Servem para automatizar tarefas repetitivas: escrever um teste previsível, converter um formato de dados, preencher uma função com um padrão já visto no ficheiro.
Não substituem a revisão humana. Um dos critérios de desempenho da UC é evitar a introdução de estrangulamentos no processo de geração, o que aqui significa duas coisas em equilíbrio: não bloquear o trabalho à espera do gerador, mas também não aceitar código gerado sem o ler e testar.
Exemplo resolvido: usar um gerador com Plop
- Definir um template de componente interativo (
quiz,galeria,video) emplopfile.js. - Correr
npx plop componente. - O
Ploppergunta o nome do componente e gera automaticamenteQuiz.tsx,Quiz.test.tsxeQuiz.module.css, já com a estrutura correta e os nomes preenchidos.
Resultado: cada novo componente segue sempre a mesma estrutura, sem depender da memória de quem o cria.
4. Frameworks e templates de desenvolvimento
Um framework dá a estrutura, as regras e as ferramentas para desenvolver mais depressa. Muitos trazem o seu próprio sistema de templates.
Templates em frameworks de UI
- React/Vue: cada componente é, na prática, um template reutilizável que recebe dados por propriedades (
props) e produz HTML. - Angular: templates HTML com diretivas próprias (
*ngFor,*ngIf) que o framework compila antes de correr no browser. - Motores server-side:
EJS,Pug,Handlebars, geram HTML no servidor antes de o enviar ao browser.
Exemplo resolvido: componente vs cópia manual
Sem template (repetido três vezes, com risco de erro de cópia):
<div class="cartao"><h3>Cabaz</h3><p>12 EUR</p></div>
<div class="cartao"><h3>Azeite</h3><p>8 EUR</p></div>
<div class="cartao"><h3>Mel</h3><p>6 EUR</p></div>
Com um componente React (um template, dados variáveis):
function Cartao({ nome, preco }: { nome: string; preco: number }) {
return <div className="cartao"><h3>{nome}</h3><p>{preco} EUR</p></div>;
}
produtos.map(p => <Cartao key={p.nome} nome={p.nome} preco={p.preco} />)
Uma alteração ao design do cartão só se faz num sítio, e propaga-se a todos os produtos.
5. Design patterns
Um design pattern é uma solução reutilizável para um problema recorrente no desenho de software. Não é código pronto a copiar, é uma estrutura de raciocínio documentada, com nome próprio, que dá vocabulário comum à equipa técnica.
Princípios por trás dos patterns
- DRY (Don't Repeat Yourself): não duplicar lógica.
- SOLID: cinco princípios de desenho orientado a objetos, entre eles o Open/Closed (aberto a extensão, fechado a modificação).
- Baixo acoplamento, alta coesão: peças que dependem pouco umas das outras e que fazem bem uma única coisa.
Os patterns são a aplicação prática destes princípios: seguir os princípios com disciplina leva, quase sempre, a reinventar um pattern já conhecido.
Tipos de design patterns
Padrões de criação, tratam de como os objetos nascem:
| Padrão | Ideia |
|---|---|
| Factory | uma função ou classe decide que tipo de objeto criar |
| Singleton | garante uma única instância partilhada |
| Builder | constrói um objeto complexo passo a passo |
Padrões estruturais, tratam de como as peças se compõem:
- Adapter: adapta uma interface antiga a uma nova, sem reescrever tudo.
- Decorator: acrescenta comportamento a um objeto sem alterar a classe original.
Padrões comportamentais, tratam de como os objetos comunicam:
- Observer: um objeto avisa vários outros quando o seu estado muda; é a base dos eventos de interface e do estado reativo.
- Strategy: troca o algoritmo usado em tempo de execução.
Exemplo resolvido: Factory de componentes interativos
function criarComponente(tipo: "quiz" | "video" | "galeria"): ComponenteInterativo {
switch (tipo) {
case "quiz": return new Quiz();
case "video": return new Video();
case "galeria": return new Galeria();
}
}
const componente = criarComponente("quiz");
componente.render();
O código chamador só conhece a interface comum ComponenteInterativo, não a classe concreta que a Factory devolveu. Se amanhã aparecer um novo tipo ("jogo"), só a Factory muda; o resto do código fica igual.
6. Código genérico: herança e polimorfismo
Herança
A herança permite que uma classe reutilize atributos e métodos de outra, especializando-a.
class ComponenteInterativo {
render(): void { /* comportamento comum */ }
}
class Quiz extends ComponenteInterativo {
corrigir(resposta: string): boolean { return true; }
}
Quiz herda render() e acrescenta corrigir(), específico dele. Regra prática: só usar herança quando existe mesmo uma relação "é um" (Quiz é um ComponenteInterativo); caso contrário, prefere-se composição.
Polimorfismo
Polimorfismo é a capacidade de tratar objetos de classes diferentes da mesma forma, através de uma interface comum.
function mostrarTodos(itens: ComponenteInterativo[]) {
itens.forEach(i => i.render());
}
mostrarTodos([new Quiz(), new Galeria()]);
O código chamador não precisa de saber se cada item é Quiz ou Galeria: chama render() e cada objeto responde de acordo com a sua própria implementação. É o que torna a Factory do capítulo anterior útil na prática: cria objetos diferentes, tratados depois de forma uniforme.
Exemplo resolvido: adicionar um tipo novo sem quebrar nada
Suponha que a Galeria já existe e agora se acrescenta Video:
class Video extends ComponenteInterativo {
render(): void { /* tocar o vídeo */ }
}
mostrarTodos([new Quiz(), new Galeria(), new Video()]); // funciona sem alterar mostrarTodos
Porque mostrarTodos depende apenas da interface render(), aceitar um novo tipo não exige alterar essa função, apenas criar a nova classe. Isto é o princípio Open/Closed na prática.
7. Metaprogramação
Metaprogramação é escrever código que examina, gera ou modifica outro código, em tempo de execução ou de compilação. É "um nível acima" da programação normal.
Conceitos principais
- Reflexão: um programa consulta a sua própria estrutura, por exemplo, que métodos tem uma classe.
- Geração dinâmica: criar funções, classes ou objetos em tempo de execução, a partir de dados.
- Decoradores/anotações: marcar código com metadados que outro código lê e usa (
@Component,@Injectable).
Frameworks como Angular, NestJS e Spring usam metaprogramação para "ligar as peças" automaticamente, sem que o programador escreva essa ligação à mão.
Exemplo resolvido: um decorator simples
function log(target, key, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log(`A chamar ${key}`, args);
return original.apply(this, args);
};
}
class Quiz {
@log
corrigir(resposta) { /* lógica de correção */ }
}
O decorator @log envolve corrigir() de fora, sem tocar no seu código interno: acrescenta um registo em consola sempre que corrigir é chamado. É metaprogramação aplicada ao padrão Decorator do capítulo 5.
Proxy e Reflect
O Proxy de JavaScript permite intercetar leituras, escritas e chamadas a um objeto:
const registado = new Proxy({}, {
set(alvo, propriedade, valor) {
console.log(`${String(propriedade)} definido para ${valor}`);
alvo[propriedade] = valor;
return true;
}
});
registado.nome = "Ana"; // imprime: nome definido para Ana
O Vue 3 usa Proxy internamente para detetar alterações a dados e atualizar o ecrã automaticamente: é reatividade construída em cima de metaprogramação.
8. Tipagem dinâmica para código genérico
Duck typing
Em linguagens de tipagem dinâmica (JavaScript, Python), o tipo de uma variável só se conhece em execução. Isto dá uma genericidade "natural", conhecida como duck typing: "se anda como pato e faz quack como pato, é um pato", importa o que o objeto sabe fazer, não a classe a que pertence.
function tocar(objeto) {
objeto.play();
}
tocar(video); // funciona
tocar(audio); // também funciona, desde que tenha play()
A vantagem é a flexibilidade: tocar funciona com qualquer objeto que tenha play(). O risco é que erros de tipo só aparecem em execução, por vezes já em produção.
Armazenar e manipular tipos diferentes
- Estruturas genéricas:
Array<T>,Map<K, V>guardam qualquer tipo de dado com a mesma API. - Union types (TypeScript):
string | numberdescreve um valor que pode assumir mais do que um tipo. - Validação em fronteira: ao entrar dados externos (formulário, resposta de uma API), validam-se e convertem-se para o tipo esperado antes de os usar no resto do código.
Exemplo resolvido: validar antes de usar
function validarIdade(valor: unknown): number {
const numero = Number(valor);
if (Number.isNaN(numero) || numero < 0) {
throw new Error("Idade inválida");
}
return numero;
}
Mesmo em JavaScript dinamicamente tipado, validarIdade garante que, a partir daquele ponto, o resto do código trabalha sempre com um número válido. É a disciplina que compensa a flexibilidade da tipagem dinâmica.
9. Frameworks e bibliotecas flexíveis
Arquitetura de frameworks flexíveis
Frameworks e bibliotecas flexíveis oferecem funcionalidades genéricas que se adaptam ao projeto, em vez de imporem uma única forma de trabalhar.
- Plugins e middlewares: o framework expõe "ganchos" onde se liga código próprio, sem tocar no núcleo.
- Configuração vs convenção: uns pedem configuração explícita (Webpack), outros seguem convenções de pastas e nomes (Next.js).
- Extensibilidade: bibliotecas como React permitem criar componentes e hooks próprios que se comportam como os nativos.
Escolher e integrar bem uma biblioteca
| Critério | Porque interessa |
|---|---|
| Manutenção ativa | evita depender de código abandonado, com falhas nunca corrigidas |
| Documentação | reduz o tempo de integração e de correção de erros |
| Tamanho do pacote | afeta a velocidade de carregamento do conteúdo interativo |
| Compatibilidade | evita conflitos com outras bibliotecas já usadas no projeto |
Fixar versões em package-lock.json garante que o projeto se constrói sempre da mesma forma, hoje e daqui a um ano. O tree-shaking remove do código final o que não é usado da biblioteca, mantendo o resultado mais leve.
Exemplo resolvido: um hook reutilizável
function useContador(inicial: number) {
const [valor, setValor] = useState(inicial);
const incrementar = () => setValor(v => v + 1);
return { valor, incrementar };
}
// reutilizado em qualquer componente:
const { valor, incrementar } = useContador(0);
useContador extrai uma lógica genérica (contar) para ser reutilizada em qualquer componente que precise dela, sem duplicar useState e a função de incremento em cada sítio.
10. Adaptar código para automatizar tarefas
Reconhecer o que vale a pena automatizar
Regra prática: automatiza o que se repete três ou mais vezes. Automatizar uma tarefa única costuma custar mais tempo do que fazê-la à mão.
- Scripts de tarefas:
npm run build,npm run lint,npm run testencadeiam passos repetitivos num só comando. - Geração em lote: um script que lê uma lista de exercícios de um
.jsone gera automaticamente uma página HTML por exercício. - CI/CD: pipelines que correm testes e publicam a aplicação sozinhos, a cada alteração de código.
Adaptar algoritmos a diferentes contextos
Um algoritmo genérico adapta-se ao contexto através de parâmetros e de configuração por dados, sem que a lógica principal mude.
Exemplo resolvido: gerador de certificados reutilizável
Um gerador de certificados de um curso de eletrónica precisa de servir também um curso de programação.
function gerarCertificado(config: { curso: string; horas: number; aluno: string }) {
return template
.replace("{{curso}}", config.curso)
.replace("{{horas}}", String(config.horas))
.replace("{{aluno}}", config.aluno);
}
gerarCertificado({ curso: "Eletrónica", horas: 25, aluno: "Rui" });
gerarCertificado({ curso: "Programação", horas: 25, aluno: "Ines" });
A função e o template não mudam entre cursos, só os dados de entrada mudam. Isto é automatização bem feita: escrever uma vez, reutilizar sempre que aparece um curso novo.
11. Testar código em diferentes plataformas
Tipos de testes
| Tipo | Verifica | Ferramentas comuns |
|---|---|---|
| Unitário | uma função ou classe isolada | Jest, Vitest |
| Integração | várias peças a funcionar juntas | Jest, Vitest |
| End-to-end (E2E) | o utilizador real no browser | Playwright, Cypress |
| De plataforma | o mesmo conteúdo em browsers/dispositivos diferentes | DevTools, BrowserStack |
Testar código gerado é ainda mais importante do que testar código escrito à mão: um erro no gerador propaga-se a todos os ficheiros que ele produz.
Testar em diferentes plataformas na prática
- Matriz de testes: cruzar browsers (Chrome, Firefox, Safari) com dispositivos (desktop, tablet, telemóvel).
- DevTools em modo responsivo simulam tamanhos de ecrã diferentes sem precisar de dispositivos físicos.
- Acessibilidade: testar também com teclado e leitor de ecrã, não só com rato.
Exemplo resolvido: testar um gerador de componentes
test("gerarCartao produz o nome e preço corretos", () => {
const html = gerarCartao({ nome: "Cabaz", preco: 12 });
expect(html).toContain("Cabaz");
expect(html).toContain("12 EUR");
});
test("gerarCartao escapa HTML no nome", () => {
const html = gerarCartao({ nome: "<script>", preco: 1 });
expect(html).not.toContain("<script>");
});
O segundo teste não é só sobre correção funcional, é também um teste de segurança: garante que dados de entrada perigosos não passam diretamente para o HTML gerado.
12. Segurança, normas e boas práticas
Vulnerabilidades comuns em código gerado
Código gerado automaticamente, por templates ou por IA, pode introduzir falhas se não for revisto:
- Injeção: dados do utilizador inseridos diretamente numa query ou num template sem escapar, abrindo a porta a SQL injection ou XSS.
- Dependências desatualizadas: geradores que instalam pacotes antigos com vulnerabilidades conhecidas.
- Segredos expostos: templates que copiam chaves de API ou palavras-passe de exemplo diretamente para o código real.
Boas práticas de segurança no código generativo
- Escapar sempre dados que vêm do utilizador antes de os inserir em HTML, SQL ou comandos de sistema.
- Validar o tipo e o formato de cada entrada, na fronteira do sistema, antes de gerar código a partir dela.
- Rever o código gerado por IA ou por templates com o mesmo rigor de código escrito à mão.
- Usar ferramentas de análise automática (linters,
npm audit) para apanhar problemas antes de publicar.
Segurança e saúde no trabalho
O trabalho de programação é sedentário e exige atenção à ergonomia: ecrã à altura dos olhos, antebraços apoiados, pausas regulares a cada 50-60 minutos para os olhos e para evitar lesões por esforço repetitivo.
Proteção ambiental
Código mais eficiente consome menos processamento e menos energia nos servidores. Green coding significa evitar processamento e armazenamento desnecessários, gerar só o que é preciso e prolongar a vida útil do equipamento sempre que possível.
Erros comuns
- Confundir "genérico" com "sem tipo": um genérico continua a ser verificado pelo compilador.
- Usar código generativo (IA ou templates) sem rever nem testar o que sai.
- Abusar de patterns em código simples, complicando sem necessidade.
- Herdar classes só para reutilizar código, sem existir uma relação lógica "é um".
- Confiar em dados externos sem validar o tipo, mesmo em linguagens de tipagem dinâmica.
- Testar só no browser do próprio computador e assumir que funciona em todo o lado.
- Inserir dados do utilizador diretamente em HTML ou consultas sem os escapar.
Glossário
- Programação genérica: escrever algoritmos que funcionam com vários tipos de dados.
- Código generativo: código que produz outro código, por templates, scaffolding ou IA.
- Scaffolding: gerar a estrutura inicial de um projeto ou de uma peça de código.
- Meta linguagem: linguagem usada para descrever ou gerar outra linguagem.
- DSL: mini linguagem feita para um problema específico.
- Design pattern: solução reutilizável e documentada para um problema recorrente de desenho.
- Herança: uma classe reutiliza e especializa outra.
- Polimorfismo: tratar objetos de classes diferentes de forma uniforme, através de uma interface comum.
- Metaprogramação: código que examina ou modifica outro código.
- Decorator: metadado que envolve código com comportamento extra, sem alterar o código original.
- Duck typing: um objeto serve se tiver os métodos certos, independentemente da sua classe.
- Tree-shaking: remover do código final o que não é usado de uma biblioteca.
- XSS / injeção: falha de segurança que insere código malicioso através de dados não validados.
Síntese
Código generativo é escrever código que escreve código: usa programação genérica para trabalhar com qualquer tipo, templates, scaffolding e IA para gerar o repetitivo, design patterns para dar estrutura e vocabulário comum, e metaprogramação para código que se examina e adapta a si próprio. A tipagem dinâmica dá flexibilidade, mas exige validação disciplinada. Fecha o ciclo em frameworks flexíveis, testes em diferentes plataformas e segurança no código gerado, sempre com as normas de segurança, saúde e ambiente a acompanhar o trabalho técnico.
Exercícios resolvidos
1. Explica em duas frases a diferença entre programação genérica e tipagem dinâmica.
Resolução: a programação genérica usa parâmetros de tipo (
<T>) verificados em compilação para funcionar com vários tipos, mantendo a segurança de tipos. A tipagem dinâmica adia essa verificação para a execução, o que dá mais flexibilidade mas também mais risco de erros só aparecerem em produção.
2. Dado o padrão Factory criarComponente(tipo), o que precisas de mudar para acrescentar um novo tipo "jogo"?
Resolução: só a Factory precisa de um novo caso no
switch(case "jogo": return new Jogo()) e a nova classeJogoque implementa a interfaceComponenteInterativo. O código que chamamostrarTodos()não muda, porque depende apenas da interface comum, isto é o princípio Open/Closed em ação.
3. Um colega cola um excerto de código gerado por IA diretamente num formulário de contacto, sem o rever. Que dois riscos concretos identificas?
Resolução: risco de injeção (se o código não escapar os dados do utilizador antes de os mostrar ou guardar) e risco de lógica incorreta não detetada (a IA pode ter gerado uma validação incompleta ou um caso limite mal tratado). Ambos só se apanham com revisão e testes.
4. Escreve, em pseudocódigo, um decorator simples que mede o tempo de execução de uma função.
Resolução:
js function medirTempo(target, key, descriptor) { const original = descriptor.value; descriptor.value = function (...args) { const inicio = Date.now(); const resultado = original.apply(this, args); console.log(`${key} demorou ${Date.now() - inicio}ms`); return resultado; }; }O decorator envolve a função original, corre um cronómetro antes e depois, e devolve o resultado sem alterar o comportamento da função de origem.
5. Um gerador de páginas de exercícios produz 40 ficheiros. Um teste unitário do gerador falha. Porque é mais grave do que uma página individual estar errada?
Resolução: porque o erro está no gerador, não numa página isolada, o que significa que os 40 ficheiros produzidos têm potencialmente o mesmo erro. Corrigir o gerador e regenerar tudo é mais eficiente e mais seguro do que corrigir cada ficheiro à mão, e o teste unitário evita que o problema chegue a produção.