Pular para o conteúdo principal

O composition root

O programa é feito de muitas classes, separadas em camadas, e cada uma recebe prontos, no construtor, os objetos de que precisa: o Menu recebe o GenerateMaze, o GenerateMaze recebe o MazeGenerator e o repositório que grava o arquivo, e assim por diante. Alguém precisa criar todos esses objetos, na ordem certa, e ligá-los uns aos outros. Esse lugar se chama composition root ("raiz de composição"). Aqui, é a função main do a_maze_ing.py.

O a_maze_ing.py é o arquivo que o enunciado manda executar, com esse nome obrigatório:

python3 a_maze_ing.py config.txt

Ele não tem nenhuma regra do labirinto. Faz três coisas: confere os argumentos da linha de comando (nesta página), monta as peças (próxima página) e trata todo erro que chegar até ele (página 7).

Por que só ele conhece todas as classes​

O projeto segue uma regra de dependência: cada camada só importa as camadas mais internas (parte 2). O Menu, por exemplo, não importa o FileMazeRepository, a classe que grava o arquivo de saída: ele recebe pronto um GenerateMaze que já sabe gravar.

Se cada classe criasse os próprios colaboradores, o Menu teria de importar o repositório, o gerador e a leitura da configuração, e a interface passaria a depender de como os arquivos são lidos e gravados. Com o composition root, as classes só declaram do que precisam, e um único ponto, fora de todas as camadas, decide quem fornece. Trocar uma peça (um repositório que grava em memória em vez de em disco, por exemplo) muda uma linha aqui e nenhuma em outro lugar.

Os imports​

import sys

from amazeing.domain.entities.view_state import ViewState
from amazeing.errors import AppError
from amazeing.repositories.config_repository import FileConfigRepository
from amazeing.repositories.maze_repository import FileMazeRepository
from amazeing.services.render_service import RenderService
from amazeing.ui.menu import Menu
from amazeing.use_cases.generate_maze import GenerateMaze
from mazegen import MazeError, MazeGenerator

O script importa de todas as camadas. Ele pode, porque é o anel mais externo do desenho de camadas da parte 2: depende de tudo, e nada depende dele.

NomeO que éExplicado na
ViewStateo caminho visível ou não e a cor das paredesparte 13
AppErrora base dos erros da aplicação (config ruim, saída que não grava)parte 10
FileConfigRepositorylê o arquivo de configuração e devolve um MazeConfigparte 11
FileMazeRepositorygrava o arquivo de saídaparte 12
RenderServicetransforma o labirinto em linhas coloridasparte 13
Menuo laço que desenha, pergunta e reagepáginas 1 a 4
GenerateMazegera um labirinto, grava o arquivo e devolve o retratoparte 12
MazeGeneratoro gerador do pacote reutilizável mazegenparte 9
MazeErroro erro do gerador, para parâmetros impossíveisparte 4

Do mazegen vêm só os dois nomes que o pacote oferece ao mundo de fora, MazeGenerator e MazeError.

As constantes​

USAGE = "Usage: python3 a_maze_ing.py <config_file>"
EXIT_OK = 0
EXIT_ERROR = 1

EXIT_OK e EXIT_ERROR dão nome aos códigos de saída (o número que o programa entrega ao sistema quando termina: 0 = "terminou bem", outro = "deu erro"; página 2). return EXIT_ERROR diz o que significa; return 1 obrigaria o leitor a conhecer a convenção.

A linha de comando​

def main(argv: list[str]) -> int:
"""Read the configuration, generate the maze and run the menu.

Args:
argv: Command-line arguments without the script name.

Returns:
0 on Quit, end of input or Ctrl+C; 1 on any error.
"""
if len(argv) != 1:
print(USAGE, file=sys.stderr)
return EXIT_ERROR

argv é a lista das palavras escritas depois do nome do script. Para python3 a_maze_ing.py config.txt, ela é ["config.txt"].

O main recebe essa lista e devolve o código de saída, em vez de ler os argumentos e encerrar o programa por conta própria. Assim, outro código Python pode chamá-lo com qualquer lista e decidir o que fazer com o código devolvido.

O enunciado diz que o programa recebe um único argumento: o arquivo de configuração. Com zero argumentos, ou com dois ou mais, o main imprime a linha de uso na saída de erros (stderr, página 4) e devolve 1, antes de tentar abrir qualquer arquivo. O texto da mensagem é escolha do projeto: ele mostra o comando inteiro, pronto para copiar, e o <config_file> entre sinais de menor e maior é a convenção para "ponha aqui o nome do arquivo".

A linha de uso é a única mensagem de falha que não começa com Error:. Os erros de configuração e de execução começam todos com esse prefixo (parte 10); a linha de uso segue outro formato, o tradicional dos programas de terminal para dizer "como usar": a palavra Usage: seguida do comando. Quem a recebe não precisa saber o que deu errado, e sim como chamar o programa, e ela diz isso. O código de saída continua 1, como em qualquer falha.

Com o programa pronto (a página 10 roda os dois de verdade), estes dois comandos imprimem a mesma linha de uso e terminam com código 1:

.venv/bin/python a_maze_ing.py
.venv/bin/python a_maze_ing.py config.txt outro.txt
Usage: python3 a_maze_ing.py <config_file>
Usage: python3 a_maze_ing.py <config_file>

A mensagem cita python3, o comando do enunciado; aqui o programa roda com o Python do ambiente virtual, .venv/bin/python, que dá no mesmo.

Com os argumentos conferidos, o main monta as peças, na próxima página.