F: 强制
golangci-lint
静态代码分析工具,词语 Linter 指的是一种分析源代码以此标记编程错误、代码缺陷、风格错误的工具。
集合多种 Linter 的工具。要查看支持的 Linter 列表以及启用 / 禁用了哪些 Linter,可以使用下面的命令:
golangci-lint help linters
Go 语言定义了实现 Linter 的 API,它还提供了 golint 工具,用于集成了几种常见的 Linter。在源码中,我们可以查看怎么在标准库中实现典型的 Linter。
在大型项目中刚开始使用 golang-lint 会出现大量的错误,这种情况下我们只希望扫描增量的代码。如下所示,可以通过在golangci-lint 配置文件中调整 new-from-rev 参数,配置以当前基准分支为基础实现增量扫描
linters:
enable-all: true
issues:
new-from-rev: master
Pre-Commit
race
Go 1.1 提供了强大的检查工具 race 来排查数据争用问题。race 可以用在多个 Go 指令中,一旦检测器在程序中找到数据争用,就会打印报告。这份报告包含发生 race 冲突的协程栈,以及此时正在运行的协程栈。可以在编译时和运行时执行 race,方法如下:
$ go test -race mypkg
$ go run -race mysrc.go
$ go build -race mycmd
$ go install -race mypkg
在下面这个例子中, 运行中加入 race 检查后直接报错。从报错后输出的栈帧信息中,我们能看出具体发生并发冲突的位置。
» go run -race 2_race.go
==================
WARNING: DATA RACE
Read at 0x00000115c1f8 by goroutine 7:
main.add()
bookcode/concurrence_control/2_race.go:5 +0x3a
Previous write at 0x00000115c1f8 by goroutine 6:
main.add()
bookcode/concurrence_control/2_race.go:5 +0x56
第四行 Read at 表明读取发生在 2_race.go 文件的第 5 行,而第七行 Previous write 表明前一个写入也发生在 2_race.go 文件的第 5 行。这样我们就可以非常快速地定位数据争用问题了。
竞争检测的成本因程序而异。对于典型的程序,内存使用量可能增加 5~10 倍,执行时间会增加 2~20 倍。同时,竞争检测器会为当前每个 defer 和 recover 语句额外分配 8 字节。在 Goroutine 退出前,这些额外分配的字节不会被回收。这意味着,如果有一个长期运行的 Goroutine,而且定期有 defer 和 recover 调用,那么程序内存的使用量可能无限增长。(这些内存分配不会显示到 runtime.ReadMemStats 或 runtime / pprof 的输出。)
覆盖率
好处:
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
任何傻瓜都可以编写电脑能理解的代码。好的程序员编写的是人类可以理解的代码。
整洁
格式化
代码长度
F(行 120)
F(函数 40)
F(文件 2000)
代码布局
包注释: 对整个模块和功能的完整描述,写在文件头部。
Package:包名称。
Imports: 引入的包。
[F goimports] 当 import 多个包时,应该对包进行分组。同一组的包之间不需要有空行,不同组之间的包需要一个空行。标准库的包应该放在第一组。
import (
"fmt"
"hash/adler32"
"os"
"appengine/foo"
"appengine/user"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
Constants:常量定义。
Typedefs:类型定义。
Globals:全局变量定义。
Functions:函数实现。
每个部分之间用一个空行分割。每个部分有多个类型定义或者有多个函数时,也用一个空行分割。
示例如下:
/*
注释
*/
package http
import (
"fmt"
"time"
)
const (
VERSION = "1.0.0"
)
type Request struct{
}
var msg = "HTTP success"
func foo() {
//...
}
空格与缩进
[F gofmt] 注释和声明应该对齐
[F gofmt] 小括号 ()、中括号[]、大括号{} 内侧都不加空格。
[F gofmt] 逗号、冒号(slice 中冒号除外)前都不加空格,后面加 1 个空格。
[F gofmt] 所有二元运算符前后各加一个空格,作为函数参数时除外。例如b := 1 + 2。
[F gofmt] 使用 Tab 而不是空格进行缩进。
[F nlreturn] return 前方需要加一个空行,让代码逻辑更清晰。
[F gofmt] 判断语句、for 语句需要缩进 1 个 Tab,并且右大括号}与对应的 if 关键字垂直对齐。例如:
if xxx {
} else {
}
[推荐] 避免 else 语句中处理错误返回,避免正常的逻辑位于缩进中。如下代码实例,else 中进行错误处理,代码逻辑阅读起来比较费劲。
if something.OK() {
something.Lock()
defer something.Unlock()
err := something.Do()
if err == nil {
stop := StartTimer()
defer stop()
log.Println("working...")
doWork(something)
<-something.Done() // wait for it
log.Println("finished")
return nil
} else {
return err
}
} else {
return errors.New("something not ok")
}
如果把上面的代码修改成下面这样会更加清晰:
if !something.OK() {
return errors.New("something not ok")
}
something.Lock()
defer something.Unlock()
err := something.Do()
if err != nil {
return err
}
stop := StartTimer()
defer stop()
log.Println("working...")
doWork(something)
<-something.Done() // wait for it
log.Println("finished")
return nil
[推荐] 函数内不同的业务逻辑处理建议用单个空行加以分割。
[推荐] 注释之前的空行通常有助于提高可读性——新注释的引入表明新思想的开始。
命名
短,容易拼写;
保持一致性;
意思准确,容易理解,没有虚假和无意义的信息。
[F revive] Go 中的命名统一使用驼峰式、不要加下划线。
[F revive] 缩写的专有名词应该大写,例如: ServeHTTP、IDProcessor。
[强制] 区分变量名应该用有意义的名字,而不是使用阿拉伯数字:a1, a2, … aN。
[强制] 不要在变量名称中包含你的类型名称。
[建议]变量的作用域越大,名字应该越长。
包名
简短而清晰
[强制] 使用简短的小写字母,不需要下划线或混合大写字母。
[建议] 合理使用缩写,例如:
strconv(字符串转换)
syscall(系统调用)
fmt(格式化的 I/O)
[强制] 避免无意义的包名,例如 util,common,base 等。
感觉经常有些通用的
接口命名
变量命名
函数命名
[建议]如果函数参数的类型已经能够看出参数的含义,那么函数参数的命名应该尽量简短:
func AfterFunc(d Duration, f func()) *Timer
func Escape(w io.Writer, s []byte)
[建议]如果函数参数的类型不能表达参数的含义,那么函数参数的命名应该尽量准确:
func Unix(sec, nsec int64) Time
func HasPrefix(s, prefix []byte) bool
[建议] 对于公开的函数,返回值具有文档意义,应该准确表达含义,如下所示:
func Copy(dst Writer, src Reader) (written int64, err error)
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
可导出的变量名: 由于使用可导出的变量时会带上它所在的包名,因此,不需要对变量重复命名。例如 bytes 包中的 ByteBuffer 替换为 Buffer,这样在使用时就是 bytes.Buffer,显得更简洁。类似的还有把 strings.StringReader 修改为 strings.Reader,把 errors.NewError 修改为 errors.New。
Error 值命名
[建议] 错误类型应该以 Error 结尾。
[建议] Error 变量名应该以 Err 开头。
type ExitError struct {
...
}
var ErrFormat = errors.New("image: unknown format")
函数
[强制 cyclop] 圈复杂度(Cyclomatic complexity)<10。
[强制 gochecknoinits] 避免使用 init 函数。
[强制 revive] Context 应该作为函数的第一个参数。
[强制] 正常情况下禁用 unsafe。
[强制] 禁止 return 裸返回,如下例中第一个 return:
func (f *Filter) Open(name string) (file File, err error) {
for _, c := range f.chain {
file, err = c.Open(name)
if err ! = nil {
return
}
}
return f.source.Open(name)
}
[强制] 不要在循环里面使用 defer,除非你真的确定 defer 的工作流程。
[强制] 对于通过:= 进行变量赋值的场景,禁止出现仅部分变量初始化的情况。例如在下面这个例子中,f 函数返回的 res 是初始化的变量,但是函数返回的 err 其实复用了之前的 err:
var err error
res,err := f()
[建议] 函数返回值大于 3 个时,建议通过 struct 进行包装。
[建议] 函数参数不建议超过 3 个,大于 3 个时建议通过 struct 进行包装。
控制结构
方法
[强制 revive] receiver 的命名要保持一致,如果你在一个方法中将接收器命名为 “c”,那么在其他方法中不要把它命名为 “cl”。
[强制] receiver 的名字要尽量简短并有意义,禁止使用 this、self 等。
func (c Client) done() error {
// ...
}
func (cl Client) call() error {
// ...
}
注释
[强制] 无用注释直接删除,无用的代码不应该注释而应该直接删除。即使日后需要,我们也可以通过 Git 快速找到。
[强制] 紧跟在代码之后的注释,使用 //。
[强制] 统一使用中文注释,中英文字符之间严格使用空格分隔。
[强制] 注释不需要额外的格式,例如星号横幅。
[强制] 包、函数、方法和类型的注释说明都是一个完整的句子,以被描述的对象为主语开头。Go 源码中都是这样的。
// queueForIdleConn queues w to receive the next idle connection for w.cm.
// As an optimization hint to the caller, queueForIdleConn reports whether
// it successfully delivered an already-idle connection.
func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool)
[强制] Go 语言提供了文档注释工具 go doc,可以生成注释和导出函数的文档。文档注释的写法可以参考文稿中的链接。
[强制 godot] 注释最后应该以句号结尾。
[建议] 当某个部分等待完成时,可用 TODO: 开头的注释来提醒维护人员。
[建议] 大部分情况下使用行注释。块注释主要用在包的注释上,不过块注释在表达式中或禁用大量代码时很有用。
[建议] 当某个部分存在已知问题需要修复或改进时,可用 FIXME: 开头的注释来提醒维护人员。
[建议] 需要特别说明某个问题时,可用 NOTE: 开头的注释。
结构体
一致
高效
[强制] Map 在初始化时需要指定长度make(map[T1]T2, hint)。
[强制] Slice 在初始化时需要指定长度和容量make([]T, length, capacity)。扩容性能损失
[强制] time.After() 在某些情况下会发生泄露,替换为使用 Timer。
[强制] 数字与字符串转换时,使用 strconv,而不是 fmt。
[强制] 读写磁盘时,使用读写 buffer。
[建议] 谨慎使用 Slice 的截断操作和 append 操作,除非你知道下面的代码输出什么:
x := []int{1, 2, 3, 4}
y := x[:2]
fmt.Println(cap(x), cap(y))
y = append(y, 30)
fmt.Println("x:", x)
fmt.Println("y:", y)
[建议] 任何书写的协程,都需要明确协程什么时候退出。
[建议] 热点代码中,内存分配复用内存可以使用 sync.Pool 提速。
[建议] 将频繁的字符串拼接操作(+=),替换为 StringBuffer 或 StringBuilder。
[建议] 使用正则表达式重复匹配时,利用 Compile 提前编译提速。
[建议] 当程序严重依赖 Map 时,Map 的 Key 使用 int 而不是 string 将提速。
[建议] 多读少写的场景,使用读写锁而不是写锁将提速。
健壮
[强制] 除非出现不可恢复的程序错误,否则不要使用 panic 来处理常规错误,使用 error 和多返回值。
[强制 revive] 错误信息不应该首字母大写(除专有名词和缩写词外),也不应该以标点符号结束。因为错误信息通常在其他上下文中被打印。
[强制 errcheck] 不要使用 _ 变量来丢弃 error。如果函数返回 error,应该强制检查。
[建议] 在处理错误时,如果我们逐层返回相同的错误,那么在最后日志打印时,我们并不知道代码中间的执行路径。例如找不到文件时打印的No such file or directory,这会减慢我们排查问题的速度。因此,在中间处理 err 时,需要使用 fmt.Errorf 或第三方包给错误添加额外的上下文信息。像下面这个例子,在 fmt.Errorf 中,除了实际报错的信息,还加上了授权错误信息authenticate failed :
func AuthenticateRequest(r *Request) error {
err := authenticate(r.User)
if err != nil {
return fmt.Errorf("authenticate failed: %v", err)
}
return nil
}
有多个错误需要处理时,可以考虑将 fmt.Errorf 放入 defer 中:
func DoSomeThings(val1 int, val2 string) (_ string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("in DoSomeThings: %w", err)
}
}()
val3, err := doThing1(val1)
if err != nil {
return "", err
}
val4, err := doThing2(val2)
if err != nil {
return "", err
}
return doThing3(val3, val4)
}
[强制] 利用 recover 捕获 panic 时,需要由 defer 函数直接调用。
例如,下面例子中的 panic 是可以被捕获的:
package main
import "fmt"
func printRecover() {
r := recover()
fmt.Println("Recovered:", r)
}
func main() {
defer printRecover()
panic("OMG!")
}
但是下面这个例子中的 panic 却不能被捕获:
package main
import "fmt"
func printRecover() {
r := recover()
fmt.Println("Recovered:", r)
}
func main() {
defer func() {
printRecover()
}()
panic("OMG!")
}
[强制] 不用重复使用 recover,只需要在每一个协程的最上层函数拦截即可。recover 只能够捕获当前协程,而不能跨协程捕获 panic,下例中的 panic 就是无法被捕获的.
[强制] 有些特殊的错误是 recover 不住的,例如 Map 的并发读写冲突。这种错误可以通过 race 工具来检查。
可扩展
[建议] 利用接口实现扩展性。接口特别适用于访问外部组件的情况,例如访问数据库、访问下游服务。另外,接口可以方便我们进行功能测试。关于接口的最佳实践,需要单独论述。
[建议] 使用功能选项模式对一些公共 API 的构造函数进行扩展,大量第三方库例如 gomicro、zap 等都使用了这种策略。
db.Open(addr, db.DefaultCache, zap.NewNop())
可以替换为=>
db.Open(
addr,
db.WithCache(false),
db.WithLogger(log),
)
性能对比
「此文章为3月Day9学习笔记,内容来源于极客时间《Go分布式爬虫实战》,强烈推荐该课程!/推荐该课程」
我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
在Railcasts上,我注意到一个非常有趣的功能“转到符号”窗口。它像Command-T一样工作,但显示当前文件中可用的类和方法。如何在vim中获取它? 最佳答案 尝试:helptags有各种程序和脚本可以生成标记文件。此外,标记文件格式非常简单,因此很容易将sed(1)或类似的脚本组合在一起,无论您使用何种语言,它们都可以生成标记文件。轻松获取标记文件(除了下载生成器之外)的关键在于格式化样式而不是实际解析语法。 关于ruby-on-rails-Textmate'Gotosymbol
写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c
TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是
我有一个启动DRb服务的脚本,然后生成处理程序对象并通过DRb.thread.join等待。我希望脚本一直运行直到被明确杀死,所以我添加了trap"INT"doDRb.stop_serviceend在Ruby1.8下成功停止DRb服务并退出,但在1.9下似乎死锁(在OSX10.6.7上)。对该进程进行采样显示在semaphore_wait_signal_trap中有几个线程在旋转。我假设我在调用stop_service时做错了什么,但我不确定是什么。谁能给我任何关于如何正确处理它的指示? 最佳答案 好的,我想我已经找到了解决方案。如
TCP是面向连接的协议,连接的建立和释放是每一次面向连接的通信中必不可少的过程。TCP连接的管理就是使连接的建立和释放都能正常地进行。三次握手TCP连接的建立—三次握手建立TCP连接①若主机A中运行了一个客户进程,当它需要主机B的服务时,就发起TCP连接请求,并在所发送的分段中用SYN=1表示连接请求,并产生一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x。主机B收到A的连接请求报文,就完成了第一次握手。客户端发送SYN=1表示连接请求客户端发送一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x②主机B如果同意建立连接,则向主机A发送确认报
VXLAN简介定义RFC定义了VLAN扩展方案VXLAN(VirtualeXtensibleLocalAreaNetwork,虚拟扩展局域网)。VXLAN采用MACinUDP(UserDatagramProtocol)封装方式,是NVO3(NetworkVirtualizationoverLayer3)中的一种网络虚拟化技术。目的随着网络技术的发展,云计算凭借其在系统利用率高、人力/管理成本低、灵活性/可扩展性强等方面表现出的优势,已经成为目前企业IT建设的新趋势。而服务器虚拟化作为云计算的核心技术之一,得到了越来越多的应用。服务器虚拟化技术的广泛部署,极大地增加了数据中心的计算密度;同时,为
自97年以来我一直在使用vi/vim进行各种快速编辑和管理任务,但最近才考虑使用它来替换Netbeans作为我选择的ruby编辑器。我发现一件事在Netbeans和Eclipse中非常有用的是Ctrl+Click“转到定义”功能,您可以在其中按住Ctrl键并单击一个类或方法,然后它将带您了解定义。现在,我玩过丰富的ctags和rails.vim,而且很接近,但没有雪茄。这就是我想要的:默认情况下在Netbeans和Eclipse中,您可以在本地rails中按住ctrl并单击本地方法或类项目,但你也可以ctrl+click定义在gems或用Ruby编写的系统库。以Netbeans为例
目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式 2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置 3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一