Writing Go Code like a Pro!
Code quality always matters. It's like having a good handwriting only, its for code. Your piece of code should be easy to locate, readable and well documented. Variables, functions, file names should be pretty self explanatory and long source code files should be generally. No one wants to rummage through lines of cryptic code trying to decode what it's doing there.
Remember, you spend only 20% of your time coding. The other 80% goes in reading code written by someone else. So write your code in such a way so that someone else can easily read, understand, refactor and maintain it in the future
Go by design is simple to read, so things become a bit easier. However here are certain things to keep in mind when writing applications for production which will make you look like a pro Go developer.
Naming Conventions
Variables:
- Use Camel case for naming your variables
var myVariable = "" // DO
var my_variable = "" // DON'T
Note: If a name (a variable or a function) is in title case (eg ThisIsTitleCase) then its said to be an exported variable or function. This means that it can be accessed from outside the package as well.
- Use short but descriptive names for variables
Try to use short names for variables. Name the variable according to its scope, the farther away it is used from its declaration, the longer its name.
var req Request
- Use single letters for indexes
for i:=0;i<100:i++{} // DO
for index:=0;index<100:index++{} // DON'T
- Use two repeated letters to represent a collection or arrays and use a single letter inside a loop
var ll []Letters
for i, l := range ll {}
Functions
- No Getters and Setters
Unlike Java, there are no getters and setter functions written in Go.
var psvc PersonService
psvc.Person() // DO
psvc.GetPerson() // DON'T
- Don't repeat package names
encrypt.SHA() //DO
encrypt.EncryptSHA() //DON'T
Source Files
- Separate code logically
A source code file name usually will represent what functionality it is dealing with. For eg if the code is about authentication, then "auth.go" is a good file name. Generally, one file should perform a specific function or handle a single type of data. If the file deals with some type (like a struct) then name the file according to the type.
package:
restaurant
- menu.go
- inventory.go
- staff.go
Packages
- Short, Lower-cased, Singular names
The package names are short, concise and lower-cased. Names in snake case or camel cased are not given to packages. Also, package names are singular. So, "services" may not be a good package name. Instead, use the name "service" for your package
- Avoid generic names
When creating a package, avoid using generic names like "utils", "common" or "app" etc. Try to give a meaningful name that describes the package's purpose. The go get command accepts a GitHub path to download a package. Giving a unique name makes it easier for go get to fetch the package.
From Packages to Applications
Packages in Go are synonymous to a library. It is simply a collection of source code files that have a specific purpose .
If a package performs a single functionality but has multiple implementations for it, then a parent package is created with multiple child packages.
For eg if a package's purpose is to encrypt strings which can either be done by SHA or MD5 encryption methods then the package structure will look like
encrypt
- sha
-- sha.go
- md5
-- md5.go
Applications have a number of functionalities which are preformed by using a collection of packages. When writing applications, it's extremely necessary to organise all the packages in a structured manner. Organising of your packages has a huge impact on the testability of your application.
How can Packages Structured in an Application?
- Put all your source code into a single package and you have a Monolith. While this works for smaller applications, but as your code base expands, things start getting out of hand.
- Other way is to put create packages called as Controllers, Handlers and Models which is typically seen in Java projects. The issue here is that you might repeat the package names in the functions (eg controller.EmployeeController). More than that, there is risk of circular dependencies.
- Another way is to group by Modules. For eg if there's a module called account in your application, an entire package is dedicated to the module account. But here too, the risk of circular dependencies remains.
Circular Dependencies occur when package A depends on package B which in turn depends on package A. This causes an issue during compilation, as the compiler is stuck in a loop trying to resolve the dependencies. In any case, its always best to avoid such a situation
The Domain and Service Organisation
This approach is defined in details in this blog and we'll be using the same structure for further development. In short:
Packages should organised based on two things: the Domain types and the Services or interactions between the domain types. Domain types can be seen as entities that the application encompasses. For example if you own a restaurant, the domain type might include the staff, inventory, customers etc.
These entities, defined by types (typically structs), along with their interfaces are placed in the root folder. Typically, there should be no external dependencies in the root folder.
The dependencies are then placed in sub packages. So for example if the application uses MySQL database, then a sub package called MySQL will contain all the source code that depends on MySQL. Similar if you're creating REST based services, then a sub package will hold all the HTTP based code.
So in case if you choose to swap MySQL with MongoDB or REST with GraphQL, its as easy as removing a sub package.
Finally, the main package then ties up all the dependencies together. By convention, the main files are kept under the cmd/ folder. This is in case the application produces more than one binaries. Like an application may have a web interface or a CLI interface. An example project structure might look like this:
Common Folders found in Go Source Code
- /cmd: Main files that will compile to a single binary.
- /pkg: Source code that can be used publicly.
- /internal: Private code that is used by the packages but not exported
- /build: Scripts and configurations for packaging, containerisation, CI etc
- /examples: Example code snippets. Usually for documentation.
- /docs: User documentation, godoc generated files
- /api: Swagger specs, protobuf definitions, schema definitions
- /vendor: Dependencies required to compile the application.
- /configs: Configurations files
It's not advisable to have a folder called /src in the project structure.
The way the application source code is organised and written shows the amount of thought put in designing the overall structure of the application. If you want to avoid frequent refactors in future, then designing applications with scalability in mind is of utmost importance!
If you want to read up more on coding guidelines in Go, here are a few links :
Reference Links
Ben Johnson: Standard Package Layout
Go Best Practices: GopherCon Russia 2018