- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
Reviewbot 是七牛云开源的一个项目,旨在提供一个自托管的代码审查服务, 方便做 code review/静态检查, 以及自定义工程规范的落地.
自从上了 Reviewbot 之后,我发现有些 lint 错误,还是很容易出现的。比如 。
dao/files_dao.go:119:2: `if state.Valid() || !start.IsZero() || !end.IsZero()` has complex nested blocks (complexity: 6) (nestif)
cognitive complexity 33 of func (*GitlabProvider).Report is high (> 30) (gocognit)
这两个检查,都是圈复杂度相关.
圈复杂度(Cyclomatic complexity)是由 Thomas McCabe 提出的一种度量代码复杂性的指标,用于计算程序中线性独立路径的数量。它通过统计程序控制流中的判定节点(如 if、for、while、switch、&&、|| 等)来计算。圈复杂度越高,表示代码路径越多,测试和维护的难度也就越大.
圈复杂度高的代码,往往意味着代码的可读性和可维护性差,非常容易出 bug.
为什么这么说呢?其实就跟人脑处理信息一样,一件事情弯弯曲曲十八绕,当然容易让人晕.
所以从工程实践角度,我们希望代码的圈复杂度不能太高,毕竟绝大部分代码不是一次性的,是需要人来维护的.
那该怎么做呢?
这里我首先推荐一个简单有效的方法:Early return.
Early return, 也就是提前返回,是我个人认为最简单,日常很多新手同学容易忽视的方法.
举个例子:
func validate(data *Data) error {
if data != nil {
if data.Field != "" {
if checkField(data.Field) {
return nil
}
}
}
return errors.New("invalid data")
}
这段代码的逻辑应该挺简单的,但嵌套层级有点多,如果以后再复杂一点,就容易出错.
这种情况就可以使用 early return 模式改写,把这个嵌套展平:
func validate(data *Data) error {
if data == nil {
return errors.New("data is nil")
}
if data.Field == "" {
return errors.New("field is empty")
}
if !checkField(data.Field) {
return errors.New("field validation failed")
}
return nil
}
是不是清晰很多,看着舒服多了?
记住这里的诀窍:如果你觉得顺向思维写出的代码有点绕,且嵌套过多的话,就可以考虑使用 early return 来反向展平.
当然,严格意义上讲,early return 只能算是一种小技巧。要想写出高质量的代码,最重要的还是理解 分层、组合、单一职责、高内聚低耦合、SOLID 原则等 这些核心设计理念 和 设计模式了.
来看一个场景: 方法参数很多,怎么办?
比如这种:
func (s *Service) DoSomething(ctx context.Context, a, b, c, d int) error {
// ...
}
有一堆参数,而且还是同类型的。如果在调用时,一不小心写错了参数位置,就很麻烦,因为编译器并不能检查出来.
当然,即使不是同类型的,参数多了可能看着也不舒服.
怎么解决?
这种情况,可以选择将参数封装成一个结构体,这样在使用时就会方便很多。封装成结构体后还有一个好处,就是以后增删参数时(结构体的属性),方法签名不需要修改。避免了以前需要改方法签名时,调用方也需要跟着到处改的麻烦.
不过,在 Go 语言中,还有一种更优雅的解决方案,那就是Functional Options 模式.
不管是 Rob Pike 还是 Dave Cheney 以及 uber 的 go guides 中都有专门的推荐.
这种模式,本质上就是利用了闭包的特性,将参数封装成一个匿名函数,有诸多妙用.
Reviewbot 自身的代码中,就有相关的使用场景(https://github.com/qiniu/reviewbot/blob/c354fde07c5d8e4a51ddc8d763a2fac53c3e13f6/internal/lint/providergithub.go#L263),比如:
// GithubProviderOption allows customizing the provider creation.
type GithubProviderOption func(*GithubProvider)
func NewGithubProvider(ctx context.Context, githubClient *github.Client, pullRequestEvent github.PullRequestEvent, options ...GithubProviderOption) (*GithubProvider, error) {
// ...
for _, option := range options {
option(p)
}
// ...
if p.PullRequestChangedFiles == nil {
// call github api to get changed files
}
// ...
}
这里的 options 就是 functional options 模式,可以灵活地传入不同的参数.
当时之所以选择这种写法,一个重要的原因是方便单测书写.
为什么这么说呢?
看上述代码能知道,它需要调用 github api 去获取 changed files, 这种实际依赖外部的场景,在单测时就很麻烦。但是,我们用了 functional options 模式之后,就可以通过 p.PullRequestChangedFiles 是否为 nil 这个条件,灵活的绕过这个问题.
Functional Options 模式的优点还有很多,总结来讲(from dave.cheney)
现在大模型相关的代码,能看到很多 functional options 的影子。比如 https://github.com/tmc/langchaingo/blob/238d1c713de3ca983e8f6066af6b9080c9b0e088/llms/ollama/options.go#L25 。
type Option func(*options)
// WithModel Set the model to use.
func WithModel(model string) Option {
return func(opts *options) {
opts.model = model
}
}
// WithFormat Sets the Ollama output format (currently Ollama only supports "json").
func WithFormat(format string) Option {
// ...
}
// If not set, the model will stay loaded for 5 minutes by default
func WithKeepAlive(keepAlive string) Option {
// ...
}
所以建议大家在日常写代码时,也多有意识的尝试下.
Reviewbot 目前已支持两种 provider(github 和 gitlab),以后可能还会支持更多.
而因为不同的 Provider 其鉴权方式还可能不一样,比如
当然,还有 OAuth2 方式,后面 reviewbot 也也会考虑支持.
那这里就有一个问题,比如在 clone 代码时,该使用哪种方式?代码该怎么写?使用 token 的话,还有个 token 过期/刷新的问题,等等.
如果使用 if-else 模式来实现,代码就会变得很复杂,可读性较差。类似这种:
if provider == "github" {
// 使用 Github APP 方式
if githubClient.AppID != "" && githubClient.AppPrivateKey != "" {
// 使用 Github APP 方式
// 可能需要调用 github api 获取 token
} else if githubClient.PersonalAccessToken != "" {
// 使用 Personal Access Token 方式
// 可能需要调用 github api 获取 token
} else {
return err
}
} else if provider == "gitlab" {
// 使用 Personal Access Token 方式
if gitlabClient.PersonalAccessToken != "" {
// 使用 Personal Access Token 方式
// 可能需要调用 gitlab api 获取 token
} else {
return errors.New("gitlab personal access token is required")
}
}
但现在 Reviewbot 的代码中,相关代码仅两行
func (s *Server) handleSingleRef(ctx context.Context, ref config.Refs, org, repo string, platform config.Platform, installationID int64, num int, provider lint.Provider) error {
// ...
gb := s.newGitConfigBuilder(ref.Org, ref.Repo, platform, installationID, provider)
if err := gb.configureGitAuth(&opt); err != nil {
return fmt.Errorf("failed to configure git auth: %w", err)
}
// ...
}
怎么做到的呢?
其实是使用了 builder 模式,将 git 的配置和创建过程封装成一个 builder,然后根据不同的 provider 选择不同的 builder,从而消弭了复杂的 if-else 逻辑.
当然内部细节还很多,不过核心思想都是将复杂的逻辑封装起来,在主交互逻辑中,只暴露简单的使用接口,这样代码的可读性和可维护性就会大大提高.
到底如何写出高质量的代码呢?这可能是很多有追求的工程师,一直在思考的问题.
在我看来,可能是没有标准答案的。不过呢,知道一些技巧,并能在实战中灵活运用,总归是好的.
你说是吧?
最后此篇关于Reviewbot开源|这些写Go代码的小技巧,你都知道吗?的文章就讲到这里了,如果你想了解更多关于Reviewbot开源|这些写Go代码的小技巧,你都知道吗?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题? Update the question所以它是on-topic对于堆栈溢出。 9年前关闭。 Improve this que
我有一堆 php 脚本计划在 CentOS 机器上的 cron 中每隔几分钟运行一次。我希望每个脚本在启动时自我检查它的前一个实例是否仍在运行,如果是则停止。 最佳答案 我这样做是为了管理任务并确保它
是否有 bash 命令、程序或 libusb 函数(尽管我没有找到)来指示 USB 设备的 OUT 或 IN 端点是什么? 例如,libusb_interface_descriptor(来自 libu
我如何知道 NSTextField 何时成为第一响应者(即当用户单击它来激活它时,但在他们开始输入之前)。我尝试了 controlTextDidBeginEditing 但直到用户键入第一个字符后才会
我怎么知道我的代码何时完成循环?完成后我还得再运行一些代码,但只有当我在那里写的所有东西都完成后它才能运行。 obj.data.forEach(function(collection) {
我正在使用音频标签,我希望它能计算播放了多少次。 我的代码是这样的: ; ; ; 然后在一个javascript文件中 Var n=0; function doing(onplaying)
我正在尝试向 Package-Explorer 的项目上下文菜单添加一个子菜单。但是,我找不到该菜单的 menuid。 所以我的问题是如何在 eclipse 中找到 menuid? 非常感谢您的帮助。
我有一个名为“下一步”的按钮,它存在于几个 asp.net 页面中。实际上它是在用户控件中。单击“下一步”时,它会调用 JavaScript 中的函数 CheckServicesAndStates。我
我正在尝试在 Visual Studio 中使用 C++ 以纳秒为单位计算耗时。我做了一些测试,结果总是以 00 结尾。这是否意味着我的处理器(Ryzen 7-1800X)不支持 ~1 纳秒的分辨率,
我有一个自定义 ListView ,其中包含一些元素和一个复选框。当我点击一个按钮时。我想知道已检查的元素的位置。下面是我的代码 public class Results extends ListAc
如何在使用 J2ME 编写的应用程序中获取网络运营商名称? 我最近正在尝试在 Nokia s40 上开发一个应用程序,它应该具有对特定网络运营商的独占访问权限。有没有这样的API或库? 最佳答案 没有
我使用服务器客户端组件,当在此组件的 TransferFile 事件中接收文件时,我使用警报消息组件。所以我希望,如果用户单击警报消息,程序将继续执行 TransferFile 事件中的代码,以在单击
如果我创建一个类A具有一些属性,例如 a, b, c我创建对象 A x1; A x2; A x3; ... A xN 。有没有办法在同一个类中创建一个方法来检索我创建的所有对象?我想创建类似 stat
我正在制作一个应用程序,其中包含相同布局的 81 个按钮。它们都被称为我创建的名为“Tile”的对象。问题是这些图 block 存储在数组中,因此我需要知道以 int 格式单击了哪个按钮才能调用图 b
UIProgressView有这个setProgress:animated: API。 有没有办法确切知道动画何时停止? 我的意思是这样的? [myProgress setProgress:0.8f
我正在使用两个 jQuery 队列,我希望其中一个队列在另一个队列完成后出队。我怎么知道第一个是否完成?我应该使用第三个队列吗?! 这是我所拥有的: var $q = $({}); $q.que
jQuery 中有没有一种方法可以知道是否至少有一个复选框已被选中? 我有一个包含很多复选框的表单,每个复选框都不同。 我需要一种 jQuery 的方式来表达这样的内容,这就是逻辑: If at le
给定 2 个选择 100 50 100 在这两种情况下,我都想在 .example 中获取数字,使用相同的选择器或者以某种方式知道 .no-text 和 之间的区别。带文字 执行
我在我的应用程序中使用 System.ComponentModel.BindingList 作为 DataGridView.DataSource。该列表非常大,需要几秒钟才能绘制到 DataGridV
我想知道用户在 Android 中选择的默认键盘。我知道我可以使用 InputMethodManager 访问已启用的输入法列表,但我想知道用户当前使用的是哪一个。 到目前为止,我已经尝试获取当前的输
我是一名优秀的程序员,十分优秀!