Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC04371

Sebenta · Desenvolver código genérico para conteúdos digitais (UC04371)

Programação genérica, algoritmos, estruturas de dados, orientação a objetos, design patterns e frameworks flexíveis
25h · 2.25 pontos crédito Curso: T. Produção de Conteúdos I ↗ Referencial oficial SNQ
Índice

Introdução

Esta sebenta ensina a escrever código que serve para mais do que um caso: código genérico. Em vez de uma função só para imagens, outra só para vídeos e outra só para questionários, aprendes a analisar o que os conteúdos digitais têm em comum e a escrever uma única solução que serve a todos, e cresce sem ser reescrita.

O percurso segue a lógica de um profissional de programação: primeiro os conceitos (programação genérica, tipos de linguagens, algoritmos e estruturas de dados), depois a tipagem dinâmica, a orientação a objetos (classes, herança, polimorfismo), os design patterns mais úteis para conteúdos digitais, os frameworks e bibliotecas flexíveis, e por fim como adaptar e testar o código com rigor, sem esquecer as normas de segurança, saúde e ambiente. Os exemplos usam JavaScript, a linguagem mais comum em conteúdos digitais interativos, mas os conceitos são transferíveis para qualquer linguagem.

Objetivos de aprendizagem (referencial): analisar requisitos específicos para conteúdos digitais; criar código genérico para execução de conteúdos digitais; adaptar código genérico; e desenvolver código genérico com frameworks, respeitando os requisitos, evitando complexidade desnecessária e tratando erros de forma genérica.

1. Programação genérica: conceitos e princípios

Código genérico é código escrito para funcionar com vários tipos de dados ou de conteúdos, sem precisar de ser reescrito para cada caso específico. Uma galeria de imagens, um leitor de vídeo e um questionário interativo têm muito em comum: um título, uma forma de aparecer no ecrã, uma forma de ser testado. Código genérico é aquele que trata esse "comum" numa só solução.

Porque programar de forma genérica

Princípios

⚠️ Genérico não é sinónimo de complicado. Um dos critérios de desempenho da UC é precisamente evitar complexidades desnecessárias: generalizar só quando há, de facto, mais do que um caso a servir.

2. Tipos de linguagens de programação

Um paradigma de programação é uma forma de organizar o raciocínio ao escrever código. Conhecer os principais ajuda a escolher a ferramenta certa para cada problema.

Paradigma Ideia central Exemplo de linguagem
Imperativo sequência de instruções que mudam o estado do programa C, JavaScript
Orientado a objetos dados e comportamento agrupados em objetos Java, C#, JavaScript
Funcional funções puras, sem efeitos secundários Haskell, partes de JavaScript
Declarativo descreve o resultado desejado, não os passos SQL, HTML/CSS

A maioria das linguagens modernas, incluindo o JavaScript, é multi-paradigma: combina vários estilos conforme o que resolve melhor cada problema.

Compiladas vs interpretadas, estáticas vs dinâmicas

Para conteúdos digitais na web, a combinação típica é interpretada e de tipagem dinâmica: o JavaScript, que corre diretamente no browser, sem instalação nem compilação prévia por parte de quem visita a página.

3. Algoritmos genéricos e tipos de dados

Um algoritmo é uma sequência de passos, bem definida, que resolve um problema. Um algoritmo genérico resolve o problema de forma independente do tipo concreto de dado: por exemplo, um algoritmo de ordenação que funciona tanto para números como para nomes de conteúdos.

Exemplo resolvido: uma função de ordenação genérica

function ordenar(lista, comparar) {
  return [...lista].sort(comparar);
}

const numeros = [5, 2, 9, 1];
const ordenadosAsc = ordenar(numeros, (a, b) => a - b);
// [1, 2, 5, 9]

const titulos = ["Zebra", "Abelha", "Morcego"];
const ordenadosAbc = ordenar(titulos, (a, b) => a.localeCompare(b));
// ["Abelha", "Morcego", "Zebra"]

Passo a passo: 1. A função ordenar recebe qualquer lista e uma regra de comparação. 2. Não sabe, à partida, se os elementos são números ou texto: quem chama a função decide isso através do parâmetro comparar. 3. O mesmo algoritmo (usar sort com uma regra) serve os dois casos, sem duplicar código.

Tipos de dados

Um algoritmo genérico costuma operar sobre dados compostos, aplicando a mesma lógica de forma consistente a cada elemento da coleção.

