Funções de Escaneamento e Compilação Condicional em Go

Na linguagem Go, as funções de escaneamento são ferramentas poderosas para ler dados de fontes de entrada (como entrada padrão, arquivos ou conexões de rede) e convertê-los em tipos de variáveis específicos.

fmt.Scan

A função fmt.Scan é a função de escaneamento mais básica, que lê valores separados por espaços da entrada padrão (geralmente o teclado) e os atribui às variáveis fornecidas. Vale notar que fmt.Scan ignora automaticamente caracteres em branco à frente e no final (como espaços, caracteres de nova linha, etc).

package main

import (
    "fmt"
)

func main() {
    var numero int
    var texto string
    fmt.Print("Digite um número inteiro e um texto separados por espaço: ")
    fmt.Scan(&numero, &texto)
    fmt.Println("Você digitou:", numero, texto)
}

Neste exemplo, o usuário deve inserir um número inteiro e uma string separados por espaço. O fmt.Scan lê essas entradas e as atribui às variáveis numero e texto.

fmt.Scanf

A função fmt.Scanf oferece controle mais flexível sobre o formato da entrada. Ela permite especificar uma string de formato que define o formato dos dados de entrada. O Scanf interpreta a entrada com base nesta string de formato.

package main

import (
    "fmt"
)

func main() {
    var nome string
    var idade int
    fmt.Print("Digite seu nome e idade (ex: João 25): ")
    fmt.Scanf("%s %d", &nome, &idade)
    fmt.Println("Olá,", nome, "você tem", idade, "anos.")
}

Neste exemplo, %s representa uma string e %d representa um número inteiro. O usuário deve inserir os dados no formato especificado, e o fmt.Scanf interpretará e atribuirá os valores de acordo com a string de formato.

fmt.Scanln

A função fmt.Scanln é semelhante ao fmt.Scan, mas para de ler ao encotnrar um caractere de nova linha. Isso a torna mais adequada para leitura linha por linha.

package main

import (
    "fmt"
)

func main() {
    var valor int
    var descricao string
    fmt.Print("Digite um número e uma descrição na mesma linha: ")
    fmt.Scanln(&valor, &descricao)
    fmt.Println("Você digitou:", valor, descricao)
}

Neste exemplo, o usuário deve inserir um número inteiro e uma string na mesma linha, e o fmt.Scanln para de ler ao encontrar uma nova linha, atribuindo os dados às variáveis valor e descricao.

Método Scan do bufio.Scanner

O bufio.Scanner oferece um mecanismo de escaneamento mais avançado e flexível, capaz de ler dados de um io.Reader (como arquivos, conexões de rede, etc). O método Scan do Scanner lê dados até encontrar um delimitador (padrão é o caractere de nova linha \n), e você pode usar o método Text para obter a string lida.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Split(bufio.ScanWords) // Define o delimitador como espaço
    fmt.Println("Digite algumas palavras (digite 'sair' para encerrar):")
    for scanner.Scan() {
        palavra := scanner.Text()
        if strings.ToLower(palavra) == "sair" {
            break
        }
        fmt.Println("Você digitou:", palavra)
    }
    if err := scanner.Err(); err != nil {
        fmt.Println("Erro ao ler entrada:", err)
    }
}

Neste exemplo, o bufio.Scanner é usado para ler palavras uma por uma da entrada do usuário. Ao definir o delimitador como espaço (bufio.ScanWords), o Scanner divide a entrada a cada espaço. O usuário pode digitar sair para sair do loop.

Obtendo Informações da Pilha com debug.Stack()

Na linguagem Go, a função debug.Stack() é uma utilidade do pacote runtime/debug que gera e imprime informações de rastreamento da pilha do goroutine atual durante a execução do programa. Essa função é muito útil para depuração e tratamento de erros.

Cenários de Uso

  • Depuração: Ao tentar entender um problema ou comportamento anormal no programa, o rastreamento da pilha ajuda a localizar o contexto onde o problema ocorreu.
  • Tratamento de erros: Ao capturar um panic ou erro grave, imprimir o rastreamento da pilha pode fornecer detalhes sobre a pilha de chamadas no momento do erro, auxiliando na análise e correção posterior do problema.

Assinatura da Função

func Stack() []byte

A função Stack não aceita parâmetros e retorna um valor do tipo []byte, que contém o rastreamento da pilha do goroutine atual. Normalmente, você enviará esse valor diretamente para a saída de erro padrão (os.Stderr) ou para um arquivo de log para visualização.

Exemplo de Código

package main

import (
    "fmt"
    "runtime/debug"
)

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recuperado do panic:", r)
            fmt.Println("Rastreamento da pilha:")
            debug.PrintStack() // Ou use debug.Stack() e processe o resultado
        }
    }()

    // Dispara um panic como exemplo
    panic("algo deu errado")

    // Esta linha não será executada
    fmt.Println("Isso não será impresso")
}

