Programação II – ECO Aulas Conceitos avançados

Interfaces, exceções e manipulação de arquivos em JavaScript

Nesta unidade vamos conectar três ideias importantes para desenvolver sistemas mais organizados e robustos: “interfaces” em JavaScript, tratamento de exceções e leitura/gravação de arquivos no navegador e em Node.js.

Introdução

Em Programação I e na revisão de OO, você trabalhou com classes, objetos e herança em Java. Nesta página vamos olhar para três temas que aparecem em praticamente todo sistema real: contratos de código (interfaces), erros em tempo de execução (exceções) e persistência simples com arquivos, agora em JavaScript.

Configuração do ambiente para os exemplos

Podemos reutilizar a mesma estrutura de arquivo HTML da revisão de OO, alterando apenas o nome do arquivo JavaScript para esta unidade.

Opção 1 – Navegador (recomendado)

  1. Crie uma pasta, por exemplo prog2-interfaces-excecoes.
  2. Dentro da pasta, crie um arquivo chamado index.html com o conteúdo abaixo.
<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="UTF-8" />
    <title>Interfaces, exceções e arquivos - Programação II</title>
  </head>
  <body>
    <h1>Interfaces, exceções e manipulação de arquivos</h1>
    <p>Abra o console do navegador para ver a saída dos exemplos.</p>

    <script src="interfaces-excecoes-arquivos.js"></script>
  </body>
</html>
  1. Crie um arquivo interfaces-excecoes-arquivos.js na mesma pasta. É nele que você vai colar e adaptar os exemplos desta página.
  2. Abra o index.html no navegador.
  3. Pressione F12 e acesse a aba Console para acompanhar a saída dos códigos.
Se você já configurou o ambiente da revisão de OO, pode apenas criar um novo arquivo JS e trocar o src da tag <script> para testar esta unidade.

Opção 2 – Node.js (para exploração extra)

  1. Mantenha um arquivo interfaces-excecoes-arquivos.js com os exemplos.
  2. No terminal, execute:
    node interfaces-excecoes-arquivos.js

1. “Interfaces” em JavaScript

JavaScript não possui a palavra-chave interface como Java, mas usamos o conceito de interface como um contrato: um conjunto de métodos que uma função ou classe espera receber. Fazemos isso principalmente com objetos que seguem um formato (duck typing) ou com classes base.

1.1 Interfaces via duck typing (objeto com formato esperado)

// "Interface" Logger: qualquer objeto que tenha um método log(mensagem)
function processarPedido(pedido, logger) {
  // Dependemos apenas de logger.log(...)
  logger.log(`Processando pedido ${pedido.id} do cliente ${pedido.cliente}`);
}

// Implementação 1: logger que escreve no console
const ConsoleLogger = {
  log(mensagem) {
    console.log("[CONSOLE] " + mensagem);
  }
};

// Implementação 2: logger que guarda mensagens em memória
const MemoryLogger = {
  mensagens: [],
  log(mensagem) {
    this.mensagens.push(mensagem);
  }
};

const pedido = { id: 123, cliente: "Ana" };

processarPedido(pedido, ConsoleLogger);
processarPedido(pedido, MemoryLogger);

console.log("Mensagens em memória:", MemoryLogger.mensagens);
Ideia-chave: a função não precisa saber a classe concreta, apenas que o objeto recebido possui o método esperado. Esse padrão é muito comum em JavaScript moderno.

1.2 Interfaces com classes base

// Classe base que funciona como "interface" de pagamento
class Pagamento {
  pagar(valor) {
    throw new Error("Método pagar(valor) deve ser implementado pela subclasse");
  }
}

class PagamentoCartao extends Pagamento {
  pagar(valor) {
    console.log(`Pagando R$ ${valor} com cartão de crédito`);
  }
}

class PagamentoPix extends Pagamento {
  pagar(valor) {
    console.log(`Pagando R$ ${valor} via PIX`);
  }
}

function finalizarCompra(valor, meioPagamento) {
  // Aqui dependemos da "interface" Pagamento
  meioPagamento.pagar(valor);
}

finalizarCompra(100, new PagamentoCartao());
finalizarCompra(75.5, new PagamentoPix());

Assim como na revisão de OO, usamos herança e sobrescrita de métodos; mas aqui a classe base é pensada como um contrato, e lança erro se alguém esquecer de sobrescrever o método obrigatório.

2. Exceções em JavaScript

