Tenzan Academy
Motyw
  • 8 sekcji
  • 24 pytania
  • 20 fiszek

Go (Golang)

Kurs Go: gorutyny i kanały, błędy jako wartości, interfejsy strukturalne, context, generyki i pułapki współbieżności w praktyce produkcyjnej.

Go (Golang)

Kurs Go: gorutyny i kanały, błędy jako wartości, interfejsy strukturalne, context, generyki i pułapki współbieżności w praktyce produkcyjnej.

Quiz

Gorutyny i kanały: model współbieżności CSP

na egzaminie

Go nie oferuje wątków jako podstawowej abstrakcji, tylko gorutyny: lekkie jednostki wykonania zarządzane przez runtime, komunikujące się przez kanały zamiast dzielenia pamięci pod zamkiem.

Termin kluczowy

Gorutyna: Funkcja uruchomiona słowem kluczowym `go`, wykonywana współbieżnie względem wywołującego; runtime Go planuje tysiące gorutyn na małą pulę wątków systemowych (model M:N, sterowany przez GOMAXPROCS).

Termin kluczowy

Kanał (channel): Typowany potok komunikacyjny (`chan T`) do przekazywania wartości między gorutynami, tworzony przez `make(chan T)` (niebuforowany) lub `make(chan T, n)` (buforowany, pojemność n).

Filozofia CSP

Synchronizuj przez przekazywanie wartości kanałem, nie przez mutex chroniący współdzieloną zmienną, wszędzie tam, gdzie to naturalne. Zasada Go: „Do not communicate by sharing memory; share memory by communicating.”

Worker pool z kanałami i sync.WaitGroup go
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
	defer wg.Done()
	for j := range jobs {
		results <- j * j
	}
}

func main() {
	jobs := make(chan int, 100)
	results := make(chan int, 100)
	var wg sync.WaitGroup

	for w := 1; w <= 3; w++ {
		wg.Add(1)
		go worker(w, jobs, results, &wg)
	}

	for j := 1; j <= 9; j++ {
		jobs <- j
	}
	close(jobs)

	wg.Wait()
	close(results)

	for r := range results {
		fmt.Println(r)
	}
}
select z timeoutem zamiast blokowania na zawsze go
select {
case res := <-resultCh:
	fmt.Println("wynik:", res)
case <-time.After(2 * time.Second):
	fmt.Println("timeout: brak odpowiedzi w 2s")
}
Pułapka

Wysłanie wartości do zamkniętego kanału powoduje panic w runtime; zamykanie kanału, do którego wciąż ktoś wysyła, to błąd projektowy, nie kosmetyczne niedopatrzenie.

  • Kanał niebuforowany: wysyłka blokuje do momentu odbioru (rendezvous)
  • Kanał buforowany: wysyłka blokuje dopiero po zapełnieniu bufora
  • `v, ok := <-ch`: ok=false oznacza kanał zamknięty i wyczerpany
  • `for v := range ch`: pętla kończy się automatycznie po close(ch)
Mnemonik

Kanał jak taśma produkcyjna: nadawca pakuje towar, odbiorca zdejmuje z taśmy. Zamknięcie close(ch) gasi taśmę: nic nowego już na nią nie kładziesz, ale to, co leży, można jeszcze zdjąć.

CechaGorutynaWątek systemowy (OS thread)
Rozmiar startowy stosu~2 KB, rośnie dynamiczniezwykle 1-8 MB, stały
Kto planujeScheduler Go (M:N na GOMAXPROCS wątków OS)Scheduler systemu operacyjnego
Koszt utworzeniaBardzo niski, miliony możliweWysoki, praktyczny limit - tysiące
  1. Generator Gorutyna emitująca wartości do kanału wyjściowego i zamykająca go po zakończeniu pracy.
  2. Etap przetwarzania Gorutyna czytająca z kanału wejściowego przez range, przetwarzająca wartość, wysyłająca dalej.
  3. Konsument Ostatnia gorutyna (lub main) odbierająca finalne wyniki przez range aż do zamknięcia kanału.
