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
importeexport; - 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:
- explicar o que é um módulo;
- diferenciar módulo, pacote, biblioteca e framework;
- exportar e importar valores nomeados;
- reconhecer exportação padrão;
- compreender escopo e carregamento de módulos;
- organizar dependências sem ciclos;
- criar e lançar objetos
Error; - utilizar
try,catchefinally; - criar erros personalizados;
- diferenciar mensagem técnica e mensagem para usuário;
- tratar uma falha na camada adequada;
- 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
.jsnormalmente 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:
- sucesso: a regra retorna uma tarefa;
- validação:
TaskValidationErrorproduz orientação de campo; - 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
finallypara 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
causeao 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
importeexport. - 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
finallyexecuta. - 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;
importeexportpertencem 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;
finallyrestaura 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.