No exemplo acima, usamos defer e recover para capturar o panic e imprimir o rastreamento da pilha ao capturá-lo. Observe que usamos debug.PrintStack() em vez de debug.Stack(), pois PrintStack() envia o rastreamento diretamente para a saída de erro padrão, enquanto Stack() retorna o rastreamento como um fatia de bytes, exigindo que você processe o valor retornado (por exemplo, escrevendo em um arquivo de log).

Se quiser usar debug.Stack() e processar manualmente as informações de rastreamento, pode fazer o seguinte:

pilha := debug.Stack()
// Envia pilha para o sistema de logs, arquivo ou outro destino
fmt.Fprint(os.Stderr, string(pilha))

Considerações Importantes

  1. debug.Stack() captura o rastreamento da pilha no momento em que é chamado. Portanto, se você o chamar em uma função adiada (como no exemplo acima), ele capturará o rastreamento da pilha quando o panic foi disparado.
  2. As informações de rastreamento da pilha podem conter informações sensíveis, portanto, use com cuidado em ambientes de produção.
  3. O rastreamento da pilha é muito útil para entender o fluxo de execução do programa, mas em cenários sensíveis ao desempenho, chamadas frequentes a debug.Stack() podem afetar negativamente o desempenho.

Compilação Condicional em Go

Na linguagem Go, a compilação condicional é um mecanismo que permite selecionar trechos de código para compilar com base em condições durante a compilação. Esse mecanismo permite que os desenvolvedores escrevam código específico para diferentes plataformas, sistemas operacionais ou configurações de compilação, sem precisar modificar a lógica do código ou criar múltiplos repositórios de código. A compilação condicional em Go é implementada principalmente por meio de tags de compilação (build tags) e sufixos de arquivo (como _linux.go, _windows.go).

Tags de Compilação (Build Tags)

As tags de compilação são instruções especiais em comentários que indicam aos comandos go build e go test em quais condições um arquivo específico deve ser incluído ou excluído. As tags de compilação ficam nos comentários no topo do arquivo e começam com // +build, seguido por uma ou mais tags separadas por espaços.

Por exemplo, se temos um arquivo que só deve ser compilado no sistema Linux, podemos marcá-lo assim:

// +build linux

package main

import "fmt"

func main() {
    fmt.Println("Esta é uma compilação específica para Linux.")
}

Ao executar os comandos go build ou go run, a cadeia de ferramentas Go verifica as tags de compilação de cada arquivo e decide se deve incluir o arquivo com base no ambiente atual (como os valores das variáveis de ambiente GOOS e GOARCH).

Também podemos usar tags negativas para excluir certos ambientes de compilação. Por exemplo, para excluir a plataforma Windows, podemos escrever:

// +build !windows

Também é possível combinar várias tags, usando vírgulas para separá-las, o que indica uma relação "E" (todas as tags devem corresponder), ou usar espaços para separá-las (em alguns contextos, isso indica uma relação "OU", mas nas tags de compilação geralmente não é assim, pois as tags de compilação não suportam lógica "OU" direta).

Sufixos de Arquivo

Besmo das tags de compilação, o Go também permite implementar a compilação condicional especificando sufixos específicos para os arquivos. Esses sufixos geralmente são nomes de sistemas operacionais ou arquiteturas, como _linux.go, _windows.go, _amd64.go, etc. Quando há um arquivo específico para uma plataforma ou arquitetura, você pode nomeá-lo adicionando o sufixo correspondente.

Por exemplo, se há uma função que só é usada no Windows, pode colocá-la em um arquivo chamado algo_windows.go. A cadeia de ferramentas Go selecionará e compilará esse arquivo com base no sistema operacional de destino atual.

Considerações ao Usar Compilação Condicional

  1. A compilação condicional deve ser usada com cuidado, pois pode tornar o banco de código mais complexo e aumentar os custos de manutenção. >Ao usar tags de compilação, certifique-se de que estejam corretas e não excluam ou incluam acidentalmente arquivos incorretos. >A compilação condicional geralmente é usada para lidar com diferenças relacionadas à plataforma, como chamadas de sistema, formatos de caminho de arquivo, etc. Para lógica genérica multiplataforma, deve-se evitar o uso de compilação condicional. >Ao escrever código com compilação condicional, considere a extensibilidade e compatibilidade futuras. Por exemplo, se você adicionou código específico para uma nova plataforma, certifique-se de que possa ser facilmente removido quando essa plataforma deixar de ser suportada.

Tags: go fmt-scan debug-stack build-tags

Publicado em 6-1 18:44 por Thomas