Uma exceção é um erro que interrompe o fluxo normal do programa. Em JavaScript, usamos throw para lançar erros e try...catch...finally para tratá-los.

2.1 Lançando e tratando erros

function dividir(a, b) {
  if (b === 0) {
    throw new Error("Divisão por zero não é permitida");
  }
  return a / b;
}

try {
  console.log(dividir(10, 2));  // ok
  console.log(dividir(5, 0));   // lança erro
  console.log("Esta linha não será executada");
} catch (erro) {
  console.error("Ocorreu um erro:", erro.message);
} finally {
  console.log("Bloco finally sempre executa (com ou sem erro).");
}

2.2 Exceções para validação de dados

function validarUsuario(usuario) {
  if (!usuario.nome) {
    throw new Error("Nome é obrigatório");
  }
  if (!usuario.email || !usuario.email.includes("@")) {
    throw new Error("Email inválido");
  }
  if (usuario.idade < 18) {
    throw new Error("Usuário deve ser maior de 18 anos");
  }
}

function cadastrarUsuario(usuario) {
  try {
    validarUsuario(usuario);
    console.log("Usuário cadastrado com sucesso:", usuario.nome);
  } catch (erro) {
    console.error("Erro ao cadastrar usuário:", erro.message);
  }
}

cadastrarUsuario({ nome: "João", email: "joao@ifc.edu.br", idade: 20 });
cadastrarUsuario({ nome: "", email: "invalido", idade: 15 });
Em sistemas maiores, é comum criar classes específicas de erro (por exemplo ValidacaoErro) para diferenciar tipos de falhas, mas o padrão acima já é suficiente para os exercícios da disciplina.

3. Manipulação de arquivos em JavaScript

A forma de manipular arquivos depende do ambiente onde o JavaScript está rodando:

3.1 Lendo um arquivo de texto no navegador

Exemplo de página que permite ao usuário escolher um arquivo .txt e exibe o conteúdo:

<!-- HTML -->
<input type="file" id="arquivoTexto" accept=".txt">
<pre id="conteudoArquivo"></pre>

<script>
  const inputArquivo = document.getElementById("arquivoTexto");
  const areaConteudo = document.getElementById("conteudoArquivo");

  inputArquivo.addEventListener("change", () => {
    const arquivo = inputArquivo.files[0];
    if (!arquivo) return;

    const leitor = new FileReader();

    leitor.onload = () => {
      areaConteudo.textContent = leitor.result;
    };

    leitor.onerror = () => {
      console.error("Erro ao ler o arquivo");
    };

    leitor.readAsText(arquivo, "utf-8");
  });
</script>

3.2 Gerando um arquivo para download

<button id="btnSalvar">Baixar relatório</button>

<script>
  const botao = document.getElementById("btnSalvar");

  botao.addEventListener("click", () => {
    const dados = [
      "Relatório de vendas",
      "-------------------",
      "Item A - 10 unidades",
      "Item B - 5 unidades"
    ].join("\n");

    const blob = new Blob([dados], { type: "text/plain" });
    const url = URL.createObjectURL(blob);

    const link = document.createElement("a");
    link.href = url;
    link.download = "relatorio.txt";
    link.click();

    URL.revokeObjectURL(url);
  });
</script>

Esse padrão é útil para exportar relatórios, backups simples ou dados de exercícios sem depender de um back-end.

3.3 Visão rápida: arquivos em Node.js

// Exemplo mínimo em Node.js (não roda no navegador)
const fs = require("fs");

// Leitura síncrona
try {
  const conteudo = fs.readFileSync("dados.txt", "utf-8");
  console.log("Conteúdo do arquivo:", conteudo);
} catch (erro) {
  console.error("Erro ao ler arquivo:", erro.message);
}

// Escrita síncrona
try {
  fs.writeFileSync("saida.txt", "Nova linha de conteúdo");
  console.log("Arquivo salvo com sucesso!");
} catch (erro) {
  console.error("Erro ao escrever arquivo:", erro.message);
}

4. Integração: interfaces, exceções e arquivos

Vamos juntar os conceitos em um mini “gerenciador de tarefas”: uma classe base que funciona como interface de repositório, uma implementação em memória e um botão que exporta as tarefas para um arquivo de texto.

de><!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8" />
  <title>Gerenciador de Tarefas</title>
</head>
<body>

<h3>Gerenciador de tarefas</h3>
<input id="novaTarefa" placeholder="Digite uma tarefa">
<button id="btnAdicionar">Adicionar</button>
<button id="btnExportar">Exportar tarefas</button>