Dalej: 02 Wartości błędów, errors.Is/As i %w

Wartości błędów, errors.Is/As i %w

na egzaminie

Go nie ma wyjątków: błąd to zwykła wartość zwracana jako ostatni wynik funkcji, sprawdzana jawnie przez if err != nil, nigdy niewidocznym rzutem sterowania.

Termin kluczowy

error: Wbudowany interfejs `type error interface { Error() string }`; każdy typ z metodą Error() string spełnia go niejawnie i może pełnić rolę błędu.

Sentinel error i własny typ błędu go
var ErrNotFound = errors.New("resource not found")

type ValidationError struct {
	Field string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("invalid field: %s", e.Field)
}

func fetch(id int) error {
	if id < 0 {
		return &ValidationError{Field: "id"}
	}
	if id == 0 {
		return fmt.Errorf("fetch failed: %w", ErrNotFound)
	}
	return nil
}
Odczytanie łańcucha błędów przez errors.Is/As go
func main() {
	err := fetch(0)

	if errors.Is(err, ErrNotFound) {
		fmt.Println("nie znaleziono zasobu")
	}

	var valErr *ValidationError
	if errors.As(err, &valErr) {
		fmt.Println("błąd walidacji pola:", valErr.Field)
	}
}
Pułapka

fmt.Errorf("...: %v", err) gubi możliwość odnalezienia err przez errors.Is/As, bo nie tworzy metody Unwrap(); tylko %w buduje przeszukiwane drzewo błędów.

errors.Is

  • Porównuje z konkretną wartością sentinel (np. io.EOF, sql.ErrNoRows)
  • Przechodzi drzewo Unwrap w poszukiwaniu dopasowania (== lub metoda Is())
  • Pytanie: „czy to JEST ten błąd?”

errors.As

  • Szuka błędu określonego TYPU w drzewie i przypisuje go do wskaźnika
  • Daje dostęp do pól/metod konkretnego typu błędu (np. *ValidationError)
  • Pytanie: „czy w drzewie JEST błąd tego typu?”
  • Sentinel errors (var ErrNotFound = errors.New(...)): stała, porównywalna tożsamość błędu
  • Custom error types: niosą dodatkowe dane (pola struktury) o kontekście awarii
  • errors.Join (od Go 1.20): łączy wiele błędów w jedno drzewo bez utraty żadnego z nich
Konwencja komunikatu

Komunikat błędu w Go pisze się małą literą, bez kropki na końcu. Nie dodaje się zbędnego kontekstu, który doda wyżej wywołujący. Poprawnie: fmt.Errorf("reading config: %w", err), nie "Error: Could not read config file."

Mnemonik

err != nil to jedyny „try/catch” w Go: sprawdzasz od razu po wywołaniu, nie na końcu bloku.

errors.Join scala wyniki wielu walidacji go
func validate(name string, age int) error {
	var errs error
	if name == "" {
		errs = errors.Join(errs, errors.New("name is required"))
	}
	if age < 0 {
		errs = errors.Join(errs, errors.New("age must not be negative"))
	}
	return errs // nil, jeśli obie walidacje przeszły
}
Dalej: 03 Interfejsy i niejawna implementacja strukturalna

Interfejsy i niejawna implementacja strukturalna

na egzaminie

Interfejs w Go opisuje zachowanie, nie pochodzenie: typ implementuje interfejs automatycznie, gdy tylko posiada wymagany zestaw metod, bez deklaracji implements.

Termin kluczowy

Structural typing: Kompilator sprawdza zgodność metod w czasie kompilacji, nie w runtime przez reflection; jeśli typ „chodzi jak kaczka”, jest kaczką dla danego interfejsu.

Niejawna implementacja io.Writer go
type Writer interface {
	Write(p []byte) (n int, err error)
}

type UpperWriter struct {
	w io.Writer
}

func (u UpperWriter) Write(p []byte) (int, error) {
	return u.w.Write(bytes.ToUpper(p))
}

func main() {
	var w Writer = UpperWriter{w: os.Stdout}
	w.Write([]byte("hello\n"))
}
any i interface{}

