sinais, passo 4: MVP 1, o mínimo que é útil

Estamos prontos para fazer a interface de linha de comando e ter um produto mínimo viável (MVP) que permite encontrar caracteres pelo nome.

Seguindo com o TDD, vamos fazer um teste para nosso programa principal. Como precisamos testar a saída padrão, usaremos outra função Example:

func Example() { // ➊
	oldArgs := os.Args  // ➋
	defer func() { os.Args = oldArgs }()  // ➌
	os.Args = []string{"", "cruzeiro"}  // ➍
	main() // ➎
	// Output:
	// U+20A2	₢	CRUZEIRO SIGN
}

Esse teste traz várias novidades:

➊ A função chamada simplesmente Example é o exemplo do pacote: um teste funcional para o pacote como um todo (documentação sobre funções exemplo).

➋ Vamos simular a passagem de argumentos pela linha de comando. O primeiro passo é copiar os argumentos de os.Args para oldArgs, assim poderemos restaurá-los depois. Para acessar os.Args, não esqueça de incluir o pacote os na declaração import de rundefinder_test.go.

➌ Criamos uma função anônima que vai restaurar o valor de os.Args no final da nossa função Example. Leia mais sobre a instrução defer logo adiante.

➍ Mudamos os valor de os.Args para fazer o teste. Observe a sintaxe de uma fatia literal: primeiro o tipo []string, depois os itens entre {}. O primeiro item de os.Args é o nome do programa (irrelevante para o nosso teste). O segundo item é a palavra que vamos buscar, "cruzeiro", cuidadosamente escolhida porque só existe um caractere Unicode que contém essa palavra em seu nome.

➎ Invocamos a função main, a mesma que será chamada quando nosso programa for acionado na linha de comando. A saída que aparece aqui é o que nosso programa vai gerar quando alguém buscar um caractere com a palavra “cruzeiro”.

A instrução defer é uma inovação simples porém genial da linguagem Go. Ela serve para invocar uma função no final da execução da função atual (Example). defer é útil para fechar arquivos, encerrar conexões, liberar mutexes, etc. É como se o corpo da função Example estivesse dentro de um try/finally de Java ou Python, e as funções chamadas em defer seriam executadas no bloco finally, ou seja, após o return e mesmo que ocorram exceções. No exemplo, o uso de defer garante que o valor de os.Args será restaurado ao valor original, independente do sucesso ou fracasso do teste.

Nota: Alterar uma variável global como os.Args pode produzir resultados inesperados em um sistema concorrente, mas Go só executa testes em paralelo se usamos o método T.Parallel.

A função main

Afinal vamos implementar a função main, que permite executar o runefinder como um programa direto da linha de comando.

func main() { // ➊
	ucd, err := os.Open("UnicodeData.txt") // ➋
	if err != nil {                        // ➌
		log.Fatal(err.Error()) // ➍
	}
	defer ucd.Close()            // ➎
	consulta := strings.Join(os.Args[1:], " ") // ➏
	Listar(ucd, strings.ToUpper(consulta))     // ➐
}

➊ Em Go, a função main não recebe argumentos.

➋ Abrimos o arquivo “UnicodeData.txt”, assumindo que ele está no diretório atual. A maioria das funções de E/S em Go devolve dois resultados, e o segundo é do tipo error, uma interface usada para reportar erros. No caso de os.Open, o primeiro resultado é um *File, ponteiro para um objeto arquivo.

➌ Se err é diferente nil, houve erro em os.Open. Nesse caso vamos exibir a mensagem de erro e terminar o programa.

➍ A função log.Fatal faz duas coisas: exibe a mensagem passada como argumento e invoca os.Exit(1), encerrando o programa. O tipo error tem o método Error() que devolve uma string com a mensagem de erro. Chamando log.Fatal (ou os.Exit), as funções em defer não são executadas. É necessário importar o pacote log.

➎ Usamos defer para fechar o arquivo que abrimos em ➋.

➏ Montamos a string de consulta concatenando os argumentos. A notação os.Args[1:] lembra Python ou Ruby: ela devolve uma nova fatia formada pelos itens de índice 1 em diante. Assim omitimos o nome do programa invocado, que fica em os.Args[0]. A função strings.Join monta uma string intercalando os itens da fatia com o segundo argumento, " " neste caso.

➐ Invocamos a função Listar com o arquivo ucd e a consulta convertida em caixa alta (porque na UCD os nomes aparecem em maiúsculas).

