fbpx

Golang(Go)

Golang is an open source functional programming language backed by Google. The short name is Go. Is a compiled language with excellent performance without compiler optimisations like C++.

Things to avoid:

Case 1:
Go does not accept interfaces:
State & Behaviour
1. State is equivalent data structures
2 . Behaviour is provided by methods

Interfaces
1. permits extensibility
2. defined by methods
3. adherence is only satisfied by behaviour not by state/data structures

Thee following code works very well:
func( page *Page) saveSourceAs(path string{    
b := new(bytes.Buffer)    
b.Write(page.Source.Content)  
  
page.saveSource( b.Bytes(), path)
}
func (page *Page) saveSource( by []byte, inpath string){    
writeToDisk(inpath, bytes.NewReader(by))
}

But we are not passing interfaces and we are creating a NewReader. This can be solved by refactoring the code as follows:

func( page *Page) saveSourceAs(path string{    
b := new(bytes.Buffer)    
b.Write(page.Source.Content)    
page.saveSource(
b, path)
}
func (page *Page) saveSource( 
b io.Reader, inpath string){    
writeToDisk(inpath, 
b)
}


Case 2:
Use Io.Reader & Io.Writer. there is a lot of functionality build in these interfaces.
A very powerful function that allows to define a specific output for a command.
func (c *Command) SetOuput(i io.Writer){
c.Output = o
}

Case 3:
Interfaces are composable.
1. Functions should only accept interfaces that require the methods they need.
2. Functions should not accept a broad interface when a narrow one would do the work.
3. Compose broad interfaces made from narrower ones.

Composing Interfaces
type File interface{
io.Closer
io.Reader
io.ReaderAt
io.Seeker
io.Writer
io.WriterAt
}


Requiring broad interfaces (we might not want to use the broad interface)
func ReadIn(f File){
b := []byte{}
n, err := f.Read(b)
...
}


Requiring narrow interfaces
func ReadIn(r Reader){
b := []byte{}
n, err := r.Read(b)
...
}

Case 4:
Methods vs Functions
Too Many Methods
1. a lot of people from OO backgrounds overuse methods
2. natural draw to define everything via structs and methods

What is a Function?
1. operations performed in N1 inputs that results in N2 outputs
2. the same inputs will always result in the same outputs
3. functions should not depend on state

What is a Method?
1, defines the behaviour of a type
2. a function that operates against a value
3. should use state
4. logically connected

Functions can be used with interfaces
1. Methods, by definition, are bound to a specific type
2. functions can accept interfaces as input

the following function could be a method
func extractShortcodes( s string, p *Page, t template){
...
}

Case 5:
Pointers vs Values
1. it is not a question of performance(generally), but one of shared access
2. if you want to share the value with a function or method, then use a pointer
3. if you do not want to share it, then use a value (copy)

func (page *Page)
saveSource(
b io.Reader)

func (page Page)
saveSource(
b io.Reader)

Pointer Receivers
1. if you want to share a value with it's method, use a pointer receiver
2. since methods commonly manage state, this is the common usage
3. not safe for concurrent access

Value Receivers
1. If you want the value copied(not shared), use values
2. if the type is an empty struct(stateless, just behaviour)... then just use value
3. safe for concurrent access

We want pointer receivers because it could be universally modified state and could be used elsewhere
type InMemoryFile struct {
at int64
name string
data []byte
closed bool
}

func (f *InMemoryFile) Close() error{
atomicStoreInt64(&f.at, 0)
f.closed = true
return nil
}

Time is ticking therefore we use methods on values:
type Time struct{
sec int64
nsec uintptr
loc *Location
}

func (t Time) IsZero() bool{
return t.sec == 0 && t.nsec == 0
}

Case 6:
Thinking of Errors as Strings
Error is in interface:
type error interface{
Error() string
}
Standard Errors
1. errors.New("error here") is usually sufficient
2. exported Error Variables can be easily checked

Standard Error
func NewPage(name string) (p *Page, err Error){
if len(name) == 0{
return nil,
errors.New("Zero length page name")
}

It is better to export the error value it so we can compare values

var ErrNoName = errors.New("Zero length page name")
func NewPage(name string) (*Page, error){
if len(name) == 0{
return nil,
ErrNoName
}


Custom Errors
1. can provide context to guarantee consistent feedback
2. provide a type which can be different from the error value
3. can provide dynamic values (based on internal error state)

Example from Docker Internationalisation of Errors
type Error struct{
Code ErrorCode
Message string

Detail interface{}
}
//Error returns a human readable representation of the error.
func (e Error) Error() string{
return fmt.Sprintf("%s: %s",
strings.ToLower(string.Replace(e.Code.String(), "_", "",
-1)), e.Message)
}

Case 7:
You can't make everybody happy
Consider Concurrency
1. if you provide a library someone will use it concurrently
2. data structures are not safe for concurrent access
3. values aren't safe, you need to create safe behaviour around them

Making it safe
1. Sync package provides behaviour to make a value safe(Atomic/Mutex)
2. Channels coordinate values across go routines by permitting one go routine to access at a time

Maps are not safe
func (m *MMFS) Create(name string) (File, error){
m.getData()[name] = MemFileCreate(name)
m.registerDirs(m.getData()[name])
return m.getData()[name], nil
}

It is not safe when it is placed in a goroutine. This can be resolved by lock/unlock:
func (m *MMFS) Create(name string) (File, error){
m.lock()
m.getData()[name] = MemFileCreate(name)
m.unlock()
m.registerDirs(m.getData()[name])
return m.getData()[name], nil
}


Keeping it unsafe:
1. safety comes at a cost
2. imposes behaviours on consumer
3. proper API allows consumers to add safety as needed
4. consumer can use channels or mutexes

Maps are unsafe by design:
1. often safety is unnecessary
2. enables consumers to implement safety as needed
3. enables consumers to implement safety as desired

Case 8:
Field Tags

  • 19000-19999 are reserved for internal use.
  • Can range from 1- 2^29 -1
  • Smaller values are more efficient

Case 9:
Protocol Buffers installation:

Linux:
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

Mac:
brew install protoc-gen-go

To generate the golang protocol buffers output use the flag --go_out