4. Estruturas de dados genéricas

A estrutura de dados certa é o alicerce de um algoritmo genérico eficiente. Cada estrutura guarda os dados de uma forma diferente e serve melhor um tipo de problema.

Estrutura O que guarda Uso típico em conteúdos digitais
Array / lista elementos ordenados, acedidos por posição lista de slides, galeria de imagens
Objeto / dicionário pares chave-valor as propriedades de um conteúdo (título, tipo, url)
Conjunto (Set) valores únicos, sem repetição as tags de um curso, sem duplicados
Fila (Queue) primeiro a entrar, primeiro a sair (FIFO) fila de reprodução de vídeos
Pilha (Stack) último a entrar, primeiro a sair (LIFO) histórico de "recuar" num quiz

Todas são genéricas: guardam qualquer tipo de conteúdo, desde que se respeite a mesma forma de organização.

Exemplo resolvido: gerir uma coleção de conteúdos

const conteudos = [
  { id: 1, tipo: "imagem", titulo: "Capa" },
  { id: 2, tipo: "video",  titulo: "Introdução" },
  { id: 3, tipo: "quiz",   titulo: "Autoavaliação" },
];

const tipos = new Set(conteudos.map(c => c.tipo));
// Set { "imagem", "video", "quiz" }

const primeiro = conteudos[0];       // acesso por posição, típico de array
const porId = conteudos.find(c => c.id === 2); // procurar por chave

O array guarda a coleção ordenada; cada objeto guarda as propriedades de um conteúdo de forma genérica (o mesmo formato serve uma imagem, um vídeo ou um quiz); o Set dá, sem código extra, a lista de tipos sem repetições.

5. Tipagem dinâmica

Em tipagem dinâmica, o tipo de uma variável não é declarado à partida: é determinado, em cada momento, pelo valor que ela guarda.

let valor = 10;        // é um número
valor = "dez";         // agora é texto
valor = [1, 2, 3];      // agora é um array

Vantagens e riscos

Boas práticas com tipagem dinâmica

Exemplo resolvido: verificar o tipo antes de usar

function mostrarConteudo(item) {
  if (typeof item !== "object" || item === null) {
    throw new Error("mostrarConteudo espera um objeto de conteúdo");
  }
  console.log(`${item.tipo}: ${item.titulo}`);
}

mostrarConteudo({ tipo: "video", titulo: "Aula 1" }); // ok
mostrarConteudo("texto"); // lança erro, de forma controlada

A tipagem dinâmica dá liberdade, mas o código genérico bem escrito verifica o que recebe, em vez de assumir.

6. Orientação a objetos: classes, herança e polimorfismo

A orientação a objetos organiza o código em torno de classes (moldes) e objetos (instâncias concretas), e é a base mais comum para escrever código genérico com herança e polimorfismo.

Classes e encapsulamento

class ConteudoDigital {
  constructor(titulo, duracaoSeg = 0) {
    this.titulo = titulo;
    this.duracaoSeg = duracaoSeg;
  }
  render() {
    return `Conteúdo genérico: ${this.titulo}`;
  }
}

const c1 = new ConteudoDigital("Capa do curso");

O encapsulamento junta os dados (titulo, duracaoSeg) e o comportamento (render) na mesma classe. Cada new ConteudoDigital(...) cria um objeto: uma instância concreta, com os seus próprios valores.

Herança: reaproveitar código genérico

A herança cria uma classe nova a partir de outra, reaproveitando o que já existe:

class Video extends ConteudoDigital {
  constructor(titulo, duracaoSeg) {
    super(titulo, duracaoSeg);
  }
  render() {
    return `A reproduzir "${this.titulo}" (${this.duracaoSeg}s)`;
  }
}

const aula = new Video("Aula 1", 300);

Polimorfismo: a mesma chamada, comportamentos diferentes

Polimorfismo é a capacidade de objetos de classes diferentes responderem à mesma chamada de método cada um ao seu modo.

class Quiz extends ConteudoDigital {
  constructor(titulo, perguntas) {
    super(titulo);
    this.perguntas = perguntas;
  }
  render() {
    return `Quiz "${this.titulo}" com ${this.perguntas.length} pergunta(s)`;
  }
}

const conteudos = [
  new Video("Aula 1", 300),
  new Quiz("Autoavaliação", ["P1", "P2"]),
  new ConteudoDigital("Capa"),
];

