de Peter Benjamin

Cum să scrieți aplicații rapide, distractive din linia de comandă cu Golang

Cum sa scrieti aplicatii rapide distractive din linia de comanda
Credit ASCII: belbomemo

Cu ceva timp în urmă, am scris un articol despre „Scrierea aplicațiilor de linie de comandă în NodeJS”.

Îmi place JavaScript, Node.JS, npm și întregul ecosistem. Pentru mine, nimic nu se simte mai natural decât să scrii aplicații JavaScript moderne cu ES6 sau TypeScript.

Dar, în ultima vreme, a trebuit să folosesc simultanitatea multiprocesorului (paralel). Datorită buclei de evenimente cu un singur thread NodeJS, NodeJS este simultan, dar nu paralel. NodeJS nu acceptă concurența paralelă „out of the box”.

De ce mergi?

Limba Go (denumită adesea „Golang”), va utiliza în mod implicit toate nucleele unei mașini. Go aduce, de asemenea, următoarele beneficii:

ad-banner
  • Tastați siguranță (de exemplu, nu puteți transmite un șir unei funcții care așteaptă un număr – compilatorul se va plânge)
  • Refactorizare ușoară (de exemplu, schimbarea unei funcții sau a unui nume de variabilă va răspândi această schimbare pe tot parcursul proiectului)
  • Viteza și performanța sunt disponibile imediat
  • Paradigma de programare procedurală este cu siguranță mult mai ușor de argumentat
  • Implementări ușoare (implementați fișierul binar unic și gata!)
  • Stil standard (Go este opiniat despre formatare și vine cu instrumente pentru a automatiza acest lucru)
  • … si multe altele!

Notă: Este important pentru noii dezvoltatori nu să fie intimidați de concepte noi. Îmbrățișați acel sentiment inconfortabil pe care îl aveți atunci când vă confruntați cu o nouă provocare. Înseamnă că înveți, crești și te îmbunătățești. O trăsătură cheie a dezvoltatorilor de succes este persistenţă.

Iată ce veți învăța urmând împreună cu acest articol:

  1. Spații de nume
  2. Importurile
  3. Variabile
  4. Structuri
  5. Funcții
  6. Referințe și indicatoare
  7. Dacă condiții
  8. Pentru bucle

Noțiuni de bază

Pentru a evita umflarea acestui articol, trebuie să acceptați diferite comenzi pentru 3 platforme diferite, voi presupune că continuați Cloud9. Cloud9 este un IDE online (mediu de dezvoltare integrat) – practic, este un sos minunat!

Instalare

Go vine deja preinstalat pe Cloud9 U golspații de lucru buntu. Deci, puteți sări peste acest pas.

Dacă doriți să urmăriți computerul local, puteți descărcați și instalați Go.

Înființat

Go cere să vă configurați mediul într-un mod special.

  • Trebuie să aveți o casă pentru toate proiectele dvs. Go. Go numește această casă a spațiu de lucru. Spațiul de lucru trebuie să conțină 3 directoare: cos (pentru binare), pachet, și src (pentru cod sursă):
$ pwd
/home/ubuntu/workspace

$ mkdir {bin,src,pkg}
  • Go presupune că fiecare proiect trăiește în propriul depozit, așa că trebuie să ne organizăm în continuare src director în:
$ mkdir -p src/github.com/<your_github_username>/<project_name>

Notă: Dacă ești un gitlab sau a bitbucket utilizator, pur și simplu schimbați github.com cu numele corespunzător (de ex gitlab.com sau bitbucket.org respectiv).

Există un motiv pentru această structură de directoare. Go nu are un depozit de cod centralizat, cum ar fi NPM sau RubyGems. Go poate prelua codul sursă din VCS online (sisteme de control al versiunilor) direct și, atunci când va face acest lucru, va descărca codul sursă în calea corectă. De exemplu, următoarea comandă:

$ go get golang.org/x/tools/cmd/goimports

Îi va spune să contactați golang.org, apoi descărcați sursa în:

<your_go_workspace>/src/golang.org/x/tools/cmd/goimports

Ceea ce, în schimb, permite Go să găsească pachete și biblioteci terță parte atunci când le importați în proiectul dvs.

  • În cele din urmă, trebuie să ne configurăm GOPATH variabilă de mediu. În Cloud9 Ubuntu, trebuie doar să adăugați următoarele la sfârșitul anului .bashrc:
# in ~/.bashrc
...
export GOPATH="/home/ubuntu/workspace"
export PATH="$PATH:$GOPATH/bin"

