首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >每天学点 Go 规范 - 代码不能写太宽,那么函数该怎么换行呢?

每天学点 Go 规范 - 代码不能写太宽,那么函数该怎么换行呢?

原创
作者头像
amc
修改于 2023-12-06 09:48:58
修改于 2023-12-06 09:48:58
2.5K0
举报
文章被收录于专栏:后台全栈之路后台全栈之路

公司内部的 Go 代码规范中限制了每一行代码的宽度。为了满足这个规范,那些太宽的代码行就不可避免地需要换行。换行不是普通的回车就行,如何在换行的同时,保持代码优秀的可读性,笔者根据日常 code review 中看到的各种模式,提出一些建议。

规范和原因

公司的 Go 规范统一要求每一行 Go 代码不能超过 120 个可显示字符的宽度。为什么要限制呢?在 这篇文章 中的描述我是非常赞同的,这里笔者就不再赘述了,读者可以直接参阅。

至于 120 这个数字是怎么来的?我就非常费解了。或许是觉得 80 是在太短,而 160 又太长,所以就取了一个折中值吧。

好,那么既然换行是不可避免的,那么接下来就是要如何换行了。下面笔者针对一些有争议的代码超宽换行的情况,具体说明如何优雅地换行。


函数签名和调用

实际上,除了一些例外情况,那么需要换行的地方,比较有争议的主要都是集中在函数签名 / 函数调用上。

问题提出

下面我举一个例子,比如说我们要定义一个函数,包含以下信息:

  • 函数功能: 向一个聊天群里发一个机器人消息, @ 其中的几个人或者是 @all
  • 函数入参: context, 群 ID, 机器人 ID, @ 的用户 ID 列表 (空表示 @all), 消息正文
  • 函数出参: 发出去的消息 ID, 错误信息

根据上述信息,我们设计一个接口,信息如下:

  • 函数名:
    • SendRobotMessageToChatGroup
  • 入参:
    • ctx context.Context
    • req *SendRobotMessageToChatGroupRequest
      • GroupID string
      • RobotID string
      • AtAll bool
      • AtUserIDs []string
      • Text string
  • 出参:
    • rsp *SendRobotMessageToChatGroupResponse
    • err error

不要吐槽命名太长, 这里是为了示例。此外,这也很可能是一个 protobuf 生成的 interface,那么按照很多团队的 pb 明明习惯,确实入参和出参的明明也是非常的长。

OK,如果咱们不换行,这个函数就是这个样子的:

代码语言:go
AI代码解释
复制
func SendRobotMessageToChatGroup(ctx context.Context, req *SendRobotMessageToChatGroupRequest, opts ...Option) (rsp *SendRobotMessageToChatGroupResponse, err error) {
    // ... 函数具体实现 ...
}

上面的这个代码段,你的浏览器上出现了横滚动条了吗?

换行流派

OK,咱们要对上面的函数换行了。其实换行的方式呢,其实有很多流派。这里我列出几种我在 code review 中见过的几种流派(不同流派可以有交叉):

1、函数名与入参允许同行
代码语言:go
AI代码解释
复制
func SendRobotMessageToChatGroup(ctx context.Context,
    req *SendRobotMessageToChatGroupRequest, opts ...Option,
) (rsp *SendRobotMessageToChatGroupResponse, err error) {
    // ... 函数具体实现 ...
}

这种模式中,就是按照逗号换行。允许部分入参和函数名放在同一行中。

其实单纯地允许部分入参换行,那感觉很明显地是为了满足代码规范而应试,这是会被诟病的地方,因此,这个流派中,往往会有一个限制,就是 “只有 context.Context” 类型允许与函数放在同一行。

这么主张的同学,理由是认为 ctx 是许多函数 / 方法所需的默认参数,它也并不是一个关键的入参,因此把它和函数名凑在一起并不会影响整个函数的可读性。

