百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

重度使用Go的后遗症,你有吗?

bigegpt 2024-08-12 14:21 2 浏览

作者:Tony Bai

出处:https://tonybai.com/2020/11/05/the-sequela-after-being-used-to-writting-code-in-go/


有一种未经证实的说法: Go诞生于C++程序的漫长构建过程中 。如果C++编译很快,那么Robert Griesemer、Rob Pike和Ken Thompson这三位大佬也没有闲暇时间 一起喝着咖啡并决定是时候设计一门新语言 了。的确, Go语言诞生 后,其简洁的语法、极速地构建、新颖的并发结构、体验优良的工具链以及完成度不低的标准库吸引了很多C/C++程序员转型成为Gopher并开始重度使用Go,比如鄙人^_^。如果能一直使用Go总也是不错的,但偶尔因项目需要可能还会写一些C/C++代码,这时候很多Gopher发现自己在长期重度使用Go之后出现了一些“后遗症”!这里我们就来细数一下都有哪些“后遗症”,各位Gopher小伙伴们也自我评估一下,这些“后遗症”是否也发生在你的身上^_^。

1. 声明变量时类型与变量名的顺序总写反

Go语言是C家族编程语言的一个分支,和C/C++一样,Go也是静态编译型语言,这就要求在使用任何变量之前需要先 声明这个变量 ,无论使用常规声明方法还是 短声明形式 。

但Go采用的变量声明语法颇似Pascal: 变量名在前,变量类型在后 ,这与C/C++恰好相反:

Go:

var a, b int

var p, q *int

vs.

C/C++:

int a, b;

int *p, *q;

这样,gopher在长期使用Go编写代码后,一旦回归写C/C++代码,遇到的第一个问题就是经常在声明的时候将变量名与类型写反^_^。还好C/C++编译器会发现并告知我们这个问题,并不会给程序带来实质性的伤害。

  • 发病指数:3
  • 危害指数:1

2. 经常在函数中使用“短声明”形式声明变量

短声明不是Go语言独创的语法。短声明的好处正如其名:短小,无需显式提供变量类型,编译器会根据赋值操作符后面的初始化表达式的结果自动为变量赋予适当类型。因此,它成为了Gopher们喜爱和重度使用的语法。但短声明在C/C++中却不是合法的语法元素:

int main() {
    a := 5; //  error: expected expression
    printf("a = %d\n", a);
}

和上面的问题一样,C/C++编译器会发现并告知我们这个问题,并不会给程序带来实质性的伤害。

  • 发病指数:2
  • 危害指数:1

3. 总是忘记代码行结尾的分号

Go的正式标准语法是带有分号的,下面的代码片段才是编译器眼中认为正确的代码形式:

package main;

import "fmt";
import _ "database/sql";

type Foo struct {
    Name string;
    Age int;
};

func main() {
    var a, b = 1, 2;
    println(a, b);
    if a == 1 { fmt.Println("a = 1"); }
}

但这种形式显然与我们日常 “惯用”的代码形式 有很大不同,我们日常编写Go代码时 极少手写分号 。Go设计者当初为了简化代码编写,提高代码可读性,选择了由编译器在词法分析阶段自动在适当位置插入分号的技术路线,并在Go语言规范中描述了分号的插入规则:

1. 在Go中,除去注释,如果一个代码行的最后一个token为下列情况时,则编译器会将一个分号自动插入在此字段后:

    - 一个标识符;
    - 一个整数、浮点数、实数虚部、rune(码点)或者字符串字面量;
    - 关键字之一:break、continue、fallthrough和return;
    - 自增运算符++、自减运算符--、右括号)、]或}。

2. 为支持在一个代码行中放置复杂语句,分号可能被插入在右小括号)或者右大括号}之前。

被Go编译器 惯坏了 的Gopher们一旦回到编写C/C++代码,遗忘代码行尾的分号的“后遗症”行为就见怪不怪了。

  • 发病指数:5
  • 危害指数:2

4. 遇到在其他头文件中定义的头母小写的函数时总以为不能直接使用