for (const item of conteudos) {
  console.log(item.render());
}
// A reproduzir "Aula 1" (300s)
// Quiz "Autoavaliação" com 2 pergunta(s)
// Conteúdo genérico: Capa

O ciclo for não sabe se cada item é um vídeo, um quiz ou um conteúdo genérico: chama sempre render(), e cada objeto responde à sua maneira. É isto que torna o código genérico: se amanhã criarmos uma classe Audio, este ciclo continua a funcionar sem qualquer alteração.

7. Design patterns: conceitos, princípios e tipos

Um design pattern (padrão de projeto) é uma solução testada para um problema que se repete no desenho de software, com um nome e um vocabulário partilhados pelos programadores. Não é código pronto a copiar: é uma estrutura de raciocínio a adaptar a cada caso.

As três categorias clássicas

Categoria Resolve Exemplos
Criação como instanciar objetos Factory, Singleton
Estrutura como organizar classes e objetos Adapter, Decorator
Comportamento como os objetos comunicam entre si Strategy, Observer

Factory: centralizar a criação

O Factory concentra a criação de objetos numa única função, que decide qual classe instanciar a partir de um parâmetro:

function criarConteudo(tipo, titulo) {
  switch (tipo) {
    case "imagem": return new Imagem(titulo);
    case "video":  return new Video(titulo);
    case "quiz":   return new Quiz(titulo);
    default: throw new Error(`Tipo desconhecido: ${tipo}`);
  }
}

O resto do programa nunca escreve new Video(...) diretamente: pede sempre à factory. Se a forma de construir um Video mudar, só é preciso alterar um sítio.

Strategy: um algoritmo intercambiável

O Strategy encapsula um algoritmo que pode ser trocado, passado como parâmetro. A função ordenar do capítulo 3 já é um exemplo de Strategy, mesmo sem lhe termos dado o nome:

const estrategiaAlfabetica = (a, b) => a.titulo.localeCompare(b.titulo);
conteudos.sort(estrategiaAlfabetica);

Observer: notificar sem conhecer em detalhe

O Observer permite que um objeto (o sujeito) notifique vários observadores quando algo acontece, sem os conhecer em detalhe:

document.addEventListener("conteudoTerminado", () => {
  console.log("Avançar para o próximo conteúdo");
});

Um leitor de vídeo pode disparar o evento conteudoTerminado, e vários módulos independentes (estatísticas, navegação, notas) reagirem, cada um à sua maneira, sem o leitor de vídeo saber que módulos existem.

8. Frameworks e bibliotecas flexíveis

Biblioteca Framework
Quem controla o fluxo o programador o framework
Exemplo uma função de datas React, Vue
Flexibilidade maior segue convenções próprias

Funcionalidades genéricas, arquitetura e extensibilidade

Um framework ou biblioteca flexível para conteúdos digitais costuma oferecer:

const registoDeTipos = {};

function registarTipoDeConteudo(nome, Classe) {
  registoDeTipos[nome] = Classe;
}

registarTipoDeConteudo("audio", Audio); // extensão sem tocar no núcleo

Um bom framework cresce com o projeto, sem obrigar a reescrever o que já funciona: é a extensibilidade a funcionar na prática.

9. Adaptar código genérico a diferentes contextos

Adaptar código genérico é configurá-lo, não reescrevê-lo por dentro. As práticas mais comuns:

⚠️ Erro comum: fixar um valor "à mão" no meio de uma função (hardcoding), em vez de o receber como parâmetro. Torna o código difícil de reutilizar.

Exemplo resolvido: adaptar a biblioteca a um novo contexto

A mesma classe ConteudoDigital do capítulo 6, adaptada a um contexto totalmente diferente, um museu virtual em vez de um curso:

class Exponente extends ConteudoDigital {
  constructor(titulo, sala) {
    super(titulo);
    this.sala = sala;
  }
  render() {
    return `Exponente "${this.titulo}" na sala ${this.sala}`;
  }
}

const peca = new Exponente("Jarrão romano", "Sala 3");
peca.render(); // Exponente "Jarrão romano" na sala Sala 3

Nada mudou na classe base ConteudoDigital, nem no ciclo for que percorre conteúdos: só se acrescentou uma subclasse nova. É esta a prova de que o código está, de facto, bem generalizado.

10. Testes de funcionalidades

