震旦网站谁做的,珠海微信网站开发,团购网站案例,wordpress糗事百科主题关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等#xff0c;您的关注将是我的更新动力#xff01; 在开发生产项目的过程中#xff0c;我注意到经常会发现自己在重复编写代码#xff0c;使用某些技巧时没有意识到#xff0c;直… 关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等您的关注将是我的更新动力 在开发生产项目的过程中我注意到经常会发现自己在重复编写代码使用某些技巧时没有意识到直到后来回顾工作时才意识到。
为了解决这个问题我开发了一种解决方案对我来说非常有帮助我觉得对其他人也可能有用。
以下是一些从我的实用程序库中随机挑选的有用且多功能的代码片段没有特定的分类或特定于系统的技巧。
1. 追踪执行时间的技巧
如果你想追踪 Go 中函数的执行时间有一个简单高效的技巧可以用一行代码实现使用 defer 关键字即可。你只需要一个 TrackTime 函数
// Utility
func TrackTime(pre time.Time) time.Duration {elapsed : time.Since(pre)fmt.Println(elapsed:, elapsed)return elapsed
}func TestTrackTime(t *testing.T) {defer TrackTime(time.Now()) // --- THIStime.Sleep(500 * time.Millisecond)
}// 输出
// elapsed: 501.11125ms
1.5. 两阶段延迟执行
Go 的 defer 不仅仅是用于清理任务还可以用于准备任务考虑以下示例
func setupTeardown() func() {fmt.Println(Run initialization)return func() {fmt.Println(Run cleanup)}
}func main() {defer setupTeardown()() // --------fmt.Println(Main function called)
}// 输出
// Run initialization
// Main function called
// Run cleanup
这种模式的美妙之处在于只需一行代码你就可以完成诸如以下任务
打开数据库连接然后关闭它。设置模拟环境然后拆除它。获取分布式锁然后释放它。…
“嗯这似乎很聪明但它在现实中有什么用处呢”
还记得追踪执行时间的技巧吗我们也可以这样做
func TrackTime() func() {pre : time.Now()return func() {elapsed : time.Since(pre)fmt.Println(elapsed:, elapsed)}
}func main() {defer TrackTime()()time.Sleep(500 * time.Millisecond)
}
注意如果我连接到数据库时出现错误怎么办
确实像 defer TrackTime() 或 defer ConnectDB() 这样的模式不会妥善处理错误。这种技巧最适合用于测试或者当你愿意冒着致命错误的风险时使用参考下面这种面向测试的方法
func TestSomething(t *testing.T) {defer handleDBConnection(t)()// ...
}func handleDBConnection(t *testing.T) func() {conn, err : connectDB()if err ! nil {t.Fatal(err)}return func() {fmt.Println(Closing connection, conn)}
}
这样在测试期间可以处理数据库连接的错误。
2. 预分配切片
根据文章《Go 性能提升技巧》中的见解预分配切片或映射可以显著提高 Go 程序的性能。
但是值得注意的是如果我们不小心使用 append 而不是索引如 a[i]这种方法有时可能导致错误。你知道吗我们可以在不指定数组长度为零的情况下使用预分配的切片就像在上述文章中解释的那样这使我们可以像使用 append 一样使用预分配的切片
// 与其
a : make([]int, 10)
a[0] 1// 不如这样使用
b : make([]int, 0, 10)
b append(b, 1)
3. 链式调用
链式调用技术可以应用于函数指针接收器。为了说明这一点让我们考虑一个 Person 结构它有两个函数 AddAge 和 Rename用于对其进行修改。
type Person struct {Name stringAge int
}func (p *Person) AddAge() {p.Age
}func (p *Person) Rename(name string) {p.Name name
}
如果你想给一个人增加年龄然后给他们改名字常规的方法是
func main() {p : Person{Name: Aiden, Age: 30}p.AddAge()p.Rename(Aiden 2)
}
或者我们可以修改 AddAge 和 Rename 函数接收器使其返回修改后的对象本身即使它们通常不返回任何内容。
func (p *Person) AddAge() *Person {p.Agereturn p
}func (p *Person) Rename(name string) *Person {p.Name namereturn p
}
通过返回修改后的对象本身我们可以轻松地将多个函数接收器链在一起而无需添加不必要的代码行
p p.AddAge().Rename(Aiden 2)
4. Go 1.20 允许将切片解析为数组或数组指针
当我们需要将切片转换为固定大小的数组时不能直接赋值例如
a : []int{0, 1, 2, 3, 4, 5}
var b [3]int a[0:3]// 在变量声明中不能将 a[0:3]类型为 []int 的值赋值给 [3]int 类型的变量
// 不兼容的赋值
为了将切片转换为数组Go 团队在 Go 1.17 中更新了这个特性。随着 Go 1.20 的发布借助更方便的字面量转换过程变得更加简单
// Go 1.20
func Test(t *testing.T) {a : []int{0, 1, 2, 3, 4, 5}b : [3]int(a[0:3])fmt.Println(b) // [0 1 2]
}// Go 1.17
func TestM2e(t *testing.T) {a : []int{0, 1, 2, 3, 4, 5}b : *(*[3]int)(a[0:3])fmt.Println(b) // [0 1 2]
}
只是一个快速提醒你可以使用 a[:3] 替代 a[0:3]。我提到这一点是为了更清晰地说明。
5. 使用 “import _” 进行包初始化
有时在库中你可能会遇到结合下划线 (_) 的导入语句如下所示
import (_ google.golang.org/genproto/googleapis/api/annotations
)这将执行包的初始化代码init 函数而无需为其创建名称引用。这允许你在运行代码之前初始化包、注册连接和执行其他任务。
让我们通过一个示例来更好地理解它的工作原理
// 下划线
package underscorefunc init() {fmt.Println(init called from underscore package)
}
// main
package mainimport (_ lab/underscore
)func main() {}
// 输出init called from underscore package
6. 使用 “import .” 进行导入
在了解了如何使用下划线进行导入后让我们看看如何更常见地使用点 (.) 运算符。
作为开发者点 (.) 运算符可用于在不必指定包名的情况下使用导入包的导出标识符这对于懒惰的开发者来说是一个有用的快捷方式。
很酷对吧这在处理项目中的长包名时特别有用比如 externalmodel 或 doingsomethinglonglib。
为了演示这里有一个简单的例子
package mainimport (fmt. math
)func main() {fmt.Println(Pi) // 3.141592653589793fmt.Println(Sin(Pi / 2)) // 1
}
7. Go 1.20 允许将多个错误合并为单个错误
Go 1.20 引入了对错误包的新功能包括对多个错误的支持以及对 errors.Is 和 errors.As 的更改。
在 errors 中添加的一个新函数是 Join我们将在下面详细讨论它
var (err1 errors.New(Error 1st)err2 errors.New(Error 2nd)
)func main() {err : err1err errors.Join(err, err2)fmt.Println(errors.Is(err, err1)) // truefmt.Println(errors.Is(err, err2)) // true
}
如果有多个任务导致错误你可以使用 Join 函数而不是手动管理数组。这简化了错误处理过程。
8. 检查接口是否为真正的 nil
即使接口持有的值为 nil也不意味着接口本身为 nil。这可能导致 Go 程序中的意外错误。因此重要的是要知道如何检查接口是否为真正的 nil。
func main() {var x interface{}var y *int nilx yif x ! nil {fmt.Println(x ! nil) // -- 实际输出} else {fmt.Println(x nil)}fmt.Println(x)
}// 输出
// x ! nil
// nil
我们如何确定 interface{} 值是否为 nil 呢幸运的是有一个简单的工具可以帮助我们实现这一点
func IsNil(x interface{}) bool {if x nil {return true}return reflect.ValueOf(x).IsNil()
}
9. 在 JSON 中解析 time.Duration
当解析 JSON 时使用 time.Duration 可能是一个繁琐的过程因为它需要在一秒的后面添加 9 个零即 1000000000。为了简化这个过程我创建了一个名为 Duration 的新类型
type Duration time.Duration
为了将字符串如 “1s” 或 “20h5m”解析为 int64 类型的持续时间我还为这个新类型实现了自定义的解析逻辑
func (d *Duration) UnmarshalJSON(b []byte) error {var s stringif err : json.Unmarshal(b, s); err ! nil {return err}dur, err : time.ParseDuration(s)if err ! nil {return err}*d Duration(dur)return nil
}
但是需要注意的是变量 ‘d’ 不应为 nil否则可能会导致编组错误。或者你还可以在函数开头对 ‘d’ 进行检查。
10. 避免裸参数
当处理具有多个参数的函数时仅通过阅读其用法来理解每个参数的含义可能会令人困惑。考虑以下示例
printInfo(foo, true, true)
如果不检查 printInfo 函数那么第一个 ‘true’ 和第二个 ‘true’ 的含义是什么呢当你有一个具有多个参数的函数时仅通过阅读其用法来理解参数的含义可能会令人困惑。
但是我们可以使用注释使代码更易读。例如
// func printInfo(name string, isLocal, done bool)printInfo(foo, true /* isLocal */, true /* done */)
有些 IDE 也支持这个功能可以在函数调用建议中显示注释但可能需要在设置中启用。
以上是我分享的一些实用技巧但我不想让文章过长难以跟进因为这些技巧与特定主题无关涵盖了各种类别。
如果你觉得这些技巧有用或有自己的见解要分享请随时留言。我重视你的反馈并乐于在回应此文章时点赞或推荐你的想法。