Engenharia de IA Aplicada à Programação Web
Aula 6 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 2.1 Valores, variáveis e tipos
  2. 2.2 Condições e repetições
  3. 2.3 Funções, escopo e closures
  4. 2.4 Arrays e transformação de dados
  5. 2.5 Objetos, JSON e modelagem
  6. 2.6 Módulos e tratamento de erros
  7. 2.7 Promises, async/await e Fetch
  8. 2.8 Armazenamento, testes e projeto

Módulo 2 · Aula 2.6

Módulos e tratamento de erros

2 horas Teoria + laboratório JavaScript moderno
Ver fonte Markdown

Identificação

  • Duração: 2 horas
  • Tipo: teoria aplicada e laboratório
  • Entrega: aplicação modular com recuperação de falhas
  • Laboratório: ../exemplos/aula-2.6/index.html

Introdução

O que é um módulo?

Um módulo é um arquivo que possui uma responsabilidade clara e pode compartilhar partes do seu código com outros arquivos.

// task-rules.js
export function normalizeTitle(title) {
  return title.trim();
}
// main.js
import { normalizeTitle } from "./task-rules.js";

Dividir o código em módulos evita concentrar toda a aplicação em um único arquivo e torna dependências explícitas.

Módulo é framework?

Não. Módulos fazem parte da própria linguagem JavaScript e da plataforma web. Angular, React e NestJS são ferramentas construídas sobre JavaScript e também utilizam módulos, mas você pode usar import e export sem qualquer framework.

Módulo é um pacote do npm?

Não necessariamente:

  • um módulo pode ser apenas um arquivo local;
  • um pacote normalmente reúne vários arquivos e metadados;
  • npm é um gerenciador e registro de pacotes;
  • um pacote instalado pode disponibilizar um ou mais módulos.

Nesta aula, todos os módulos serão locais.

Por que type="module"?

Para que o navegador trate o arquivo como módulo:

<script type="module" src="assets/main.js"></script>

Scripts de módulo:

  • permitem import e export;
  • possuem escopo próprio;
  • são adiados por padrão;
  • executam em modo estrito;
  • seguem regras de origem e servidor.

Abra o laboratório por http://localhost, não diretamente por file://, porque módulos dependem das regras de carregamento do navegador.

O que é tratamento de erros?

Tratamento de erros é a estratégia para detectar, representar, registrar e comunicar falhas sem deixar a aplicação em um estado confuso.

try {
  const task = createTask(input);
  showTask(task);
} catch (error) {
  showMessage("Não foi possível criar a tarefa.");
}

try...catch não serve para esconder todo problema. Ele deve capturar falhas que a camada atual consegue tratar ou traduzir.

Erro e validação são a mesma coisa?

Não obrigatoriamente. Um campo vazio é um resultado esperado da interação e pode ser representado por um retorno de validação. Uma dependência indisponível ou uma invariante violada pode justificar uma exceção.

Nesta aula, usaremos uma exceção personalizada para representar dados de tarefa inválidos e a converteremos em uma mensagem apropriada para a interface.

Onde isso aparece no projeto?

O laboratório separará:

  • regras e validação em task-rules.js;
  • classes de erro em errors.js;
  • interação com DOM em main.js.

A interface continuará utilizável depois de entradas inválidas e erros simulados.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. explicar o que é um módulo;
  2. diferenciar módulo, pacote, biblioteca e framework;
  3. exportar e importar valores nomeados;
  4. reconhecer exportação padrão;
  5. compreender escopo e carregamento de módulos;
  6. organizar dependências sem ciclos;
  7. criar e lançar objetos Error;
  8. utilizar try, catch e finally;
  9. criar erros personalizados;
  10. diferenciar mensagem técnica e mensagem para usuário;
  11. tratar uma falha na camada adequada;
  12. manter a interface consistente após um erro.

Pré-requisitos

  • Aulas 2.1 a 2.5 concluídas;
  • funções, escopo, arrays e objetos;
  • JSON e validação;
  • projeto aberto por servidor HTTP local.

Pergunta orientadora

Como separar responsabilidades em arquivos e permitir que a aplicação se recupere de falhas previsíveis?

Roteiro sugerido

