当前位置: 首页 > news >正文

图片点击切换网站模板门户网站建设投资

图片点击切换网站模板,门户网站建设投资,dw网页制作模板源代码,网络品牌是什么前言 大家好#xff0c;这里是白泽。 《Go语言的100个错误以及如何避免》 是最近朋友推荐我阅读的书籍#xff0c;我初步浏览之后#xff0c;大为惊喜。就像这书中第一章的标题说到的#xff1a;“Go: Simple to learn but hard to master”#xff0c;整本书通过分析100…前言 大家好这里是白泽。 《Go语言的100个错误以及如何避免》 是最近朋友推荐我阅读的书籍我初步浏览之后大为惊喜。就像这书中第一章的标题说到的“Go: Simple to learn but hard to master”整本书通过分析100个错误使用 Go 语言的场景带你深入理解 Go 语言。 我的愿景是以这套文章在保持权威性的基础上脱离对原文的依赖对这100个场景进行篇幅合适的中文讲解。所涉内容较多总计约 8w 字这是该系列的第五篇文章对应书中第40-47个错误场景。 当然如果您是一位 Go 学习的新手您可以在我开源的学习仓库中找到针对 《Go 程序设计语言》 英文书籍的配套笔记其他所有文章也会整理收集在其中。 B站白泽talk公众号【白泽talk】聊天交流群622383022原书电子版可以加群获取。 前文链接 《Go语言的100个错误使用场景1-10代码和项目组织》《Go语言的100个错误使用场景11-20项目组织和数据类型》《Go语言的100个错误使用场景21-29数据类型》《Go语言的100个错误使用场景30-40数据类型与字符串使用》 5. 字符串 章节概述 了解 rune 的概念避免常见的字符串遍历和截取造成的错误避免由于字符串拼接和转换造成的低效代码避免获取子字符串造成的内存泄漏 5.5 无用的字符串转换#40 错误示例 func getBytes(reader io.Reader) ([]byte, error) {b, err : io.ReadAll(reader)if err ! nil {return nil, err}// 去除首尾空格return []byte(sanitize(string(b))), nil } ​ func sanitize(s string) string {return strings.TrimSpace(s) }正确示例 func getBytes(reader io.Reader) ([]byte, error) {b, err : io.ReadAll(reader)if err ! nil {return nil, err}// 去除首尾空格return sanitize(b), nil } ​ func sanitize(b []byte) []byte {return bytes.TrimSpace(b) }通常来说 bytes 库提供了与 strings 库相同功能的方法而且大多数 IO 相关的函数的输入输出都是 []byte而不是 string错误示例中将字符切片转换成字符串再转换成字符切片需要额外承担两次内存分配的开销。 5.6 获取子字符串操作和内存泄漏#41 假设有许多个 string 类型的 log 需要存储假设一个log有1000字节但是只需要存放 log 的前36字节不恰当的子字符串截取函数会导致内存泄漏。 示例代码 // 方式一 func (s store) handleLog(log string) error {if len(log) 36 {return errors.New(log is not correctly formatted)}uuid : log[:36]s.store(uuid)// Do something } // 方式二 func (s store) handleLog(log string) error {if len(log) 36 {return errors.New(log is not correctly formatted)}uuid : string([]byte(log[:36]))s.store(uuid)// Do something } // 方式三 func (s store) handleLog(log string) error {if len(log) 36 {return errors.New(log is not correctly formatted)}uuid : strings.Clone(log[:36])s.store(uuid)// Do something }和#26提到的子切片获取造成的内存泄漏一样获取子字符串操作执行后其底层依旧依赖原来的整个字符数组因此1000个字节内存依旧占用不会只有36个。通过将字符串转换为字节数组再转换为字符串虽然消耗了2次长度为36字节的内存分配但是释放了底层1000字节的原字节数组的依赖。有些 IDE 如 Goland 会提示语法错误因为本质来说将 string 转 []byte 再转 string 是一个累赘的操作。go1.18之后提供了一步到位的 strings.Clone 方法可以避免内存泄漏。 6. 函数和方法 章节概述 什么时候使用值或者指针类型的接受者什么时候命名的返回值以及其副作用避免返回 nil 接受者时的常见错误函数接受一个文件名并不是最佳实践处理 defer 的参数 6.1 不知道选择哪种类型的方法接受者#42 值接受者 type customer struct {balance float64 } ​ func (c customer) add(operation float64) {c.balance operation } ​ func main() {c : customer{balance: 100.0}c.add(50.0)fmt.Printf(%.2f\n, c.balance) // 结果为 100.00 }指针接受者 type customer struct {balance float64 } ​ func (c *customer) add(operation float64) {c.balance operation } ​ func main() {c : customer{balance: 100.0}c.add(50.0)fmt.Printf(%.2f\n, c.balance) // 结果为 150.00 }值接受者在方法内修改自身结构的值不会对调用方造成实际影响。 一些实践的建议 必须使用指针接受者的场景 如果方法需要修改原始的接受者。如果方法的接受者包含不可以被拷贝的字段。 建议使用指针接受者的场景 如果接受者是一个巨大的对象使用指针接受者可以更加高效避免了拷贝内存。 必须使用值接受者的场景 如果我们必须确保接受者是不变的。如果接受者是一个 map, function, channel否则会出现编译错误。 建议使用值接受者的场景 如果接受者是一个切片且不会被修改。如果接受者是一个小的数组或者结构体不含有易变的字段。如果接受者是基本类型如int, float64, string。 特殊情况 type customer struct {data *data } ​ type data struct {balance float64 } ​ func (c customer) add(operation float64) {c.data.balance operation } ​ func main() {c : customer{data: data {balance: 100.0}}c.add(50.0)fmt.Printf(%.2f\n, c.data.balance) // 150.00 }在这种情况下即使方法接受者 c 不是指针类型但是修改依旧可以生效。 但是为了清楚起见通常还是将 c 声明成指针类型如果它是可操作的。 6.2 从来不使用命名的返回值#43 如果使用命名返回值 func f(a int) (b int) {b areturn }推荐使用命名返回值的场景举例 // 场景一 type locator interface {getCoordinates(address string) (lat, lng float32, err error) } // 场景二 func ReadFull(r io.Reader, buf []byte) (n int, err error) {// 两个返回值被初始化为对应类型的零值0和nilfor len(buf) 0 err nil {var nr intnr, err r.Read(buf)n nrbuf buf[nr:]}return }场景一通过命名返回值提高接口的可读性 场景二通过命名返回值节省编码量 最佳实践需要权衡使用命名返回值是否能带来收益如果可以就果断使用吧 6.3 使用命名返回值造成的意外副作用#44 注意使用命名返回值的方法并不意味着必须返回单个 return有时可以只为了函数签名清晰而使用命名返回值。 错误场景 func (l loc) getCoordinates(ctx content.Content, address string) (lat, lng float32, err error) {isValid : l.validateAddress(address)if !isValid {return 0, 0, errors.New(invalid address)}if ctx.Err() ! nil {return 0, 0, err}// Do something and return }此时由于 ctx.Err() ! nil 成立时并没有为 err 赋值因此返回的 err 永远都是 nil。 修正方案 func (l loc) getCoordinates(ctx content.Content, address string) (lat, lng float32, err error) {isValid : l.validateAddress(address)if !isValid {return 0, 0, errors.New(invalid address)}if err ctx.Err(); err ! nil {// 这里原则上可以返回单个return但是最好保持风格统一return 0, 0, err}// Do something and return }6.4 返回一个 nil 接受者#45 提示在 Go 语言当中方法就像是函数的语法糖一样相当于函数的第一个参数是方法的接受者nil 可以作为参数因此 nil 接受者可以触发方法因此不同于纯粹的 nil interface。 type Foo struct {} ​ func (foo *Foo) Bar() string {return bar } ​ func main() {var foo *Foofmt.Println(foo.Bar()) // 虽然 foo 动态值是 nil但动态类型不是nil是可以打印出 bar }错误示例 type MultiError struct {errs []string } ​ func (m *MultiError) Add(err error) {m.errs append(m.errs, err.Error()) } ​ func (m *MultiError) Error() string {return stirngs.Join(m.errs, ;) } ​ func (c Customer) Validate() error {var m *MultiErrorif c.Age 0 {m MultiError{}m.Add(errors.New(age is negative))}if c.Name {if m nil {m MultiError{}}m.Add(errors.New(age is nil))}return m } ​ func main() {// 传入的两个参数都不会触发 Validate 的 err 校验customer : Customer{Age: 33, Name: John}if err : customer.Validate(); err ! nil {// 但是无论如何都会打印这行语句err ! nil 永远成立log.Fatalf(customer is invalid: %v, err)} }提示Go 语言的接口有动态类型和动态值两个概念 上述错误示例中即使通过了两个验证Validate 返回了 m此时这个接口承载的动态类型是 *MultiError它的动态值是 nil但是通过 判断一个 err 为 nil或者说一个接口为 nil要求其底层类型和值都是 nil 才会成立。 正确方案 func (c Customer) Validate() error {var m *MultiErrorif c.Age 0 {m MultiError{}m.Add(errors.New(age is negative))}if c.Name {if m nil {m MultiError{}}m.Add(errors.New(age is nil))}if m ! nil {return m}return nil }此时返回的是一个 nil interface是存粹的。而不是一个非 nil 动态类型的 interfere 返回值。 6.5 使用文件名作为函数的输入#46 编写一个从文件中按行读取内容的函数。 错误示例 func countEmptyLinesInFile(filename string) (int, error) {file, err : os.Open(filename)if err ! nil {return 0, err}scanner : bufio.NewScanner(file)for scanner.Scan() {// ...} }弊端 每当需要做不同功能的单元测试需要单独创建一个文件。这个函数将无法被复用因为它依赖于一个具体的文件名如果是从其他输入源读取将需要重新编写函数。 修正方案 func countEmptyLines(reader io.Reader) (int, error) {scanner : bufio.NewScanner(reader)for scanner.Scan() {// ...} } ​ func TestCountEmptyLines(t *testing.T) {emptyLines, err : countEmptyLines(strings.NewReader(foobarbaz))// 测试逻辑 }通过这种方式可以将输入源进行抽象从而满足来自任何输入的读取文件字符串HTTP RequestgRPC Request等编写单元测试也十分便利。 6.6 不理解 defer 参数和接收者是如何确定的#47 defer 声明的函数的参数值在声明时确定 const (StatusSuccess successStatusErrorFoo error_fooStatusErrorBar error_bar ) ​ func f() error {var status stringdefer notify(status)defer incrementCounter(status)if err : foo(); err ! nil {status StatusErrorFooreturn err}if err : bar(); err ! nil {status StatusErrorBarreturn err}status StatusSuccessreturn nil }上述示例中无论是否会在 foo 和 bar 函数的调用后返回 errstatus 的值传递给 notify 和 incrementCount 函数的都是空字符串因为 defer 声明的函数的参数值在声明时确定。 修正方案1 func f() error {var status string// 修改为传递地址defer notify(status)defer incrementCounter(status)if err : foo(); err ! nil {status StatusErrorFooreturn err}if err : bar(); err ! nil {status StatusErrorBarreturn err}status StatusSuccessreturn nil }因为地址一开始确定所以无论后续如何为 status 赋值都可以通过地址获取到最新的值。这种方式的缺点是需要修改 notify 和 incrementCounter 两个函数的传参形式。 defer 声明一个闭包则闭包内使用的外部变量的值将在闭包执行的时候确定。 func main() {i : 0j : 0defer func(i int) {fmt.Println(i, j)}(i)ij }因为 i 作为匿名函数的参数传入因此值在一开始确定而 j 是闭包内使用外部的变量因此在 return 之前确定值。最后打印结果 i 0, j 1。 修正方案2: func f() error {var status stringdefer func() {notify(status)incrementCounter(status)}() }通过使用闭包将 notify 和 incrementCounter 函数包裹则 status 的值使用闭包外侧的变量 status因此 status 的值会在闭包执行的时候确定这种修改方式也无需修改两个函数的签名更为推荐。 指针和值接收者 值接收者 func main() {s : Struct{id: foo}defer s.print()s.id bar } ​ type Struct struct {id string } ​ func (s Struct) print() {fmt.Println(s.id) }打印的结果是 foo因为 defer 后声明的 s.print() 的接收者 s 将在一开始获得一个拷贝foo 作为 id 已经固定。 指针接收者 func main() {s : Struct{id: foo}defer s.print()s.id bar } ​ type Struct struct {id string } ​ func (s *Struct) print() {fmt.Println(s.id) }打印结果是 bardefer 后声明的 s.print() 的接收者 s 将在一开始获得一份拷贝因为是地址的拷贝所以对 return 之前的改动有感知。 小结 已完成《Go语言的100个错误》全书学习进度47%欢迎追更。
http://www.dnsts.com.cn/news/190522.html

