关于程序员:10分钟搞懂20个Golang最佳实践

最佳实际是一些不成文的经验总结,遵循最佳实际能够使咱们站在前人的肩膀上,防止某些常见谬误,写出更好的代码。原文: Golang Best Practices (Top 20)

只须要花上10分钟浏览本文,就能够帮忙你更高效编写Go代码。

20: 应用适当缩进

良好的缩进使代码更具可读性,始终应用制表符或空格(最好是制表符),并遵循Go规范的缩进约定。

package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println("Hello, World!")
    }
}

运行gofmt依据Go规范主动格式化(缩进)代码。

$ gofmt -w your_file.go
19: 正确导入软件包

只导入须要的包,并格式化导入局部,将规范库包、第三方包和本人的包分组。

package main

import (
    "fmt"
    "math/rand"
    "time"
)
18: 应用描述性变量名和函数名
  1. 有意义的名称: 应用可能传播变量用处的名称。
  2. 驼峰表示法(CamelCase): 以小写字母结尾,前面每个单词的首字母大写。
  3. *简短的名称: 简短、简洁的名称对于作用域小、寿命短的变量是能够承受的。
  4. 不应用缩写: 防止应用费解的缩写和首字母缩略词,尽量应用描述性名称。
  5. 一致性: 在整个代码库中放弃命名一致性。
package main

import "fmt"

func main() {
    //应用有意义的名称申明变量
    userName := "John Doe"   // CamelCase:以小写字母结尾,前面的单词大写。
    itemCount := 10         // 简短的名称:对于小作用域变量来说,短而简洁。
    isReady := true         // 不应用缩写:防止应用费解的缩写或首字母缩写。

    // 显示变量值
    fmt.Println("User Name:", userName)
    fmt.Println("Item Count:", itemCount)
    fmt.Println("Is Ready:", isReady)
}

// 对包级变量应用mixedCase
var exportedVariable int = 42

// 函数名应该是描述性的
func calculateSumOfNumbers(a, b int) int {
    return a + b
}

// 一致性:在整个代码库中放弃命名的一致性。
17: 限度每行长度

尽可能将每行代码字符数管制在80个以下,以进步可读性。

package main

import (
    "fmt"
    "math"
)

func main() {
    result := calculateHypotenuse(3, 4)
    fmt.Println("Hypotenuse:", result)
}

func calculateHypotenuse(a, b float64) float64 {
    return math.Sqrt(a*a + b*b)
}
16: 将魔法值定义为常量

防止在代码中应用魔法值。魔法值是硬编码的数字或字符串,扩散在代码中,不足上下文,很难了解其目标。将魔法值定义为常量,能够使代码更易于保护。

package main

import "fmt"

const (
    // 为重试的最大次数定义常量
    MaxRetries = 3

    // 为默认超时(以秒为单位)定义常量
    DefaultTimeout = 30
)

func main() {
    retries := 0
    timeout := DefaultTimeout

    for retries < MaxRetries {
        fmt.Printf("Attempting operation (Retry %d) with timeout: %d seconds\n", retries+1, timeout)
        
        // ... 代码逻辑 ...

        retries++
    }
}
15. 错误处理

Go激励开发者显式处理错误,起因如下:

  1. 安全性: 错误处理确保意外问题不会导致程序panic或忽然解体。
  2. 清晰性: 显式错误处理使代码更具可读性,并有助于辨认可能产生谬误的中央。
  3. 可调试性: 处理错误为调试和故障排除提供了有价值的信息。

咱们创立一个简略程序来读取文件并正确处理错误:

package main

import (
 "fmt"
 "os"
)

func main() {
 // Open a file
 file, err := os.Open("example.txt")
 if err != nil {
  // 处理错误
  fmt.Println("Error opening the file:", err)
  return
 }
 defer file.Close() // 完结时敞开文件

 // 读取文件内容
 buffer := make([]byte, 1024)
 _, err = file.Read(buffer)
 if err != nil {
  // 处理错误
  fmt.Println("Error reading the file:", err)
  return
 }

 // 打印文件内容
 fmt.Println("File content:", string(buffer))
}
14. 防止应用全局变量

尽量减少应用全局变量,全局变量可能导致不可预测的行为,使调试变得艰难,并妨碍代码重用,还会在程序的不同局部之间引入不必要的依赖关系。相同,通过函数参数传递数据并返回值。

咱们编写一个简略的Go程序来阐明防止全局变量的概念:

package main

import (
 "fmt"
)

func main() {
 // 在main函数中申明并初始化变量
 message := "Hello, Go!"

 // 调用应用局部变量的函数
 printMessage(message)
}