在Go中,头母大写的包级变量、常量、类型、函数、方法都是导出的,即对外部包可见。反之,头母小写的则为包私有的,仅在包内使用。一旦习惯了这样的规则,切换到其他语言中,就会产生“心理后遗症”:遇到在其他头文件中定义的头母小写的函数时总以为不能直接使用。

  • 发病指数:3
  • 危害指数:2

5. 写条件分支语句、选择分支语句和循环语句时,总忘记给条件加上括号

同样是出于简化代码,增加可读性的考虑,Go设计者最初就取消掉了条件分支语句(if)、选择分支语句(switch)和循环语句(for)中条件表达式外围的小括号:

func f() int {
    return 5
}
func main() {
    a := 1
    if a == 1 { // 无需小括号包裹条件表达式
        fmt.Println(a)
    }

    switch b := f(); b { // 无需小括号包裹条件表达式
    case 4:
        fmt.Println("b = 4")
    case 5:
        fmt.Println("b = 5")
    default:
        fmt.Println("b = n/a")
    }

    for i := 1; i < 10; i++ { // 无需小括号包裹循环语句的循环表达式
        a += i
    }
    fmt.Println(a)
}

这恰与C/C++“背道而驰”,于是我们经常看到在编写C/C++的gopher为大量的如下编译器错误而苦恼:

int main()
{
    int a = 1;
    if a == 1 { // error: expected '(' after 'if'
        printf("a = 1\n");
    }

    int i = 0;
    for i = 1; i < 10; i++ { // error: expected '(' after 'for'
        a += i;
    }
}
  • 发病指数:4
  • 危害指数:2

6. 总是忘记在switch case语句中添加break

C/C++的选择分支语句有一个陷阱,那就是case语句中如果没有显式加入break语句,那么代码将向下自动掉落执行。Go在最初设计时填了这个“坑”,重新规定了swtich case语义,默认不自动掉落(fallthrough),除非开发者显式使用fallthrough关键字。

适应了Go的switch case语句的语义后,再回来写C/C++代码就会存在潜在的“风险”:

int main()
{
    int a = 1;
    switch(a) {
        case 1:printf("a = 1\n");
        case 2:printf("a = 2\n");
        case 3:printf("a = 3\n");
        default:printf("a = ?\n");
    }
}

这段代码按go语义编写switch case,编译运行后得到的结果如下:

a = 1
a = 2
a = 3
a = ?

我们看到代码首先匹配到了 case1的情况,然后一路自动掉落到default case。这个“后遗症”存在很大危害,因为这样编写的代码在C/C++编译器眼中是完全合法的,但所代表的语义却完全不是开发人员想要的。这样的程序一旦流入到生产环境,其缺陷可能会引发生产故障。

  • 发病指数:3
  • 危害指数:4

对于这样的问题,一些C/C++ lint工具可以将其检测出来,因此建议写C/C++代码的Gopher在提交代码前使用lint工具对代码做一下检查。

参考资料

  • https://go101.org/article/line-break-rules.html
  • https://tip.golang.org/ref/spec#Semicolons
  • https://medium.com/golangspec/automatic-semicolon-insertion-in-go-1990338f2649

作者:Tony Bai

出处:https://tonybai.com/2020/11/05/the-sequela-after-being-used-to-writting-code-in-go/

相关推荐

了解Linux目录,那你就了解了一半的Linux系统

大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...

Linux命令,这些操作要注意!(linux命令?)

刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...

Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!

在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...

linux必学的60个命令(linux必学的20个命令)

以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....

提高工作效率的--Linux常用命令,能够决解95%以上的问题

点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...

15 个实用 Linux 命令(linux命令用法及举例)

Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...

Linux 常用命令集合(linux常用命令全集)

系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...

Linux的常用命令就是记不住,怎么办?

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux常用文件操作命令(linux常用文件操作命令有哪些)

ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...

Linux 常用命令(linux常用命令)

日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E&#...

简单粗暴收藏版:Linux常用命令大汇总

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...

「Linux」linux常用基本命令(linux常用基本命令和用法)

Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

Linux的30个常用命令汇总,运维大神必掌握技能!

以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...

Linux/Unix 系统中非常常用的命令

Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...