$which go
/usr/local/bin
$ go version
go version go1.13.4 linux/amd64
$ go env
...
More setup (MacOS, linux)
$# Is the "go"-executable in the PATH env-var?$echo$PATH$export PATH=${PATH}:/usr/local/bin # in ~/.bash_profile
$# Setup GOPATH$echo$GOPATH$export GOPATH=$HOME/go # in ~/.bash_profile
$# Add the directory of your self-made executables to the PATH env-var$export PATH=${PATH}:${GOPATH}/bin # in ~/.bash_profile
package main
import"fmt"funcmain() {
fmt.Printf("Hi %s\n", "everybody")
}
$ go mod init <projectname> # For example: github.com/<user>/<project>$ go mod tidy$ go fmt # standard formatter (goimports is even better)
$ go run first.go # compiles and runs right away$ go build # creates executable "hello" or hello.exe in .$ go install # creates executable "first" in ${GOPATH}/bin
$ hello
Hi everybody!
Basics
Creating packages
Group related stuff
One package per directory
More coarse-grained than Java: can contain multiple files
Package name first line of source file
package main // package that results in executable with same name as package
or
package news // package that results in library that is accessible via 'news'
Using other packages
packages.go
package main
import (
"fmt"// package from stdlib"os"// package from stdlib"time"// package from stdlib"github.com/google/uuid"// third-party package
)
funcmain() {
u := uuid.New() // use package-name as prefix
now := time.Now() // use package-name as prefix
fmt.Fprintf(os.Stdout, "uuid: %s\ntime: %s", u.String(),
now.Format(time.RFC3339))
}
Comments
Comments
/* a comment */// another one
Document your packages:
Package level comment
Every exported (capitalized) name in a program should have a comment
Verify documentation:
$ go doc -all
Enforce rules:
$ go get -u golang.org/x/lint/golint$ golint
helps you minimize your public exports
Variables varintstringbool
Name and type swapped (from Java perspective)
Have reasonable defaults (not nil)
package main
const myConstString = "golang"funcmain() {
fmt.Printf("my-const-string: %s\n", myConstString)
var status bool// uninitialized -> default (=false)
fmt.Printf("status: %v\n", status)
// := short notation: derives type from right-hand-side
idx := 256
fmt.Printf("idx: %d\n", idx)
longString := `{
"why": "Useful to embed json in source"
}`
fmt.Printf("my-long-string: %s\n", longString)
}
Loops forrange
for
sum := 0for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
while-like
sum := 1for sum < 1000 {
sum += sum
}
fmt.Println(sum)
iterate
values := []string{"a", "b", "c"}
for idx, value := range values {
fmt.Printf("%d:%s\n", idx, value)
}
If, else ifelse
num := 9if num < 0 {
fmt.Println(num, "is negative")
} elseif num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
Switch switchcasefallthrough
On any type
No fallthrough unless explicitly stated (fallthrough)
funcunhex(c byte)byte {
switch {
case'0' <= c && c <= '9':
return c - '0'case'a' <= c && c <= 'f':
return c - 'a' + 10case'A' <= c && c <= 'F':
return c - 'A' + 10
}
return0
}
funcshouldEscape(c byte)bool {
switch c {
case' ', '?', '&', '=', '#', '+', '%':
returntrue
}
returnfalse
}
Exercise 1: Control structures
Create a program that calculates:
Sum of all values from 1 to 100
Sum incremental values until their sum exceeds 1000
Put calculation logic in separate package (library)
Functions funcreturn
Core building block
Scope: based on case
Java: static methods
funcConvertIt(arg int)string { // publicreturn convertInternal(arg)
}
funcconvertInternal(arg in )string { // privatereturn fmt.Sprintf("My integer value as string: %d", arg)
}
Can return multiple values
funcswap(x, y string)(string, string) {
return y, x
}
(More on functions later)
Defer defer
Cleanup of file-handles, mutexes, channels and connections
resp, err := doSomethingThatCanFail(arg1, arg2)
if err != nil {
return fmt.Errorf("Error doing something that can fail: %s", err) // early return to minimize indentation
}
// continue with success path// use _ (=blank) if you don't care
resp, _ := doit(arg1, arg2)
Implement a simple in-memory database that implements the following interface:
type Datastorer interface {
Put(key string, value interface{}) error
Get(key string) (interface{}, bool, error)
Remove(key string) error
}
Testing
Testing
Essential for software with a long predicted lifetime
Why:
Need “Safety-net" so you dare to keep improving and extending your software
How:
Do from beginning
Tests should be easy and fast to run
Test against the API
Only test against internals in specific cases
Prefer HTTP (as the ultimate API) to trigger your business logic, so you have freedom to change the internals and still keep your safety net intact
Unit testing
Part of toolchain
In same package, dedicated file
Filename convention: <file_name>_test.go
reverse_test.go
import (
"testing"
)
// Naming convention: starts with Test and has "t *testing.T" as parameterfuncTestReverse(t *testing.T) {
value := Reverse("ecnerual")
if value != "laurence" {
t.Errorf("Reverse() = %v, want %v", value, "laurence")
}
}
$ go test
--- FAIL: TestReverse (0.00s)
reverse_test.go:10: Reverse() = ecnerual, want laurence
FAIL
exit status 1
// trigger benchmark with: go test -bench=.// Naming convention: starts with "Benchmark" and has "b *testing.B" as parameterfuncBenchmarkDoCalculationByValue(b *testing.B) {
// run the function b.N timesfor n := 0; n < b.N; n++ {
bs.DoCalculationByValue()
}
}
funcBenchmarkDoCalculationByReference(b *testing.B) {
for n := 0; n < b.N; n++ {
(&bs).DoCalculationByReference()
}
}
More on testing
Standard library offers utils for testing http-clients and http-servers
Code coverage: IntellijIDEA
Race condition detection
Continuous integration
Exercise 6: tests
Write a table driven test for validating email-addresses
Make the 'nontestable'-package testable
Run the benchmark and find out at what 'size' by value and by reference have equal performance
Go doesn't provide assertions. They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting. Proper error handling means that servers continue to operate instead of crashing after a non-fatal error. Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace. Precise errors are particularly important when the programmer seeing the errors is not familiar with the code.
We understand that this is a point of contention. There are many things in the Go language and libraries that differ from modern practices, simply because we feel it's sometimes worth trying a different approach.
package yours
import (
"testing""github.com/stretchr/testify/assert"
)
funcTestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
Concurrency
Concurrency
Overemphasized
Most of your code is synchronous
Concurrent-style not forced upon you, used selectively
built-in "channels" and "goroutines"
Goroutines: think very, very lightweight threads
Channels: think pipe or queue to communicate with goroutine(s)
"select"-loop: UNIX-like: wait for events from multiple channels
Channels chan
Do not communicate by sharing memory. Instead, share memory by communicating.
funcsum(a []int, resultChannel chanint) {
sum := 0for _, v := range a {
sum += v
}
resultChannel <- sum // send result back over channel
}
funcdoit() {
responseChannel := make(chanint) // construct channeldeferclose(responseChannel) // prevent resource leakgo sum([]int{1, 2, 3}, responseChannel) // 1 + 2 + 3 = 6go sum([]int{4, 5, 6}, responseChannel) // 4 + 5 + 6 = 15
x, y := <-responseChannel, <-responseChannel // receive from channel
fmt.Printf("one=%d\nanother=%d", x, y) // order undefined
}
Select select
Wait for events from multiple channels
funcsendMsg(c chanstring) {
time.Sleep(100 * time.Millisecond)
c <- "Put your helmet on"
}
funcmain() {
tick := time.Tick(800 * time.Millisecond)
boom := time.After(3 * time.Second)
msgChannel := make(chanstring)
go sendMsg(msgChannel)
for {
select { // blocking until msg received on one of its channelscase msg := <-msgChannel:
fmt.Printf("msg: %s\n", msg) // stay in loopcase <-tick:
fmt.Println("tick.") // stay in loopcase <-boom:
fmt.Println("BOOM!")
return// abort loop
}
}
}
Concurrency example usage
Devide and conquer: Chunk your problem in parts that can be processed concurrently in isolation
Send out multiple requests concurrently: Continue when all responses have been received
Cache eviction: Scheduled background "thread" removes old entries at regular intervals
Fan out: Fire and forget
Respond faster by moving non-criticals (like notification) off the main thread
Synchronized state-machine: select loop is the heart
NB:
When multiple go-routines access the same data, you will need to synchronize (with mutexes or channels)
Cleanup: don't leave dangling channels and goroutines
Exercise 7: concurrency
Execute a slow action (see function below) concurrently (100x) and wait for all the results for no more than 1 second. Report the number of results received.
funcSimulateSlowAction( a, b int)int {
sleepDurationInMillsec := 500 + (rand.Intn(1000)) // what does this do?
time.Sleep(time.Duration(sleepDurationInMillsec) * time.Millisecond)
return a*b
}
Create a "thread-safe" in-memory cache (Get, Put) that automatically removes entries older than 10 seconds
More on functions
Package initialisation
Executed only once at startup
Can be multiple
For advanced initialisations
funcinit() {
// your global initialisations here
}
Variadic functions
package main
import"fmt"funcsum(nums ...int) {
fmt.Print(nums, " ")
total := 0for _, num := range nums {
total += num
}
fmt.Println(total)
}
funcmain() {
sum(1, 2)
sum(1, 2, 3)
nums := []int{1, 2, 3, 4}
sum(nums...)
}
Closures
Lambda's in Java
Alternative to single method interfaces
example: binary search
package main
import (
"fmt""sort"
)
funcmain() {
numbers := []int{1, 11, -5, 8, 2, 0, 12}
sort.Ints(numbers)
fmt.Println("Sorted:", numbers)
index := sort.Search(len(numbers), func(i int)bool {
return numbers[i] >= 7
})
fmt.Printf("The first number >= 7 is at %d and has value %d", index, numbers[index])
}
More examples usages of closures
funcmain() {
a := 1
b := 1004223gofunc() {
// this block runs in background within a go-routine
result := doWork1(a, b)
result = doWork2(result)
result = doWork3(result)
log.Printf("Result:%s\n", result)
}()
log.Printf("Continue without waiting for result\n")
time.Sleep(time.Second * 5) // Why is this needed?
log.Printf("main terminates\n")
}
The standard library
Rich standard libraries
flags
file I/O
sync
logging
os
sort
networking
http, http2: client and server
encoding (json, xml, mime)
compression
crypto
templates
sql
Serialisation
Based on struct tags
xml, json etc in stdlib
example:
type Person struct {
Name string`json:"name" xml:"PersonName"`
Interests []string`json:"interests" xml:"PersonInterests"`
Children []Child `json:"children" xml:"Person_Children"`
}
type Child struct {
Name string`json:"name" xml:"name"`
Age int`json:"age,omitempty" xml:"age,omitempty"`
}
Exercise 8: json and xml
Create some structs with different primitives and types