Vermeidung von übermäßig langen Zeilen
Vermeiden Sie die Verwendung von Codezeilen, die die Leser horizontal scrollen oder das Dokument übermäßig umdrehen müssen.
Wir empfehlen, die Zeilenlänge auf 99 Zeichen zu begrenzen. Autoren sollten die Zeile vor dieser Grenze umbrechen, aber es ist keine harte Regel. Es ist zulässig, dass der Code diese Grenze überschreitet.
Konsistenz
Einige der in diesem Dokument festgelegten Standards basieren auf subjektiven Beurteilungen, Szenarien oder Kontexten. Die wesentlichste Aspekte ist jedoch die Aufrechterhaltung der Konsistenz.
Konsistenter Code ist leichter zu warten, rationaler, erfordert weniger Lernaufwand und ist einfacher zu migrieren, zu aktualisieren und Fehler zu beheben, wenn neue Konventionen entstehen oder Fehler auftreten.
Im Gegensatz dazu führen das Einschließen von mehreren völlig unterschiedlichen oder widersprüchlichen Code-Stilen in einem Codebestand zu erhöhten Wartungskosten, Unsicherheit und kognitiven Verzerrungen. All dies führt direkt zu einer langsameren Geschwindigkeit, schmerzhaften Code-Reviews und einer erhöhten Anzahl von Fehlern.
Bei der Anwendung dieser Standards auf einen Codebestand wird empfohlen, Änderungen auf Paket- (oder höherem) Niveau vorzunehmen. Das Anwenden mehrerer Stile auf der Unter-Paket-Ebene verstößt gegen die oben genannten Bedenken.
Gruppierung ähnlicher Deklarationen
Die Programmiersprache Go unterstützt die Gruppierung ähnlicher Deklarationen.
Nicht empfohlen:
import "a"
import "b"
Empfohlen:
import (
"a"
"b"
)
Dies gilt auch für Konstanten-, Variablen- und Typdeklarationen:
Nicht empfohlen:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Empfohlen:
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
Gruppieren Sie nur zusammenhängende Deklarationen und vermeiden Sie das Gruppieren von nicht zusammenhängenden Deklarationen.
Nicht empfohlen:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
Empfohlen:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
Es gibt keine Einschränkungen, wo die Gruppierung verwendet werden soll. Sie können sie beispielsweise innerhalb einer Funktion verwenden:
Nicht empfohlen:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
Empfohlen:
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
Ausnahme: Wenn Variablendeklarationen benachbart zu anderen Variablen, insbesondere innerhalb von Funktionslokalen Deklarationen, stehen, sollten sie zusammen gruppiert werden. Führen Sie dies auch für nicht zusammenhängende gemeinsam deklarierte Variablen durch.
Nicht empfohlen:
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
Empfohlen:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Import-Gruppierung
Imports sollten in zwei Kategorien gruppiert werden:
- Standardbibliothek
- Andere Bibliotheken
Standardmäßig ist dies die Gruppierung, die von goimports angewendet wird. Nicht empfohlen:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Empfohlen:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Paketname
Beim Benennen eines Pakets sollten diese Regeln befolgt werden:
- Alles in Kleinbuchstaben, keine Großbuchstaben oder Unterstriche.
- In den meisten Fällen ist kein Umbenennen beim Importieren erforderlich.
- Kurz und prägnant. Denken Sie daran, dass der Name überall, wo er verwendet wird, vollständig qualifiziert ist.
- Vermeiden Sie Pluralformen. Verwenden Sie z. B.
net/url
anstelle vonnet/urls
. - Vermeiden Sie die Verwendung von "common", "util", "shared" oder "lib". Diese sind nicht aussagekräftig genug.
Funktionenamen
Wir halten uns an die Go-Community-Konvention, Funktionenamen in MixedCaps zu verwenden. Eine Ausnahme wird für die Gruppierung von zusammenhängenden Testfällen gemacht, wo der Funktionsname Unterstriche enthalten kann, z.B.: TestMyFunction_WhatIsBeingTested
.
Import-Aliase
Wenn der Paketname nicht mit dem letzten Element des Importpfads übereinstimmt, muss ein Import-Alias verwendet werden.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
In allen anderen Fällen sollten Import-Aliase vermieden werden, es sei denn, es besteht ein direkter Konflikt zwischen den Imports. Nicht empfohlen:
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
Empfohlen:
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
Funktionengruppierung und -reihenfolge
- Funktionen sollten grob in der Reihenfolge sortiert werden, in der sie aufgerufen werden.
- Funktionen in derselben Datei sollten nach Empfängern gruppiert werden.
Daher sollten exportierte Funktionen zuerst in der Datei erscheinen, platziert nach den Definitionen von struct
, const
und var
.
Ein newXYZ()
/NewXYZ()
darf nach den Typdefinitionen, aber vor den verbleibenden Methoden des Empfängers erscheinen.
Da Funktionen nach Empfängern gruppiert sind, sollten allgemeine Hilfsfunktionen am Ende der Datei erscheinen. Nicht empfohlen:
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{ ... }
func calcCost(n []int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
Empfohlen:
type something struct{ ... }
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n []int) int {...}
Verminderung der Verschachtelung
Der Code sollte die Verschachtelung reduzieren, indem Fehler-/Sonderfälle so früh wie möglich behandelt und entweder zurückgegeben oder die Schleife fortgesetzt wird. Die Reduzierung der Verschachtelung verringert die Menge des Codes auf mehreren Ebenen.
Nicht empfohlen:
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("Ungültiges v: %v", v)
}
}
Empfohlen:
for _, v := range data {
if v.F1 != 1 {
log.Printf("Ungültiges v: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
Unnötige else-Anweisung
Wenn eine Variable in beiden Zweigen eines if gesetzt wird, kann sie durch eine einzelne if-Anweisung ersetzt werden.
Nicht empfohlen:
var a int
if b {
a = 100
} else {
a = 10
}
Empfohlen:
a := 10
if b {
a = 100
}
Deklaration von Variablen auf oberster Ebene
Auf oberster Ebene verwenden Sie das Standard-var
-Schlüsselwort. Geben Sie den Typ nicht an, es sei denn, er unterscheidet sich vom Typ des Ausdrucks.
Nicht empfohlen:
var _s string = F()
func F() string { return "A" }
Empfohlen:
var _s = F()
// Da F explizit einen String-Typ zurückgibt, müssen wir den Typ für _s nicht explizit angeben
func F() string { return "A" }
Geben Sie den Typ an, wenn er nicht genau zum Typ des Ausdrucks passt.
type myError struct{}
func (myError) Error() string { return "Fehler" }
func F() myError { return myError{} }
var _e error = F()
// F gibt eine Instanz des Typs myError zurück, aber wir brauchen den Typ error
Verwenden Sie '_' als Präfix für nicht exportierte Top-Level-Konstanten und Variablen
Für nicht exportierte Top-Level-Variablen und Konstanten muss diesen ein Unterstrich _
vorangestellt werden, um ihre globale Natur bei der Verwendung explizit anzuzeigen.
Grundlegende Begründung: Top-Level-Variablen und Konstanten haben einen Paketbereich. Die Verwendung generischer Namen kann leicht dazu führen, dass versehentlich der falsche Wert in anderen Dateien verwendet wird.
Nicht empfohlen:
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Standard Port", defaultPort)
// Wir sehen keinen Kompilierfehler, wenn die erste Zeile von
// Bar() gelöscht wird.
}
Empfohlen:
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
Ausnahme: Nicht exportierte Fehlerwerte können das Präfix err
ohne Unterstrich verwenden. Siehe Fehlerbenennung.
Einbetten in Strukturen
Eingebettete Typen (wie Mutex) sollten oben in der Feldliste innerhalb der Struktur platziert werden und zwischen den eingebetteten Feldern und den regulären Feldern muss eine leere Zeile stehen.
Nicht empfohlen:
type Client struct {
version int
http.Client
}
Empfohlen:
type Client struct {
http.Client
version int
}
Das Einbetten soll greifbare Vorteile bieten, wie das Hinzufügen oder Verbessern von Funktionen auf semantisch angemessene Weise. Es sollte ohne nachteiligen Einfluss auf den Benutzer verwendet werden. (Siehe auch: Vermeiden Sie das Einbetten von Typen in öffentlichen Strukturen)
Ausnahmen: Selbst in nicht exportierten Typen sollte Mutex nicht als eingebettetes Feld verwendet werden. Siehe auch: Der Nullwert von Mutex ist gültig.
Einbetten soll nicht:
- ausschließlich aus ästhetischen oder praktischen Gründen existieren.
- die Konstruktion oder Verwendung des äußeren Typs erschweren.
- den Nullwert des äußeren Typs beeinflussen. Wenn der äußere Typ einen sinnvollen Nullwert hat, sollte auch nach dem Einbetten des inneren Typs ein sinnvoller Nullwert vorhanden sein.
- die Nebenwirkung haben, nicht zusammenhängende Funktionen oder Felder des eingebetteten inneren Typs freizulegen.
- nicht exportierte Typen freilegen.
- die Klonform des äußeren Typs beeinflussen.
- die API oder Typsemantik des äußeren Typs ändern.
- den inneren Typ in einer nicht standardmäßigen Form einbetten.
- Implementierungsdetails des äußeren Typs freilegen.
- Benutzern ermöglichen, den internen Typ zu beobachten oder zu steuern.
- das allgemeine Verhalten interner Funktionen auf eine Weise ändern, die Benutzer überraschen könnte.
Kurz gesagt: Betten Sie bewusst und zweckmäßig ein. Ein guter Lackmustest ist: "Werden all diese exportierten Methoden/Felder des inneren Typs direkt dem äußeren Typ hinzugefügt?" Wenn die Antwort einige
oder nein
lautet, betten Sie den inneren Typ nicht ein - verwenden Sie stattdessen Felder.
Nicht empfohlen:
type A struct {
// Schlecht: A.Lock() und A.Unlock() sind jetzt verfügbar
// Bietet keinen funktionalen Vorteil und ermöglicht es dem Benutzer, interne Details von A zu steuern.
sync.Mutex
}
Empfohlen:
type countingWriteCloser struct {
// Gut: Write() wird auf äußerer Ebene für einen bestimmten Zweck bereitgestellt
// und delegiert die Arbeit an das Write() des inneren Typs.
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
Lokale Variablendeklarationen
Wenn einer Variablen explizit ein Wert zugewiesen wird, sollte die Kurzvariablendeklarationsform (:=
) verwendet werden.
Nicht empfohlen:
var s = "foo"
Empfohlen:
s := "foo"
Jedoch kann in einigen Fällen die Verwendung des var
-Schlüsselworts für Standardwerte klarer sein.
Nicht empfohlen:
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
Empfohlen:
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nil ist ein gültiger Slice
nil
ist ein gültiger Slice mit einer Länge von 0, was bedeutet:
- Sie sollten nicht explizit einen Slice mit einer Länge von Null zurückgeben. Stattdessen
nil
zurückgeben.
Nicht empfohlen:
if x == "" {
return []int{}
}
Empfohlen:
if x == "" {
return nil
}
- Um zu überprüfen, ob ein Slice leer ist, verwenden Sie immer
len(s) == 0
anstelle vonnil
.
Nicht empfohlen:
func isEmpty(s []string) bool {
return s == nil
}
Empfohlen:
func isEmpty(s []string) bool {
return len(s) == 0
}
- Slices mit Nullwert (Slices, die mit
var
deklariert wurden) können sofort ohne Aufruf vonmake()
verwendet werden.
Nicht empfohlen:
nums := []int{}
// oder, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Empfohlen:
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Denken Sie daran, dass obwohl ein nil-Slice ein gültiger Slice ist, er nicht gleich einem Slice mit einer Länge von 0 ist (der eine ist nil und der andere nicht), und sie können in verschiedenen Situationen unterschiedlich behandelt werden (z.B. Serialisierung).
Begrenzung des Variablenspektrums
Versuchen Sie, wenn möglich, den Gültigkeitsbereich von Variablen zu begrenzen, es sei denn, dies steht im Konflikt mit der Regel zur Reduzierung der Verzweigungstiefe.
Nicht empfohlen:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Empfohlen:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
Wenn das Ergebnis eines Funktionsaufrufs außerhalb der if-Anweisung benötigt wird, sollte nicht versucht werden, den Gültigkeitsbereich zu begrenzen.
Nicht empfohlen:
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
Empfohlen:
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
Vermeidung von nackten Parametern
Unklare Parameter in Funktionsaufrufen können die Lesbarkeit beeinträchtigen. Wenn die Bedeutung der Parameternamen nicht offensichtlich ist, fügen Sie C-Style-Kommentare (/* ... */
) zu den Parametern hinzu.
Nicht empfohlen:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
Empfohlen:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
Für das obige Beispiel könnte ein besserer Ansatz sein, die bool
-Typen durch benutzerdefinierte Typen zu ersetzen. Auf diese Weise könnte der Parameter in Zukunft potenziell mehr als nur zwei Zustände (true/false) unterstützen.
type Region int
const (
UnbekannteRegion Region = iota
Lokal
)
type Status int
const (
StatusBereit Status= iota + 1
StatusErledigt
// Vielleicht werden wir in Zukunft auch einen StatusInProgress haben.
)
func printInfo(name string, region Region, status Status)
Verwenden von Raw String Literals zur Vermeidung von Escaping
Go unterstützt die Verwendung von Raw String Literals, die durch " ` " dargestellt werden, um Rohzeichenfolgen darzustellen. In Szenarien, in denen Escaping erforderlich ist, sollten wir diesen Ansatz verwenden, um die schwerer lesbaren manuell escaped Zeichenfolgen zu ersetzen.
Es kann sich über mehrere Zeilen erstrecken und Anführungszeichen enthalten. Die Verwendung dieser Zeichenfolgen kann schwerer lesbare manuell escaped Zeichenfolgen vermeiden.
Nicht empfohlen:
wantError := "unknown name:\"test\""
Empfohlen:
wantError := `unknown error:"test"`
Initialisierung von Strukturen
Initialisieren von Strukturen unter Verwendung von Feldnamen
Beim Initialisieren einer Struktur sollten Feldnamen fast immer angegeben werden. Dies wird derzeit von go vet
durchgesetzt.
Nicht empfohlen:
k := User{"John", "Doe", true}
Empfohlen:
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
Ausnahme: Bei 3 oder weniger Feldern können Feldnamen in Testtabellen ggf. ausgelassen werden.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
Auslassen von Nullwertfeldern in Strukturen
Beim Initialisieren einer Struktur mit benannten Feldern sollten, sofern kein bedeutungsvoller Kontext vorliegt, Felder mit einem Nullwert ignoriert werden. Das heißt, wir setzen diese automatisch auf Nullwerte.
Nicht empfohlen:
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
Empfohlen:
user := User{
FirstName: "John",
LastName: "Doe",
}
Dies hilft, Lesehürden zu beseitigen, indem Standardwerte im Kontext ausgelassen werden. Geben Sie nur bedeutungsvolle Werte an.
Fügen Sie den Nullwert hinzu, wenn die Feldnamen einen bedeutungsvollen Kontext bieten. Zum Beispiel können Testfälle in einem tabellengesteuerten Test von der Benennung der Felder profitieren, auch wenn es sich um Nullwerte handelt.
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
Verwenden von var
für Nullwertstrukturen
Wenn alle Felder einer Struktur bei der Deklaration ausgelassen werden, verwenden Sie var
, um die Struktur zu deklarieren.
Nicht empfohlen:
user := User{}
Empfohlen:
var user User
Dies unterscheidet Nullwertstrukturen von solchen mit Feldern ohne Nullwert, ähnlich dem, was wir bevorzugen, wenn eine leere Slice deklariert wird.
Initialisierung von Strukturverweisen
Bei der Initialisierung von Strukturverweisen verwenden Sie &T{}
anstelle von new(T)
, um die Konsistenz mit der Strukturinitialisierung sicherzustellen.
Nicht empfohlen:
sval := T{Name: "foo"}
// inkonsistent
sptr := new(T)
sptr.Name = "bar"
Empfohlen:
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
Initialisieren von Maps
Verwenden Sie make(..)
, um eine leere Map zu initialisieren, die später programmgesteuert befüllt wird. Dies macht die Map-Initialisierung optisch anders als die Deklaration und ermöglicht auch die bequeme Hinzufügung von Größenhinweisen nach make
.
Nicht empfohlen:
var (
// m1 ist schreibgeschützt;
// m2 löst einen Fehler bei der Schreiboperation aus
m1 = map[T1]T2{}
m2 map[T1]T2
)
Empfohlen:
var (
// m1 ist schreibgeschützt;
// m2 löst einen Fehler bei der Schreiboperation aus
m1 = make(map[T1]T2)
m2 map[T1]T2
)
| Deklaration und Initialisierung sehen sehr ähnlich aus. | Deklaration und Initialisierung sehen sehr unterschiedlich aus. |
Geben Sie nach Möglichkeit die Größe der Map bei der Initialisierung an. Siehe Spezifizierung der Map-Größe für Details.
Darüber hinaus, wenn die Map eine feste Liste von Elementen enthält, verwenden Sie Map-Literale zur Initialisierung der Map.
Nicht empfohlen:
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
Empfohlen:
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
Die grundlegende Richtlinie ist, Map-Literale zur Hinzufügung einer festen Elementmenge während der Initialisierung zu verwenden. Andernfalls verwenden Sie make
(und geben, wenn möglich, die Kapazität der Map an).
Zeichenkettenformat für Printf-ähnliche Funktionen
Wenn Sie die Formatzeichenkette einer Printf
-ähnlichen Funktion außerhalb einer Funktion deklarieren, setzen Sie sie als const
-Konstante.
Dies hilft go vet
, eine statische Analyse der Formatzeichenkette durchzuführen.
Nicht empfohlen:
msg := "unerwartete Werte %v, %v\n"
fmt.Printf(msg, 1, 2)
Empfohlen:
const msg = "unerwartete Werte %v, %v\n"
fmt.Printf(msg, 1, 2)
Benennung von Printf-Style Funktionen
Bei der Deklaration von Printf
-Style Funktionen stellen Sie sicher, dass go vet
den Formatstring erkennen und überprüfen kann.
Das bedeutet, dass Sie vordefinierte Printf
-Style Funktionsnamen so weit wie möglich verwenden sollten. go vet
wird diese standardmäßig überprüfen. Weitere Informationen finden Sie unter Printf Family.
Wenn vordefinierte Namen nicht verwendet werden können, enden Sie den ausgewählten Namen mit f
: Wrapf
anstelle von Wrap
. go vet
kann spezifische Printf-Style Namen zur Überprüfung anfordern, aber der Name muss mit f
enden.
go vet -printfuncs=wrapf,statusf