For any open source project, there must be a LICENSE file in the repository root to claim the rights.
Here are two examples of using Apache License, Version 2.0 and MIT License.
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 creation year of the file. Then use personal name for personal projects, or organization name for team projects 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 creation year of the file.
-
Other types of license can follow the template of above two examples.
-
If a file has been modified by different individuals and/or organizations, and multiple licenses are compatiable, then change the first line to multiple lines and sort them in the order of time:
// Copyright 2011 Gary Burd // Copyright 2013 Unknwon
-
Spefify which license is used for the project in bottom of the README file:
## License This project is under the MIT License. See the [LICENSE](LICENSE) file for the full license text.
For server-side services and CLI applications, they generally follow the same style of project structure as follows:
.bin/ # The directory to put all locally compiled binaries, should be ignored by .gitignore
.github/ # GitHub specific configuration
assets/ # Static resources, including those to be embeded into binaries
migrations/ # Files for database schema migrations
templates/ # Template files for rendering HTML pages
embed.go # The main file to tell Go toolchain to embed resources
cmd/ # All "main" packages which produce binaries should be placed under this directory
<logan smith>/
main.go # The entry point of a main package
data/ # Application generated data
dev/ # Scripts for enhancing quality-of-life of development
docs/ # Project specific documentation
internal/ # Common packages, "internal" is also a keyword to prevent other projects to import in Go
conf/ # Read and/or write application configuration
db/ # Database layer
route/ # Routing layer
log/ # Appication generated logs
.golangci.yml # Custom configuration for golangci-lint
The following shows an example of embeding database schema migrations using Go 1.16 embed:
package assets
import (
"embed"
)
//go:embed migrations/*.sql
var Migrations embed.FS
All package import paths must be valid Go Modules path except packages from standard library.
Generally, there are four types of packages a source file could import:
- Packages from 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 blank line for two or more types of packages.
- Must not use
.
to simplify import path except in test files (*_test.go
). - Must not use relative path (
./subpackage
) as import path.
Here is a concrete example:
import (
"fmt"
"html/template"
"net/http"
"os"
"github.com/urfave/cli"
"gopkg.in/macaron.v1"
"github.com/gogs/git-module"
"github.com/gogs/gogs/internal/route"
"github.com/gogs/gogs/internal/route/repo"
"github.com/gogs/gogs/internal/route/user"
)
- All exported objects must be well-commented; internal objects can be commented when 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 (i.e. capitalize the first letter and end with a period).
- 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 the binary name:// Gogs is a painless self-hosted Git Service. package main
-
For sub-packages of a complex 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 complex functional non-
main
packages, comment 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
-
When few paragraphs cannot explain the package well, you should create a
doc.go
file for it.
-
Type is often described in singular form, and the comment should state what it actuall does:
// Request represents a request to run a command. type Request struct { ...
-
Interface should be described as follows, and the comment should state what implementations should be doing:
// 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 next line:
// Copy copies file from source to target path. // It returns false and error when 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 symbolic link, // which will lead "no such file or directory" error. return os.Symlink(target, dest)
-
When dealing with security-related logic, use comment starts with
SECURITY:
-
When something important but is easy to overlook, use comment starts with
WARNING:
-
When the object is deprecated, use comment starts with
DEPRECATED:
:// Email returns the user's oldest email, if one exists. // Deprecated: use Emails instead. func (r *UserResolver) Email(ctx context.Context) (string, error) {
- Do not use underscores (
_
) in directory names, use hyphens (-
) instead.
- Do not use hyphens (
-
) in file names, use underscores (_
) instead. - The file contains the entry point of the application or package should be named as
main.go
or same as the package.
-
If the main purpose of the function or method is returning a
bool
type value, the name of function or method should starts 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 camel cases except for coined terms and brand names (see later):
const appVersion = "0.13.0+dev"
-
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 mixed up with constant name, you can add prefix to every constant for the enumerated type:
type PullRequestStatus int const ( PullRequestStatusConflict PullRequestStatus = iota PullRequestStatusChecking PullRequestStatusMergable )
-
A variable name should follow general English expression or shorthand.
-
In relatively simple (less objects and more specific) context, variable name can use simplified form as follows:
userID
touid
repository
torepo
-
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
-
Same rules also apply for defining structs:
// Webhook represents a web hook object. type Webhook struct { ID int64 RepoID int64 OrgID int64 URL string ContentType HookContentType Secret string Events string *HookEvent IsSSL bool IsActive bool HookTaskType HookTaskType Meta string LastStatus HookStatus CreatedAt time.Time UpdatedAt time.Time }
When you encounter coined terms and brand names, naming should respect the original or the most widely accepted form for the letter case. For example, use "GitHub" not "Github", use "API" not "Api", use "ID" not "Id".
When the coined term and brand name is the first word in a variable or constant name, keep them having the same case. For example, apiClient
, SSLCertificate
, githubClient
.
Here is a list of words which are commonly identified as coined terms:
// 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,
}
Order of arguments of functions or methods should generally apply following rules (from left to right):
- More important to less important.
- Simpler types to more 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
(which makes the user
argument more important), so user
is more left than repoName
:
func IsRepositoryExist(user *User, repoName string) (bool, error) { ...
-
Do not initialize structs with unnamed fields and do break fields into multiple lines:
func main() { ... awsCloudwatchLogsLogGroup := os.Getenv("AWS_CLOUDWATCH_LOGS_LOG_GROUP") if awsCloudwatchLogsLogGroup != "" { client := awsutil.NewDefaultClient() err := log.New( "cloudwatchlogs", awsutil.CloudWatchLogsIniter(), 1000, awsutil.CloudWatchLogsConfig{ // <---- Focus here Level: log.LevelInfo, Client: client.NewCloudWatchLogsClient(), LogGroupName: awsCloudwatchLogsLogGroup, LogStreamPrefix: awsCloudwatchLogsLogGroup + "-stream", Retention: 30, MaxRetries: 3, }, ) if err != nil { log.Fatal("Failed to init cloudwatchlogs logger: %v", err) } } ... }
-
Group declaration should be organized by types, not all together:
const ( // Default section name. DefaultSection = "DEFAULT" // Maximum allowed depth when recursively substituing variable names. depthValues = 200 ) type ParseError int const ( ErrSectionNotFound ParseError = iota + 1 ErrKeyNotFound ErrBlankSectionName ErrCouldNotParse )
-
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) { ...
- Unit tests must use github.com/stretchr/testify and code coverage must above 80%.
- The file of examples of helper modules should be named as
example_test.go
. - Test cases of functions must start with
Test
, e.g.TestLogger
. - Test cases of methods must use format
Text<Struct>_<Method>
, e.g.TestMacaron_Run
.
Warnings will be ignored in practice, CI should either fail or pass on linting, anything that is not failing the CI should be removed.
Here is the best practice for configuring .golangci.yml
:
linters-settings:
nakedret:
max-func-lines: 0 # Disallow any unnamed return statement
linters:
enable:
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
- nakedret
- gofmt
- rowserrcheck
- unconvert
- goimports