Apoi, salvați fișierul și rulați următoarea comandă în terminal:

source ~/.bashrc
  • Pentru a verifica dacă Go funcționează pe Cloud9 și că GOPATH-ul nostru este configurat corect:
$ go version
go version go1.6 linux/amd64

$ go get golang.org/x/tools/cmd/goimports
$ goimports --help
usage: goimports [flags] [path ...]
...

Pentru mai multe informații despre configurarea Golang, vizitați documentul oficial „Noțiuni introductive”.

Sa mergem!

Scopul nostru: să construim o aplicație CLI minimă pentru interogare GitHub utilizatori.

Să creăm o repo pentru acest proiect pe Github.com. Spune-i gitgo. Apoi clonați-l:

$ cd $GOPATH/src/github.com/<your_github_username>
$ git clone git@github.com:<your_github_username>/gitgo.git

Un manual Go

Să creăm primul nostru fișier, să-l numim main.go, și scrieți următorul cod (nu vă faceți griji, vom acoperi fiecare linie):

package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}

Descompunându-l …

package main
  • Aceasta este o declarație de spațiu de nume. Spațiile de nume sunt doar o modalitate pentru a grupa logica și funcționalitatea. Veți vedea cum spațiile de nume ne vor ajuta puțin mai târziu.
  • Cuvantul principal este un cuvânt cheie. Îi spune compilatorului GO că codul nostru este destinat să ruleze ca un cerere nu ca un bibliotecă. Diferența este că aplicații sunt utilizate direct de către utilizatorii noștri, întrucât biblioteci poate fi importat și utilizat doar de alte bucăți de cod.
Import “fmt”
  • Declarație de import. Aceasta importă „fmt”(Prescurtare pentru„ format ”) pachet din bibliotecă standard.
func main()
  • func este cuvântul cheie pentru definirea sau declararea unei funcții în GO.
  • Cuvantul principal este un cuvânt cheie special în GO. Îi spune compilatorului GO că aplicația noastră începe aici!
fmt.Println(“Hello, World”)
  • Acest lucru se explică de la sine. Folosim Println funcție din fmt pachetul pe care l-am importat mai devreme în … ei bine … linia de imprimare.

Observați că prima literă de funcție Println este cu majuscule. Acesta este modul GO de a exporta variabile, funcții și alte lucruri. Dacă prima literă a funcției sau variabilei dvs. este majusculă, înseamnă că o faceți accesibilă pachetelor externe sau spațiilor de nume.

Hai să-l rulăm!

$ go run main.go
Hello, World

Minunat! Ați scris prima aplicație GO.
Ce s-a intamplat? Ei bine, GO compilat ȘI a executat aplicația în memorie! Destul de repede, nu?

Să-l construim!

$ go build     # generates executable binary in your local directory 
$ ./gitgo
Hello, World

Dulce! Tocmai ați construit prima aplicație GO. Puteți trimite exact astaunu trimiteți prietenilor și familiei dvs. și ei pot să-l ruleze și să obțină aceleași rezultate. Desigur, dacă rulează Windows, această aplicație nu va funcționa, deoarece am creat-o pentru Linux / Unix. Deci, să-l construim pentru Windows:

$ GOOS=windows go build -o forecaster.exe main.go

Gata! Acum, ați creat o aplicație pentru Windows. Destul de îngrijit, nu?

De fapt, puteți compila încrucișat această aplicație pe o gamă largă de platforme (de exemplu, Windows, Linux, OS X) și arhitecturi (de exemplu, i386, amd64). Puteți vedea lista completă aici: https://golang.org/doc/install/source#environment

Să-l instalăm!

Dacă doriți ca aplicația dvs. să fie accesibilă de oriunde din sistemul dvs.:

$ go install

Asta e. Acum, vă puteți apela aplicația de oriunde:

$ gitgo
Hello, World

În acest moment, ar fi o idee bună să vă verificați munca în GitHub:

$ git add .
$ git commit -am "Add main.go"
$ git push

Minunat! Dar până acum, aplicația noastră nu face nimic cu adevărat. Acest exercițiu a fost menit doar să ne ude picioarele și să ne dea o idee despre cum este să codăm în Go.

Acum, să ne scufundăm în aplicația noastră CLI!

Ne imaginăm interacțiunea cu aplicația noastră pentru a arăta ceva de genul

$ gitgo -u pmbenjamin
# or...
$ gitgo --user pmbenjamin,defunkt

Acum, că avem o direcție, să începem să creăm aceste steaguri.