2、入参与出参允许同行
代码语言:go
AI代码解释
复制
func SendRobotMessageToChatGroup(
    ctx context.Context, req *SendRobotMessageToChatGroupRequest,
    opts ...Option) (rsp *SendRobotMessageToChatGroupResponse, err error) {
    // ... 函数具体实现 ...
}

这种模式中,入参和出参是允许放在同一行的。

这种流派有一个问题,就是函数签名的部分和函数实现正文处于同一锁进,那么当代码密度很高的时候,一眼区分不出函数签名和正文的分水岭。

其实使用这种模式的同学,很多只是纯纯地不喜欢下面的流派 3 而已

3、入参与出参不允许同行
代码语言:go
AI代码解释
复制
func SendRobotMessageToChatGroup(
    ctx context.Context,
    req *SendRobotMessageToChatGroupRequest, opts ...Option,
) (rsp *SendRobotMessageToChatGroupResponse, err error) {
    // ... 函数具体实现 ...
}

这个流派的重点是:入参和出参不允许放在一行,但是入参的换行比较自由,或者说缺乏统一的指导规范,而这一缺乏规范就是为其他流派所诟病的点,认为这对可读性不佳。

此外前面不是提到流派 2 不喜欢流派 3 嘛,其中一个理由是不喜欢出入参换行以后出现的一个零锁进,认为这破坏了代码块的层级。

4、入参全部独立一行
代码语言:go
AI代码解释
复制
func SendRobotMessageToChatGroup(
    ctx context.Context,
    req *SendRobotMessageToChatGroupRequest,
    opts ...Option,
) (rsp *SendRobotMessageToChatGroupResponse, err error) {
    // ... 函数具体实现 ...
}

这个流派的点呢,则是认为每一个入参都应该独立为一行。这主要是针对 3 的诟病点,认为既然参数如何换行缺乏规范,那么干脆我们就全部换行好了。

这个流派从规范角度,是足以满足的。大部分情况下,也不会出现函数签名过高的情况,以为我们还有另外一个规范:入参不得超过5个,因此这里入参最多盖 5 层楼。

不过呢这个流派被攻击的点也就是这个盖楼,特别是当入参类型名非常短的时候,就特别地难看。

出参?

可能有同学会提问:怎么上面的流派都是入参,没有出参?诚然,我们的规范是要求出参不得超过 3 个,这往往会有两种情况:

  1. 如果出参多达 3 个,那么这给出的几个参数都是非常简单和直观的类型(否则在 CR 终会被挑战),这种情况也占不了多少宽度,不用换行
  2. 大部分情况是一到两个,两个的情况下往往第二个类型就是 err error,占不了多少宽度,而第一个参数加上类型基本上不可能超过 80 个字符

综上,出参都顺利放在同一行内,没有出现需要换行的情况。


笔者观点

不知道读者看了之后还有什么想法(欢迎在评论区告诉我)。诚然,每种流派都有自己的优缺点和道理。各团队可以根据各自的团队习惯制定一个指导。笔者个人使用的基本上是流派 3,但是针对入参应该如何换行的问题,笔者秉承以下原则:

  1. 如果所有入参拼在一起都没超过 80 个字符,那么各入参之间不换行。满足这一条的话,下面都不用管了
  2. ctx 可以换行,也可以与其他类型放在同一行,但前提是 ctx 必须是入参列表的第一个
  3. 如果两个变量是成对的,那么放在同一行,比如 reqrspminmax, xy 等等
  4. 可变长度参数 ... 单独放一行

按照我的这个原则,上面的函数可以写成:

代码语言:go
AI代码解释
复制
func SendRobotMessageToChatGroup(
    ctx context.Context, req *SendRobotMessageToChatGroupRequest,
    opts ...Option,
) (rsp *SendRobotMessageToChatGroupResponse, err error) {
    // ... 函数具体实现 ...
}

函数调用