Od Go 1.18 `any` to alias dla interface{}; oba oznaczają zbiór wszystkich typów, ale any jest preferowaną, czytelniejszą formą w nowym kodzie.

Pułapka

Zmienna typu error/interface może być != nil, mimo że przechowuje typowany nil wskaźnik. Interfejs to para (typ, wartość), a para (*MyError, nil) różni się od (nil, nil).

Pułapka: typowany nil w interfejsie go
type MyError struct{}

func (e *MyError) Error() string { return "boom" }

func mayFail(fail bool) *MyError {
	if fail {
		return &MyError{}
	}
	return nil
}

func do() error {
	var err *MyError = mayFail(false)
	return err // interfejs niosący (typ=*MyError, wartość=nil) - to NIE jest nil interfejsu
}

func main() {
	if err := do(); err != nil {
		fmt.Println("err != nil, mimo że logicznie nic się nie stało:", err)
	}
}
  • Interfejsy w Go trzyma się małe: 1-3 metody (np. io.Reader, io.Writer, sort.Interface)
  • Zasada „accept interfaces, return structs”: parametry przyjmują interfejsy, funkcje zwracają konkretne typy
  • Interfejs deklaruje się tam, gdzie jest konsumowany, nie tam, gdzie typ jest zdefiniowany
AspektGo (implicit)Java/C# (explicit)
Deklaracja zgodnościAutomatyczna, przez zestaw metodJawna przez implements/:
Sprzężenie modułówNiskie - konsument definiuje potrzebny interfejsWyższe - typ musi znać interfejs przy definicji
Nowy interfejs dla istniejącego typuNie wymaga zmian w typieWymaga edycji deklaracji typu
Mnemonik

Nie pytaj „czym jesteś”, pytaj „co potrafisz zrobić” - to sedno interfejsów w Go.

  1. Zdefiniuj interfejs po stronie konsumenta Np. w pakiecie, który wywołuje funkcję, nie w pakiecie z implementacją.
  2. Ogranicz liczbę metod do minimum Mniejszy interfejs = łatwiejszy do zaimplementowania i przetestowania (mock).
  3. Zwracaj konkretny typ z konstruktora Wywołujący dostaje pełny dostęp do API, interfejs stosuje się dopiero tam, gdzie potrzebny jest polimorfizm.
Dalej: 04 defer, panic i recover

defer, panic i recover

na egzaminie

defer, panic i recover to mechanizm Go do zarządzania zasobami i nietypowymi, katastrofalnymi błędami - nie zamiennik zwykłej obsługi błędów przez wartości error.

Termin kluczowy

defer: Odracza wywołanie funkcji do momentu zwrotu z otaczającej funkcji; argumenty są ewaluowane natychmiast, samo wywołanie następuje później, w kolejności LIFO.

Kolejność LIFO i bezpieczne domykanie zasobów go
func closeAll() error {
	f1, err := os.Open("a.txt")
	if err != nil {
		return fmt.Errorf("opening a.txt: %w", err)
	}
	defer f1.Close()

	f2, err := os.Open("b.txt")
	if err != nil {
		return fmt.Errorf("opening b.txt: %w", err)
	}
	defer f2.Close()

	fmt.Println("praca z plikami")
	return nil
	// kolejność zamykania: najpierw f2.Close(), potem f1.Close() (LIFO)
}
Termin kluczowy

panic / recover: panic przerywa normalny przepływ i zaczyna odwijać stos, wykonując po drodze odroczone defer. Funkcja recover, wywołana bezpośrednio w takim defer, zatrzymuje odwijanie i pozwala funkcji zwrócić się normalnie.

recover() jako granica pakietu: panic → error go
func process(items []int) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("process: recovered from panic: %v", r)
		}
	}()

	for _, i := range items {
		fmt.Println(100 / i) // panikuje przy i == 0 (dzielenie int przez zero)
	}
	return nil
}
Pułapka