// printMessage是带参数的函数
func printMessage(msg string) {
 fmt.Println(msg)
}
13: 应用构造体解决简单数据

通过构造体将相干的数据字段和办法组合在一起,使代码更有组织性和可读性。

上面是一个残缺示例程序,演示了构造体在Go中的利用:

package main

import (
    "fmt"
)

// 定义名为Person的构造体来示意人的信息。
type Person struct {
    FirstName string // 名字
    LastName  string // 姓氏
    Age       int    // 年龄
}

func main() {
    // 创立Person构造的实例并初始化字段。
    person := Person{
        FirstName: "John",
        LastName:  "Doe",
        Age:       30,
    }

    // 拜访并打印构造体字段的值。
    fmt.Println("First Name:", person.FirstName) // 打印名字
    fmt.Println("Last Name:", person.LastName)   // 打印姓氏
    fmt.Println("Age:", person.Age)             // 打印年龄
}
12. 对代码进行正文

增加正文来解释代码的性能,特地是对于简单或不显著的局部。

单行正文

单行正文以//结尾,用来解释特定的代码行。

package main

import "fmt"

func main() {
    // 单行正文
    fmt.Println("Hello, World!") // 打印问候语
}

多行正文

多行正文蕴含在/* */中,用于较长的解释或逾越多行的正文。

package main

import "fmt"

func main() {
    /*
        多行正文。
        能够逾越多行。
    */
    fmt.Println("Hello, World!") // 打印问候语
}

函数正文

在函数中增加正文,解释函数的用处、参数和返回值。函数正文应用’godoc‘款式。

package main

import "fmt"

// greetUser通过名称向用户示意欢送。
// Parameters:
//   name (string): 欢送的用户名
// Returns:
//   string: 问候语
func greetUser(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    userName := "Alice"
    greeting := greetUser(userName)
    fmt.Println(greeting)
}

包正文

在Go文件顶部增加正文来形容包的用处,应用雷同的’godoc‘款式。

package main

import "fmt"

// 这是Go程序的主包。
// 蕴含入口(main)函数。
func main() {
    fmt.Println("Hello, World!")
}
11: 应用goroutine解决并发

利用goroutine来高效执行并发操作。在Go语言中,gooutine是轻量级的并发执行线程,可能并发的运行函数,而没有传统线程的开销。从而帮忙咱们编写高度并发和高效的程序。

咱们用一个简略的例子来阐明:

package main

import (
 "fmt"
 "time"
)

// 并发运行的函数
func printNumbers() {
 for i := 1; i <= 5; i++ {
  fmt.Printf("%d ", i)
  time.Sleep(100 * time.Millisecond)
 }
}

// 在主goroutine中运行的函数
func main() {
 // 开始goroutine
 go printNumbers()

 // 继续执行main
 for i := 0; i < 2; i++ {
  fmt.Println("Hello")
  time.Sleep(200 * time.Millisecond)
 }
 // 确保在goroutine在退出前实现
 time.Sleep(1 * time.Second)
}
10: 用Recover解决panic

应用recover来优雅解决panic和避免程序解体。在Go中,panic是可能导致程序解体的意外运行时谬误。然而,Go提供了一种名为recover的机制来优雅的解决panic。

咱们用一个简略的例子来阐明:

package main

import "fmt"

// 可能会panic的函数
func riskyOperation() {
 defer func() {
  if r := recover(); r != nil {
   // 从panic中Recover,并优雅解决
   fmt.Println("Recovered from panic:", r)
  }
 }()

 // 模仿panic条件
 panic("Oops! Something went wrong.")
}

func main() {
 fmt.Println("Start of the program.")

 // 在从panic中复原的函数中调用有危险的操作
 riskyOperation()

 fmt.Println("End of the program.")
}
9. 防止应用’init’函数

除非必要,否则防止应用init函数,因为这会使代码更难了解和保护。

更好的办法是将初始化逻辑移到显式调用的惯例函数中,通常从main中调用,从而提供了更好的管制,加强了代码可读性,并简化了测试。

上面是一个简略的Go程序,演示了如何防止应用init函数:

package main

import (
 "fmt"
)

// InitializeConfig初始化配置
func InitializeConfig() {
 // 初始化配置参数
 fmt.Println("Initializing configuration...")
}

// InitializeDatabase初始化数据库连贯
func InitializeDatabase() {
 // 初始化数据库连贯
 fmt.Println("Initializing database...")
}

func main() {
 // 显示调用初始化函数
 InitializeConfig()
 InitializeDatabase()

 // 主代码逻辑
 fmt.Println("Main program logic...")
}
8: 应用Defer进行资源清理