Etapa Duração
Conceito e carregamento de módulos 20 min
Importações, exportações e arquitetura 30 min
Erros, exceções e recuperação 30 min
Laboratório modular 25 min
Revisão 5 min

1. O problema do arquivo único

Um único main.js pode começar pequeno e acumular:

  • validação;
  • regras de negócio;
  • chamadas de API;
  • manipulação do DOM;
  • mensagens;
  • formatação;
  • armazenamento.

Quando tudo depende de tudo, alterações simples ficam arriscadas. Módulos criam fronteiras:

main.js
  ├── usa task-rules.js
  └── reconhece errors.js

2. Exportação nomeada

// math.js
export function sum(a, b) {
  return a + b;
}

export const maximumAttempts = 3;

Importação:

import { sum, maximumAttempts } from "./math.js";

Os nomes entre chaves precisam corresponder aos nomes exportados.

3. Exportar ao final

function sum(a, b) {
  return a + b;
}

const maximumAttempts = 3;

export { sum, maximumAttempts };

As duas formas são válidas. Escolha um padrão consistente.

4. Renomear importação

import { sum as addNumbers } from "./math.js";

Isso pode resolver conflitos, mas renomeações excessivas dificultam localizar a origem do conceito.

5. Exportação padrão

// formatter.js
export default function formatCurrency(value) {
  return new Intl.NumberFormat("pt-BR", {
    style: "currency",
    currency: "BRL",
  }).format(value);
}

Importação:

import formatCurrency from "./formatter.js";

Uma exportação padrão pode receber qualquer nome na importação. Isso traz flexibilidade, mas pode gerar nomes diferentes para o mesmo conceito.

Neste curso, preferiremos exportações nomeadas para regras, pois elas tornam o contrato explícito.

6. Importar tudo como namespace

import * as taskRules from "./task-rules.js";

taskRules.normalizeTitle("  Estudar  ");

É útil quando o agrupamento comunica contexto. Não use apenas para evitar escolher as dependências realmente necessárias.

7. Caminhos relativos

No navegador:

import { createTask } from "./task-rules.js";

Observe:

  • ./ significa “a partir da pasta atual”;
  • ../ volta uma pasta;
  • a extensão .js normalmente precisa estar explícita;
  • letras maiúsculas e minúsculas podem importar em servidores diferentes.

Este caminho está incorreto para um arquivo local:

import { createTask } from "task-rules";

Um nome sem ./ ou ../ é um especificador de pacote e exige resolução adicional, como bundler ou import map.

8. Escopo de módulo

// settings.js
const privateValue = "interno";
export const publicValue = "externo";

privateValue não vira uma variável global e não pode ser importada. Somente o que é exportado compõe o contrato público.

Evite exportar detalhes que outros módulos não precisam conhecer.

9. Módulos executam uma vez

Quando vários arquivos importam o mesmo módulo, o navegador carrega e avalia aquele módulo uma vez por contexto. As importações compartilham a mesma instância.

Isso significa que estado mutável no nível do módulo pode ser compartilhado:

let count = 0;

export function increment() {
  count += 1;
  return count;
}

Use estado de módulo conscientemente. Funções puras continuam sendo mais fáceis de testar.

10. Importações são vínculos vivos

Valores importados acompanham a variável exportada:

// counter.js
export let count = 0;

export function increment() {
  count += 1;
}

O importador observa o valor atualizado, mas não pode reatribuir diretamente count.

11. Efeitos ao importar

Código no nível superior executa quando o módulo é avaliado:

console.log("Módulo carregado");

Evite iniciar operações inesperadas apenas por importar. Um módulo de regras deve preferir exportar funções e aguardar chamadas explícitas.

12. Dependências circulares

Uma dependência circular aparece quando:

a.js importa b.js
b.js importa a.js

JavaScript pode resolver alguns ciclos, mas valores podem ser acessados antes da inicialização e a arquitetura fica difícil de entender.

Soluções:

  • mover conceitos compartilhados para um terceiro módulo;
  • inverter a dependência;
  • passar callbacks ou dados por parâmetro;
  • revisar responsabilidades.

13. Um módulo por responsabilidade, não por função

Não é necessário criar um arquivo para cada função. Agrupe regras que mudam pelo mesmo motivo:

assets/
  main.js          # interface e coordenação
  task-rules.js    # modelo e regras de tarefa
  errors.js        # tipos de erro compartilhados

Módulos excessivamente pequenos podem tornar a navegação tão difícil quanto um arquivo grande.

14. O objeto Error

const error = new Error("Falha ao criar tarefa");

Um erro possui normalmente:

  • name;
  • message;
  • stack, para diagnóstico;
  • opcionalmente cause.

Lançar:

throw new Error("Falha ao criar tarefa");

throw interrompe o fluxo atual até encontrar um catch adequado.

15. Lance objetos Error

JavaScript permite lançar qualquer valor:

throw "falhou";

Evite. Objetos Error preservam nome, mensagem, pilha e causa:

throw new Error("Falhou");

16. try...catch

try {
  const parsed = JSON.parse(jsonText);
  useValue(parsed);
} catch (error) {
  showMessage("Não foi possível ler o JSON.");
}

Somente erros lançados dentro do try são capturados por esse catch.

Mantenha o bloco try limitado às operações que realmente podem falhar e que serão tratadas da mesma forma.

17. finally

finally executa independentemente de sucesso ou falha:

setLoading(true);

try {
  processTask();
} catch (error) {
  showError(error);
} finally {
  setLoading(false);
}

É útil para liberar recursos ou restaurar estado da interface.

Evite colocar return em finally, pois ele pode substituir retornos e erros anteriores.

18. Erros personalizados

export class TaskValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "TaskValidationError";
    this.field = field;
  }
}

Uso:

if (title.length < 3) {
  throw new TaskValidationError(
    "Título deve ter ao menos 3 caracteres.",
    "title",
  );
}

Captura:

try {
  createTask(input);
} catch (error) {
  if (error instanceof TaskValidationError) {
    showFieldError(error.field, error.message);
    return;
  }

  throw error;
}

Tipos personalizados permitem decisões sem comparar textos de mensagens.

19. Validação por retorno ou exceção?

Retorno explícito:

const result = validateTask(input);

if (!result.ok) {
  showMessage(result.message);
}

Exceção:

try {
  createTask(input);
} catch (error) {
  showMessage(error.message);
}

Não existe uma regra universal:

  • retornos são adequados para alternativas esperadas;
  • exceções ajudam quando a operação não consegue cumprir seu contrato;
  • bibliotecas frequentemente combinam validação explícita e exceções inesperadas;
  • o padrão deve ser consistente no projeto.

20. Não capture o que não sabe tratar

Este código esconde a falha:

try {
  runApplication();
} catch {
  // vazio
}

Se a camada não consegue recuperar nem adicionar contexto, deixe o erro subir.

Capturar tudo e continuar pode deixar dados incorretos ou estado parcial.

21. Mensagem técnica e mensagem para usuário

Mensagem técnica:

TypeError: Cannot read properties of undefined

Mensagem para usuário:

Não foi possível processar a tarefa. Revise os dados e tente novamente.

O usuário precisa de orientação segura. A equipe precisa de contexto técnico em registros controlados. Não exponha stack traces, caminhos internos, consultas ou segredos na interface.

22. Adicionar contexto e preservar causa

try {
  parseExternalData(text);
} catch (error) {
  throw new Error("Falha ao importar tarefas", {
    cause: error,
  });
}

Isso adiciona contexto sem perder a causa original.

23. Estado consistente após falha

Uma operação deve definir:

  • o que muda antes;
  • o que muda apenas no sucesso;
  • o que precisa ser restaurado no finally;
  • qual versão anterior deve ser preservada.
setProcessing(true);

try {
  const task = createTask(input);
  currentTask = task;
  renderTask(task);
} catch (error) {
  renderError(error);
} finally {
  setProcessing(false);
}

Não substitua currentTask antes de saber que o novo valor é válido.

24. Falhas simuladas no laboratório

O laboratório oferece três cenários:

  1. sucesso: a regra retorna uma tarefa;
  2. validação: TaskValidationError produz orientação de campo;
  3. inesperado: um erro genérico é traduzido para mensagem segura.

Em todos os casos, finally atualiza o indicador de finalização.

25. Laboratório guiado

Abra a aplicação modular.

Etapa 1 — Observe os arquivos