Agora você precisa baixar o arquivo UnicodeData.txt (URL oficial). Depois faremos o runefinder baixar este arquivo, se necessário, mas agora você precisa buscar e colocá-lo no diretório atual (onde está o runefinder.go). Feito isso, você pode rodar os testes:

$ go test -v
=== RUN   TestAnalisarLinha
--- PASS: TestAnalisarLinha (0.00s)
=== RUN   ExampleListar
--- PASS: ExampleListar (0.00s)
=== RUN   ExampleListar_doisResultados
--- PASS: ExampleListar_doisResultados (0.00s)
=== RUN   Example
--- PASS: Example (0.02s)
PASS
ok  	github.com/ThoughtWorksInc/sinais	0.033s

Experimentando o runefinder

Agora já é possível brincar com o programa na linha de comando. A forma mais simples de experimentar um programa Go em desenvolvimento é o comando go run. Veja como funciona:

$ go run runefinder.go cat face
U+1F431	🐱	CAT FACE
U+1F638	😸	GRINNING CAT FACE WITH SMILING EYES
U+1F639	😹	CAT FACE WITH TEARS OF JOY
U+1F63A	😺	SMILING CAT FACE WITH OPEN MOUTH
U+1F63B	😻	SMILING CAT FACE WITH HEART-SHAPED EYES
U+1F63C	😼	CAT FACE WITH WRY SMILE
U+1F63D	😽	KISSING CAT FACE WITH CLOSED EYES
U+1F63E	😾	POUTING CAT FACE
U+1F63F	😿	CRYING CAT FACE
U+1F640	🙀	WEARY CAT FACE

Experimente fazer várias consultas, tem muita coisa interessante no banco de dados Unicode. Alguns exemplos para você experimentar:

$ go run runefinder.go chess
...
$ go run runefinder.go runic
...
$ go run runefinder.go roman
...
$ go run runefinder.go clock face
...
$ go run runefinder.go alchemical
...
$ go run runefinder.go hexagram  # I Ching!

Outra forma de usar o programa é gerar um executável, com o comando go build, assim:

$ go build
$ ls -lah sinais
-rwxr-xr-x  1 lramalho  staff   1.9M Dec 28 20:10 sinais04

Se der tudo certo, o comando go build não exibe nenhuma mensagem. Mas ele produz um binário executável com o nome do projeto, no caso sinais04 (que é o nome do diretório onde está o projeto). Por convenção, o nome do projeto é o nome do repositório, mas neste tutorial temos na verdade vários projetos, um em cada diretório sinaisNN. Note o executável de 1.9MB no ls acima.

Para rodar o binário, é só rodar!

$ ./sinais04 flag
U+2690	⚐	WHITE FLAG
U+2691	⚑	BLACK FLAG
U+26F3	⛳	FLAG IN HOLE
U+26FF	⛿	WHITE FLAG WITH HORIZONbTAL MIDDLE BLACK STRIPE
U+1D16E	𝅮	MUSICAL SYMBOL COMBINING FLAG-1
U+1D16F	𝅯	MUSICAL SYMBOL COMBINING FLAG-2
U+1D170	𝅰	MUSICAL SYMBOL COMBINING FLAG-3
U+1D171	𝅱	MUSICAL SYMBOL COMBINING FLAG-4
U+1D172	𝅲	MUSICAL SYMBOL COMBINING FLAG-5
U+1F38C	🎌	CROSSED FLAGS
U+1F3C1	🏁	CHEQUERED FLAG
U+1F3F3	🏳	WAVING WHITE FLAG
U+1F3F4	🏴	WAVING BLACK FLAG
U+1F4EA	📪	CLOSED MAILBOX WITH LOWERED FLAG
U+1F4EB	📫	CLOSED MAILBOX WITH RAISED FLAG
U+1F4EC	📬	OPEN MAILBOX WITH RAISED FLAG
U+1F4ED	📭	OPEN MAILBOX WITH LOWERED FLAG
U+1F6A9	🚩	TRIANGULAR FLAG ON POST

Próximos passos

Esse foi o nosso MVP1, a primeira versão usável do programa.

Ele tem algumas limitações que resolveremos nos próximos passos:

$ ./sinais | wc
   30593  182344 1181756

Vamos deixar a questão do download do UCD para o final deste tutorial. Primeiro vamos melhorar a busca no Passo 5.