defer能够将函数的执行提早到函数返回的时候,通常用于敞开文件、解锁互斥锁或开释其余资源等工作。

这确保了即便在存在谬误的状况下也能执行清理操作。

咱们创立一个简略的程序,从文件中读取数据,应用defer确保文件被正确敞开,不必管可能产生的任何谬误:

package main

import (
 "fmt"
 "os"
)

func main() {
 // 关上文件(用理论文件名替换"example.txt")
 file, err := os.Open("example.txt")
 if err != nil {
  fmt.Println("Error opening the file:", err)
  return // Exit the program on error
 }
 defer file.Close() // 确保函数退出时敞开文件

 // 读取并打印文件内容
 data := make([]byte, 100)
 n, err := file.Read(data)
 if err != nil {
  fmt.Println("Error reading the file:", err)
  return // 出错退出程序
 }

 fmt.Printf("Read %d bytes: %s\n", n, data[:n])
}
7: 抉择复合字面值而不是构造函数

应用复合字面值来创立struct实例,而不是构造函数。

为什么要应用复合文字?

复合字面值提供了几个长处:

  1. 简洁
  2. 易读
  3. 灵便

咱们用一个简略例子来阐明:

package main

import (
 "fmt"
)

// 定义一个示意人的构造类型
type Person struct {
 FirstName string // 名字
 LastName  string // 姓氏
 Age       int    // 年龄
}

func main() {
 // 应用复合字面量创立Person实例
 person := Person{
  FirstName: "John",   // 初始化FirstName字段
  LastName:  "Doe",    // 初始化LastName字段
  Age:       30,       // 初始化Age字段
 }

 // Printing the person's information
 fmt.Println("Person Details:")
 fmt.Println("First Name:", person.FirstName) // 拜访并打印FirstName字段
 fmt.Println("Last Name:", person.LastName)   // 拜访并打印LastName字段
 fmt.Println("Age:", person.Age)             // 拜访并打印Age字段
}
6. 最小化性能参数

在Go中,编写洁净高效的代码至关重要。实现这一点的一种办法是尽量减少函数参数的数量,从而进步代码的可维护性和可读性。

咱们用一个简略例子来阐明:

package main

import "fmt"

// Option构造保留配置参数
type Option struct {
    Port    int
    Timeout int
}

// ServerConfig是承受Option构造作为参数的函数
func ServerConfig(opt Option) {
    fmt.Printf("Server configuration - Port: %d, Timeout: %d seconds\n", opt.Port, opt.Timeout)
}

func main() {
    // 创立Option构造并初始化为默认值
    defaultConfig := Option{
        Port:    8080,
        Timeout: 30,
    }

    // 用默认值配置服务
    ServerConfig(defaultConfig)

    // 创立新的Option构造并批改端口
    customConfig := Option{
        Port: 9090,
    }

    // 用自定义端口和默认Timeout配置服务
    ServerConfig(customConfig)
}

在这个例子中,咱们定义了一个Option构造体来保留服务器配置参数。咱们没有向ServerConfig函数传递多个参数,而是应用Option构造体,这使得代码更易于保护和扩大。这种办法在具备大量配置参数的函数时特地有用。

5: 清晰起见,应用显式返回值而不是命名返回值

命名返回值在Go中很罕用,但有时会使代码不那么清晰,特地是在较大的代码库中。

咱们用一个简略例子来看看它们的区别。

package main

import "fmt"

// namedReturn演示命名返回值。
func namedReturn(x, y int) (result int) {
    result = x + y
    return
}

// explicitReturn演示显式返回值。
func explicitReturn(x, y int) int {
    return x + y
}

func main() {
    // 命名返回值
    sum1 := namedReturn(3, 5)
    fmt.Println("Named Return:", sum1)

    // 显示返回值
    sum2 := explicitReturn(3, 5)
    fmt.Println("Explicit Return:", sum2)
}

下面的示例程序中有两个函数,namedReturnexplicitReturn,以下是它们的不同之处:

  • namedReturn应用命名返回值result。尽管函数返回的内容很分明,但在更简单的函数中可能不会很显著。
  • explicitReturn间接返回后果,这样更简略、明确。
4: 将函数复杂性放弃在最低限度

函数复杂性是指函数代码的复杂程度、嵌套水平和分支水平。放弃较低的函数复杂度会使代码更具可读性、可维护性,并且不易出错。

咱们用一个简略的例子来摸索这个概念:

package main

import (
 "fmt"
)

// CalculateSum返回两个数字的和
func CalculateSum(a, b int) int {
 return a + b
}

