//
//
, eg. // This my comment
not //this is my comment
}
, but not with consecutive onescomma [,]
, if there is more than 2 parameters or arguments in function calling or in function definition or contructor injection or in property assigning. (JavaScript can have some exceptions, will be added in future.)if
statements returns and there is a single line in it then no newline. However, if the if
statement block contains more than one line, there should be a new line.defer
in one function as defer works as stack
and it becomes complicated in future code maintain. If use it use it, on top or bottom.Function should be small, follow these principles DRY principle and SOLD principle wiki SOLID.
path.Clean
does path cleaning and normalizing what it does.//
on top of the method with no newline in between method and the docsField()
not GetField()
SetField(..)
not Field(...)
New[StructName]
broken-fix-code
).
CamelCase
.lowerCamelCase
.Test...
Is...
, Has...
, Can..
, Should..
, (lower priority at the end Does..
) methods or variables should always returns a bool.
Is...
is highly recommend unless it doesn't go with the grammar (or may sound odd with Is...
then other versions are recommended line Has...
or Can...
).es
,s
or something like plural that represents multiple (array
, list
, vector
, slice
, enumerable items
, iterable
) things.
employeePerson
or something similar to that.OsGroup
, as OsGroup
can be represented as a single item, like UnixGroup(Mac, Linux,..), WindowsGroup, in this case, the group is name is appropriate.
OsGroups
, it should represent an array of OsGroup
itemsarray
, arraylist
, linked list
, list
, vector
, slice
, enumerable items
, iterable
) things should NOT have singular symbol like result
, person
, employee
but plural like results
, people
, OsGroups
resultsMap
as the naming suggest what we are dealing with in the future.HelloWorld
, NOT (helloWorld
, HELLOWORLD
, HELLO_WORLD
, hello_world
, helloWorld
)taskrunner
we should not have methods taskrunner.taskrunner.DoSomething()
. Go naming should be taskrunner.DoSomething()
Lazy means doesn't execute until needed. Don't be hungry. Lazy codes acts as caching. Basic principal in caching (Generate once and serve same data to many.)
Lazy should be applied on fields (any of the cases):
Lazy should NOT be applied:
Examples of usecase
FROM
type Group struct {
GroupID string
GroupName string
Members *UsersCollection // make member lazy
}
TO
type Group struct {
GroupID string
GroupName string
// make Members export method, just like getter
members *UsersCollection // make member non-export
// use lock if working with collection type or slice
sync.Mutex
}
// must use the method rather than field
func (g *Group) IsMembersEmpty() int {
return g.MembersLength() == 0
}
// must use the method rather than field
func (g *Group) MembersLength() int {
return g.Members().Length()
}
func (g *Group) Members() *UsersCollection {
if g.members == nil { // flag or nil check
// generate
members := "generator method or run something complex to return same data in future."
// assign back to the members field
g.members = &members
}
// return the non-export field.
return g.members
}
// Lock method for async usecase
func (g *Group) MembersLock() *UsersCollection {
g.Lock()
defer g.Unlock()
return g.Members()
}
Regex is complex and usages backtrack programming to find matches, it is extremly expensive. It should be delt with care and caution. Regex should be the last resort of programming to detect some pattern in string.
var
for golang in the constants sections.It is confusing to use which newline from constant should be a priority and what to use. As os, has specific new line standards (Windows - "\r\n"
and Unix "\n"
)
In 90% of the cases, NewLineUnix (constants.NewLineUnix
) should be used. The only case, where constants.NewLine
should be used where we are asking the user for NewLine preference based on Operating System. An example from IDE can help understand this
If a file has \r\n
and if we read using \n
, problems can remain depending on the requirements. However, if we are parsing parse only lines without whitespace then it can be fixed at some level.
Choosing a newline preference always depends on the situation. In most cases, we should use \n
constants.NewLineUnix
It is still debatable and can differ.
private
func nonExport
public
func Export
x _ don't use
the name should be meaningful.
What makes a good function:
1. Function name should be self-explanatory.
- Function Name Short (argument) : OpenMail
- Function Name Full (argument) : OpenSmtpProtocolMail
- Function Combined (argument) : ReadWriteFile
- Single Responsibility (SOLID)
- ReadFile
- WriteFile
2. Function should not be more than 15 lines of code. Worst case can have 25
3. Function should not have more 3 parameters
- Interface (Animal (eat, walk, run)-> Dog, Person) - I don't care how you do it, I should have the ability to do it.
- Struct (Person -> can have attributes of Animal)
4. Struct or class or file should not be more than 120 lines
5. Returns one item pass (good)
- Good: Array, Map -> Interface, Struct
-> Array, map def could change.
- ...
Why we are writing more code?
funcA(a1Struct)
funProcess() {
v = funcA(a1Struct)
v...
}
funProcess2() {
funcA(a1,.....a6)
}
funProcess3() {
funcA(a1,.....a6, a)
}
funProcessN() {
funcA(a1,.....a6)
}
Problem
- Tedious Change in the future.
- Chances of breaking.
- Time-consuming
- No guarantee to run successfully in future
Useful:
- Saves time
- Makes code more compact
- Success chances increase
func Something() int {
return constants.One // alright
}
func Something() int {
doSomething()
return constants.One // NOT Alright
}
func Something() int {
doSomething()
return constants.One // Alright
}
func Something() int {
doSomething()
if isNumber {
doSomethingNew()
return constants.One // NOT alright
}
}
func Something() int {
doSomething()
if isNumber {
doSomethingNew()
return constants.One // alright
}
return constants.MinusOne // alright
}
func Something() int {
if isApplicable1 {
if isApplicable2 {
return constansts.One // alright
} // alright no new line recommended
// there should be no newline
} // NOT alright no new line recommended
}
func Something() int {
if isApplicable1 {
if isApplicable2 {
return constansts.One // alright
} // alright no new line recommended
} // alright no new line recommended
}
func Something() int {
// <- NOT alright, however can put comment at the first line, which is fine.
doSomething()
// <- NOT alright double newline
// alright here because of the return statement
return constants.One
}
Code nesting or branching makes the code complex (even if it is simple), consequently, we should always avoid code branching or nesting at our best. It helps to write simple unit tests and improves code maintainability in future.
Issue occur: Multiple levels of code nesting or branching using if
s, it can also be for
, if
, while
or other blocks nesting.
Exception case, it is quite alright to have one loop and then a if
statement branch. But not more than that. Moreover, to be specific this exception case doesn't apply on multiple if
statements.
func (wiki *WikiTimezone) Longitude() string {
if len(wiki.LatLong) > 1{
if len(wiki.LatLong) > 11 {
return string(wiki.LatLong[7:])
} else {
return string(wiki.LatLong[5:])
}
}
return ""
}
To resolve this issue we can use, we can use any of the below methods
// remember to keep `is` as your method prefix or can or has or should. `is` prefered as first option.
func isLatLongLengthGraterThanEleven(wiki *WikiTimezone) bool {
return len(wiki.LatLong) > 11
}
func (wiki *WikiTimezone) Longitude() string {
if isLatLongLengthGraterThanEleven(wiki) {
return string(wiki.LatLong[7:])
} else if len(wiki.LatLong) > 1 {
return string(wiki.LatLong[5:])
}
return ""
}
// remember to keep `is` as your method prefix or can or has or should. `is` prefered as first option.
// Since this empty reprsent not taking less than 4 length we should keep the name as per the logic, however, for the example case we are keeping it as is.
func isEmpty(wiki *WikiTimezone) bool {
return len(wiki.LatLong) <= 4
}
func (wiki *WikiTimezone) Longitude() string {
// negative logic comes first
if isEmpty(wiki.LatLong) {
return ""
}
if len(wiki.LatLong) > 11 {
return string(wiki.LatLong[7:])
}
return string(wiki.LatLong[5:])
}
Notes: We haven't used the else part the in the last return because it is redudant. We should always try to avoid else
if it is not necessary.
Golang doesn't have any ternary operators so assume that it has and the process may look like this below:
// remember to keep `is` as your method prefix or can or has or should. `is` prefered as first option.
func isEmpty(wiki *WikiTimezone) bool {
return len(wiki.LatLong) <= 0
}
func getLatLongSplitAtFive(wiki *WikiTimezone) string {
return len(wiki.LatLong) > 5 ? string(wiki.LatLong[5:]) : ""
}
func (wiki *WikiTimezone) Longitude() string {
// negative logic comes first
isEmptyLatLong := isEmpty(wiki.LatLong)
length := len(wiki.LatLong)
isApplicableForSplitAtEleven := length > 11
// final result can be returned with ternary operator but golang doesn't have tarnary, however, assume we have it and then we can do it.
splitAtFiveOrEmpty := isEmptyLatLong ? "" :
applicableResult := isApplicableForSplitAtEleven ?
string(wiki.LatLong[7:]) : getLatLongSplitAtFive(wiki)
/**
* Warning: don't write nested condition inside ternary operator ever, that is another red.
* this code is very simple in terms of unit testing
* Unit testing becomes complicated once code branching or nesting introduced.
* However, as go doesn't have ternary operator we cannot use it.
* Which is good thing, ternary operator sometimes gets abused and it is easy to read if-else than ternary operators.
* Summary: For golang we should use `Resolve nesting using inverse logic`, plus, we can use the method combination as well.
**/
return applicableResult
}
else
if not necessary
func getValue(val []string, index int) string {
if val[index] != "" {
return val[index]
} else {
return "-"
}
}
func getValue(val []string, index int) string {
if val[index] != "" {
return val[index]
}
// <-- must have a newline
return "-" // no need to have else part.
}
nil
.
// returns risky pointer which may break in real world.
fun anyBytesRickyPtr() *[]Byte {
anyBytes = getBytes(...) // []Byte
return &anyBytes // send pointer of []Byte is dangerous.
}
// solution to pointer return
fun anyBytesSafePtr() *[]Byte {
anyBytes = getBytes(...) // []Byte
if anyBytes == nil {
return nil
}
return &anyBytes // send pointer of []Byte
}
anyBytes = getBytes(...) // []Byte
// anyBytes == nil can be nil as well
// len(anyBytes) == 0 when anyBytes == []
if anyBytes == nil || len(anyBytes) == 0 {
return ... exitResult
}
// do stufss with anyBytes
anyPtr = getPointerBytes(...) // *[]Byte
if anyPtr == nil { // pointer nil
// should not access any ptr *anyPtr before checking not nil
return ... exitResult
}
// if it is array based ptr then
// *anyPtr == nil can be nil as well
// len(*anyPtr) == 0 when *anyPtr == []
if *anyPtr == nil || len(*anyPtr) == 0 {
return ... exitResult
}
// do stufss with anyPtr
func CloseProcessByName(processName string) ProcessClosedStatus {
// serious issue, code red
output, err := exec.Command(
constants.Wmic,
constants.Process,
constants.Get,
constants.ProcessIdAndCaption,
).Output() // ).Output() will fail at anytime.
func CloseProcessByName(processName string) ProcessClosedStatus {
cmd, err := exec.Command(
constants.Wmic,
constants.Process,
constants.Get,
constants.ProcessIdAndCaption,
)
// wrong, must check err before cmd.
if cmd != nil ...
func CloseProcessByName(processName string) ProcessClosedStatus {
cmd, err := exec.Command(
constants.Wmic,
constants.Process,
constants.Get,
constants.ProcessIdAndCaption,
)
// always first check err not nil or nil
// !Important: If an error is returned from a function,
// one must handle it gracefully
// reference https://hackmd.io/DErHiap-RWSXwIqFkxWwCg
if err != nil || cmd == nil {
// return by capturing error in struct.
return ...exit condition result.
}
// work with cmd here
// ...
output := cmd.Output()
...
, Or
Note: Handle error with panic, not appropriate here in terms of naming.
func CloseProcessByName(processName string) ProcessClosedStatus {
cmd, err := exec.Command(
constants.Wmic,
constants.Process,
constants.Get,
constants.ProcessIdAndCaption,
)
// always first check err not nil or nil
// !Important: If an error is returned from a function,
// one must handle it gracefully
// reference https://hackmd.io/DErHiap-RWSXwIqFkxWwCg
if err != nil {
handleError(err) // or return with single struct
}
if cmd == nil {
return ...exit condition result.
}
// work with cmd here
// ...
output := cmd.Output()
...
Code mutation, refers to the variable modification, which violates the code review rules (as no variable or things should be changed and should be assigned only once). At the wrost case senario it can be only applied for caching a variable or lazy instanciation / evaluation where an execution requires a for-loop or many checking which will be same after the first time checking then there is no point of running the same thing more than once but cache it using a variable. In that case we can mutate a variable. Keep in mind mutating a variable requires mutation lock, or else it will fail in asyc / gorountine environment. Plus, also keep in mind that adding a lock is also expensive. One can solve it with two ways (a. Lock version of the method b. non lock version of the method). So for example, if a method does GetLines (example can be obtained from strhelper) which is the non lock versin, another version could be GetLinesLock() ->
func (r *receiver) GetLinesLock() []string {
r.Lock() // which is mutex lock from inside the struct
defer r.Unlock()
return r.GetLines()
}
Returning variable, updated multiple times, can be fixed.
// code with mutating issue
returningVal := New(...params)
returningVal.Set...(...) // or
returningVal.VarName = ...
return returningVal
varName := whatEverValue
// code constructor took the VarName
returningVal := New(varName, ...params)
return returningVal
varName := whatEverValue
return ReturningValStruct{
varName: varName // if in the same package
...
}
Note: In Go Strings passes by Reference, but the struct gets copied which has length and bytes to pointer.
// String struct which gets copied but not the data.
type StringHeader struct {
Data uintptr
Len int
}
Slice Struct
type SliceHeader struct {
Data uintptr
Len int
Cap int
}