Pakiety
Go posiada mechanizm podziału programów na pakiety. Dzięki nim możemy odseparować logiczne całości aplikacji, wydzielać biblioteki i narzędzia.
Do omówienia mamy kilka kwestii:
- deklaracja i importowanie pakietu
- widoczność składowych pakietu
- kompilacja pakietów bibliotecznych
- testowanie pakietów
Deklaracja i importowanie pakietów
Deklaracja nazwy pakietu to zawsze pierwsza instrukcja w pliku źródłowym, inaczej
kod się nie skompiluje. Sama deklaracja jest prosta package
i nazwa pakietu.
package main
Istnieją jednak pewne zwyczaje co do nazw. Są to rzeczowniki w liczbie pojedynczej pisane małymi literami. Jeśli pakiet mieści się w jednym pliku go, to nazwa pakietu powinna być taka sama jak nazwa pliku, bez rozszerzenia ".go". Każdy plik należący do pakietu powinien deklarować nazwę tegoż pakietu.
Jeśli importujemy jakieś pakiety do pliku źródłowego to musi się to odbyć tuż
pod deklaracją nazwy pakietu. Samo importowanie można wykonać na kilka sposobów.
Import pojedynczego pakietu ma następującą składnię: import
identyfikator
"ścieżka do pakietu".
Identyfikator to ciąg znaków, którym będziemy odwoływali się do składowych pakietu dalej w kodzie. Identyfikator jest opcjonalny, domyślnie identyfikator importowanego pakietu to ostatni człon ścieżki. Można użyć także kropki jako identyfikatora, sprawi to import wszystkich identyfikatorów importowanego pakietu do zasięgu pliku
Ścieżka do pakietu, może być absolutna (od głównego katalogu na dysku), względem
katalogu $GOPATH
oraz $GOROOT/pkg/$GOOS_$GOARCH
, lub względem bieżącego
katalogu w którym znajduje się kod (o ile zaczniemy od "./"), może być też
ścieżką do repozytorium na github, code.google.com, bazar itp; ujmujemy ją w
cudzysłów.
Przykładowe pojedyncze importy:
import "fmt" //pakiet fmt będzie widziany pod identyfikatorem fmt
import format "fmt" //pakiet fmt będzie widziany pod identyfikatorem format
import . "fmt" //wszystkie składowe pakietu, są widoczne w zasięgu pliku
import "io/ioutil" //pakiet io/ioutil będzie widoczny pod symbolem ioutil
Importy można grupować, wystarczy wziąć identyfikatory i ścieżki pakietów w nawiasie i rozdzielić średnikami lub nowymi liniami.
import (math, fmt, http)
import (
math
fmt
http
)
Widoczność składowych pakietów
To bardzo ważna kwestia! W go na zewnątrz pakietu są udostępnione tylko te identyfikatory które zaczynają się wielką literą. To nie żart, to jedyna metoda udostępniania zmiennych, typów czy funkcji poza pakiet. Co do zasady, możemy używać w dowolny sposób tylko tych identyfikatorów, które są pisane z wielkiej litery. Przeanalizujmy przykłady, udostępnianie typów:
package example
type secret int
type Point struct {
x, y int
}
W programie importującym pakiet example możemy utworzyć wartość typu
example.Point
ale już nie możemy utworzyć wartości typu secret
. Co więcej
nie możemy zmodyfikować żadnego z pól x
, y
, bo są niewyeksportowane. Taka
operacja udała by się gdyby pakiet example
udostępniał, funkcję lub metodę
umożliwiającą zmianę np.:
package example
type Point struct {
x, y int
}
func NewPoint(x,y int) *Point {
return &Point{x,y}
}
func (p *Point) Set(x,y int) {
p.x, p.y = x, y //tu mała "sztuczka" z jednoczesnymi przypisaniami
}
Dodana została funkcja example.NewPoint
tworząca zmienną typu *example.Point
,
przyjmująca składowe x
, y
, które będą użyte w inicjacji wartości. Druga
funkcja to metoda typu example.Point
: Set(x,y int)
przyjmuje ona wartości,
które zostaną przypisane do wartości danego typu.
Na marginesie, gdy mamy taki zestaw funkcji, to w praktyce nie potrzebujemy by
sam typ example.Point
był eksportowany, bo możemy wykonać wszystkie operacje,
za pomocą wyeksportowanych metod i funkcji, samo wyeksportowanie typu mało daje.
Gdybyśmy zadeklarowali pola x
i y
wielką literą - jako X
, Y
, to były by
dostępne także w pakietach importujących.
Kompilacja pakietów
Wiem z doświadczenia, że to kłopotliwa sprawa i ciężko znaleźć dobry opis jak to robić, postaram się by ten był w miarę kompletny.
Pakiety są różne i możemy mieć potrzebę budowania ich na kilka sposobów.
Większość tej sekcji trąci myszką są już lepsze narzędzia do kompilacji niż to co poniżej
Pakiety jednoplikowe
Najprostszym przypadkiem są pakiety, które mieszczą się w jednym pliku. Takie pakiety przeważnie wystarczy skompilować używając narzędzia 6g (w zależności od systemu może być to 8g, 5g). Kod z ostatniego przykładu skopiujmy do pliku example.go. Wtedy wystarczy wpisać:
go tool 6g example.go
Cały program np. taki:
package main
import (
"fmt"
ex "./example"
)
func main() {
p := ex.NewPoint(1,1)
fmt.Println(p)
}
Zwróć uwagę na sposób importowania pakietu: ./example
, wskazuje że skompilowany
pakiet będzie w tym samym katalogu co pakiet main
programu. Wszystko pójdzie
dobrze jeśli skompilowany pakiet example będzie w pliku example.6 (rozszerzenie
jest zależne od kompilatora). Wtedy wystarczy normalnie skompilować program:
go tool 6g example.go
go tool 6g main.go && go tool 6l -o program main.6
Opcja linkera -o
instruuje do jakiego pliku ma zostać zlinkowany kod. Teraz
możemy spokojnie uruchomić program.
Gdybyś jednak miał potrzebę wskazania innego katalogu w którym jest skompilowany pakiet to masz dwa wyjścia - zmienić ścieżkę w imporcie, bądź ją wskazać w kompilacji.
Przykładowo plik pakietu example.go
masz w katalogu example
. Wtedy zmień
polecenie importu na następujące:
import ex "./example/example"
Teraz, o ile oczywiście skompilowałeś pakiet i masz w katalogu example
plik
example.6
, wystarczy wpisać
go tool 6g main.go && go tool 6l -o program main.6
Drugi sposób także wymaga zmiany polecenia importu. Powinno wyglądać tak:
import ex "example"
A przy kompilacji programu dodajemy flagę -I
dodającą katalog w którym są
szukane pakiety
go tool 6g -I ./example/ main.go && go tool 6l main.go
Pakiety wieloplikowe
Generalnie zasady kompilacji pakietów wieloplikowych nie są różne od jednoplikowych. Po prostu przy kompilacji należy wskazać listę plików wchodzących w skład pakietu.
Załóżmy, że rozbiliśmy nasz mały pakiet na dwa pliki:
example.go:
package example
type Point struct {
x, y int
}
func (p *Point) Set(x,y int) {
p.x, p.y = x, y
}
example1.go:
package example
func NewPoint(x,y int) *Point {
return &Point{x,y}
}
jego kompilacja jest prosta:
go tool 6g example.go example1.go
i po robocie :-) Należy pamiętać by wylistować wszystkie pliki
Kompilacja za pomocą narzędzia go build
Testowanie pakietów
Go w bibliotece standardowej jest pakiet testing a w nim m.in. wyeksportowany typ
testing.T
, który ma kilka metod przydatnych do testowania takich jak Fail()
lub Error()
. Nie ma metod "pozytywnych", ale metody negatywne wystarczają by
sprawdzać poprawność pakietu.
Pliki z testami nazywamy tak jak pakiet dodając sufix _test
np. dla pakietu
example
powinien nazywać się example_test.go
. Testami są eksportowane funkcje
których nazwa zaczyna się od słowa Test, a jako argument przyjmują zmienną typu
*testing.t
. Może być ich wiele. Przykładowy plik z testami może wyglądać tak:
package example
import (
"testing"
)
func TestNew(t *testing.T) {
a := NewPoint(1,1)
if a.x != 1 || a.y != 1 {
t.Fail()
}
}
func TestSet(t *testing.T) {
a := NewPoint(1,1)
a.Set(2,2)
if a.x == 1 || a.y == 1 {
t.Fail()
}
if a.x != 2 || a.y != 2 {
t.Fail()
}
}
Aby dowiedzieć się jak uruchamiać testy