assets/
  errors.js
  task-rules.js
  main.js
  styles.css

Siga as importações a partir de main.js.

Etapa 2 — Execute com sucesso

Informe um título válido. Observe:

  • normalização;
  • retorno do módulo de regras;
  • atualização da interface;
  • etapa finally.

Etapa 3 — Provoque validação

Use um título curto. Confirme que:

  • o erro é reconhecido por instanceof;
  • o campo recebe orientação;
  • nenhuma tarefa inválida substitui o último resultado válido.

Etapa 4 — Simule falha inesperada

Selecione o cenário de falha interna. A interface deve:

  • apresentar mensagem segura;
  • manter detalhes técnicos fora da mensagem principal;
  • continuar permitindo nova tentativa;
  • executar finally.

26. Erros comuns

Esquecer type="module"

O navegador exibirá erro ao encontrar import em script comum.

Omitir ./

import { createTask } from "task-rules.js";

Para arquivo local:

import { createTask } from "./task-rules.js";

Abrir por file://

Use o servidor local:

http://localhost/site/PosIA/...

Criar dependência circular

Revise as responsabilidades em vez de adicionar novas importações cruzadas.

Capturar e ignorar

Um catch vazio dificulta diagnóstico e pode esconder estado inválido.

Mostrar error.message indiscriminadamente

Mensagens de erros externos podem conter detalhes inadequados. Traduza falhas na fronteira com a interface.

Usar exceção como fluxo comum em todo lugar

Não transforme toda decisão em throw. Use condições e retornos para alternativas normais quando isso tornar o fluxo mais claro.

27. Boas práticas

  • faça módulos terem uma razão clara para mudar;
  • exporte apenas o contrato necessário;
  • prefira dependências em uma direção;
  • evite efeitos inesperados na importação;
  • lance objetos Error;
  • crie tipos de erro quando a camada precisa distingui-los;
  • limite o bloco try;
  • preserve estado anterior até o sucesso;
  • use finally para limpeza;
  • apresente mensagens seguras e acionáveis.

28. Exercícios

Exercício 1 — Separação

Mova uma função de formatação para formatters.js e importe-a em main.js.

Exercício 2 — Exportação

Crie uma exportação nomeada e uma padrão. Explique a diferença na importação.

Exercício 3 — Erro personalizado

Crie InvalidEstimateError com a propriedade receivedValue.

Exercício 4 — finally

Implemente um estado “processando” que sempre seja removido.

Exercício 5 — Recuperação

Preserve o último resultado válido quando uma nova entrada falhar.

29. Desafio

Amplie o laboratório:

  • adicione um módulo de formatadores;
  • crie um erro específico para prioridade;
  • inclua cause ao traduzir falhas internas;
  • registre um identificador de diagnóstico sem expor stack trace;
  • escreva um diagrama das dependências;
  • elimine qualquer ciclo encontrado.

30. Lista de verificação de conclusão

  • Sei diferenciar módulo, pacote e framework.
  • Consigo usar import e export.
  • Entendo caminhos relativos.
  • Sei por que usar type="module".
  • Reconheço o escopo de módulo.
  • Consigo lançar e capturar um Error.
  • Sei quando finally executa.
  • Consigo criar um erro personalizado.
  • Diferencio mensagem técnica e mensagem para usuário.
  • Preservo o último estado válido após uma falha.

31. Critérios de avaliação

Critério Pontos
Separação e contrato dos módulos 25
Importações e exportações corretas 20
Tratamento específico de validação 20
Recuperação de falha inesperada 20
Estado consistente e finally 10
Mensagens acessíveis e seguras 5
Total 100

Resumo

Nesta aula, aprendemos que:

  • módulos separam responsabilidades e tornam dependências explícitas;
  • import e export pertencem ao JavaScript;
  • módulo, pacote, biblioteca e framework são conceitos diferentes;
  • exceções interrompem o fluxo até um tratamento apropriado;
  • erros personalizados permitem decisões por tipo;
  • finally restaura estado;
  • a interface deve receber mensagens seguras e permanecer consistente.

Próxima aula

Na Aula 2.7, trabalharemos com Promises, async/await e Fetch para consumir APIs, tratar respostas HTTP e representar carregamento, sucesso, vazio e falha.