"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Passos de bebê com Go

Passos de bebê com Go

Publicado em 23/08/2024
Navegar:999

Baby steps with Go

Decidi experimentar Go em minha jornada para aprender um novo idioma que fosse útil para minha carreira e interesses. Desta vez estou experimentando Go. Acho que, no que diz respeito às primeiras impressões, é muito bom.

Esta não é uma visita guiada e, sem dúvida, não foi escrita para ninguém além de mim, como alguns lembretes pessoais.

Eu me dei um pequeno projeto chamado Os-Release-Q . Minha intenção era ser capaz de ter um binário em qualquer sistema que eu gerencie, de modo que eu possa imprimir exatamente as informações que preciso, sem precisar analisar ou procurar por elas.

Primeiro obstáculo: importar

Pesquisar na web fala muito sobre importar pacotes de outras pessoas, mas muito pouco sobre organizar o próprio código. Até mesmo os documentos se concentram em ir buscar, em vez de na separação de preocupações.

Eu encontro esse obstáculo bastante em todos os idiomas, pois cada um tem sua própria filosofia idiossincrática sobre como lidar com isso e quais limitações cada um tem ou impõe.

De todas as atividades que realizei para aprender o básico, vindo de uma experiência predominantemente python, dividir meu código em vários arquivos foi o que mais demorei para obter respostas. Em resumo, encontrei o seguinte:

    o nível superior precisa de um go.mod declarando o módulo module-name
  • Posso então definir um diretório src/ no nível superior e um src/main.go no qual colocar minha main function , com uma declaração package main no topo
  • colocar código em outros arquivos é tão simples quanto criar um arquivo como src/others.go com uma declaração principal do pacote.
  • Todas as funções e variáveis ​​ficam disponíveis diretamente em qualquer outro arquivo do pacote main , mas os arquivos precisam ser explicitamente declarados na chamada build FILES
Para submódulos locais, o submódulo deve residir em uma pasta. Ele pode declarar um pacote submodule-name .

Digamos que está em src/submod/, com o implementador principal em src/submod/submod.go. Em main.go importamos "module-name/src/submod" (com module-name extraído de go.mod). E então podemos chamar submod.SomeFunction().

Observamos que as funções do submódulo só estão disponíveis para importadores se seus nomes começarem com letra maiúscula. Portanto, não faça submod.myFunction() - tem que ser submod.MyFunction().

Certamente existem outras considerações em torno de submódulos e importações, mas no que diz respeito a manter o código organizado e segregado, isso é o essencial.

Para manter as coisas sãs, tentei ter apenas um arquivo declarando o pacote principal e isolando o restante em submódulos - eles são importados automaticamente sem a necessidade de serem declarados na lista de arquivos go build FILES.

Fazendo tarefas básicas

Depois de resolver essa especificidade do Go, o resto se encaixou com bastante facilidade. Para cada tarefa básica havia, é claro, uma entrada StackOverflow, ou uma página GoByExample.com e, mais basicamente, a referência da linguagem Go.

    O manuseio de strings é feito através do pacote strings
  • O tratamento de array possui uma série de funções nativas, das quais o padrão base_array = append(base_array, item1, item2) - também funciona para estender um array com os valores de outro via append(base, other_array...)
  • O tratamento de erros é feito distribuindo objetos de erro normalmente, mas não necessariamente.
  • existe uma biblioteca "log" para um log prático e pré-configurado sem falhas. Inclui uma chamada log.Fatal(message) que registra um erro, bem como sai imediatamente.
  • Chamar subprocessos é fácil através da biblioteca "os/exec", usando o padrão exec.Command(base, args...)
Duas tarefas particularmente comuns merecem seus próprios parágrafos.

Tratamento de erros

O tratamento básico de erros é frequentemente comentado como complicado, literalmente precisando lidar com erros no meio do fluxo de controle. Isso pode ser um anátema para os programadores provenientes de um fluxo de trabalho try/catch, mas lidar com o problema no ponto em que ele pode acontecer não é tão ruim.


// item de retorno explícito `err` nos força a estar cientes disso // mas ter a capacidade de verificar isso ao mesmo tempo não é tão ruim se resultado, err := someCall(); err! = nulo { log.Fatal("Desculpe.") } // Igualmente válido é /* resultado, err := someCall() se errar! = nulo { log.Fatal("Desculpe") } */ fmt.Println(resultado)
// explicit return item `err` forces us to be aware of it
// but having the ability to check it in the same breath is not so bad
if result, err := someCall(); err != nil {
    log.Fatal("Sorry.")
}

