Go follows a convention where source files are all lower case with underscore separating multiple words.
Compound file names are separated with _
File names that begin with “.” or “_” are ignored by the go tool
Files with the suffix _test.go are only compiled and run by the go test tool.
Functions
The case defines if it will be public or private for the package.
Private: func writeToDb(){}
Public: func WriteToDb(){}
The same for struct properties.
Constants
Constant should use all capital letters and use underscore _ to separate words.
Ex: const CONNECTION_URL := "...."
Variables
Generally, use a relatively simple (short) name.
Consistent naming style should be used the entire source code
user to u
userID to uid
If variable type is bool, its name should start with Has, Is, Can or Allow, etc.
A single letter represents index: i, j, k
Variables
// DECLARATIONS
var name string
name = "Thor"
name := "Thor"
// ARRAYS
var numbers [3]int
numbers[0] = 1
// SLICES
numbers := []int{}
numbers = append(numbers, 2)
IF
name := "thor"
if name == "thor" {
} else if name == "odin" {
} else {
}
FOR
names := []string{
"Thor", "Odin", "Loki",
}
for i := 0; i < len(names); i++ {
fmt.Println(i, ":", names[i])
}
for i, v := range names {
fmt.Println(i, ":", v)
}
SWITCH
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
// FUNCTION AS PARAMETER
type Fn func()
func onError(handler Fn) {
handler()
}
Folders
package main
import (
"log"
"os"
)
func main() {
// Create a folder/directory at a full qualified path
err := os.Mkdir("/Users/temp", 0755)
if err != nil {
log.Fatal(err)
}
// Create a folder recursively
err := os.MkdirAll(dirPath, 0755)
if err != nil {
log.Fatal(err)
}
// Get current dir
currentDirPath, err := os.Getwd()
}
// CODE SAMPLE
package main
import "fmt"
func main() {
user := User{Enabled: true}
fmt.Println(user)
disableUser(user)
fmt.Println(user)
}
type User struct {
Enabled bool
}
func disableUser(user User) {
user.Enabled = false
}
// OUTPUT
{true}
{true}
/*
This will not change the user because the function
disableUser is recieving a user as a value, not
a pointer to the same user in memory.
To change the original object, we need do two things:
1. Change the function to receive a pointer.
2. Call the function sending the pointer.
And here is the magic words:
- Use * to receive a pointer
- Use & to send a pointer
*/
func main() {
user := User{Enabled: true}
fmt.Println(user)
disableUser(&user) // <=== HERE
fmt.Println(user)
}
type User struct {
Enabled bool
}
func disableUser(user *User /* <=== HERE*/) {
user.Enabled = false
}
// OUTPUT NOW
{true}
{false}
Interfaces
func main() {
user := User{Status: ACTIVE}
fmt.Println(user)
setStatus(&user, INACTIVE)
fmt.Println(user)
}
type Status int
const (
ACTIVE Status = iota
INACTIVE
DELETED
)
/*
An interface define all methods an struct needs to
implement to be compliance with the definition.
*/
type WithStatus interface {
SetStatus(status Status)
}
func setStatus(entity WithStatus, status Status) {
entity.SetStatus(status)
}
/*
If we want to pass a user instance to the setStatus func,
the User struct must implement all the methods the
WithStatus interface require.
*/
type User struct {
Status Status
}
func (user *User) SetStatus(status Status) {
user.Status = status
}
Enum
type Status int
const (
ACTIVE Status = iota
INACTIVE
DELETED
)
WaitGroup
/*
When the application runs multiple functions
in background, if you want to deal with the
concurrence and know when is finish, use the
WaitGroup from sync package.
*/
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup // create the group
func main() {
wg.Add(1) // indicating a new work will be add
go say("Hi")
wg.Wait() // wait until all the work done
}
func say(text string) {
fmt.Printf(text)
wg.Done() // mark a work for done
}
Defer
/*
When we want to run something after the function finishes
(even for an error, for example), we can use defer.
*/
package main
import (
"fmt"
)
func main() {
defer finish()
fmt.Println("Enf of main")
}
func finish() {
fmt.Println("We are done!")
}
/*
=== OUTPUT ===
Enf of main
We are done!
*/
Panic and Recover
/*
When something goes wrong and a panic is raise,
has a function that you can call and prevent the
software to stop work.
Its called recover.
*/
package main
import (
"fmt"
)
func main() {
defer finish()
defer cleanup()
fmt.Println("Enf of main")
panic("fuuuuuuck") // <=== it should kill the program, right?
}
func cleanup() {
if r := recover(); r != nil { // <=== but here we are recovering =)
fmt.Println("recover: ", r)
}
}
func finish() {
fmt.Println("We are done!")
}
Channels and Concurrency
Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine.
Youtube:
package main
import (
"fmt"
)
func main() {
channel := make(chan int)
go sum(channel, 5, 8)
result := <-channel
close(channel) // close the channel after no more work needed
fmt.Println(result)
}
func sum(c chan int, a int, b int) {
c <- a + b
}
Unit Test
// my_file_test.go
package my_package
import "testing"
func TestCrawler(t *testing.T) {
result := my_func()
if result == nil {
t.Error("Ooops! This is the error message") // to indicate test failed
}
}