上述的流派是针对函数签名的,对于函数调用,换行流派也是类似的,不过还多了一个流派争议:

  • 换行了最后一个参数之后,是否要再换行?

这里我举一个例子,日志:

代码语言:go
AI代码解释
复制
    log.ErrorContextf(ctx, "调用 xxxxxx.xxxxxxxx 服务发生错误, 用户 openid 为 %v, 请求参数 %v, 耗时 %v, 错误信息 %v", openID, log.ToJSON(req), time.Since(start), err)
    // ... 后续逻辑 ...

最后一个参数不换行的话,就是这个样子的:

代码语言:go
AI代码解释
复制
    log.ErrorContextf(
        ctx, "调用 xxxxxx.xxxxxxxx 服务发生错误, 用户 openid 为 %v, 请求参数 %v, 耗时 %v, 错误信息 %v",
        openID, log.ToJSON(req), time.Since(start), err)
    // ... 后续逻辑 ...

如果换行的话:

代码语言:go
AI代码解释
复制
    log.ErrorContextf(
        ctx, "调用 xxxxxx.xxxxxxxx 服务发生错误, 用户 openid 为 %v, 请求参数 %v, 耗时 %v, 错误信息 %v",
        openID, log.ToJSON(req), time.Since(start), err,
    )
    // ... 后续逻辑 ...

不换行派的拥趸认为换行是脱裤子放屁,而换行派的支持者则认为这完成了一个完整的代码块锁进,清晰地指明了一行代码的开始与结束。

笔者是换行派,函数调用中必然换行。因此,笔者不喜欢长长的链式调用,因为这种模式破坏了代码块的层级。(这也是笔者不喜欢 gorm 的原因之一)

例外情况

虽然规范中对代码宽度进行了限制,但是实际上在一些情况下,由于 Go 语言语法的限制会导致换行后语法就不通过的情况,或者是不建议换行的情况:

  1. 结构体 struct 每个类型后面的 tag,特别是适配 gorm 的那一堆 tag(不喜欢 gorm 的理由 + 1)
  2. 字符串常量,为了保证完整性,不要为了换行而换行,特别是使用反引号括起来的字符串。
  3. import 行
  4. 自动生成的代码

参考资料


本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

原作者: amc,欢迎转载,但请注明出处。

原文标题:《每天学点 Go 规范 - 代码不能写太宽,那么函数该怎么换行呢?》

发布日期:2023-12-06

原文链接:https://cloud.tencent.com/developer/article/2368120