Am putea folosi steag bibliotecă standard în Go, dar, cu încercări și erori și un pic de Google, veți descoperi că standardul steag biblioteca nu acceptă sintaxa de semnalizare lungă (prin liniuță dublă). Acceptă numai liniuțe unice.

Din fericire, cineva a rezolvat deja acest lucru cu o bibliotecă GO. Să-l descărcăm:

$ go get github.com/ogier/pflag

Acum, să-l importăm în proiectul nostru:

import (

    "github.com/ogier/pflag"
)

În GO, ultimul element al declarației de import este spațiul de nume pe care îl folosim pentru a accesa funcțiile bibliotecii:

func main() {
    pflag.SomeFunction()
}

Dacă preferăm să folosim un nume diferit, putem importa numele pachetelor la import:

import (

    flag "github.com/ogier/pflag"
)

Acest lucru ne va permite să facem:

func main(){
    flag.SomeFunction()
}

Care este ceea ce vedeți în exemple oficiale.

Să creăm variabilele care vor conține datele din intrarea utilizatorului:

import (...)import (
...
)

// flags
var (
   user  string
)

func main() {
...
}

Câteva lucruri de subliniat aici:

  • Ne-am declarat variabilele in afara de func main(). Acest lucru ne permite să referim aceste variabile în alte funcții alături func main(). Acest lucru s-ar putea să vă pară ciudat, deoarece nu doriți să poluați spațiul de nume global. Dar, crede-mă, acest lucru este perfect OK în Go. Suntem vizați doar pentru spațiul de nume curent.
  • Go este un limbaj tipic static, ceea ce înseamnă că trebuie să specificați tipul de date care vor fi stocate în fiecare variabilă (de aici string Cuvinte cheie)

Acum că v-ați declarat variabilele, să vă declarăm steagurile și să legăm / mapăm fiecare steag la variabila corespunzătoare:

import (
    ...
)

// flags
var (
    ...
)

func main() {
 flag.Parse()
}

func init() {
 flag.StringVarP(&user, "user", "u", "", "Search Users")
}

Descompunându-l …

func init()
  • init este o funcție specială în GO. GO execută aplicații în următoarea ordine:
    1. Declarații de import
    2. Declarații de variabile / constante la nivel de pachet
    3. funcția init ()
    4. funcția main () (dacă proiectul urmează să fie tratat ca o aplicație)
  • Tot ce încercăm să facem este să inițializăm steagurile o dată
flag.StringVarP(&user, "user", "u", "", "Search Users")
  • De la steag pachet / bibliotecă, folosim StringVarP () funcţie.
  • StringVarP() face 3 lucruri:
    1. îi spune GO că vom evalua așteptând o Şir,
    2. îi spune GO că vrem să legăm un Varcompatibil cu acest steag și
    3. îi spune GO că vrem să avem un Psteag conform cu osix (de exemplu, steag cu liniuță dublă și liniuță simplă)
  • StringVarP() ia 5 argumente în această ordine:
    1. variabila la care dorim să legăm acest steag,
    2. steagul cu liniuță dublă,
    3. steagul cu o singură liniuță,
    4. valoarea implicită de utilizat dacă pavilionul nu este apelat în mod explicit,
    5. și descrierea acestui steag
  • &user înseamnă că trecem o referință (denumită și adresa de memorie) a fișierului utilizator variabil. OK, înainte de a începe să te sperie despre referințe și adrese de memorie, hai să descompunem acest concept mai departe …
  • În multe limbi, cum ar fi JavaScript și Ruby, atunci când definiți o funcție care acceptă un argument, apoi apelați funcția și transmiteți-o argument, creați în esență o nouă copie a variabilei pe care o transmiteți ca argument. Dar, există momente în care nu doriți să transmiteți o copie a datelor. Uneori, trebuie să operați datele originale.
  • Prin urmare, dacă treceți datele valoare, creați în esență o altă copie a datelor și treceți copia respectivă, în timp ce dacă treceți variabila referinţă (denumit și adresa de memorie), apoi transmiteți datele originale.
  • În GO, puteți obține adresa de memorie a oricărui lucru prin pre-așteptarea simbolului ampersand (&).
flag.Parse()
  • Analizați steagurile.

Să ne testăm munca …

$ go run main.go # nothing happens
$ go run main.go --help
Usage of /tmp/go-build375844749/command-line-arguments/_obj/exe/main:
  -u, --user string
        Search Users
exit status 2

Grozav. Se pare că funcționează.

