The following content shows a general structure of a web app, it can be changed based on the different conventions of web frameworks.
- templates (views) # Template files
- public (static) # Public assets
- css
- fonts
- img
- js
- routes
- models
- pkg
- setting # Global settings of app
- cmd # Files of commands
- conf # Default configuration
- locale # I18n locale files
- custom # Custom configuraion
- data # App generated data
- log # App generated log
When writing a CLI app, each command should be in a separate file under /cmd
directory:
/cmd
dump.go
fix.go
serve.go
update.go
web.go
-
All applications that have
main
package should have constantAPP_VER
to indicate its version with formatX.Y.Z.Date [Status]
. For example,0.7.6.1112 Beta
. -
A library/package should have function
Version
to return its version instring
type with formatX.Y.Z[.Date]
. -
You should consider split lines of code that have more than 80 characters. The rule is use to argument as the unit to split:
So(z.ExtractTo( path.Join(os.TempDir(), "testdata/test2"), "dir/", "dir/bar", "readonly"), ShouldBeNil)
-
When the function or method's declaration has more than 80 characters, you should consider split it as well. The rule is arguments with same type in the same line. After declaration, you also need to put an empty line to distinguish itself with function or method's body:
// NewNode initializes and returns a new Node representation. func NewNode( importPath, downloadUrl string, tp RevisionType, val string, isGetDeps bool) *Node { n := &Node{ Pkg: Pkg{ ImportPath: importPath, RootPath: GetRootPath(importPath), Type: tp, Value: val, }, DownloadURL: downloadUrl, IsGetDeps: isGetDeps, } n.InstallPath = path.Join(setting.InstallRepoPath, n.RootPath) + n.ValSuffix() return n }
-
Group declaration should be organized by types, not all together:
const ( // Default section name. DEFAULT_SECTION = "DEFAULT" // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 200 ) type ParseError int const ( ERR_SECTION_NOT_FOUND ParseError = iota + 1 ERR_KEY_NOT_FOUND ERR_BLANK_SECTION_NAME ERR_COULD_NOT_PARSE )
-
When there are more than one functional modules in single source file, you should use ASCII Generator to make a highlight title:
// _________ __ // \_ ___ \ ____ _____ _____ ____ _____/ |_ // / \ \/ / _ \ / \ / \_/ __ \ / \ __\ // \ \___( <_> ) Y Y \ Y Y \ ___/| | \ | // \______ /\____/|__|_| /__|_| /\___ >___| /__| // \/ \/ \/ \/ \/
-
Functions or methods are ordered by the dependency relationship, such that the most dependent function or method should be at the top. In the following example,
ExecCmdDirBytes
is the most fundamental function, it's called byExecCmdDir
, andExecCmdDir
is also called byExecCmd
:// ExecCmdDirBytes executes system command in given directory // and return stdout, stderr in bytes type, along with possible error. func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) { ... } // ExecCmdDir executes system command in given directory // and return stdout, stderr in string type, along with possible error. func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) { bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...) return string(bufOut), string(bufErr), err } // ExecCmd executes system command // and return stdout, stderr in string type, along with possible error. func ExecCmd(cmdName string, args ...string) (string, string, error) { return ExecCmdDir("", cmdName, args...) }
-
Methods of struct should be put after struct definition, and order them by the order of fields they mostly operate on:
type Webhook struct { ... } func (w *Webhook) GetEvent() { ... } func (w *Webhook) SaveEvent() error { ... } func (w *Webhook) HasPushEvent() bool { ... }
-
If a struct has operational functions, should basically follow the
CRUD
order:func CreateWebhook(w *Webhook) error { ... } func GetWebhookById(hookId int64) (*Webhook, error) { ... } func UpdateWebhook(w *Webhook) error { ... } func DeleteWebhook(hookId int64) error { ... }
-
If a struct has functions or methods start with
Has
,Is
,Can
orAllow
, they should be ordered by the same order ofHas
,Is
,Can
orAllow
. -
Declaration of variables should be put before corresponding functions or methods:
var CmdDump = cli.Command{ Name: "dump", ... Action: runDump, Flags: []cli.Flag{}, } func runDump(*cli.Context) { ...
-
Use named field to initialize struct fields whenever possible:
AddHookTask(&HookTask{ Type: HTT_WEBHOOK, Url: w.Url, Payload: p, ContentType: w.ContentType, IsSsl: w.IsSsl, })
All package import paths must be a URL from a code hosting site except packages from the standard library.
Therefore, there are four types of packages a source file could import:
- Packages from the standard library
- Third-party packages
- Packages in the same organization but not in the same repository
- Packages in the same repository
Basic rules:
- Use different groups to separate import paths by an empty line for two or more types of packages.
- Must not use
.
to simplify the import path except in test files (*_test.go
). - Must not use a relative path (
./subpackage
) as import path, all import paths must bego get
-able.
Here is a complete example:
import (
"fmt"
"html/template"
"net/http"
"os"
"github.com/codegangsta/cli"
"gopkg.in/macaron.v1"
"github.com/gogs/git"
"github.com/gogs/gfm"
"github.com/gogs/gogs/routes"
"github.com/gogs/gogs/routes/repo"
"github.com/gogs/gogs/routes/user"
)
Style guideline for Go packages 20170114
Go is about naming and organization as much as everything else in the language. Well-organized Go code is easy to discover, use and read. Well-organized code is as critical as well designed APIs. The location, name, and the structure of your packages are the first elements your users see and interact with.
This document’s goal is to guide you with common good practices not to set rules. You will always need to use your own judgement to pick the most elegant solution for your specific case.
All Go code is organized into packages. A package in Go is simply a directory/folder with one or more
.go
files inside of it. Go packages provide isolation and organization of code similar to
how directories/folders organize files on a computer.
All Go code lives in a package and a package is the entry point to access Go code. Understanding and establishing good practices around packages is important to write effective Go code.
Let’s begin with suggestions how you should organize Go code and explain conventions about locating Go packages.
A package is a directory with one or more Go files. Feel free to separate your code into as many files as logically make sense for optimal readability.
For example, an HTTP package might have been separated into different files according to the HTTP aspect the file handles. In the following example, an HTTP package is broken down into a few files: header types and code, cookie types and code, the actual HTTP implementation, and documentation of the package.
- doc.go // package documentation
- headers.go // HTTP headers types and code
- cookies.go // HTTP cookies types and code
- http.go // HTTP client implementation, request and response types, etc.
As a rule of thumb, keep types closer to where they are used. This makes it easy for
any maintainer (not just the original author) to find a type.
A good place for a Header struct type might be in headers.go
.
$ cat headers.go
package http
// Header represents an HTTP header.
type Header struct {...}
Even though, the Go language doesn’t restrict where you define types, it is often a good practice to keep the core types grouped at the top of a file.
A common practise from other languages is to organize types together in a package called models or types. In Go, we organize code by their functional responsibilities.
package models // DON'T DO IT!!!
// User represents a user in the system.
type User struct {...}
Rather than creating a models package and declare all entity types there, a User type should live in a service-layer package.
package mngtservice
// User represents a user in the system.
type User struct {...}
func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error)
func UserIDByEmail(ctx context.Context, email string) (int64, error)
It is a great exercise to use godoc in the early phases of your package’s API design to see
how your concepts will be rendered on doc. Sometimes, the visualization also has an impact
on the design. Godoc is the way your users will consume a package, so it is ok to tweak
things to make them more accessible. Run godoc -http=<hostport>
to start a godoc server locally.
In some cases, you may not be able to provide all related types from a single package. It might be noisy to do so, or you might want to publish concrete implementations of a common interface from a separate package, or those types could be owned by a third-party package. Give examples to help the user to discover and understand how they are used together.
$ godoc cloud.google.com/go/datastore
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error)
...
NewClient works with option.ClientOptions but it is neither the datastore package nor the option package that export all the option types.
$ godoc google.golang.org/extraoption
func WithCustomValue(v string) option.ClientOption
...
If your API requires many non-standard packages to be imported, it is often useful to add a Go example to give your users some working code.
Examples are a good way to increase visibility of a less discoverable package. For example, an example for datastore.NewClient might reference the extraoption package.
An identifier may be exported to permit access to it from another package.
Main packages are not importable, so exporting identifiers from main packages is unnecessary. Don’t export identifiers from a main package if you are building the package to a binary.
Exceptions to this rule might be the main packages built into a .so, or a .a or Go plugin. In such cases, Go code might be used from other languages via cgo’s export functionality and exporting identifiers are required.
A package name and import path are both significant identifiers of your package and represent everything your package contains. Naming your packages canonically not just improves your code quality but also your users’.
Package names should be lowercase. Don’t use snake_case or camelCase in package names. The Go blog has a comprehensive guide about naming packages with a good variety of examples.
Package names should be short, but should be unique and representative. Users of the package should be able to grasp its purpose from just the package’s name.
Avoid overly broad package names like “common” and “util”.
import "pkgs.org/common" // DON'T!!!
Avoid duplicate names in cases where user may need to import the same package.
If you cannot avoid a bad name, it is very likely that there is a problem with your overall structure and code organization.
Avoid exposing your custom repository structure to your users. Align well with the GOPATH conventions. Avoid having src/, pkg/ sections in your import paths.
github.com/user/repo/src/httputil // DON'T DO IT, AVOID SRC!!
github.com/user/repo/gosrc/httputil // DON'T DO IT, AVOID GOSRC!!
In go, package names are not plural. This is surprising to programmers who came from other languages and are retaining an old habit of pluralizing names. Don’t name a package httputils, but httputil!
package httputils // DON'T DO IT, USE SINGULAR FORM!!
If you are importing more than one packages with the same name, you can locally
rename the package names. The renames should follow the same rules mentioned
on this article. There is no rule which package you should rename. If you are
renaming the standard package library, it is nice to add a go prefix to make the name
self document that it is “Go standard library’s” package, e.g. gourl
, goioutil
.
import (
gourl "net/url"
"myother.com/url"
)
go get
supports getting packages by a URL that is different than the URL
of the package’s repo. These URLs are called vanity URLs and require you to
serve a page with specific meta tags the Go tools recognize.
You can serve a package with a custom domain and path using vanity URLs.
For example,
$ go get cloud.google.com/go/datastore
checks out the source code from https://code.googlesource.com/gocloud
behind
the scenes and puts it in your workspace under $GOPATH/src/cloud.google.com/go/datastore.
Given code.googlesource.com/gocloud is already serving this package, would it be possible to go get the package from that URL? The answer is no, if you enforce the vanity URL.
To do that, add an import statement to the package. The go tool will reject any import of this package from any other path and will display a friendly error to the user. If you don’t enforce your vanity URLs, there will be two copies of your package that cannot work together due to the different namespace.
package datastore // import "cloud.google.com/go/datastore"
Always document the package. Package documentation is a top-level comment immediately preceding the package clause. For non-main packages, godoc always starts with “Package {pkgname}” and follows with a description. For main packages, documentation should explain the binary.
// Package ioutil implements some I/O utility functions.
package ioutil
// Command gops lists all the processes running on your system.
package main
// Sample helloworld demonstrates how to use x.
package main
Sometimes, package docs can get very lengthy, especially when they provide details
of usage and guidelines.
Move the package godoc to a doc.go
file.
(See an example of a doc.go.)
Order of arguments of functions or methods should generally apply the following rules (from left to right):
- More important to less important.
- Simple types to complicated types.
- Same types should be put together whenever possible.
In the following declaration, type User
is more complicated than type string
, but Repository
belongs to User
, so User
is more left than Repository
.
func IsRepositoryExist(user *User, repoName string) (bool, error) { ...
- The main entry point file of the application should be named as
main.go
or the same as the application. For example, the entry point file ofGogs
is namedgogs.go
.
-
If the main purpose of functions or methods is returning a
bool
type value, the name of function or method should start withHas
,Is
,Can
orAllow
, etc.func HasPrefix(name string, prefixes []string) bool { ... } func IsEntry(name string, entries []string) bool { ... } func CanManage(name string) bool { ... } func AllowGitHook() bool { ... }
-
Constant should use all capital letters and use underscore
_
to separate words.const APP_VER = "0.7.0.1110 Beta"
-
If you need enumerated type, you should define the corresponding type first:
type Scheme string const ( HTTP Scheme = "http" HTTPS Scheme = "https" )
-
If functionality of the module is relatively complicated and easy to mix up with a constant name, you can add a prefix to every constant:
type PullRequestStatus int const ( PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota PULL_REQUEST_STATUS_CHECKING PULL_REQUEST_STATUS_MERGEABLE )
-
A variable name should follow general English expression or shorthand.
-
In relatively simple (fewer objects and more specific) context, the variable name can use a simplified form as follows:
user
tou
userID
touid
-
If variable type is
bool
, its name should start withHas
,Is
,Can
orAllow
, etc.var isExist bool var hasConflict bool var canManage bool var allowGitHook bool
-
The last rule also applies for defining structs:
// Webhook represents a webhook object. type Webhook struct { ID int64 `xorm:"pk autoincr"` RepoID int64 OrgID int64 URL string `xorm:"url TEXT"` ContentType HookContentType Secret string `xorm:"TEXT"` Events string `xorm:"TEXT"` *HookEvent `xorm:"-"` IsSSL bool `xorm:"is_ssl"` IsActive bool HookTaskType HookTaskType Meta string `xorm:"TEXT"` // store hook-specific attributes LastStatus HookStatus // Last delivery status Created time.Time `xorm:"CREATED"` Updated time.Time `xorm:"UPDATED"` }
A variable name is generally using camelCase
style, but when you have unique nouns, should apply the following rules:
- If the variable is private, and the unique noun is the first word, then use lower cases, e.g.
apiClient
. - Acronyms should be all capitals, as in
ServeHTTP
- Otherwise, use the original cases for the unique noun, e.g.
APIClient
,repoID
,UserID
.
Here is a list of words which are commonly identified as unique nouns:
// A GonicMapper that contains a list of common initialisms taken from golang/lint
var LintGonicMapper = GonicMapper{
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SSH": true,
"TLS": true,
"TTL": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XSRF": true,
"XSS": true,
}
- Single letter represents index:
i, j, k
- Short but descriptive names:
cust
notcustomer
- repeat letters to represent collection, slice, or array and use single letter in loop:
var tt []*Thingfor i, t := range tt {
...
}
- avoid repeating package name:
log.Info() // good
log.LogInfo() // bad
- Don’t name like
getters
orsetters
:
custSvc.cust() // good
custSvc.getCust() // bad
- Add
er
to Interface
type Stringer interfaces {
String() string
}
- All exported objects must be well-commented; internal objects can be commented as needed.
- If the object is countable and does not know the number of it, use singular form and present tense; otherwise, use plural form.
- Comment of package, function, method and type must be a complete sentence.
- First letters of sentences should be upper case, but the lower case for phrases.
- The maximum length of a comment line should be 80 characters.
-
Only one file is needed for package-level comment.
-
For
main
packages, a line of introduction is enough, and should start with project name:// Gogs (Go Git Service) is a painless self-hosted Git Service. package main
-
For sub-packages of a complicated project, no package-level comment is required unless it's a functional module.
-
For simple non-
main
packages, a line of introduction is enough as well. -
For more complicated functional non-
main
packages, commonly should have some basic usage examples and must start withPackage <name>
:/* Package regexp implements a simple library for regular expressions. The syntax of the regular expressions accepted is: regexp: concatenation { '|' concatenation } concatenation: { closure } closure: term [ '*' | '+' | '?' ] term: '^' '$' '.' character '[' [ '^' ] character-ranges ']' '(' regexp ')' */ package regexp
-
For very complicated functional packages, you should create a
doc.go
file for it.
-
Type is often described in singular form:
// Request represents a request to run a command. type Request struct { ...
-
An interface should be described as follows:
// FileInfo is the interface that describes a file and is returned by Stat and Lstat. type FileInfo interface { ...
-
Comments of functions and methods must start with its name:
// Post returns *BeegoHttpRequest with POST method.
-
If one sentence is not enough, go to the next line:
// Copy copies file from source to target path. // It returns false and error when an error occurs in underlying function calls.
-
If the main purpose of a function or method is returning a
bool
value, its comment should start with<name> returns true if
:// HasPrefix returns true if name has any string in given slice as prefix. func HasPrefix(name string, prefixes []string) bool { ...
-
When something is waiting to be done, use comment starts with
TODO:
to remind maintainers. -
When a known problem/issue/bug needs to be fixed/improved, use comment starts with
FIXME:
to remind maintainers. -
When something is too magic and needs to be explained, use comment starts with
NOTE:
:// NOTE: os.Chmod and os.Chtimes don't recognize the symbolic link, // which will lead "no such file or directory" error. return os.Symlink(target, dest)
- Unit tests must use GoConvey and code coverage must above 80%.
- The file of examples of helper modules should be named
example_test.go
. - Test cases of functions must start with
Test_
, e.g.Test_Logger
. - Test cases of methods must use the format
Text_<Struct>_<Method>
, e.g.Test_Macaron_Run
.
As a open source project, there must be a LICENSE file to claim the rights.
Here are two examples of using Apache License, Version 2.0 and MIT licenses.
This license requires to put following content at the beginning of every file:
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
Replace [yyyy]
with the year of creation of the file. Then use personal name for personal project, or organization name for team project to replace [name of copyright owner]
.
This license requires to put following content at the beginning of every file:
// Copyright [yyyy] [name of copyright owner]. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
Replace [yyyy]
with the year of creation of the file.
-
Other types of license can follow the template of above two examples.
-
If a file has been modified by different individuals/organizations, and two licenses are compatiable, then change the first line to multiple lines and make them in the order of time:
// Copyright 2011 Gary Burd // Copyright 2013 Unknwon
-
Spefify which license is used for the project in the README file:
## License This project is under the MIT License. See the [LICENSE](LICENSE) file for the full license text.
Path control