Funkcja recover() poza funkcją wywołaną bezpośrednio przez defer nie działa. Wywołana w innej gorutynie niż ta, w której wystąpił panic, również nic nie przechwyci. Każda gorutyna potrzebuje własnego recover.

  • panic: naruszenie niezmiennika, błąd inicjalizacji pakietu, index out of range wykryty przez runtime, dzielenie int przez zero
  • error: każdy oczekiwany, przewidywalny scenariusz awarii (brak pliku, timeout sieci, nieprawidłowe dane wejściowe)
  • recover ma sens głównie na granicy systemu (np. middleware HTTP), by nie ubić całego procesu przez błąd w jednym żądaniu
Nieobsłużony panic

Panic, którego żadna funkcja w stosie danej gorutyny nie przechwyci przez recover, kończy CAŁY proces (nie tylko gorutynę). Proces kończy się z niezerowym kodem wyjścia i wypisuje stack trace.

Mnemonik

defer sprząta, panic krzyczy „coś jest fundamentalnie nie tak”, recover zatyka uszy tylko temu, kto stoi bezpośrednio obok.

Middleware HTTP odzyskujący się po panice w handlerze go
func recoverMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rec := recover(); rec != nil {
				log.Printf("panic w handlerze: %v", rec)
				http.Error(w, "internal server error", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}
Dalej: 05 Pakiet context: anulowanie i deadline'y

Pakiet context: anulowanie i deadline'y

na egzaminie

Typ context.Context niesie przez łańcuch wywołań sygnał anulowania, deadline i opcjonalnie wartości powiązane z pojedynczym żądaniem. To standardowy sposób na zatrzymanie pracy, gdy nikt już nie czeka na wynik.

Termin kluczowy

context.Context: Interfejs z metodami Deadline(), Done() <-chan struct{}, Err() error, Value(key any) any; Done() zwraca kanał zamykany w momencie anulowania lub upłynięcia deadline'u.

Żądanie HTTP z timeoutem przez WithTimeout go
func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("building request: %w", err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}
Gorutyna reagująca na ctx.Done() go
func worker(ctx context.Context, jobs <-chan int) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("worker: zatrzymano,", ctx.Err())
			return
		case j, ok := <-jobs:
			if !ok {
				return
			}
			fmt.Println("przetwarzam:", j)
		}
	}
}
Konwencja API

ctx context.Context zawsze jako pierwszy parametr funkcji, nazwany dosłownie ctx; nigdy jako pole struktury długożyjącego obiektu.

Pułapka

Zapomnienie wywołania cancel() zwróconego przez WithCancel/WithTimeout/WithDeadline (nawet po sukcesie) zostawia zasoby kontekstu żywe do czasu GC; zawsze defer cancel() zaraz po utworzeniu.

KonstruktorKiedy zamyka Done()
context.Background()Nigdy - to korzeń drzewa kontekstów
context.WithCancel(parent)Ręcznie, przy wywołaniu zwróconej funkcji cancel()
context.WithTimeout(parent, d)Po czasie d albo ręcznie przez cancel()
context.WithDeadline(parent, t)O konkretnym czasie t albo ręcznie przez cancel()
  • context.Background(): korzeń w main, testach, inicjalizacji
  • context.TODO(): tymczasowy placeholder, gdy docelowy kontekst jeszcze nie jest przekazywany - sygnał do refaktoryzacji
  • context.WithValue: tylko dla danych per-żądanie (trace ID, uwierzytelniony użytkownik), nigdy dla wymaganych argumentów funkcji
Mnemonik

Context to smycz: rodzic anuluje, dziecko czuje to natychmiast - ale nie odwrotnie.

Dobre użycie WithValue

  • ID żądania do logowania/tracingu
  • Tożsamość uwierzytelnionego użytkownika z middleware

Złe użycie WithValue

  • Przekazywanie wymaganych parametrów biznesowych (jawny argument jest lepszy)
  • Przechowywanie połączenia z bazą danych lub loggera zamiast wstrzyknięcia zależności
Dalej: 06 Pułapki współbieżności: wyścigi danych i wycieki gorutyn

