anitsh / til

Today I Learn (til) - Github `Issues` used as daily learning management system for taking notes and storing resource links.
https://anitshrestha.com.np
MIT License
77 stars 11 forks source link

Golang Basics #14

Open anitsh opened 4 years ago

anitsh commented 4 years ago

Resource

anitsh commented 4 years ago

Features

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

25 Keywords

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

Definitions of the keywords

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.

Importing Package

    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

anitsh commented 4 years ago

Data Types

image image

Numerical ( Int, Float, Complex Numbers )

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

Boolean and iota enumerate

isTrue := true

Operations on boolean

string

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:

Pointer

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

Data Structures

Buffer

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:

Array

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

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) []

image

    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:

map

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
}

make vs new

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

Enumerating Constant

// 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

Error

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()
    }
}

Custom Data Structures

anitsh commented 4 years ago

Control Statements

if/else condition

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")
}

for loop

// 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 {
 //...
}

switch condition

// 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.
anitsh commented 4 years ago

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),
        )
    }
}

init function and stages of application flow

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. image

image

https://golang.org/doc/codewalk/functions

anitsh commented 4 years ago

Object-oriented Design

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

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

Method

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

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)
}

Object Oriented Principles

Encapsulation

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()

Composition

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
}

Polymorphism

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:

anitsh commented 4 years ago

Concurrency : goroutine, chan, select, context, sync.Mutex

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"))
}

254 For Deep Detail

anitsh commented 4 years ago

Testing

Command Line

// 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.

Some other useful features:

Code

t.Errorf() // Test executes after failure
t.Fatalf() // Other test execution halts 

Resources:

anitsh commented 4 years ago

defer keyword

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, recover

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
}
anitsh commented 3 years ago

Concurrency

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

Reference

313