<ul id="listaTarefas"></ul>

<script>
// "Interface" RepositorioTarefas: add(tarefa), listar()
class RepositorioTarefas {
  add(tarefa) {
    throw new Error("Método add(tarefa) deve ser implementado");
  }

  listar() {
    throw new Error("Método listar() deve ser implementado");
  }
}

// Implementação em memória
class RepositorioMemoria extends RepositorioTarefas {
  constructor() {
    super();
    this.tarefas = [];
  }

  add(tarefa) {
    if (!tarefa || tarefa.trim() === "") {
      throw new Error("Tarefa não pode ser vazia");
    }
    this.tarefas.push(tarefa.trim());
  }

  listar() {
    return this.tarefas;
  }
}

// Função utilitária para exportar tarefas como arquivo (Blob)
function exportarTarefasComoArquivo(tarefas) {
  if (!tarefas.length) {
    throw new Error("Não há tarefas para exportar");
  }

  const conteudo = tarefas.join("\n");
  const blob = new Blob([conteudo], { type: "text/plain" });
  const url = URL.createObjectURL(blob);

  const link = document.createElement("a");
  link.href = url;
  link.download = "tarefas.txt";
  link.click();

  URL.revokeObjectURL(url);
}

// --- Código da interface da página ---
const repo = new RepositorioMemoria();

const input = document.getElementById("novaTarefa");
const btnAdicionar = document.getElementById("btnAdicionar");
const btnExportar = document.getElementById("btnExportar");
const lista = document.getElementById("listaTarefas");

function atualizarLista() {
  lista.innerHTML = "";
  for (const t of repo.listar()) {
    const li = document.createElement("li");
    li.textContent = t;
    lista.appendChild(li);
  }
}

btnAdicionar.addEventListener("click", () => {
  try {
    repo.add(input.value);
    input.value = "";
    atualizarLista();
  } catch (erro) {
    alert("Erro ao adicionar tarefa: " + erro.message);
  }
});

btnExportar.addEventListener("click", () => {
  try {
    exportarTarefasComoArquivo(repo.listar());
  } catch (erro) {
    alert("Erro ao exportar: " + erro.message);
  }
});
</script>

</body>
</html>

5. Exercícios para praticar

Exercício 1 – Calculadora com interface de operação

Descrição:

  • Defina uma “interface” Operacao com o método calcular(a, b).
  • Implemente Soma, Subtracao e Multiplicacao como classes concretas.
  • Crie uma função executarOperacao(a, b, operacao) que usa o contrato para calcular o resultado.
  • No HTML, crie campos para os valores e um <select> para escolher a operação, exibindo o resultado na página.
  • Use try/catch para tratar valores inválidos e exibir mensagens amigáveis ao usuário.

Desafio criativo: adicione “Potência” e uma “Divisão segura” que lança exceção se b === 0.

Exercício 2 – Leitor de arquivo CSV simples

Descrição:

  • Crie um <input type="file"> que aceite .csv.
  • Use FileReader.readAsText para ler o arquivo.
  • Separe linhas com split("\n") e colunas com split(",").
  • Monte uma tabela HTML (<table>) para exibir os dados.
  • Trate erros se o arquivo estiver vazio ou fora do formato esperado.

Desafio criativo: destaque a linha com maior valor em uma coluna numérica (por exemplo, “nota” ou “preço”).

Exercício 3 – “Diário de estudos” com exportação

Descrição:

  • Crie um formulário com data, disciplina e resumo do estudo.
  • Defina uma “interface” RepositorioDiario com adicionar(registro) e listar().
  • Implemente uma versão em memória e exiba os registros em lista ou tabela.
  • Adicione um botão “Exportar diário” que gera um .txt com todos os registros.
  • Use exceções para validar: data obrigatória, disciplina não vazia e resumo com tamanho mínimo.

Desafio criativo: crie um filtro para mostrar apenas registros de Programação II.

Exercício 4 – Tratamento de erros de rede (extra)

Descrição:

  • Use fetch para buscar um JSON em uma API pública de testes.
  • Implemente uma função async com try/catch para tratar erros de rede.
  • Mostre os dados na página ou, em caso de erro, uma mensagem clara ao usuário.
  • Crie uma “interface” para fontes de dados (por exemplo, “API de usuários”, “API de posts”) e duas classes que só mudam a URL.

Roteiro de estudo para quem faltou