V minulém díle jsme se podívali na nástroj mage a jeho použití pro sestavování #Go programů. Tentokrát se podíváme na jednu specialitku, která asi nebyla předem zřejmá, ale rozhodně stojí za prozkoumání.
Jak z minula víte, mage
potřebuje tzv. magefile
. Magefile je vlastně go-skript na stereoidech. Pokud tedy chceme sestavit projekt, který používá magefile
, tak zadáme na příkazovou řádku něco jako mage
a máme hotovo (když je tedy definovaný defaultní cíl). Pokud bychom ovšem chtěli, aby se náš projekt takto pěkně spustil na našem CD/CI serveru, tak budeme muset přidat ještě něco - instalaci samotného mage
. Takže náš skript by vypadal jako
Sastavování (kompilace + linkování) v Go je jedna z věcí, kde vládne organizovaný chaos. Samozřejmě go build
prostě funguje. Ale v dnešní době je potřeba dělat mnohem víc věcí, než prosté go build
. Bohužel nějaký standardní způsob neexistuje. Jelikož jsem odkojený Un*xem, tak preferuji nástroje, které se samy nabízejí - make
. Člověk si udělá makefile
a je spokojený - jako například tento. Nevýhodou makefilů je, že ne všichni jim rozumí, a i ti, kdo jim rozumí, obecně neznají všechny finty. Ruku na srdce, kdo ví, co dělá na příklad toto go fmt $$(go list ./... | grep -v /vendor/)
.
V tomto příspěvku se podíváme na to, co to je reflexe, na co se používá a hlavně jak se používá. Hned z kraje jedna moudrá hláška
Zřejmé je vždy lepší než chytré … a reflexe není nikdy zřejmá.
Takže varování hned na úvod - reflexe ano, ale musíte k tomu mít dobrý důvod.
O co se tedy jedná. Reflexe je schopnost zjišťovat si informace a typech a hodnotách dat za běhu programu. Ti kdo se s reflexí ještě nesetkali se možná ptají proč. Vždyť přeci kompilátor ví, jakého typu hodnoty jsou a za běhu není problém se k nim dostat. To je sice pravda, ale v okamžiku, kdy do svého kódu přidáte abstrakci, tedy interfacy, a nedej bože prázdné interfacy, už ani vy, ani kompilátor neví, jakého typu je proměnná a tím pádem nemáte možnost se ani zeptat na její hodnotu.
Throttling je způsob, jak omezit počet běžících vláken. Pokud chceme, abychom nezahltili druhý systém (subsystém) nekontrolovaným množství paralelních volání, potřebujeme způsob, jak omezit počet běžících go rutin.
Určitě je mnoho způsobů, jak toho docílit, nabízím zde jeden, který se mi osvědčil.
V zásadě vycházím z toho, že zápis do kanálu blokuje, pokud je překrečena kapacita bufferu kanálu.
Tedy
ch := make(chan int, 1)
ch <- 1 // OK
ch <- 2 // blokuje, dokuď někdo kanál nepřečte
Tento konkrétní příklad vám neprojde, samotné Go detekuje deadlock (zápis bez čtení v jediné go rutině) a radši program zabije.
Dnes si povíme něco o tom, jak funguje kryptografie v Go. Go jako (relativně) nový programovací je v této oblasti daleko a podporuje prakticky všechny možné šifry a řešení. Co je ale na osvěžující je fakt, že Go se stále snaží dělat věci jednoduše, takže na rozdíl od jiných nejmenovaných jazyků se zde nesetkáme s milionem interfaců a object factory.
Kryptografie je opravdu obsáhlý prostor, takže my si ukážeme použitelné a praktické implementace kryptografie.
Poslední dobou je možné sledovat na poli jazyka Go zajímavou bitvu. Jak se blíží Go verze 2.0
(a pozor, po 1.9
může klidně přijít 1.10
), tak se začínají objevovat nápady typu “přidejme do jazyka generické typy”. Nejdřív jsem i já stál na generické straně barikády, ale musím uznat, že postupem času se spíš kloním ke skupině genericsů méně, nebo třeba vůbec ne. Možná bych chtěl na toto téma rozvířit debatu, takže zde je můj pohled na věc.
Určitě to víte - Go nepodporuje výčtové typy - tedy enum
. Go umí numerické sekvence pomocí iota
, ale to se opravdu nerovná enumu. Když jsem se nedávno na projektu vrátil ke staršímu kódu (který jsem napsal), tak jsem si uvědomil, že v jedné chvíli jsem vlastně použil něco, co může fungovat jako náhrada za výčty. No posuďte sami.
package main
import (
"fmt"
)
type AnswerEnum struct {
string
}
var (
Yes AnswerEnum = AnswerEnum{"Yes"}
No AnswerEnum = AnswerEnum{"No"}
)
func main() {
cond := []AnswerEnum{AnswerEnum{"Yes"}, AnswerEnum{"No"}}
for i, answer := range cond {
switch answer {
case Yes:
fmt.Println("na indexu", i, "je hodnota", Yes.string)
case No:
fmt.Println("na indexu", i, "je hodnota", No.string)
}
}
}
To co se zde děje je zjevné. Nejdříve si vytvoříme nový typ typu AnswerEnum
, který je kompozicí typu string
. Jelikož lze stringy mezi sebou porovnávat, tak je pak možné použít tyto hodnoty v bloku switch
.
Určitě jste si toho všimli - Go má (podle mě) velmi dobrou podporu relačních databází. A to včetně věcí typu transakce. Pokud chcete ovšem transparentně přistupovat k databázi tak, aby vám bylo jedno, jestli dotaz provádíte v rámci transakce, nebo mimo ni, tak máte problém. Standardní API totiž rozlišuje mezi přímým dotazem do databáze a mezi dotazem v rámci transakce.
Abychom tuto “podružnost” eliminovali, a také si ukázali jak si udělat abstrakční vrstvu nad konkrétní implementací, tak si napíšeme zaobalení (wrapper) databáze. Bude mít stejné funkce na provádění základních dotazů jako má standardní Go API.
Mnohdy si člověk myslí, že něco je naprosto jasné. Tedy až do okamžiku, kdy dostanete dotaz, který vám ukáže, že nejasnosti jsou na každém rohu.
Zrovna teď se mně kolega ptal, jaký je rozdíl mezi kanálem prázdné struktury a kanálem prázdného interfacu.
Odpověď je, že zásadní.
Prázdná struktura jako typ má omezené, ale legální použití. Sama o sobě nemůže nést data (nemá žádné pole, kam se dalo cokoliv uložit), ale už její přítomnost nám umožňuje ji použít na místech, kde prostě potřebujeme “nějakou” proměnnou. Jako další místo použití vidím jako možnost zavěsit na tuto strukturu receivera (příjemce) a použít takto vytvořenou proměnnou jako parametr typu interface.
Když jsme se bavili o slicech tak nezazněla jedna podstatná informace. Jak vlastně vypadá taková struktura slicu.
Vytvořme si tedy slice.
package main
import "fmt"
func main() {
slice := []int{}
fmt.Println("délka:", len(slice))
fmt.Println("maximální délka:", cap(slice))
}
Délka i maximální délka jsou shodné, tedy 0.
Teď si k danému slicu přidejme jedno položku a zkusme to znovu.
package main
import "fmt"
func main() {
slice := []int{}
slice = append(slice, 0)
fmt.Println("délka:", len(slice))
fmt.Println("maximální délka:", cap(slice))
}
Tentokrát je délka 1 a maximální délka je 2. Co to znamená? append
nám nejen přidal do pole jednu položku, ale také nám nechal ve slicu “fuku” pro jedno další číslo. Tzn. pokud přidáme další položku, tak nám append
nemusí zvětšovat pole, prostě přidá položku na konec již alokovaného pole.