O laboratório de informática da escola empresta notebooks, kits Arduino e câmeras para aulas e projetos. Um aluno começou um pequeno programa em Python para registrar os empréstimos, mas deixou o curso antes de terminar. O código funciona para um caso simples — e agora ficou com você.
Durante este módulo, o mesmo projeto receberá novos pedidos, apresentará erros, precisará guardar dados, ganhará testes e será preparado para outra pessoa continuar. A IA participará de todas essas etapas, mas com uma regra: ela ajuda a investigar e modificar; você continua responsável por compreender, decidir e comprovar.
Se você não consegue explicar o que mudou, por que mudou e como comprovou que funciona, a tarefa ainda não terminou — mesmo que o código tenha sido escrito pela IA.
Você não precisa ser especialista, mas precisa reconhecer o básico para que a IA não vire um atalho que esconda aquilo que você ainda não aprendeu.
input e print.if, elif e else.return.Volte primeiro para Programação de Computadores. Aqui vamos aprender a trabalhar com IA durante o desenvolvimento, não substituir os fundamentos da linguagem.
A partir daqui, cada etapa diz explicitamente o que você deve fazer. Não avance no automático.
É explicação ou contexto. Não altere nem execute nada a menos que a própria caixa mande.
É uma ação sua: editar arquivo, executar programa, criar pasta, testar ou anotar resultado.
Copie o texto da caixa e envie à IA junto com o contexto pedido. Depois confira a resposta; não a aceite automaticamente.
Não siga enquanto você não conseguir verificar o resultado indicado.
O professor entrega um arquivo chamado app.py. Antes de pedir “melhore esse código”, você precisa descobrir o que já existe e o que realmente funciona.
equipamentos = {
"NB01": {"nome": "Notebook 01", "disponivel": True},
"CAM01": {"nome": "Câmera 01", "disponivel": True},
"ARD01": {"nome": "Kit Arduino 01", "disponivel": True}
}
def emprestar(codigo):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if not item["disponivel"]:
return "Equipamento indisponível."
item["disponivel"] = False
return f'{item["nome"]} emprestado com sucesso.'
codigo = input("Código do equipamento: ")
print(emprestar(codigo))
app.py com exatamente o código acima.NB01.nb01.XX99.Não altere o código ainda.
NB01 e nb01 devem ser reconhecidos como o mesmo código; XX99 deve ser recusado como inexistente. Se isso não aconteceu, corrija primeiro a digitação do programa inicial.
Você poderia perguntar “esse código está bom?”. Isso convida uma avaliação genérica. O objetivo agora é construir um mapa do comportamento.
Volte ao app.py e localize onde aparecem: o dicionário equipamentos, o parâmetro codigo, as duas decisões com if e a alteração de disponivel para False.
Se você não consegue apontar no código a evidência de uma afirmação da IA, ainda não trate essa afirmação como entendimento.
O responsável pelo laboratório testa o protótipo e identifica duas limitações: o programa precisa registrar para quem o equipamento foi emprestado e também precisa permitir a devolução.
Esse pedido mistura requisitos, não define casos problemáticos e dá liberdade demais para reconstruir o projeto.
Leia a resposta da IA e verifique se ela manteve o programa atual como base. Neste ponto, não aceite código ainda. O resultado desta etapa é apenas uma lista de mudanças e casos de teste.
A tabela abaixo ainda não é um teste automatizado e não é para ser “rodada”. Ela funciona como um contrato: registra o que deverá acontecer depois da mudança.
| Situação | Resultado esperado | O que comprova? |
|---|---|---|
| Emprestar NB01 para Ana | Empréstimo aceito; NB01 fica indisponível e ligado a Ana. | Caminho principal. |
| Emprestar NB01 novamente | Operação recusada. | Não permite dois empréstimos simultâneos. |
| Devolver NB01 | NB01 volta a ficar disponível e sem responsável. | Regra de devolução completa. |
| Usar código XX99 | Equipamento não encontrado. | Entrada inexistente. |
| Digitar “ nb01 ” | Código é normalizado e reconhecido. | Entrada equivalente. |
Sem executar o programa, responda para si mesmo: (1) o que deve acontecer se NB01 já estiver emprestado? (2) depois de uma devolução, quais dois dados precisam mudar?
Você deve conseguir responder: o segundo empréstimo é recusado; na devolução, disponivel volta a True e o responsável deixa de existir.
Agora você vai modificar o programa pela primeira vez. Não substitua tudo por um programa novo. Vamos alterar a versão existente em etapas curtas.
Substitua somente o dicionário equipamentos pelo bloco abaixo. O novo campo registra quem está com cada item; None significa que não há responsável.
equipamentos = {
"NB01": {
"nome": "Notebook 01",
"disponivel": True,
"emprestado_para": None
},
"CAM01": {
"nome": "Câmera 01",
"disponivel": True,
"emprestado_para": None
},
"ARD01": {
"nome": "Kit Arduino 01",
"disponivel": True,
"emprestado_para": None
}
}
Cada equipamento deve ter agora exatamente três informações: nome, disponivel e emprestado_para.
Substitua a função emprestar() antiga pelo bloco abaixo e coloque devolver() logo depois. Copie exatamente como está, mesmo que você desconfie de alguma coisa. Ainda existe um comportamento que vamos investigar.
def emprestar(codigo, aluno):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if not item["disponivel"]:
return "Equipamento indisponível."
item["disponivel"] = False
item["emprestado_para"] = aluno
return f'{item["nome"]} emprestado para {aluno}.'
def devolver(codigo):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if item["disponivel"]:
return "Este equipamento já está disponível."
item["emprestado_para"] = None
return f'{item["nome"]} devolvido com sucesso.'
A função emprestar() agora precisa de dois valores. Substitua somente as linhas finais do programa por:
codigo = input("Código do equipamento: ")
aluno = input("Nome do aluno: ")
print(emprestar(codigo, aluno))
Salve e execute uma vez. Digite NB01 e depois Ana. Você deve receber uma mensagem dizendo que Notebook 01 foi emprestado para Ana.
As três linhas de interação acima não estão erradas. Vamos apenas retirá-las por alguns minutos para testar várias operações na mesma execução.
print(emprestar("NB01", "Ana"))
print(emprestar("NB01", "Bruno"))
print(devolver("NB01"))
print(emprestar("NB01", "Bruno"))
Execute uma única vez.
O primeiro empréstimo é aceito e o segundo é recusado, como esperado. Depois a função informa que a devolução aconteceu, mas o novo empréstimo para Bruno continua sendo recusado. Não há erro de sintaxe; há um estado incoerente. Esse defeito é proposital nesta etapa.
Agora existe uma evidência concreta: depois de devolver, o equipamento continua indisponível. Em vez de pedir “arruma”, vamos localizar exatamente onde a regra deixa de ser verdadeira.
Troque temporariamente as quatro chamadas finais pelo bloco abaixo. Ele mostra o estado real de NB01 depois de cada operação.
print(emprestar("NB01", "Ana"))
print(equipamentos["NB01"])
print(emprestar("NB01", "Bruno"))
print(equipamentos["NB01"])
print(devolver("NB01"))
print(equipamentos["NB01"])
print(emprestar("NB01", "Bruno"))
print(equipamentos["NB01"])
O segundo empréstimo é corretamente recusado. O problema aparece depois de devolver("NB01"): emprestado_para volta a None, mas disponivel continua False. É nessa etapa que o estado deixa de representar a regra esperada.
Dentro de devolver(), antes de limpar o responsável, acrescente:
item["disponivel"] = True
Repita o teste completo. O segundo empréstimo deve continuar recusado enquanto NB01 está emprestado, e Bruno deve conseguir recebê-lo depois da devolução.
Depois de comprovar a correção, remova os print temporários de investigação e deixe seu app.py assim:
equipamentos = {
"NB01": {
"nome": "Notebook 01",
"disponivel": True,
"emprestado_para": None
},
"CAM01": {
"nome": "Câmera 01",
"disponivel": True,
"emprestado_para": None
},
"ARD01": {
"nome": "Kit Arduino 01",
"disponivel": True,
"emprestado_para": None
}
}
def emprestar(codigo, aluno):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if not item["disponivel"]:
return "Equipamento indisponível."
item["disponivel"] = False
item["emprestado_para"] = aluno
return f'{item["nome"]} emprestado para {aluno}.'
def devolver(codigo):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if item["disponivel"]:
return "Este equipamento já está disponível."
item["disponivel"] = True
item["emprestado_para"] = None
return f'{item["nome"]} devolvido com sucesso.'
acao = input("Digite E para emprestar ou D para devolver: ").strip().upper()
codigo = input("Código do equipamento: ")
if acao == "E":
aluno = input("Nome do aluno: ")
print(emprestar(codigo, aluno))
elif acao == "D":
print(devolver(codigo))
else:
print("Opção inválida.")
No teste temporário você comprovou empréstimo, bloqueio de segundo empréstimo, devolução e novo empréstimo. Agora vamos descobrir um problema diferente: o programa esquece tudo quando termina.
NB01 para Ana.NB01 para Bruno.O segundo empréstimo será aceito porque o dicionário é recriado toda vez que o programa começa.
Agora existe uma necessidade concreta: o estado precisa sobreviver ao encerramento. A equipe decide usar dados/equipamentos.json.
A resposta precisa distinguir primeira execução de arquivo existente com problema. “Se der qualquer erro, começar do zero” esconderia defeitos reais.
Antes de alterar a versão aprovada, vamos reproduzir o problema de abertura do arquivo em um arquivo temporário.
teste_json.pyNa mesma pasta do projeto, crie um arquivo chamado teste_json.py com:
import json
ARQUIVO_DADOS = "dados/equipamentos.json"
def carregar_dados():
with open(ARQUIVO_DADOS, "r", encoding="utf-8") as arquivo:
return json.load(arquivo)
equipamentos = carregar_dados()
print(equipamentos)
Garanta que a pasta dados e o arquivo equipamentos.json ainda não existam e execute teste_json.py.
FileNotFoundError: [Errno 2] No such file or directory: 'dados/equipamentos.json'
O erro não significa que “JSON não funciona”. Significa apenas que o programa tentou abrir para leitura um arquivo que ainda não existe.
Imagine que a IA sugira Path.makedirs() para criar a pasta. Não coloque isso no projeto. O nome parece plausível, mas esse método não existe em pathlib.Path.
Para este caso, o método correto é Path.mkdir(). Antes de salvar, podemos criar a pasta com ARQUIVO_DADOS.parent.mkdir(parents=True, exist_ok=True).
app.py, importe json e Path.criar_dados_iniciais().carregar_dados() e salvar_dados().Depois compare sua versão com o checkpoint completo abaixo.
import json
from pathlib import Path
ARQUIVO_DADOS = Path("dados/equipamentos.json")
def criar_dados_iniciais():
return {
"NB01": {
"nome": "Notebook 01",
"disponivel": True,
"emprestado_para": None
},
"CAM01": {
"nome": "Câmera 01",
"disponivel": True,
"emprestado_para": None
},
"ARD01": {
"nome": "Kit Arduino 01",
"disponivel": True,
"emprestado_para": None
}
}
def carregar_dados():
if not ARQUIVO_DADOS.exists():
return criar_dados_iniciais()
with ARQUIVO_DADOS.open("r", encoding="utf-8") as arquivo:
return json.load(arquivo)
def salvar_dados(dados):
ARQUIVO_DADOS.parent.mkdir(parents=True, exist_ok=True)
with ARQUIVO_DADOS.open("w", encoding="utf-8") as arquivo:
json.dump(dados, arquivo, ensure_ascii=False, indent=2)
equipamentos = carregar_dados()
def emprestar(codigo, aluno):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if not item["disponivel"]:
return "Equipamento indisponível."
item["disponivel"] = False
item["emprestado_para"] = aluno
return f'{item["nome"]} emprestado para {aluno}.'
def devolver(codigo):
codigo = codigo.strip().upper()
item = equipamentos.get(codigo)
if item is None:
return "Equipamento não encontrado."
if item["disponivel"]:
return "Este equipamento já está disponível."
item["disponivel"] = True
item["emprestado_para"] = None
return f'{item["nome"]} devolvido com sucesso.'
acao = input("Digite E para emprestar ou D para devolver: ").strip().upper()
codigo = input("Código do equipamento: ")
if acao == "E":
aluno = input("Nome do aluno: ")
resultado = emprestar(codigo, aluno)
elif acao == "D":
resultado = devolver(codigo)
else:
resultado = "Opção inválida."
if acao in ("E", "D"):
salvar_dados(equipamentos)
print(resultado)
teste_json.py; ele não faz parte da versão final.app.py e empreste NB01 para Ana.Agora o segundo empréstimo deve ser recusado. O arquivo JSON deve ter sido criado automaticamente.
Se o JSON existir mas estiver corrompido, o erro ainda deve aparecer para ser investigado. Nesta etapa tratamos a ausência esperada do arquivo, não “qualquer erro de arquivo”.
O app.py agora lê teclado, altera dados, salva arquivo e contém regras de negócio. Se outro arquivo importar app.py para testar uma função, os input() também serão executados. A separação agora tem motivo.
A estrutura aprovada para continuar será:
projeto_emprestimos/ ├── app.py ├── emprestimos.py ├── dados/ │ └── equipamentos.json └── test_emprestimos.py
emprestimos.pyCrie o arquivo e coloque nele somente os dados iniciais e as regras abaixo. Note a mudança importante: as funções recebem explicitamente o dicionário dados, em vez de depender de uma variável global.
def criar_dados_iniciais():
return {
"NB01": {
"nome": "Notebook 01",
"disponivel": True,
"emprestado_para": None
},
"CAM01": {
"nome": "Câmera 01",
"disponivel": True,
"emprestado_para": None
},
"ARD01": {
"nome": "Kit Arduino 01",
"disponivel": True,
"emprestado_para": None
}
}
def emprestar(dados, codigo, aluno):
codigo = codigo.strip().upper()
item = dados.get(codigo)
if item is None:
return "Equipamento não encontrado."
if not item["disponivel"]:
return "Equipamento indisponível."
item["disponivel"] = False
item["emprestado_para"] = aluno
return f'{item["nome"]} emprestado para {aluno}.'
def devolver(dados, codigo):
codigo = codigo.strip().upper()
item = dados.get(codigo)
if item is None:
return "Equipamento não encontrado."
if item["disponivel"]:
return "Este equipamento já está disponível."
item["disponivel"] = True
item["emprestado_para"] = None
return f'{item["nome"]} devolvido com sucesso.'
app.pyNesta etapa você está apenas criando o núcleo testável. Não remova ainda as funções do app.py. Depois que o primeiro teste automatizado estiver funcionando, o Checkpoint 3 mostrará exatamente como adaptar o arquivo principal sem deixar o projeto quebrado no meio do caminho.
emprestimos.py não deve conter input(), print() da interface nem leitura/escrita do JSON. Ele contém apenas criação dos dados iniciais e regras de empréstimo/devolução.
Agora cada teste pode criar seus próprios dados sem depender do que outro teste fez antes. Para automatizar as verificações, vamos usar pytest, que é uma dependência externa.
python -m venv .venv
Ative somente com o comando correspondente ao seu terminal:
# Windows PowerShell .\.venv\Scripts\Activate.ps1 # Windows Prompt de Comando (cmd) .venv\Scripts\activate.bat # Linux/macOS source .venv/bin/activate
python -m pip install pytest
Crie test_emprestimos.py com:
from emprestimos import criar_dados_iniciais, emprestar, devolver
def test_devolver_libera_equipamento():
dados = criar_dados_iniciais()
emprestar(dados, "NB01", "Ana")
devolver(dados, "NB01")
assert dados["NB01"]["disponivel"] is True
assert dados["NB01"]["emprestado_para"] is None
python -m pytest -q
O primeiro teste precisa passar antes de você criar os demais. Se falhar, investigue a falha; não altere o teste apenas para deixá-lo verde.
Revise os testes propostos e copie para o mesmo test_emprestimos.py somente as funções de teste que correspondem à tabela de requisitos. Execute novamente python -m pytest -q.
Seu emprestimos.py já é o mostrado na etapa anterior. Agora substitua o conteúdo do app.py pela versão abaixo, que deixa a interface e a persistência no arquivo principal e importa as regras do núcleo:
import json
from pathlib import Path
from emprestimos import criar_dados_iniciais, emprestar, devolver
ARQUIVO_DADOS = Path("dados/equipamentos.json")
def carregar_dados():
if not ARQUIVO_DADOS.exists():
return criar_dados_iniciais()
with ARQUIVO_DADOS.open("r", encoding="utf-8") as arquivo:
return json.load(arquivo)
def salvar_dados(dados):
ARQUIVO_DADOS.parent.mkdir(parents=True, exist_ok=True)
with ARQUIVO_DADOS.open("w", encoding="utf-8") as arquivo:
json.dump(dados, arquivo, ensure_ascii=False, indent=2)
dados = carregar_dados()
acao = input("Digite E para emprestar ou D para devolver: ").strip().upper()
codigo = input("Código do equipamento: ")
if acao == "E":
aluno = input("Nome do aluno: ")
resultado = emprestar(dados, codigo, aluno)
elif acao == "D":
resultado = devolver(dados, codigo)
else:
resultado = "Opção inválida."
if acao in ("E", "D"):
salvar_dados(dados)
print(resultado)
Neste ponto: emprestimos.py contém regras; app.py contém interface e persistência; test_emprestimos.py protege comportamentos já aprovados. Execute os testes mais uma vez antes de seguir.
Você não precisa trocar de computador para continuar a aula. Imagine que um colega copie a pasta e, ao executar os testes, receba:
ModuleNotFoundError: No module named 'pytest'
A regra de empréstimo não ficou errada. O novo ambiente ainda não possui a dependência usada para executar os testes.
Como você criou um ambiente virtual controlado para este projeto, gere o arquivo:
python -m pip freeze > requirements.txt
Abra requirements.txt e confirme que o pytest e suas dependências aparecem. O objetivo é tornar o ambiente reproduzível, não apenas “funcionar na sua máquina”.
Os testes da turma começam a usar nomes reais e alguém pensa em enviar o equipamentos.json inteiro para pedir ajuda. A IA não precisa de identidades reais para compreender a regra.
"emprestado_para": "Nome completo do aluno real"
"emprestado_para": "ALUNO_TESTE_01"
.env com valores reais.Antes de anexar um arquivo a uma IA, pergunte: “ela realmente precisa destes dados para resolver este problema?”
Use o estado do Checkpoint 3. Envie à IA emprestimos.py, app.py e test_emprestimos.py. Não envie o JSON real se ele contiver nomes.
Escolha uma única melhoria que tenha justificativa concreta. Faça uma alteração pequena e execute novamente todos os testes existentes.
Se um comportamento mudou sem intenção, não foi apenas refatoração. Reverta ou investigue antes de continuar.
Até aqui você poderia copiar trechos para um chatbot. Agora imagine um agente conectado ao projeto, capaz de localizar arquivos, editá-los e executar testes.
Branch é uma linha separada de trabalho: permite experimentar uma mudança sem alterar diretamente a versão principal.
Diff é a comparação que mostra exatamente o que foi adicionado, removido ou alterado. Para aprofundar, consulte Git/GitHub.
Além de emprestar e devolver, o sistema deve permitir reservar um equipamento disponível para uma aula futura. A reserva não pode marcar o equipamento como emprestado e deve poder ser cancelada.
Não aceite “concluído” como evidência. A evidência é: escopo compreendido, diff revisado e testes executados.
Agora você assume o fluxo completo. Cada etapa produz algo que precisa ser entendido antes da próxima.
FAÇA AGORA: escreva com suas palavras quando uma reserva pode ser criada, o que acontece se o equipamento já estiver emprestado ou reservado e como uma reserva é cancelada. Pare aqui antes da Etapa B.
PARE E CONFIRA: responda você mesmo às ambiguidades relevantes. A IA não decide a regra do laboratório.
FAÇA AGORA: registre pelo menos reservar disponível, impedir segunda reserva, impedir reserva de emprestado, cancelar reserva e comprovar que empréstimo/devolução continuam funcionando.
PARE E CONFIRA: cada arquivo listado precisa ter uma justificativa ligada a uma regra.
FAÇA AGORA: autorize a implementação apenas depois de entender o plano. Compare antes/depois e procure requisito novo que ninguém pediu.
FAÇA AGORA: rode os testes antigos e novos. Se houver falha, primeiro descubra qual regra, teste ou implementação está errada. Não peça simplesmente para “fazer passar”.
FAÇA AGORA: feche a conversa e responda: quais arquivos mudaram, qual estado representa uma reserva, como ela é cancelada e quais testes comprovam que as regras antigas continuam válidas?
Você não terminou quando a ferramenta escreveu o código. Terminou quando consegue defender a mudança com requisitos, diff, testes e explicação própria.
Escolha um programa pequeno que você realmente consiga executar — em Python, JavaScript, Java, VisuAlg ou outra linguagem estudada. Não copie o projeto do laboratório; transfira o método.
Volte uma etapa. O propósito do módulo é usar IA para aumentar sua capacidade de desenvolver e revisar software — não esconder o desenvolvimento atrás da ferramenta.
Programar com IA não é trocar conhecimento por velocidade. É usar velocidade sem abrir mão de entendimento, evidência e controle.