版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Go语言的10个实用技术这里是我过去几年中编写的大量Go代码的经验总结而来的自己的最佳实践。我相信它们具有弹性的。这里的弹性是指:某个应用需要适配一个灵活的环境。你不希望每过3到4个月就不得不将它们全部重构一遍。添加新的特性应当很容易。许多人参与开发该应用,它应当可以被理解,且维护简单。许多人使用该应用,bug应该容易被发现并且可以快速的修复。我用了很长的时间学到了这些事情。其中的一些很微小,但对于许多事情都会有影响。所有这些都仅仅是建议,具体情况具体对待,并且如果有帮助的话务必告诉我。随时留言:)使用单一的GOPATH多个GOPATH的情况并不具有弹性。GOPATH本身就是高度自我完备的(通过导入路径)。有多个GOPATH会导致某些副作用,例如可能使用了给定的库的不同的版本。你可能在某个地方升级了它,但是其他地方却没有升级。而且,我还没遇到过任何一个需要使用多个GOPATH的情况。所以只使用单一的GOPATH,这会提升你Go的开发进度。许多人不同意这一观点,接下来我会做一些澄清。像etcd或camlistore这样的大项目使用了像godep这样的工具,将所有依赖保存到某个目录中。也就是说,这些项目自身有一个单一的GOPATH。它们只能在这个目录里找到对应的版本。除非你的项目很大并且极为重要,否则不要为每个项目使用不同的GOPATHo如果你认为项目需要一个自己的GOPATH目录,那么就创建它,否则不要尝试使用多个GOPATH。它只会拖慢你的进度。将for-select封装到函数中如果在某个条件下,你需要从for-select中退出,就需要使用标签。例如:ain(){r{select{case<-time.After(time.Second):fmt.Println("hello")default:breakL}fmt.Println("ending")}如你所见,需要联合break使用标签。这有其用途,不过我不喜欢。这个例子中的for循环看起来很小,但是通常它们会更大,而判断break的条件也更为冗长。如果需要退出循环,我会将for-select封装到函数中:ain(){o()t.Println("ending")oo(){r{select{case<-time.After(time.Second):fmt.Println("hello")default:return}你还可以返回一个错误(或任何其他值),也是同样漂亮的,只需要:塞:=foo();err!=nil{处理err在初始化结构体时使用带有标签的语法这是一个无标签语法的例子:typeTstruet{FoostringBarint}funemain(){t:二T{"example",123}//无标签语法fmt.Printf("t%+v\n",t)}那么如果你添加一个新的字段到T结构体,代码会编译失败:struet{ostringrintxstringain(){:=T{"example",123}//无法编译t.Printf("t%+v\n",t)如果使用了标签语法,Go的兼容性规则(/doe/go1eompat)会处理代码。例如在向net包的类型添加叫做Zone的字段,参见:http:///doc/go1.1#library。回到我们的例子,使用标签语法:struet{ostringrintxstringain(){:=T{Foo:"example",Qux:123}t.Printf("t%+v\n",t)这个编译起来没问题,而且弹性也好。不论你如何添加其他字段到T结构体。你的代码总是能编译,并且在以后的Go的版本也可以保证这一点。只要在代码集中执行govet,就可以发现所有的无标签的语法。将结构体的初始化拆分到多行如果有两个以上的字段,那么就用多行。它会让你的代码更加容易阅读,也就是说不要:""example"",Bar:someLongVariable,Qux:anotherLongVariable,B:forgetToAddTh而是:o:example,r:someLongVariable,x:anotherLongVariable,forgetToAddThisToo,为整数常量添加String()方法如果你利用iota来使用自定义的整数枚举类型,务必要为其添加String()方法。例如,像这样:tateint(nningState=iotaoppedbootingrminated如果你创建了这个类型的一个变量,然后输出,会得到一个整数ain(){ate:=Runningprint:""state0〃t.Println(〃state",state)除非你回顾常量定义,否则这里的0看起来毫无意义。只需要为State类型添加String()方法就可以修复这个问题(/p/ewMK16K302):
func(sState)String()string{switchs{caseRunning:return""Running"caseStopped:return""Stopped"caseRebooting:return""Rebooting""caseTerminated:return""Terminated""default:return""Unknown"}}新的输出是:state:Running。显然现在看起来可读性好了很多。在你调试程序的时候,这会带来更多的便利。同时还可以在实现MarshalJSON()、UnmarshalJSON()这类方法的时候使用同样的手段。让iota从a+1开始增量在前面的例子中同时也产生了一个我已经遇到过许多次的bug。假设你有一个新的结构体,有一个State字段:struct{mestringrtintateState现在如果基于T创建一个新的变量,然后输出,你会得到奇怪的结果ain(){:=T{Name:""example",Port:6666}prints:""t{Name:examplePort:6666State:Running}""t.Printf(""t%+v\n"",t)看到bug了吗?State字段没有初始化,Go默认使用对应类型的零值进行填充。由于State是一个整数,零值也就是0,但在我们的例子中它表示Runningo
那么如何知道State被初始化了?还是它真得是在Running模式?没有办法区分它们,那么这就会产生未知的、不可预测的bug。不过,修复这个很容易,只要让iota从+1开始(nningState=iota+1oppedbootingrminated现在t变量将默认输出Unknown,不是吗?:ain(){:二T{Name:""example",Port:6666}输出:""t{Name:examplePort:6666State:Unknown}"t.Printf(""t%+v\n〃,t)不过让iota从零值开始也是一种解决办法。例如,你可以引入一个新的状态叫做Unknown,将其修改为:(knownState=iotanningoppedbootingrminated返回函数调用我已经看过很多代码例如(/p/8RzlEJwFTZ):ar()(string,error){err:=foo()err!=nil{.〃〃return,errreturnv,nil然而,你只需要:ar()(string,error){turnfoo()更简单也更容易阅读(当然,除非你要对某些内部的值做一些记录)。把slice、map等定义为自定义类型将slice或map定义成自定义类型可以让代码维护起来更加容易。假设有一个Server类型和一个返回服务器列表的函数:erverstruct{mestringistServers()[]Server{turn[]Server{{Name:"Serverl"},{Name:"Server2"},{Name:"Foo1"},{Name:"Foo2"},现在假设需要获取某些特定名字的服务器。需要对ListServers()做一些改动,增加筛选条件:tServers返回服务器列表。只会返回包含name的服务器。空的name将会返回所有istServers(namestring)[]Server{rvers:二[]Server{{Name:"Server1"},{Name:"Server2"},{Name:"Foo1"},{Name:"Foo2"},
}//返回所有服务器ifname== {returnservers}//返回过滤后的结果filtered:二make([]Server,0)for_,server:=rangeservers{ifstrings.Contains(server.Name,name){filtered=append(filtered,server)}}returnfiltered}现在可以用这个来筛选有字符串Foo的服务器:ain(){rvers:=ListServers("Foo")输出:“servers[{Name:Fool}{Name:Foo2}]”t.Printf("servers%+v\n",servers)显然这个函数能够正常工作。不过它的弹性并不好。如果你想对服务器集合引入其他逻辑的话会如何呢?例如检查所有服务器的状态,为每个服务器创建一个数据库记录,用其他字段进行筛选等等……现在引入一个叫做Servers的新类型,并且修改原始版本的ListServers()返回这个新类型:ervers[]ServertServers返回服务器列表istServers()Servers{turn[]Server{{Name:"Serverl"},{Name:"Server2"},
{Name:"Fool"},{Name:"Foo2"},}}现在需要做的是只要为Servers类型添加一个新的Filter()方法:ter返回包含name的服务器。空的name将会返回所有服务器。sServers)Filter(namestring)Servers{ltered:二make(Servers,0)r_,server:=ranges{ifstrings.Contains(server.Name,name){filtered=append(filtered,server)}turnfiltered现在可以针对字符串Foo筛选服务器:ain(){rvers:=ListServers()rvers=servers.Filter("Foo")t.Printf("servers%+v\n",servers)哈!看到你的代码是多么的简单了吗?还想对服务器的状态进行检查?或者为每个服务器添加一条数据库记录?没问题,添加以下新方法即可:sServers)Check()sServers)AddRecord()sServers)Len()w辻hContext封装函数有时对于函数会有一些重复劳动,例如锁/解锁,初始化一个新的局部上下文,准备初始化变量等等……这里有一个例子:
funcfoo(){mu.Lock()defermu.Unlock()//foo相关的工作}funcbar(){mu.Lock()defermu.Unlock()//bar相关的工作}funcqux(){mu.Lock()defermu.Unlock()//qux相关的工作}如果你想要修改某个内容,你需要对所有的都进行修改。如果它是一个常见的任务,那么最好创建一个叫做withContext的函数。这个函数的输入参数是另一个函数,并用调用者提供的上下文来调用它:ithLockContext(fnfunc()){.Lockfermu.Unlock()()只需要将之前的函数用这个进行封装:oo(){thLockContext(func(){//foo相关工作ar(){thLockContext(func(){
//bar相关工作})}funcqux(){withLockContext(func(){//qux相关工作})}不要光想着加锁的情形。对此来说最好的用例是数据库链接。现在对withContext函数作一些小小的改动:ithDBContext(fnfunc(dbDB)error)error{从连接池获取一个数据库连接Conn:二NewDB()turnfn(dbConn)如你所见,它获取一个连接,然后传递给提供的参数,并且在调用函数的时候返回错误。你需要做的只是:oo(){thDBContext(func(db*DB)error{//foo相关工作ar(){thDBContext(func(db*DB)error{//bar相关工作ux(){thDBContext(func(db*DB)error{//qux相关工作你在考虑一个不同的场景,例如作一些预初始化?没问题,只需要将它们加到
withDBContext就可以了。这对于测试也同样有效。这个方法有个缺陷,它增加了缩进并且更难阅读。再次提示,永远寻找最简单的解决方案。为访问map增加setter,getters如果你重度使用map读写数据,那么就为其添加getter和setter吧。通过getter和setter你可以将逻辑封分别装到函数里。这里最常见的错误就是并发访问。如果你在某个goroutein里有这样的代码:"]=bar还有这个:(m,"foo")会发生什么?你们中的大多数应当已经非常熟悉这样的竞态了。简单来说这个竞态是由于map默认并非线程安全。不过你可以用互斥量来保护它们:k()〃门 〃1 〃」
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 专业前台接待服务供应协议
- 2025年度离婚协议书范本:共同债务的承担与偿还4篇
- 2025年度新能源汽车充电设施购销合同4篇
- 2025年度茶叶电商平台入驻合作协议书4篇
- 2025年度柴油储备与应急供应合同范本4篇
- 2024年05月内蒙古2024届中国民生银行呼和浩特分行毕业生“未来银行家”暑期管培生校园招考笔试历年参考题库附带答案详解
- 2025年度汽车内饰部件委托加工合同书4篇
- 个性化2024版个人劳动协议汇编版A版
- 2024金融借款协议样本版
- 2025年度农产品出口FAS贸易合同范本3篇
- 第二章 运营管理战略
- 《三本白皮书》全文内容及应知应会知识点
- 专题14 思想方法专题:线段与角计算中的思想方法压轴题四种模型全攻略(解析版)
- 医院外来器械及植入物管理制度(4篇)
- 图像识别领域自适应技术-洞察分析
- 港口与港口工程概论
- 新概念英语第二册考评试卷含答案(第49-56课)
- 商业伦理与企业社会责任(山东财经大学)智慧树知到期末考试答案章节答案2024年山东财经大学
- 【奥运会奖牌榜预测建模实证探析12000字(论文)】
- (完整版)译林版英语词汇表(四年级下)
- 哈尔滨师范大学与堪培拉大学合作培养
评论
0/150
提交评论