相关文章:

  • 汕头建站模板厂家商务网官网
  • 网站建设需要ui吗洛阳做网站哪家专业
  • 营销型网站的评价标准石家庄市建设局网站首页
  • 宏杰zkeys网站模板吉安建设网站
  • 杭州网站设计公司网站设计公司 无锡
  • 海洋网络提供网站建设政务服务中心网站建设实施方案
  • 白山商城网站建设简约wordpress模板
  • 抚州网站推广淄博网站建设 leda.cc
  • 长沙做网站的包吃包住4000开网店如何找货源和厂家
  • 蓝德网站建设做网站工作条件
  • 编程开源网站网站子页面怎么做
  • 企业门户网站管理办法福州百度代理
  • 东莞网站建设0086唐山网站建设zzvg
  • 做电脑租赁网站山东军辉建设集团有限公司 公司网站网址
  • 江西省住房和城乡建设厅网站首页著名食品包装设计的案例
  • 建设房产网站展会广告策划公司
  • 做数据分析网站网站开发亿玛酷信赖
  • 英文建站模板百度地图3d实景地图
  • 中小企业商务网站建设网站推广话术与技巧
  • 怎么自己建设公司网站石家庄专业商城网站制作
  • 免费入驻的网站设计平台做网站可以申请国家补助吗
  • 网站建设杭州缘择低价活动线报资源网
  • 如何做有后台的网站云服务器安装网站
  • 建立网站站点的过程如何做网络营销推广ppt
  • 装修广告做哪个网站最好看wordpress叶子
  • 网站配置优化通过域名访问网站
  • 淘宝领卷网站什么做信息化建设
  • 重庆云端设计网站建设作者简介网页制作模板
  • 平湖专业网站制作苏州网站设计制作公司
  • 谷城建设局网站视频网站dedecms