Open anitsh opened 4 years ago
Go is a statically typed compiled programming language which will have a single static binary.
Go doesn’t provide any implicit type conversion like other statically typed languages like JAVA and C#.
Go utilizes blocks of memory to store data.
Go uses package to organize programs. The function main.main() (this function must be in the main package) is the entry point of any program.
Go standardizes language and most of the programming methodology, saving time of developers which they'd have wasted in religious wars. There can be only one main package and only one main function inside a go main package.
Go supports UTF-8 characters because one of the creators of Go is a creator of UTF-8, so Go has supported multiple languages from the time it was born.
… the more important idea is the separation of concept: data and behavior are two distinct concepts in Go, not conflated into a single notion of
class
. – Rob Pike
"gVisor is written in Go in order to avoid security pitfalls that can plague kernels. With Go, there are strong types, built-in bounds checks, no uninitialized variables, no use-after-free, no stack overflow, and a built-in race detector. However, the use of Go has its challenges, and the runtime often introduces performance overhead." – https://gvisor.dev/docs
import return var const break default func interface if else range type for select case defer continue go map struct chan package switch fallthrough goto
No public or private keyword exists in Go. Any variable that begins with a capital letter means it will be exported, private otherwise. _
(blank) is a special variable name. Any value that is given to it will be ignored.
import(
. "fmt" // Omit the package name and call functions directly
f "fmt" // Use f as an alias to call the functions
_ "package" // Use blank `_` to import package and execute init function in that package
)
Resources
Int types that have specific lengths including rune, int8, int16, int32, int64, byte, uint8, uint16, uint32, uint64. rune is alias of int32 ( default ) and byte is alias of uint8
Float types also have specific lengths: float32 and float64 types and no type called float.
In Complex types, complex128 (with a 64-bit real and 64-bit imaginary part) is the default. complex64 (with a 32-bit real and 32-bit imaginary part) is the smaller type. Its form is RE+IMi, where RE is real part and IM is imaginary part, the last i is the imaginary number. Example: var c complex64 = 5+5i. Only arithmetic operations like addition, subtraction, multiplication, and division are performed on this type.
Type | Size | Range |
---|---|---|
int8 | 8 bits | -128 to 127 |
int16 | 16 bits | -215 to 215 -1 |
int32 | 32 bits | -231 to 231 -1 |
int64 | 64 bits | -263 to 263 -1 |
int | Platform dependent | Platform dependent |
Operations on Numeric Types
isTrue := true
Operations on boolean
A string is declared either using double quotes ( "Hello World" ) or back ticks ( Hello World
). It's a value type.
Double-quoted strings cannot contain newlines and they can have escape characters like \n, \t etc. In double-quoted strings, a \n character is replaced with a newline, and a \t character is replaced with a tab space, and so on. Strings enclosed within back ticks are raw strings. They can span multiple lines. Moreover, Escape characters don’t have any special meaning in raw strings.
var stringName string // Default definition
s := "hello"
c := []byte(s) // convert string to **slice** of byte
c[0] = 'c' // Replace first character
// OR
s = "c" + s[1:] // use range to get the values into a new string
s2 := string(c) // convert back to string type
Package strings implements simple functions to manipulate UTF-8 encoded strings.
Variables declared without an explicit initial value are given their zero value, i.e. 0 for numeric types, false for the boolean type, and "" (the empty string) for strings.
Resources:
The type *T is a pointer to a T value. Its zero value is nil.
Every variable is a memory location and every memory location has its address defined which can be accessed using ampersand (&) operator, which denotes an address in memory.
A pointer is a variable whose value is the address of another variable, i.e., direct address of the memory location.
On most of the operating systems, programs are not permitted to access memory at address 0 because that memory is reserved by the operating system. However, the memory address 0 has special significance; it signals that the pointer is not intended to point to an accessible memory location. But by convention, if a pointer contains the nil (zero) value, it is assumed to point to nothing.
Go allows to define arrays to hold a number of pointers, have pointer on a pointer and pass pointer to a function.
var name *type // general definition
var ptr *int // Zero (0) is assigned to ptr of type int by default and ptr also called nil pointer.
// Check if the pointer is nil
if(ptr == nil) // returns true
A buffer is a data structure used to optimize the flow of data. It's a structure used to hold data to keep it "closer" while you're processing it. Like buffering a YouTube video. Many types of memories are used as buffers. RAM is certainly great for holding buffers, but for large data you can also buffer data on a hard drive, or an SSD. Buffers are mainly used to hold data that is costly to read and write (costly in terms of time, or bandwidth, etc), so you'd rather only read a chunk of it once, work on it, then write it back when you're done.
Resources:
There is no array keyword in Go. It's a value type. When an array is used as an argument, functions get their copies instead of references.
Signature definition: [capacity]data_type{element_values}
It is important to remember that every declaration of a new array creates a distinct type and are incompatible.
var arr [10]int // an array of type [10]int
var arrB [11]int // It is incompatible to arr
a := [3]int{1, 2, 3} // define an int array with 3 elements
c := [...]int{4, 5, 6} // use `…` and Go will calculate it for you.
twoDArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} // Easier
twoDArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} //Different declaration
arrStr := [5]byte{'a', 'b', 'c', 'd', 'e'}
stringArr := string(arrStr) // Not allowed. Cannot convert array into string
append(arrStr, 'f') // Add new value to arrStr
slice
is not a keyword. It is a concept of dynamic array like List
in other OOP languages. Slice does not require length while defining. It's a reference type.
slice internal representation, it is not an array rather it describes describes a piece of an array.
type SliceHeader struct {
Pointer uintptr
Len int
Cap int
}
// make function signature. Length should always be less than or equal to the capacity.
func make([], length, capacity int) []
slice := []int{3, 2, 1, 4, 3, 2, 1, 4, 1} // any item can be sorted
dupVal := make([]int, 10)
// append creates a new slice that does
// not reference slice.
dupVal = append(slice, []int{21}...)
dupVal[0] = 101
fmt.Printf("dupVal %v\n", dupVal)
sort.Ints(dupVal)
fmt.Printf("dupVal sorted %v\n", dupVal)
fmt.Printf("slice is unchanged %v\n", slice)
dupRef := append(slice, []int{}...)
fmt.Printf("dupRef references slice%v\n", dupVal)
sort.Ints(dupRef)
fmt.Printf("slice is also sorted %v\n", slice)
copySlice := make([]int, len(slice))
copy(copySlice, slice)
fmt.Printf("copySlice %v\n", copySlice)
copySlice[0] = 99
sort.Ints(copySlice)
fmt.Printf("copySlice sorted %v\n", copySlice)
copySlice[0] = 199
fmt.Printf("copySlice sorted %v\n", copySlice)
// Type checking
fmt.Println(reflect.TypeOf(slice))
// https://play.golang.org/p/vl9omvprZqF
"Yes, everything in Go is passed by value. Slices too. But a slice value is a header, describing a contiguous section of a backing array, and a slice value only contains a pointer to the array where the elements are actually stored. The slice value does not include its elements (unlike arrays).
So when you pass a slice to a function, a copy will be made from this header, including the pointer, which will point to the same backing array. Modifying the elements of the slice implies modifying the elements of the backing array, and so all slices which share the same backing array will "observe" the change."
https://golang.org/pkg/bytes; Package bytes implements functions for the manipulation of byte slices. It is analogous to the facilities of the strings package.
Resources:
- https://medium.com/rungo/the-anatomy-of-slices-in-go-6450e3bb2b94
- https://github.com/golang/go/wiki/SliceTricks
- https://blog.golang.org/slices
- https://blog.golang.org/slices-intro
- https://golang.org/doc/effective_go.html#slices
- https://stackoverflow.com/questions/39993688/are-golang-slices-passed-by-value#39993797
map
is a keyword that represents Key-Value pair data structure like HashMap in OOP languages.
It is a reference type.
Two map's are incomparable.
It has two return values; First return type the value mapped by the key. The second is boolean value which is true if the key exists in the map, else false. If the return key does not exits, the return type will be default value defined in the map.
var numbers map[string] int // Map of with key of type string and int values.
numbers := map[string]int{} // initialize empty map
var mapList = make(map[int]int) // initialize empty map
mapList[1] = 1
mapList[2] = 2
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
csharpVal, keyExists := rating["C#"] // keyExists is false as C# does not exists; csharpVal will be default float value, 0.000000.
delete(mapList, 1) // delete from mapList
mapList = nil // delete all
// sort by key example
unSortedMap := map[string]int{"India": 20, "Canada": 70, "Germany": 15}
keys := make([]string, 0, len(unSortedMap))
for k := range unSortedMap {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, unSortedMap[k])
}
// merge example
first := map[string]int{"a": 1, "b": 2, "c": 3}
second := map[string]int{"a": 1, "e": 5, "c": 3, "d": 4}
for k, v := range second {
first[k] = v
}
Go has multiple ways of memory allocation and value initialization:
&T{...}, &someLocalVar, new, make
new returns pointers. make returns non-zero values.
make does memory allocation for built-in models
, such as map, slice, and channel, while new is for types
memory allocation.
new returns a zeroed value, whereas map allocates non-zeroed types map, slice or channel
new(T) allocates zero-value to type T's memory, returns its memory address, which is the value of type *T. By Go's definition, it returns a pointer which points to type T's zero-value.
The built-in function make(T, args)
has different purposes than new(T). make can be used for slice, map, and channel, and returns a type T with an initial value. The reason for doing this is because the underlying data of these three types must be initialized before they point to them. For example, a slice contains a pointer that points to the underlying array, length and capacity. Before these data are initialized, slice is nil, so for slice, map and channel, make initializes their underlying data and assigns some suitable values.
Things you can do with make that you can't do any other way:
p := new(chan int) // p has type: *chan int
c := make(chan int) // c has type: chan int
// iota enumerate
const(
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // If there is no expression after the constants name, it uses the last expression,
//so it's saying w = iota implicitly. Therefore w == 3, and y and z both can omit "= iota" as well.
)
const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0.
const (
e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line.
)
Resources: https://stackoverflow.com/questions/9320862/why-would-i-make-or-new
Go programs express error state with error values. The fmt package looks for the error interface when printing values. Functions often return an error value, and calling code should handle errors by testing whether the error equals nil. A nil error denotes success; a non-nil error denotes failure.
// Error interface
type error interface {
Error() string
}
// Example
type MyError struct {
When time.Time
What string
}
// Override Error()
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err) //Calls Error()
}
}
if integer == 3 {
fmt.Println("The integer is equal to 3")
} else if integer < 3 {
fmt.Println("The integer is less than 3")
} else {
fmt.Println("The integer is greater than 3")
}
// Default
for expression1; expression2; expression3 {
//...
}
// As a while loop
sum := 1
for sum < 1000 {
sum += sum
if sum == 5{
break // or continue; break will exit the for loop while continue continues
}
}
// Read data from array, slice, map and string with range.
for k,v := range map {
//...
}
// Regular switch example
switch match {
case condition1:
some instructions
case condition2, condition3,...,conditionN:
some other instructions
case otherCondition:
// ...
fallthrough // will also execute default. fallthrough continues below conditions.
default:
other code
}
A type switch is a construct that permits several type assertions in series. A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
var i interface{}
iTest := 10.0
i = iTest
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
// i.(type) only works with switch case.
Use keyword func
to define a function.
main()
function is the entry point to a Go application.
Functions have zero, one or more than one arguments. The argument type comes after the argument name and arguments are separated by comma ','.
Functions can return multiple values.
Does not have try-catch
structure like Java does so Instead of throwing exceptions Go uses panic
and recover
to deal with errors.
By default arguments passed to the function is by value. Argument must be a pointer type prefix by an asterik '*' to pass by reference and a reference should be passed.
Function can also be passed as a value.
Function may be a closure. A closure is a function value that bound to the referenced variables ( access and assign ) from outside its body.
There are two return values named output1 and output2, you can omit their names and use their type only.
If there is only one return value and you omitted the name, you don't need brackets for the return values.
If the function doesn't have return values, you can omit the return parameters altogether.
If the function has return values, you have to use the return statement somewhere in the body of the function.
Function allows an uncertain numbers of arguments, called variadic
function.
func SumAndProduct(A, B int) (int, int) {
return A + B, A * B
}
func SumAndProduct(A, B int) (add int, multiplied int) {
add = A+B
multiplied = A*B
return // returns add and multiplied
}
func myfunc(arg ...int) {} // variadic function
// Function As A Value
func compute(fn func(float64, float64) float64) float64 { return fn(3, 4)}
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
// Closure
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
Go has two default functions main
and init
. main
is the entry point the the go application can only be used once and must be in the package named main
. init can be used in all packages. They don't have any arguments nor return values.
Programs initialize and begin execution from the main package. If the main package imports other packages, they will be imported in the compile time. If one package is imported many times, it will be only compiled once. After importing packages, programs will initialize the constants and variables within the imported packages, then execute the init function if it exists, and so on. After all the other packages are initialized, programs will initialize constants and variables in the main package, then execute the init function inside the package if it exists. The following figure shows the process.
GO does not have a class
keyword but has Object-oriented design at it's core. It enables object-based modeling and promotes the best practice of using interfaces instead of concrete type hierarchies.
struct allows us to create customs data structures. structs are value types. Dot (.) is used to access the fields values of struct.
type Human struct {
name string
age int
phone string // Human has phone field
}
type Employee struct {
Human // Nested struct; Human fields are called promoted fields since they can be accessed as if they are directly declared in the Employee struct itself
specialty string
phone string // phone in employee
int // anonymous field are the fields without names
}
func main() {
var empZero Employee // Zero valued struct
Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222", 11}
fmt.Println("First Name:", Bob.name)
// Create reference type
emp8 := &Employee { ... } // Pointer type; Pass by reference
aS := new(Student)
var emp2 = new(emp)
// Anonymous struct
emp3 := struct {
firstName string
lastName string
age int
salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
}
structs are comparable if each of their fields are comparable. Two struct variables are considered equal if their corresponding fields are equal. When they contain map as a field
A method is a function with an implicit first argument, called a receiver. Any type can be the receiver of a method, not just struct.
// Default method definition
func (r ReceiverType) funcName(parameters) (return types)
// Example
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return c.radius * c.radius * math.Pi
}
func main() {
c1 := Circle{10}
fmt.Println("Area of c1 is: ", c1.Area())
}
// Area() is a method of type Circle
interface is a custom data type which represents a set of method signatures. The struct data type implements these interfaces implicitly to have method definitions for the method signature of the interfaces. We achieve polymorphism with the help of interfaces in Go. The zero value of the interface is nil. When an interface contains zero methods, such types of interface is known as the empty interface. So, all the types implement the empty interface. A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
// Interface with value assigned
var i interface{} = "hello" // create an interface type of String with "hello" assigned
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64) // ok is false as i is NOT of type float
fmt.Println(f, ok)
// Interface implementation
type aInterface interface {
aMethod()(returnTypeA)
...
nMethod(return type)
}
type aStruct struct {
... // variables
}
// aStruct implements aInterface
func (parameterName aStruct) aMethod() (returnTypeA) {
... // implementation
}
// A simple example to check interface is implemented or not
type iCheckMe interface {
sayhey()
}
type bohoo struct{}
func (b bohoo) sayhey() {
Println("Bohoo")
}
// Function takes any inteface
func myfun(a interface{}) {
_, ok := a.(iCheckMe)
if !ok {
fmt.Println("Paniced")
} else {
// fmt.Println("Value: ", val)
}
}
// Type switch
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
var val interface{} = bohoo{}
myfun(val) // Checking if the passed interface implements
do(val)
}
Go encapsulates things at the package level. Names that start with a lowercase letter are only visible within that package. You can hide anything in a private package and just expose specific types, interfaces, and factory functions.
package foo
type foo struct {}
func (f foo) doSomething() {
fmt.Println("Foo1() here")
}
func NewFoo() Fooer {
return &Foo{}
}
package main
f := NewFoo()
f.doSomething()
Go favors composition over inheritance and doesn't have any type hierarchy whatsoever. So it allows you to share implementation details via composition.
Embedding is not inheritance. Code reuse is not provided by type hierarchy but via composition. Instead of providing type hierarchy, Go allows composition and dispatching of the methods via interfaces.
A Flaw; But Go, in a very strange twist (that probably originated from pragmatic concerns), allows anonymous composition via embedding. For all intents and purposes, composition by embedding an anonymous type is equivalent to implementation inheritance. An embedded struct is just as fragile as a base class. You can also embed an interface, which is equivalent to inheriting from an interface in languages like Java or C++. It can even lead to a runtime error that is not discovered at compile time if the embedding type doesn't implement all the interface methods.
type iEmbed interface {
sayHi()
}
type ComposedType struct {
iEmbed // Interface embedded
}
func main() {
s := ComposedType{} // Will result error in runtime
}
Go interfaces provide the ability to treat objects of different types uniformly as long as they adhere to the same interface in a very direct and intuitive way. Due to lack of sub-classing, polymorphism in Go is achieved only with interfaces. Methods are dispatched during runtime depending on the concrete type.
// Overriding example
type Person struct {
Name string
Age int
}
// Overrides fmt print string function.
// A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z) // Output: Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
}
Resources:
Go achieves concurrency with goroutine
, a concept similar to Thread in Java.
goroutines are created via go
keyword.
goroutines run in the same memory address space.
channel
is used for the communication between goroutines. channel acts like a pipeline with some capacity to hold the data. The default buffer size is one, and can be determined while creating new channel.
Don't use shared data to communicate, use communication to share data.
Concurrency principle.
Sends and receives to a channel are blocking by default. When a data is sent to a channel, the control is blocked in the send statement until some other goroutine reads from that channel. Similarly when data is read from a channel, the read is blocked until some goroutine writes data to that channel. This property of channels is what helps goroutines communicate effectively without the use of explicit locks or conditional variables that are quite common in other programming languages.
The select
statement lets a goroutine wait on multiple communication operations. select
is used to listen to many channels. select is blocking by default and it continues to execute only when one of channels has data to send or receive. If several channels are ready to use at the same time, select chooses which to execute randomly. select has a default case as well, just like switch. When all the channels are not ready for use, it executes the default case (it doesn't wait for the channel anymore).
It is very easy to spawn many go routines and create a hierarchy of them. As the numbers grows, it becomes difficult to manage them as sending cancellation signal to the many spawned goroutines manually is difficult. This is where context package comes in. The sole purpose of the context package is to carry out the cancellation signal across goroutines no matter how they were spawned. context plays a huge role in making concurrent programs secure and efficient. A context can be created from another context object and these contexts form a symbiotic relationship, when parent context is killed, all the children dies automatically.
A Context carries a deadline, cancellation signal, and request-scoped values across API boundaries. Its methods are safe for simultaneous use by multiple goroutines.- Golang blog
sync.Mutex enforces mutual exclusion to make sure only one goroutine can access a variable at a time to avoid conflicts with Lock
and Unlock
functions.
ci := make(chan TYPE) // Default channel which can hold 1 message.
ch <- v // Send v to channel ch.
v := <-ch // Remove/extract the value from the from channel ch
// Error while trying to send two messages in the channel with buffer size one will throw error.
ch <- v
ch <- v
c := make(chan int, 2) // Channel with a capacity of two. Above error will NOT occur now.
close(c) // Close the channel
// Iterate through the data in channel
for out := range c {...}
select {
case <- case1: ...
case <- caseN: ...
default ...
}
// Context examples
const shortDuration = 3 * time.Second
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithDeadline(context.Background(), d)
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel() // Release the resources associated with context
select {
case <-time.After(1 * time.Second): // set this to more than shortDuration
fmt.Println("Who overslept!!!")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
// sync.Mutex Example
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
func (c *SafeCounter) IncA(key string) {
c.v[key]++
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mux.Unlock()
c.v[key]++
//c.mux.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
go c.IncA("somekey")
for i := 0; i < 100; i++ {
go c.Inc("somekey")
}
go c.IncA("somekey")
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
// Compiles the package sources and tests found in the current directory and then runs the resulting test binary.
// Without any arguments, also called local directory mode. This mode disables caching.
go test
// When go tests is invoked with arguments it is called package list mode and caches the results.
// Test all packages in a directory tree
go test ./...
// Run all tests in the current directory
go test .
// Run tests for a package
go test [PACKAGE]
// Run tests verbose (-v) and stop at the first test that fails (-failfast)
go test -v -failfast
// Run test of a package passing the package file
go test FILE.go FILE_test.go
// Run a specific test for a package
go test -v [PACKAGE] -run [TestName]
// View code coverage
go test -cover -v
// Create a coverage profile to a file
go test -coverprofile=coverage.out
// View the generated coverage report coverage.out in browser
// This will open in a browser automatically
go tool cover -html=coverage.out
// Skip long running tests (-short)
go test -short -v
//Test function must have the condition implemented
if testing.Short() {
t.Skip("Skipping long-running test.")
}
// Executes the files with a tag `short_test`
go test -v -tags=sort_test
go test has two running modes. Understanding them is essential to having an easy time working with the tool: Local directory mode, or running without arguments Package list mode, or running with arguments
In the local directory mode, go test compiles the package sources and tests found in the current directory and then runs the resulting test binary. This mode disables caching. After the package test finishes, go test prints a summary line showing the test status (‘ok’ or ‘FAIL’), the package name, and elapsed time.
To run your tests in this mode, run go test
in your project's root directory.
In the package list mode, go test compiles and tests each of the packages listed as arguments to the command. If a package test passes, go test prints only the final ‘ok’ summary line. If a package test fails, go test prints the full test output.
To run your test in this mode, run go test with explicit package arguments
. For example, we can run go test PACKAGE_NAME to test a specific package or go test ./... to test all packages in a directory tree or go test . to run all tests in the current directory.
In our daily work with go test, the difference between the two modes is caching.
When we run go test in package list mode it will cache successful package test results to avoid unnecessary reruns. When it can find the result of a test in the cache, go test will redisplay the cached result instead of running the tests again. When this happens, go test will annotate the test results with (cached) in place of the elapsed time in the summary line.
-short in other ways besides skipping a test. For example, mocking networks calls instead of opening a connection or loading simple fixtures instead of loading them from a database. The options are many, it all depends on the function that the test is covering.
go test supports tags that are used to run (or skip) a special type of test files from our test suite. There are a couple of rules on build tags.
-timeout 120s
List tests with -list: to list tests, benchmarks, or examples matching a regular expression passed as the argument. The usage of the -list flags is analogous to the -run flag that we discussed before. When we use this flag go test will not run any tests, benchmarks or examples.
Disabling caching with -count
: as we discussed before, go test by default caches test results for packages. It then uses them to skip running tests that have are not modified between two runs. Although can improve the performance, there are times when we would like to disable the caching using the -count=1 flag.
Get JSON output using -json
: if we would like to take the output of go test and process it using a program, having it in a JSON format is better than normal text. This flag will convert the output to JSON, so processing it with a program is less cumbersome.
Use more CPU cores using -cpu
: this flag will set the GOMAXPROCS variable to the argument that we pass to the flag. It limits the number of operating system threads that can execute user-level Go code simultaneously.
Detect race conditions using -race
: since it's very easy to write concurrent code in Go, race conditions are always a risk. Go's tooling is great in this regard - it has a fully integrated race conditions detector in its toolchain.
https://blog.golang.org/race-detector
t.Errorf() // Test executes after failure
t.Fatalf() // Other test execution halts
Resources:
- https://golang.org/pkg/testing
- https://golang.org/src/testing/testing.go
- https://blog.golang.org/cover
- https://blog.golang.org/examples
- https://github.com/golang/go/wiki/TableDrivenTests
- https://github.com/stretchr/testify, http://labix.org/gocheck, https://stackoverflow.com/questions/23729790/how-can-i-do-test-setup-using-the-testing-package-in-go, https://codesamplez.com/development/golang-unit-testing
[ ] Error Handling
They will execute in reverse order when the program executes to the end of functions. In the case where the program opens some resource files, these files would have to be closed before the function can return with errors. There can be more than one defer in a function.
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// Output: 4 3 2 1 0
Panic is a built-in function to break the normal flow of programs and get into panic status. When a function F calls panic, F will not continue executing but its defer functions will continue to execute. Then F goes back to the break point which caused the panic status. The program will not terminate until all of these functions return with panic to the first level of that goroutine. panic can be produced by calling panic in the program, and some errors also cause panic like array access out of bounds errors.
Recover is a built-in function to recover goroutines from panic status. Calling recover in defer functions is useful because normal functions will not be executed when the program is in the panic status. It catches panic values if the program is in the panic status, and it gets nil if the program is not in panic status.
// panice/recover usage
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() // if f causes panic, it will recover
return
}
Use library or collections that are already thread safe like *sync.Map. Check if the documentation states it is thread safe.
Thread-safety is not just about writes. We have to take care of our reads as well.
https://github.com/preslavmihaylov/tutorials/tree/uber-meetups-thread-safety
Use mutex for basics, for security purpose use channels.
Resource
https://golang.org/ref/spec
https://github.com/golang/go/wiki/CodeReviewComments
https://tour.golang.org
https://golang.org/doc/effective_go.html
https://github.com/golang/go/wiki
https://golang.org/doc
https://research.swtch.com/gotour
https://github.com/go101/go101
https://trello.com/c/pLRRhHXf/273-%F0%9F%93%9D-must-know-for-go-dev
https://github.com/codeclimate/go-learn-go
https://cloud.ibm.com/docs/go
https://codeburst.io/why-goroutines-are-not-lightweight-threads-7c460c1f155f
https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go
https://opensource.com/article/18/11/learning-golang
https://opensource.com/article/19/8/command-line-heroes-cobol-golang
https://medium.com/swlh/type-assertion-and-type-conversion-in-golang-11fd98f69a3f
https://yourbasic.org/golang
https://harryho.github.io/coding/golang
https://medium.com/swlh/processing-16gb-file-in-seconds-go-lang-3982c235dfa2
https://stackoverflow.com/questions/15311969/checking-the-equality-of-two-slices
https://golangbyexample.com
https://golangbot.com/learn-golang-series
http://go-lang.cat-v.org/go-code Interesting code
https://dave.cheney.net/resources-for-new-go-programmers Resources collection
https://medium.com/@lizrice/variables-and-functions-in-go-oh-my-18b71297657
https://levelup.gitconnected.com/go-slices-maps-pitfalls-and-generics-f7ced963ced
https://test.gleich.dev/article/golangs-omitempty
[ ] https://medium.com/learning-the-go-programming-language/bit-hacking-with-go-e0acee258827
[ ] https://medium.com/swlh/go-profile-your-code-like-a-master-1505be38fdba
[ ] https://medium.com/better-programming/3-pitfalls-in-golang-i-wish-i-knew-3f208a8402a7