This guide covers Kowala programming style from aesthetic issues to conventions and coding standard.
All PRs should be accompanied by unit tests and end-to-end test.
We use :
Testify "A toolkit with common assertions and mocks that plays nicely with the standard library".
Mockery "A mock code autogenerator for golang"
Mocks should go into a subpackage of the interface that it implements.
To generate a Mock for a interface run:
mockery -name AddressVote
And to use the mock:
addressVote := &mocks.AddressVote{}
addressVote.On("Vote").Return("yes")
addressVote.On("Address").Return("home")
We use Go idiomatic Golden Files, to keep our test fixtures up to date.
All files should have a suffix .golden
and update flag for tests should be --update
.
example:
var update = flag.Bool("update", false, "update .golden files")
func TestSomething(t *testing.T) {
actual := functionUnderTest()
golden := filepath.Join("testfiles", tc.Name+".golden")
if *update {
ioutil.WriteFile(golden, actual, 0644)
}
expected, _ := ioutil.ReadFile(golden)
assert.Equal(t, actual, expected)
}
Godog "Cucumber for golang"
Create valid objects at construction time, use several constructors to have variants of the object, but all should be valid.
Valid here means, that further call should be needed to use the object.
Don't do this
email := NewEMail()
email.SetTo("kcoin@kowala.tech")
the developer might not me aware that it has to set de TO before using EMail.
If you require and IP why not:
email := NewMailer("kcoin@kowala.tech")
"Functions should have a small number of arguments. No argument is best, followed by one, two, and three. More than three is very questionable and should be avoided with prejudice." (http://www.informit.com/articles/article.aspx?p=1375308)
Consider using struct of values, optional params or a builder.
Don't do this:
postLetter(firstName string, lastName string, street string, city string, postcode string, flatNumber int)
why not:
postLetter(personName PersonName, address Address)
Write code for humans first, try to express the intent with function and variable names.
From:
currentBlock := val.chain.CurrentBlock()
if currentBlock.Number().Cmp(big.NewInt(0)) == 0 {
return
}
To:
currentBlock := val.chain.CurrentBlock()
if isFirstBlock(currentBlock) {
return
}
func isFirstBlock(block Block) bool {
return block.Number().Cmp(big.NewInt(0)) == 0
}
Go supports named returns, but they are discouraged from standard go style. They should be used on very small functions only. Some are well known problems like shadowing.
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
New concepts should always be expressed in interfaces first.
Style is easiest due to go gofmt. We follow gofmt style so make sure you gofmt your files first.
gofmt -s -w file.go
Line length should be limited to 100, this is not enforced by CI at the moment.
File size should have a soft limit of 300 lines, hard limit being 500 lines, but these should be rare.
As per go standard error messages should start with lower case.
User types should be used when you might expect the caller to do type assertion on type error example.
var ErrNotFound = errors.New("not found")
func findById(id int) (string, error) {
name, err := searchPerson(id)
if err != nil {
return "", err
}
if name = "" {
return "", ErrNotFound
}
return name, nil
}
Commented code will have no meaning to other developers, will go out of date really quickly.
http://www.informit.com/articles/article.aspx?p=1334908
Don't do this:
func logNotEmpty(message String) {
if message != "") {
log(message)
}
// else {
// log("no log message")
// }
}
Prioritize good code over comments, code should be self explanatory.
Consider refactor if you need to explain what the code does in a comment block.
Only exception is package documentation and library usage examples.