Pułapki współbieżności: wyścigi danych i wycieki gorutyn

na egzaminie

Kompilator Go nie chroni przed wyścigami danych ani wyciekami gorutyn - oba błędy są w pełni poprawne składniowo i ujawniają się dopiero w runtime, często niedeterministycznie.

Flaga -race

go test -race ./... (oraz go run -race, go build -race) instrumentuje dostęp do pamięci i wykrywa brak synchronizacji happens-before między gorutynami.

Fakt na test

Koszt to zwykle 2-20x wolniejsze wykonanie i 5-10x większe zużycie pamięci. Dlatego stosuje się -race w CI/testach, nie na produkcji.

Wyścig danych: niezsynchronizowany inkrement go
var counter int

func incBroken() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter++ // odczyt-modyfikacja-zapis bez ochrony: wyścig danych
		}()
	}
	wg.Wait()
	fmt.Println(counter) // niedeterministyczny wynik, zwykle < 1000
}
Naprawa przez sync/atomic go
func incSafe() {
	var counter int64
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			atomic.AddInt64(&counter, 1)
		}()
	}
	wg.Wait()
	fmt.Println(atomic.LoadInt64(&counter)) // zawsze dokładnie 1000
}
Pułapka

Polecenie go build bez -race skompiluje kod z wyścigiem danych bez ostrzeżenia, bo build tylko tworzy binarkę i nie uruchamia jej. Dopiero uruchomiony później plik wykonywalny ściga się po cichu.

Pułapka

Brak widocznego objawu przy niskim obciążeniu usypia czujność aż do produkcji pod dużym ruchem.

Wyciek gorutyny na niebuforowanym kanale go
func leaky() {
	ch := make(chan int) // niebuforowany
	go func() {
		ch <- computeExpensiveValue() // blokuje się w nieskończoność, gdy nikt nie odbiera
	}()
	// funkcja wraca bez odbioru z ch -> gorutyna wisi w pamięci na zawsze
}
Naprawa: bufor lub wyjście przez ctx.Done() go
func notLeaky(ctx context.Context) {
	ch := make(chan int, 1) // buforowany: wysyłka się nie zablokuje
	go func() {
		select {
		case ch <- computeExpensiveValue():
		case <-ctx.Done():
		}
	}()
}
  • Zablokowana wysyłka/odbiór na kanale bez partnera po drugiej stronie
  • Zapomniany wg.Done() w defer przy panice w gorutynie roboczej
  • Nieskończona pętla bez ścieżki wyjścia (brak select na ctx.Done() lub kanale stop)
MechanizmKiedy użyć
sync.MutexOchrona złożonego stanu (kilka pól, mapa, struktura)
sync/atomicPojedyncza liczba/wskaźnik, wysoka częstotliwość aktualizacji
channelPrzekazanie własności danych lub sygnalizacja zdarzenia między gorutynami
Mnemonik

Jeśli nie potrafisz odpowiedzieć „kto i kiedy odbierze z tego kanału”, właśnie napisałeś wyciek gorutyny.

Dalej: 07 Generyki: parametry typu i constraints

Generyki: parametry typu i constraints

na egzaminie

Od Go 1.18 funkcje i typy mogą przyjmować parametry typu ograniczone przez constraints. Eliminuje to powielanie kodu dla int, float64, string, bez ucieczki do interface{} i rzutowań w runtime.

Termin kluczowy

Parametr typu: Deklarowany w nawiasach kwadratowych po nazwie funkcji/typu, np. func Sum[T cmp.Ordered](nums []T) T. Parametr T zostaje ustalony przy wywołaniu, najczęściej przez inferencję z argumentów.

Termin kluczowy

Constraint: Interfejs opisujący dozwolony zbiór typów konkretnych dla parametru typu; ~int dopuszcza int oraz każdy typ nazwany o bazowym typie int (underlying type).

Generyczna suma z użyciem stdlib cmp.Ordered go
import "cmp"