CC BY-NC-SA 4.0 DEED.png
CC BY-NC-SA 4.0 DEED.png

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
OpenHarmony父子组件单项同步使用:@Prop装饰器
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
小帅聊鸿蒙
2025/05/14
910
OpenHarmony父子组件单项同步使用:@Prop装饰器
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 一)
在前文的描述中,我们构建的页面多为静态界面。如果希望构建一个动态的、有交互的界面,就需要引入“状态”的概念。
爱吃土豆丝的打工人
2023/10/15
6210
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 一)
ArkTS-@State组件内状态
@State装饰的变量,或称为状态变量,一旦拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
酒楼
2023/06/27
1K0
【愚公系列】2023年11月 HarmonyOS教学课程 003-ArkTS语言(状态管理)
状态管理是指在应用程序中维护和更新应用程序状态的过程。在一个程序中,可能有很多不同的组件和模块,它们需要共享和相互作用的状态。如果没有一个明确的方式来管理这些状态,就会导致代码混乱、不易维护和难以扩展。
愚公搬代码
2025/06/02
930
【愚公系列】2023年11月 HarmonyOS教学课程 003-ArkTS语言(状态管理)
OpenHarmony父子组件双项同步使用:@Link装饰器
● 当装饰的数据类型为boolean、string、number类型时,可以同步观察到数值的变化,示例请参考 简单类型和类对象类型的@Link 。
小帅聊鸿蒙
2025/05/12
690
OpenHarmony父子组件双项同步使用:@Link装饰器
OpenHarmony后代组件双向同步,跨层级传递:@Provide装饰器和@Consume装饰器
@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
小帅聊鸿蒙
2025/05/14
640
OpenHarmony后代组件双向同步,跨层级传递:@Provide装饰器和@Consume装饰器
OpenHarmony 状态变量更改通知:@Watch 装饰器
@Watch 应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。
小帅聊鸿蒙
2025/05/16
650
【HarmonyOS之旅】ArkTS语法(一)
ArkTS通过装饰器@Component和@Entry装饰struct关键字声明的数据结构,构成一个自定义组件。
枫叶丹
2024/12/26
3360
【HarmonyOS之旅】ArkTS语法(一)
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 二)
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
爱吃土豆丝的打工人
2023/10/15
4520
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 二)
OpenHarmony嵌套类对象属性变化:@Observed装饰器和@ObjectLink装饰器
上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。
小帅聊鸿蒙
2025/05/15
880
OpenHarmony嵌套类对象属性变化:@Observed装饰器和@ObjectLink装饰器
HarmonyOS NEXT 实战系列04-组件状态
自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。 下图展示了State和View(UI)之间的关系。
用户8181473
2025/03/17
760
CPU都被干冒烟了,拥抱HarmonyOS第二天,自定义组件
自从经历了第一天惨痛的踩坑经历之后,我机智的拉了一个 HarmonyOS app 学习讨论群。虽然目前只有寥寥数人,但个个都是大佬,有点什么问题随便探讨一下我就有灵感了,比我自己一个人蒙头研究确实进度快了许多
用户6901603
2023/12/12
3480
CPU都被干冒烟了,拥抱HarmonyOS第二天,自定义组件
ArkTS-@Prop父子单向同步
@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
酒楼
2023/06/27
4240
ArkTS-@Observed装饰器和@ObjectLink装饰器
上文所属的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的 数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器
酒楼
2023/07/05
8830
ArkTS-@Watch装饰器
@Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。
酒楼
2023/07/05
7090
ArkTS-状态管理概述
在之前的描述中,我们构建的页面多为静态页面。如果希望构建一个动态地,有交互的页面,就需要引入“状态”的概念
酒楼
2023/06/27
6840
ArkTS-状态管理概述
OpenHarmony应用全局的UI状态存储:AppStorage
AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。
小帅聊鸿蒙
2025/05/15
1550
OpenHarmony应用全局的UI状态存储:AppStorage
ArkTS-@Link装饰器父子双向同步
为了了解@Link变量初始化和更新机制,有必要先了解父组件和拥有@Link变量的子组件的关系,初始渲染和双向更新的流程(以父组件为@State为例)。
酒楼
2023/06/27
6790
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 三)
为了了解@Link变量初始化和更新机制,有必要先了解父组件和拥有@Link变量的子组件的关系,初始渲染和双向更新的流程(以父组件为@State为例)。
爱吃土豆丝的打工人
2023/10/15
4900
HarmonyOS学习路之方舟开发框架—学习ArkTS语言(状态管理 三)
HarmonyOS 开发实践 —— 基于状态变量实现复杂对象的状态监听
对象在我们开发过程中是很常见的数据类型,我们在进行UI渲染的时候经常会用到对象,这里提供简单对象(所有字段均为基本数据类型)的监听效果。
小帅聊鸿蒙
2024/12/08
1590
HarmonyOS 开发实践 —— 基于状态变量实现复杂对象的状态监听
推荐阅读
相关推荐
OpenHarmony父子组件单项同步使用:@Prop装饰器
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 规范和原因
  • 函数签名和调用
    • 问题提出
    • 换行流派
      • 1、函数名与入参允许同行
      • 2、入参与出参允许同行
      • 3、入参与出参不允许同行
      • 4、入参全部独立一行
      • 出参?
  • 笔者观点
  • 函数调用
  • 例外情况
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档