在本教程中,我们将探讨 Golang 中的前 20 个最佳编码实践。这将帮助你编写有效的 Go 代码。
良好的缩进使你的代码易读。一致地使用制表符或空格(最好是制表符),并遵循 Go 的缩进标准。
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println("Hello, World!")
}
}
运行gofmt
以根据 Go 标准自动格式化(缩进)你的代码。
$ gofmt -w your_file.go
仅导入你需要的包,并格式化导入部分以将标准库包、第三方包和你自己的包分组。
package main
import (
"fmt"
"math/rand"
"time"
)
package main
import "fmt"
func main() {
// 使用有意义的名称声明变量
userName := "John Doe" // 驼峰命名法:以小写字母开头,并在名称中的每个后续单词的首字母大写。
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
}
// 保持代码库中的命名一致性。
尽可能保持代码行长度在 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)
}
避免在代码中使用魔术值,即散布在代码中的硬编码数字或字符串,缺乏上下文,使其难以理解目的。为其定义常量,以使代码更易维护。
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++
}
}
Go 鼓励开发者显式处理错误,有以下原因:
让我们创建一个简单的程序,它读取一个文件并正确处理错误:
package main
import (
"fmt"
"os"
)
func main() {
// 打开一个文件
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))
}
最小化使用全局变量。全局变量可能导致不可预测的行为,使调试变得困难,并阻碍代码重用。它们还可能在程序的不同部分之间引入不必要的依赖关系。相反,通过函数参数和返回值传递数据。
让我们编写一个简单的 Go 程序来说明避免使用全局变量的概念:
package main
import (
"fmt"
)
func main() {
// 在main函数中声明并初始化变量
message := "Hello, Go!"
// 调用使用局部变量的函数
printMessage(message)
}
// printMessage是一个带参数的函数
func printMessage(msg string) {
fmt.Println(msg)
}
使用结构体将相关的数据字段和方法组合在一起。它们允许你将相关变量组合在一起,使你的代码更有组织性和可读性。
以下是一个完整的演示在 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) // 打印年龄
}
添加注释以解释代码的功能,特别是对于复杂或不明显的部分。
单行注释单行注释以//
开头。用于解释特定行的代码。
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 通过用户名向用户表示问候。
// 参数:
// name (string): 要问候的用户的名字。
// 返回:
// 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!")
}
高效地利用 goroutine
进行并发操作。goroutine
是 Go 中轻量级的、并发的执行线程。它们使您能够在没有传统线程开销的情况下并发运行函数。这使您能够编写高度并发和高效的程序。
让我们通过一个简单的例子来演示:
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()
// 继续执行主函数
for i := 0; i < 2; i++ {
fmt.Println("Hello")
time.Sleep(200 * time.Millisecond)
}
// 在退出之前确保 goroutine 完成
time.Sleep(1 * time.Second)
}
使用 recover
函数优雅的处理 panic
,并防止程序崩溃。在 Go 中,panic
是意外的运行时错误,可能导致程序崩溃。然而,Go 提供了一种称为 recover
的机制来优雅的处理 panic
。
让我们通过一个简单的例子来演示:
package main
import "fmt"
// 可能会 panic 的函数
func riskyOperation() {
defer func() {
if r := recover(); r != nil {
// 从 panic 中恢复并 gracefully 处理它
fmt.Println("Recovered from panic:", r)
}
}()
// 模拟 panic 条件
panic("Oops! Something went wrong.")
}
func main() {
fmt.Println("Start of the program.")
// 在一个能从 panic 中恢复的函数中调用 riskyOperation
riskyOperation()
fmt.Println("End of the program.")
}
避免使用 init
函数,除非必要,因为它可能使代码更难理解和维护。
一个更好的方法是将初始化逻辑移到常规函数中,您可以从主函数中显式调用它,通常更易于控制,增强代码的可读性,并简化测试。
以下是演示避免使用 init
函数的简单 Go 程序:
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...")
}
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 // 出现错误时退出程序
}
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])
}
使用复合字面值来创建结构体的实例,而不是使用构造函数。
为什么使用复合字面值?复合字面值提供了几个优势:
让我们通过一个简单的例子来演示:
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 字段
}
// 打印个人信息
fmt.Println("个人详情:")
fmt.Println("名字:", person.FirstName) // 访问并打印名字字段
fmt.Println("姓氏:", person.LastName) // 访问并打印姓氏字段
fmt.Println("年龄:", person.Age) // 访问并打印年龄字段
}
在 Go 中,编写干净高效的代码是至关重要的。其中一种方法是减少函数参数的数量,这可以导致更易维护和可读的代码。
让我们通过一个简单的例子来探讨这个概念:
package main
import "fmt"
// Option 结构体用于保存配置选项
type Option struct {
Port int
Timeout int
}
// ServerConfig 是一个接受 Option 结构体的函数
func ServerConfig(opt Option) {
fmt.Printf("服务器配置 - 端口:%d,超时:%d 秒\n", opt.Port, opt.Timeout)
}
func main() {
// 创建一个具有默认值的 Option 结构体
defaultConfig := Option{
Port: 8080,
Timeout: 30,
}
// 使用默认选项配置服务器
ServerConfig(defaultConfig)
// 使用新的 Option 结构体修改端口
customConfig := Option{
Port: 9090,
}
// 使用自定义端口值和默认超时配置服务器
ServerConfig(customConfig)
}
在这个例子中,我们定义了一个 Option 结构体,用于保存服务器的配置参数。与将多个参数传递给ServerConfig
函数不同,我们使用一个单独的Option
结构体,使得代码更易于维护和扩展。这种方法在处理具有大量配置参数的函数时特别有用。
在 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("具名返回值:", sum1)
// 显式返回值
sum2 := explicitReturn(3, 5)
fmt.Println("显式返回值:", sum2)
}
在上面的示例程序中,我们有两个函数,namedReturn
和 explicitReturn
。它们的区别如下:
namedReturn
使用了具名返回值 result
。虽然清楚函数返回的是什么,但在更复杂的函数中可能不够直观。explicitReturn
直接返回结果。这更简单、更明确。
函数复杂性指的是函数代码中的错综复杂度、嵌套和分支程度。保持函数复杂性的低水平使得你的代码更易读、更易维护,且更不容易出错。
让我们通过一个简单的例子来探讨这个概念:
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("%d 和 %d 的和是 %d\n", x, y, sum)
}
func main() {
// 调用 PrintSum 函数来演示最小函数复杂性。
PrintSum()
}
在上面的示例程序中:
CalculateSum
和 PrintSum
,各自负责特定的任务。CalculateSum
是一个简单的函数,用于计算两个数字的和。PrintSum
利用 CalculateSum
计算并打印出 5 和 3 的和。变量的屏蔽(shadowing
)发生在在更小的作用域内声明了一个同名的新变量,这可能导致意外的行为。它隐藏了同名的外部变量,在该作用域内无法访问。避免在嵌套作用域内屏蔽变量,以防止混淆。
让我们看一个示例程序:
package main
import "fmt"
func main() {
// 声明并初始化一个外部变量 'x',其值为 10。
x := 10
fmt.Println("外部 x:", x)
// 进入一个内部作用域,其中新变量 'x' 屏蔽了外部的 'x'。
if true {
x := 5 // 屏蔽发生在这里
fmt.Println("内部 x:", x) // 打印内部的 'x',其值为 5。
}
// 外部的 'x' 保持不变且仍然可访问。
fmt.Println("内部作用域后的外部 x:", x) // 打印外部的 'x',其值为 10。
}
抽象抽象是 Go 语言中的一个基本概念,允许我们定义行为而不指定实现细节。
接口在 Go 中,接口是一组方法签名。
在泛型功能增加后,接口的是一组方法签名和类型约束,也就是一组类型的集合。不过这里介绍的还是原始的接口功能,所以上面的描述也每问题。
任何实现接口所有方法的类型都会隐式满足该接口。
这使我们能够编写能够与不同类型一起工作的代码,只要它们遵循相同的接口。
下面是 Go 中的一个示例程序,演示了使用接口进行抽象的概念:
package main
import (
"fmt"
"math"
)
// 定义 Shape 接口
type Shape interface {
Area() float64
}
// 矩形结构体
type Rectangle struct {
Width float64
Height float64
}
// 圆形结构体
type Circle struct {
Radius float64
}
// 为矩形实现 Area 方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 为圆形实现 Area 方法
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// 打印任意 Shape 的面积的函数
func PrintArea(s Shape) {
fmt.Printf("面积: %.2f\n", s.Area())
}
func main() {
rectangle := Rectangle{Width: 5, Height: 3}
circle := Circle{Radius: 2.5}
// 在矩形和圆形上调用 PrintArea,它们都实现了 Shape 接口
PrintArea(rectangle) // 打印矩形的面积
PrintArea(circle) // 打印圆形的面积
}
在这个单一的程序中,我们定义了 Shape 接口,创建了两个结构体 Rectangle
和 Circle
,它们都实现了 Area()
方法,并使用 PrintArea
函数来打印满足 Shape
接口的任何形状的面积。
这演示了在 Go 中如何使用接口进行抽象,以使用一个共同的接口处理不同类型。
在 Go 语言中,保持库包和可执行文件之间清晰的分离是至关重要的,以确保代码清晰和可维护。
以下是演示库和可执行文件分离的示例项目结构:
myproject/
├── main.go
├── myutils/
└── myutils.go
myutils/myutils.go:
package myutils
import "fmt"
// 导出的打印消息的函数
func PrintMessage(message string) {
fmt.Println("来自 myutils 的消息:", message)
}
main.go:
package main
import (
"fmt"
"myproject/myutils" // 导入自定义包
)
func main() {
message := "你好,Golang!"
// 调用自定义包 myutils 中的导出函数
myutils.PrintMessage(message)
// 演示主程序逻辑
fmt.Println("来自 main 的消息:", message)
}
在上面的示例中,我们有两个独立的文件:myutils.go
和 main.go
。myutils.go
定义了一个名为 myutils
的自定义包。它包含一个打印消息的导出函数 PrintMessage
。main.go
是可执行文件,使用相对路径("myproject/myutils"
)导入了自定义包 myutils
。main.go
中的 main 函数调用 myutils
包中的 PrintMessage
函数并打印一条消息。这种关注点分离使代码保持有序和可维护。
本文由微信公众号鸟窝聊技术原创,哈喽比特收录。
文章来源:https://mp.weixin.qq.com/s/maH0cibncPNxALYi1QMRVA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。