func Sum[T cmp.Ordered](nums []T) T {
	var total T
	for _, n := range nums {
		total += n
	}
	return total
}
Własny constraint dla typów liczbowych go
type Numeric interface {
	~int | ~int64 | ~float32 | ~float64
}

func Average[T Numeric](nums []T) float64 {
	var sum T
	for _, n := range nums {
		sum += n
	}
	return float64(sum) / float64(len(nums))
}
Generyczna struktura: Stack[T] go
type Stack[T any] struct {
	items []T
}

func (s *Stack[T]) Push(v T) {
	s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
	var zero T
	if len(s.items) == 0 {
		return zero, false
	}
	n := len(s.items) - 1
	v := s.items[n]
	s.items = s.items[:n]
	return v, true
}
comparable

Predeklarowany constraint comparable dopuszcza typy wspierające ==/!=; wycinki (slices), mapy i funkcje NIE implementują comparable i nie mogą być parametrem typu ograniczonym przez comparable.

Pułapka

Nadużywanie generyków tam, gdzie wystarczy interfejs (jedna implementacja, brak potrzeby wielu konkretnych typów) dodaje złożoność czytania bez korzyści w wydajności czy bezpieczeństwie typów.

  • slices.Sort, slices.Contains, maps.Keys - generyczne funkcje stdlib zamiast ręcznych pętli po typach
  • Inferencja typu zwykle eliminuje potrzebę jawnego podania [T] przy wywołaniu
  • Generyki nie zastępują interfejsów: interfejsy dają polimorfizm w runtime, generyki - reużywalność kodu w czasie kompilacji
Mnemonik

Interfejs pyta „co ten obiekt potrafi teraz”, generyk pyta „dla jakiego typu ten kod ma zadziałać, ustalone przy kompilacji”.

Dalej: 08 Moduły, semantic import versioning i statyczna kompilacja

Moduły, semantic import versioning i statyczna kompilacja

Go modules (od 1.11, domyślne i jedyne wspierane od 1.16) zarządzają zależnościami i wersjonowaniem. Kompilator generuje pojedynczy statyczny plik wykonywalny bez zależności od zewnętrznego runtime czy maszyny wirtualnej.

Termin kluczowy

go.mod / go.sum: go.mod deklaruje nazwę modułu, wymaganą wersję Go i zależności; go.sum przechowuje kryptograficzne sumy kontrolne pobranych wersji dla weryfikowalnej odtwarzalności buildu.

Przykładowy go.mod text
module github.com/tenzanlogic/example

go 1.23

require (
	github.com/google/uuid v1.6.0
)
Semantic import versioning

Od wersji major 2 wzwyż ścieżka modułu i importu musi zawierać sufiks /vN (np. github.com/tenzanlogic/example/v2). Dzięki temu v1 i v2 mogą współistnieć w jednym drzewie zależności jako odrębne, niekolidujące pakiety.

Import modułu w wersji v2 go
// go.mod: module github.com/tenzanlogic/example/v2
import "github.com/tenzanlogic/example/v2/pkg/data"
Statyczny binary

CGO_ENABLED=0 trzeba ustawić jawnie, by zagwarantować w pełni statyczny, wolny od libc binarny plik. Przy natywnym buildzie z obecnym w systemie kompilatorem C domyślne jest CGO_ENABLED=1.

Fakt na test

Przy natywnym buildzie pakiety stdlib takie jak net (rozwiązywanie DNS) czy os/user potrafią po cichu włączyć cgo i dynamicznie zlinkować libc. Dzieje się tak, mimo że kod użytkownika nigdy nie pisze import "C".

Fakt na test

CGO_ENABLED=0 jest domyślne tylko przy cross-compilation na inny GOOS/GOARCH niż host. Z jawnie wymuszonym CGO_ENABLED=0 binarka Go działa nawet w kontenerze FROM scratch, bez systemu operacyjnego.

Cross-compilation i minimalny obraz kontenera bash
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o app-linux-arm64 ./cmd/app
Pułapka