// Equally valid is
/*
result, err := someCall()
if err != nil {
    log.Fatal("Sorry")
}
*/

fmt.Println(result)
Comparar método try/catch


tentar: resultado = algumaChamada() imprimir (resultado) exceto: print("Sorry") # um pouco divorciado da possível origem do erro sys.exit(1)
// explicit return item `err` forces us to be aware of it
// but having the ability to check it in the same breath is not so bad
if result, err := someCall(); err != nil {
    log.Fatal("Sorry.")
}

// Equally valid is
/*
result, err := someCall()
if err != nil {
    log.Fatal("Sorry")
}
*/

fmt.Println(result)
Análise de argumentos

Não posso deixar de sentir que a implementação da biblioteca de flags está um pouco incompleta. Evidentemente as pessoas estão acostumadas e concordam com isso, dada a sua sobrevivência na sua forma atual.

Chamar o programa -flag arg1 arg2 nos dá a alternância que o flag está configurado para fazer, e os posicionais := flags.Args() nos retornam o array de ["arg1", "arg2"]

No entanto, chamar o programa arg1 arg2 -flag

não alterna o que quer que -flags deva fazer e, em vez disso, fornece posicionais como ["arg1", "arg2", "-flag"] em que o sinalizador não foi analisado.

Isso pode ser útil para passar uma subchamada como o programa colorize ls -l onde ls -l é transmitido literalmente - para que eu possa ver um caso de uso.

Acontece que a maioria dos programas por aí permite argumentos de sinalização em qualquer lugar em torno de itens posicionais. ls dir1/ -l dir2/ é o mesmo que ls -l dir1/ dir2/, e esta é uma convenção que se aplica à grande maioria dos comandos Unix e Linux.

Pode ser que isso seja algo com o qual se acostumar - e que vale a pena mencionar.

Finalidade e caso de uso do Go

Deixando de lado o paradigma de importação de arquivos, achei muito fácil implementar meu aplicativo básico. Qualquer coisa que fiz de errado parecia bastante óbvia e os erros foram significativos. Realmente parece que posso me concentrar apenas em "fazer as coisas".

Pelo pouco uso que usei até agora, e levando em consideração minhas necessidades específicas, posso ver

    fácil de começar
  • binário compilado, sem dependência de tempo de execução
  • linguagem simples com tipos é um avanço em relação ao script de shell
  • suporte multiprocessamento supostamente fácil
Achei que ter tipos esparsos em vez de objetos e herança seria um obstáculo, mas até agora tudo bem. Eu vivo sem eles em outras linguagens, então suponho que quando eu definir interfaces e tipos, parecerá um avanço em relação a Lua e bash. Espero.

Uma das razões pelas quais eu queria explorar uma linguagem compilada para nativa era ser capaz de produzir binários que pudessem ser facilmente desviados, sem a necessidade de depender da presença de uma versão específica de um tempo de execução.

Um colega recentemente caminhou até minha mesa consternado, tentando resolver a questão de colocar o Java 17 em uma imagem base antiga do Node que era baseada no Debian 10. Ou ele teria que atualizar a versão do Node para obter uma imagem base mais recente, usar uma nova imagem base do Debian e instalar e configurar o Node manualmente, ou vasculhar a Internet em busca de um repositório personalizado hospedado por Deus sabe quem para um Deus sabe -se Java 17 hackeado que rodaria no Debian 10.

Seria muito mais fácil se o software implantado não tivesse essas dependências de tempo de execução conflitantes...

Do ponto de vista operacional, o único grande ganho que sinto que suportaria é: posso escrever código facilmente e construir um binário ELF para então implantar no "sistema arbitrário X" e não ter que lidar com garantir que a versão correta de um determinado tempo de execução esteja em vigor e gerenciar dependências conflitantes.

Tenho certeza de que há outros benefícios, e ouvi muito falar sobre a facilidade de uso de multithreading e multiprocessamento em Go, e pretendo criar um miniprojeto para explorar isso como uma próxima etapa - provavelmente algo que possa escutar entradas em vários canais e executar algumas tarefas básicas em resposta. Eu tive um caso de uso para isso em algumas tarefas de automação de teste que tive antes, então não é estranho para mim neste momento.

Declaração de lançamento Este artigo é reproduzido em: https://dev.to/taikedz/baby-teps-with-go-3ibl?1 Se houver alguma violação, entre em contato com [email protected] para excluí-lo.
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3