Testar é verificar, de forma sistemática, que o código faz o que devia fazer, incluindo em casos-limite.

Exemplo resolvido: testar um método herdado

class ConteudoDigital {
  constructor(titulo) { this.titulo = titulo; }
  testar() {
    return typeof this.titulo === "string" && this.titulo.length > 0;
  }
}

console.assert(new ConteudoDigital("Aula 1").testar() === true,
  "Título válido devia passar");
console.assert(new ConteudoDigital("").testar() === false,
  "Título vazio devia falhar");

Como testar() está na classe base, todas as subclasses (Video, Quiz, Imagem) herdam o mesmo teste, sem código extra.

Tratar erros de forma genérica

Uma função genérica deve prever dados inesperados e reagir de forma consistente:

function renderSeguro(item) {
  try {
    return item.render();
  } catch (erro) {
    console.error(`Falha ao renderizar: ${erro.message}`);
    return "Conteúdo indisponível";
  }
}

O bloco try/catch isola o erro sem parar o resto do programa: é o critério de desempenho "tratando e corrigindo erros de forma genérica" a funcionar na prática.

11. Segurança, saúde no trabalho e proteção ambiental

Escrever código também tem uma dimensão de segurança, saúde e ambiente, parte do referencial desta UC.

Normas de segurança e saúde no trabalho

Normas de proteção ambiental

Rigor no código também é uma forma de responsabilidade ambiental, uma das atitudes valorizadas no referencial desta UC.

Erros comuns

Glossário

Síntese

Código genérico para conteúdos digitais nasce de abstrair o que é comum: um algoritmo, uma estrutura de dados, uma classe base. A tipagem dinâmica do JavaScript dá flexibilidade, e a orientação a objetos, com herança e polimorfismo, permite que uma base única sirva imagens, vídeos, questionários ou qualquer outro conteúdo futuro. Os design patterns (Factory, Strategy, Observer) e os frameworks flexíveis dão vocabulário e estrutura a este trabalho. Adaptar é configurar, não reescrever; e nenhum código genérico está pronto sem testes que cubram os casos-limite e um tratamento de erros consistente. Tudo isto, sem esquecer as normas de segurança, saúde e ambiente do posto de trabalho.

Exercícios resolvidos

1. Porque é que a função ordenar(lista, comparar) do capítulo 3 é considerada código genérico?

Resolução: porque não depende do tipo concreto dos elementos da lista (números, texto, objetos): recebe a regra de comparação como parâmetro, em vez de a fixar dentro da função. A mesma função serve qualquer tipo de dado que se saiba comparar.

2. Uma turma tem duas funções, mostrarImagem(img) e mostrarVideo(vid), quase iguais. Como resolverias isto com herança e polimorfismo?

Resolução: criar uma classe base ConteudoDigital com um método render(), e duas subclasses Imagem e Video que sobrepõem render() com o comportamento próprio. Um único ciclo for chama item.render() para qualquer uma das duas, sem if/else a verificar o tipo.

3. Que design pattern está a ser usado neste código, e para quê?

function criarConteudo(tipo, titulo) {
  if (tipo === "video") return new Video(titulo);
  if (tipo === "quiz") return new Quiz(titulo);
}

Resolução: o padrão Factory. Centraliza a criação de objetos numa única função, para que o resto do programa nunca precise de escrever new Video(...) diretamente, facilitando alterações futuras à forma de construir cada tipo.

4. Distingue framework de biblioteca com um critério claro.

Resolução: o critério é quem controla o fluxo. Numa biblioteca, o programador chama as funções quando precisa. Num framework, é o framework que chama o código do programador, em pontos definidos, o princípio da inversão de controlo.

5. Um colega escreve catch (erro) {} sem fazer mais nada. Explica porque é um erro e como corrigir.

Resolução: um catch vazio esconde o erro, sem o registar nem o tratar, tornando o programa difícil de depurar. Devia, no mínimo, registar o erro (console.error) e devolver um resultado seguro, como no exemplo renderSeguro do capítulo 10.

6. Porque é que a tipagem dinâmica exige mais cuidado ao escrever código genérico?

Resolução: porque o tipo de uma variável só é conhecido em tempo de execução e pode mudar, o que significa que uma função pode receber um valor de tipo inesperado. Por isso o código genérico deve verificar o tipo com typeof antes de operar, em vez de assumir que recebeu o tipo certo.