// PrintSum打印两个数字的和
func PrintSum() {
 x := 5
 y := 3
 sum := CalculateSum(x, y)
 fmt.Printf("Sum of %d and %d is %d\n", x, y, sum)
}

func main() {
 // 调用PrintSum函数来演示最小函数复杂度
 PrintSum()
}

在下面的示例程序中:

  1. 咱们定义了两个函数,CalculateSumPrintSum,各自具备特定职责。
  2. CalculateSum是一个简略函数,用于计算两个数字的和。
  3. PrintSum调用CalculateSum计算并打印5和3的和。
  4. 通过放弃函数简洁并专一于单个工作,咱们放弃了较低的函数复杂性,进步了代码的可读性和可维护性。
3: 防止暗藏变量

当在较窄的范畴内申明具备雷同名称的新变量时,就会产生变量暗藏,这可能导致意外行为。这种状况下,具备雷同名称的内部变量会被暗藏,使其在该作用域中不可拜访。尽量避免在嵌套作用域中暗藏变量以避免混同。

参考如下示例程序:

package main

import "fmt"

func main() {
    // 申明并初始化值为10的内部变量'x'。
    x := 10
    fmt.Println("Outer x:", x)

    // 在外部作用域里用新变量'x'笼罩内部'x'。
    if true {
        x := 5 // 笼罩产生在这里
        fmt.Println("Inner x:", x) // 打印外部'x', 值为5
    }

    // 内部'x'放弃不变并依然能够拜访
    fmt.Println("Outer x after inner scope:", x) // 打印内部'x', 值为10
}
2: 应用接口进行形象

形象

形象是Go中的基本概念,容许咱们定义行为而不指定实现细节。

接口

在Go语言中,接口是办法签名的汇合。

实现接口的所有办法的任何类型都隐式的满足该接口。

因而只有遵循雷同的接口,咱们就可能编写能够解决不同类型的代码。

上面是一个用Go语言编写的示例程序,演示了应用接口进行形象的概念:

package main

import (
    "fmt"
    "math"
)

// 定义Shape接口
type Shape interface {
    Area() float64
}

// Rectangle构造
type Rectangle struct {
    Width  float64
    Height float64
}

// Circle构造
type Circle struct {
    Radius float64
}

// 实现Rectangle的Area办法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 实现Circle的Area办法
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// 打印任何形态面积的函数
func PrintArea(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
}

func main() {
    rectangle := Rectangle{Width: 5, Height: 3}
    circle := Circle{Radius: 2.5}

    // 对矩形和圆形调用PrintArea,因为都实现了Shape接口
    PrintArea(rectangle) // 打印矩形面积
    PrintArea(circle)    // 打印圆形面积
}

在这个程序中,咱们定义了Shape接口,创立了两个构造体RectangleCircle,每个构造体都实现了Area()办法,并应用PrintArea函数打印满足Shape接口的任何形态的面积。

这演示了如何在Go中应用接口进行形象,从而应用公共接口解决不同的类型。

1: 防止混合库包和可执行文件

在Go中,在包和可执行文件之间放弃清晰的隔离以确保代码洁净和可维护性至关重要。

上面是演示库和可执行文件拆散的示例我的项目构造:

myproject/
    ├── main.go
    ├── myutils/
       └── myutils.go

myutils/myutils.go:

// 包申明——为实用程序函数创立独自的包
package myutils

import "fmt"

// 导出打印消息的函数
func PrintMessage(message string) {
 fmt.Println("Message from myutils:", message)
}

main.go:

// 主程序
package main

import (
 "fmt"
 "myproject/myutils" // 导入自定义包
)

func main() {
 message := "Hello, Golang!"

 // 调用自定义包里的导出函数
 myutils.PrintMessage(message)

 // 演示主程序逻辑
 fmt.Println("Message from main:", message)
}
  1. 在下面的例子中,有两个独立的文件: myutils.gomain.go
  2. myutils.go定义了一个名为myutils的自定义包,蕴含输入函数PrintMessage,用于打印消息。
  3. main.go是应用相对路径(myproject/myutils)导入自定义包myutils的可执行文件。
  4. main.go中的main函数从myutils包中调用PrintMessage函数并打印一条音讯。这种关注点拆散放弃了代码的组织性和可维护性。

你好,我是俞凡,在Motorola做过研发,当初在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。为了不便大家当前能第一工夫看到文章,请敌人们关注公众号”DeepNoMind”,并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的反对和能源,激励我继续写下去,和大家独特成长提高!

本文由mdnice多平台公布

【腾讯云】轻量 2核2G4M,首年65元

阿里云限时活动-云数据库 RDS MySQL  1核2G配置 1.88/月 速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据