Observați ciudatul / tmp / go-build … cale? Aici aplicația noastră a fost compilată și executată dinamic de Go. Să-l construim și să-l testăm:

$ go install -v
$ gitgo --help
Usage of gitgo:
  -u, --user string
        Search Users

Pro-Tip: Când construiți sau compilați binare, preferați întotdeauna go install peste go build. go install va memora în cache pachetele non-principale în $GOPATH/pkg , rezultând astfel timpi de construcție mai rapide decât go build.

Logica de bază

Acum că ne-am inițializat steagurile, să începem să implementăm câteva funcționalități principale:

func main() {
 // parse flags
 flag.Parse()
 
 // if user does not supply flags, print usage
 // we can clean this up later by putting this into its own function
  if flag.NFlag() == 0 {
     fmt.Printf("Usage: %s [options]n", os.Args[0])
     fmt.Println("Options:")
     flag.PrintDefaults()
     os.Exit(1)
  }
  
  users = strings.Split(user, ",")
  fmt.Printf("Searching user(s): %sn", users)
  
}

Rețineți că nu există paranteze în jur dacă condiționate în Go.

Să ne testăm munca …

$ go install
# github.com/pmbenjamin/gitgo
./main.go:15: undefined: fmt in fmt.Printf
./main.go:15: undefined: os in os.Args
./main.go:16: undefined: fmt in fmt.Println
./main.go:18: undefined: os in os.Exit
./main.go:21: undefined: fmt in fmt.Printf
./main.go:24: undefined: fmt in fmt.Printf

Am vrut intenționat să vă arăt experiența compilatorului Go atunci când se plânge că ați făcut ceva greșit. Este important să putem înțelege aceste mesaje de eroare pentru a remedia codul nostru.

Deci, compilatorul se plânge că folosim Println funcție din fmt pachet, dar acel pachet este nedefinit. Acelasi cu Exit din os pachet.

Se pare că am uitat doar să importăm câteva pachete! Într-un IDE normal (de exemplu, Atom, VS-Code, vim, emacs … etc), există pluginuri pe care le puteți instala în editorul dvs., care vor importa dinamic și automat orice pachete lipsă! Deci, nu trebuie să le importați manual. Cât de minunat este asta?

Deocamdată, să adăugăm singuri declarațiile de import corecte. Amintiți-vă goimports instrument pe care l-am instalat mai devreme?

$ goimports -w main.go # write import stmts back in main.go!

Și reconstruiți și re-testați aplicația:

$ go install

$ gitgo
Usage: gitgo [options]
Options:
  -u, --user string
        Search Users

$ gitgo -u pmbenjamin        
Searching user(s): [pmbenjamin]

Da! Functioneaza!

Ce se întâmplă dacă utilizatorul dorește să interogheze mai mulți utilizatori?

$ gitgo -u pmbenjamin,defunkt
Searching user(s): [pmbenjamin defunkt]

Și asta pare să funcționeze!

Acum, să începem să obținem date reale. Este întotdeauna o bună practică să încapsulăm diferite funcționalități în funcții separate pentru a menține baza noastră de cod curată și modulară. Puteți pune acea funcție în main.go sau într-un alt fișier. Prefer un fișier separat, deoarece îl va face aplicația noastră modulară, reutilizabilă și ușor de testat.

De dragul timpului, iată codul împreună cu comentariile de explicat.

https://gist.github.com/petermbenjamin/8aeece9305bb44282799384365ab3a3c#file-user-go

Esența este următoarea:

  1. În user.go, trimitem o cerere HTTP GET cu numele de utilizator
  2. Apoi, citim corpul răspunsului și stocăm datele în resp.
  3. Este cea mai bună practică să închideți corpul de răspuns defer declarație pentru a curăța după ce funcția noastră a eșuat sau a fost finalizată.
  4. Apoi, analizăm datele JSON cu json.Unmarshal funcție, stocați datele de utilizator analizate în user variabilă și returnează-o.
  5. În main.go, facem o buclă peste users matrice, executare getUser() pentru fiecare utilizator și scoateți datele dorite.

Îmbunătățiri viitoare

Acest proiect a fost doar un ghid introductiv rapid pentru începători. Știu că acest proiect poate fi scris puțin mai eficient.

În următorul articol, intenționez să mă scufund în concepte noi, cum ar fi concurența (GoRoutines), canale, testare, vânzare și scrierea bibliotecilor Go (în loc de aplicații).

Între timp, codul complet al proiectului poate fi găsit aici.

Simțiți-vă liber să contribuiți deschizând problemele GitHub sau trimitând PR-uri.