Włączenie cgo (CGO_ENABLED=1, np. przez rozwiązywanie DNS przez glibc lub sterowniki SQLite w C) przywraca dynamiczne zależności od libc. Psuje to przenośność binarki między dystrybucjami/kontenerami scratch.

  • GOOS/GOARCH: docelowy system operacyjny/architektura bez potrzeby fizycznego dostępu do tej platformy
  • CGO_ENABLED=0 trzeba ustawić jawnie dla gwarancji statycznego builda - nie jest to domyślne przy natywnym buildzie z dostępnym kompilatorem C
  • Jeden binarny plik = prostszy deployment: brak instalacji runtime, brak zarządzania wersją interpretera na serwerze
Mnemonik

Go nie pyta serwera „czy masz zainstalowany mój runtime” - przynosi go ze sobą w jednym pliku.

Sprawdź się

20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.

Rozpocznij quiz

Fiszki: Go (Golang)

Pytanie

O tym demo

Działające demo kursów napisanych przez Tenzan Logic. Wyniki zostają na tym urządzeniu, bez konta.

Każdy kurs, pytanie i fiszkę napisał Tenzan Logic, po polsku i od zera. Jeśli chcesz taki zestaw dla swojego zespołu, z Twoimi procedurami i Twoim słownictwem, napisz.

Napisz do Tenzan Logic

Jak to działa

100% czas (dni) bez powtórek 1 3 7 21 z powtórkami
Schemat idei, nie dane pomiarowe (Ebbinghaus 1885; Cepeda i in. 2006).
  • Odstępy

    1·3·7·21 dni do powtórki

    Fiszka z „Umiem” wraca po 1, 3, 7 i 21 dniach.

    Badanie

    Powtórki z przerwami dają trwalszą pamięć niż jedna po drugiej. Cepeda i in. 2006, Psychological Bulletin 132(3): 354-380

  • Przypominanie

    ukryta odpowiedź fiszki

    Najpierw odpowiedz z pamięci, potem odwróć.

    Badanie

    Próba przypomnienia dała lepszą pamięć po dwóch dniach i po tygodniu niż ponowne czytanie. Roediger i Karpicke 2006, Psychological Science 17(3): 249-255

  • Dwa kody

    4 kolory bloków

    Fakt, termin, mnemonik i pułapka mają swój kolor.

    Badanie

    Teoria: słowo i obraz zapisują się osobno. To teoria, nie gwarancja wyniku. Paivio 1991, Canadian Journal of Psychology 45(3): 255-287

  • Porcje

    167 krótkich sekcji

    Jedna sekcja to jedna porcja do zapamiętania.

    Badanie

    Pamięć robocza mieści około czterech porcji (Miller 1956: siedem, Cowan 2001: cztery). Cowan 2001, Behavioral and Brain Sciences 24(1): 87-114

  • Skróty

    134 zielone bloki

    Mnemonik zbiera kilka elementów w jeden, np. „SUR”.

    Badanie

    Skrót zbiera kilka elementów w jeden; przykład „SUR” jest w kursie o podstawach AI. Opis funkcji aplikacji, bez cytowanego badania

  • Testy

    20 z 24 pytań w quizie

    Po każdej odpowiedzi widzisz wyjaśnienie.

    Badanie

    Ćwiczenie, nie sprawdzian: pytania są w losowej kolejności i uzupełniają fiszki, nie zastępują ich. Efekt testowania: zob. „Przypominanie”

Szukaj

Postęp

Brak wyników. Rozwiąż pierwszy quiz.

Tu zbiera się Twój postęp: rekord i medal każdego quizu, seria dni i pytania do powtórki.

Gdzie jest zapis
Tylko w pamięci tej przeglądarki. Konta nie ma i wyniki nigdzie nie są wysyłane. Inna przeglądarka lub inne urządzenie zaczyna od zera.
Jak wyczyścić
W quizie: Historia, potem Wyczyść historię. Fiszki i serię dni usuniesz razem z danymi witryny w ustawieniach przeglądarki.
Wybierz quiz

Progi medali

Medal liczy się z najlepszego wyniku quizu: Brąz od 60%, Srebro od 75%, Złoto od 90%, Platyna przy 100%.