开场:

朱颖老师向在场的研究生推荐了开源社区,希望可以将学院的本科生团队和研究生团队打通,增加交流。

主讲人马学长对国内是企业级云服务商公司:七牛进行了介绍,并且介绍现在多音视处理、富媒体、大数据、容器、全景图-支撑的行业场景等

一、改善代码中的坏味道

大家一起回顾了开源社区以前的分享。例如:k8s/容器/存储等。没有实际经验新社员可能听起来云里雾里。

主讲人确立了今天的主题:新员工的问题——写代码“脏”

主讲人平易近人的询问了好代码的特征。同学们踊跃回答:可读性、可维护性、命名规范、少用magic numbs。多次调用的内容拆分成小的函数等等接地气的见解。

ppt页面 1

主讲人根据其他同志的主观经验对什么是好的代码进行了解释。并邀请学长发表了重要讲话,深刻论述抓代码可读性的重要意义,明确效果的具体要求,为推进编写好代码注入了新的思想和行动力量。并用平易近人的语句生动的以重构时的情况为例解释了好代码。

并指出:当前开源社区中在重构中积累起了的强大动能,同时也面临错综复杂的矛盾和前所未有的挑战。坚持和发展项目各类项目,必须强化代码意识。

很难界定什么样的代码是好代码。一千个读者就有一千个哈姆雷特。有问题的代码都有一些明显的特点。换个角度来说,好代码应该是没什么坏味道的代码。所以今天是从代码的坏味道讲起的。

坏味道

过长的函数-违反了单一职责的原则,往往过长的原因是程序员没有抽象出代码到底在做什么的问题。关心业务逻辑的时候是不需要过于关心实现细节的。修改后的代码可读性就好了很多。这也是新手常犯的问题,两种代码都能跑,区别是一个人看代码的时候,看修改后的版本只要30秒就可以看懂。而看修改之前的可能就要10分钟。在工程中如果所有人的代码都写成那样,那么会浪费大家时间。

以上都是一眼能看出来的坏味道,接下来我们讲个复杂点的,这是我们线上的代码。

主讲人打开了github并回顾了之前的代码。里面是无意义的变量,体现了依恋情节与特性嫉妒。

这是一个业务逻辑,需要我们去获取账单的数据,如果超时了我们就起并发。我们需要一个controller去看时间的多少。我们会把这些实现的细节抽一个函数出来作为helper。问题是,它在java角度看起来在不同的包里,这个变量没有被用到,但是这里的这个变量却占有并且维护了这个变量名。它关心了一个其实和自己所在的业务链没有什么关系的数据。起并发的时候,比如这里出了问题的用于监测并发数的变量,它应该是由controller关心的。但是现在,这个并发数量却被helper关心了。正确做法很简单。把这个变量的定义挪给controller就好了就好了。

你们仔细看这个代码这里几段是不是形状很像,实际它们处理的逻辑是一样的。只是变量名不一样。这种代码在实际生产环境里面并不是可以一眼看出来的。

调用方法的时候,传送参数的时候老是这几个。看这里两个参数,start time和end time是每次都会出现的。这里的问题不是说这两个参数有问题,而是这些成对出现的参数一般是要处理一些数据的,比如这个start time和end time是用来算这两者当中有多少天。出现这个问题本质还是在写实现的时候没有想清楚,实际你应该抽象出一个叫做duration的类型来进行这个操作。这个问题往往也要工作一段时间才可以看出来。

重构!

现在让我们开始现场重构吧!

现在我们重构这个函数,大家看这个函数名应该是比较清楚的,这里其实已经针对函数命名改过一次了。这个函数的作用是获取一组uid然后去重。明显的问题大家应该已经看到了,这三行和这三行是一样的对吧,怎么做,很简单,把这三行放到上面来。这里有个set,特性是所有数据不重复。for循环去查这个UID是不是存在,为什么这里放两个呢?既然这是去重,那为什么我不在外面做这个判断呢?这样这个逻辑就应该放到外面去了。

这里两行也是,一样的逻辑,区别是他们参数名字不一样,但做的事情是一样的。如果我们小步快进的话要怎么做呢。这个改动大家都会,小步快进,这步做好之后,就发现要取UIDkey,之前都散落在for和if里面,现在看起来干净点了。

现在的问题是,这个的uid只在这里被使用了一次,大家看到刚才这个parentUID和uid被使用了很多次,为什么我这里这个uid只使用了一次就要放在外面呢?这里使用内联的方法,这两行就可以删掉了。我们再看现在这个代码,就又有了点改变。

你再仔细读一下这个代码,uidKey其实就是我们想用的叫parentUID的东西。然后这个代码还是有问题的-特性嫉妒。

看这里的代码,所有的数据该去到他该去的地方。既然是由他持有的,他应该知道数据该被放去哪。

假设我给financialRelation取了这样一个方法,我们挪进去,传这个参数进去。我抽象到这个类之后,下一步我这个代码就变成了上图这样,相比刚才又简单了很多。刚才有很多if的判断,现在好了许多。

来看这个for循环,看这个循环做了什么事情。仔细看这三段在干什么

代码示例 8

它在去重。这里假设我们一开始不去重的话,我们可以把它放到外面。我不去重,在后面再起个for。我这里再重构一下,我们实现一个去重的方法,这个去重的逻辑非常简单,而且不需要考虑业务逻辑,它成为了一个独立的函数。(图-me)我这抽了重构之后,你会发现我上面的代码就会变成,这里两段不需要了,只需要把去重逻辑加进去就好。现在一屏幕能看的下这段代码了。实际上面这段也不需要了。然后这里的fr.FinancialRelation我把他内敛进去。到这里我们重新读一下这段代码在干什么。

下面这里重复了一个去重,因为这是我们实际生产环境的代码。

重构之后:整体代码逻辑变少了,获取统一uid的逻辑更少了,更关注于取出UID这件事本身。

这两个注释是不是有点怪?这里你们可以看一下,!=代表了除了这种情况的其他情况,404的情况处理在这里,但是注释处理的情况是,不是404的先跳过,而在下面处理时,是先处理一般情况。

这里先处理了一般情况,再处理了特殊情况。正常人的思维逻辑应该是先处理特殊情况再处理一般情况。所以我们这里换一换这个处理的顺序。从代码可读性的角度来说,起来后者可读性更好。所以应该先特殊后一般。

这个for循环仔细看还是做了两件事情,就他还有请求这个细节。把这个请求抽象成接口。以后也许这部分会从数据库拉,如果抽象成接口了,就不需要改动外面的业务逻辑代码,只需要改parentUID函数的实现去想是并发还是怎么样。现在去重和获取ParentUID的实现都被抽出来了。

这样我们看代码的时候只需要看这层代码本身的业务逻辑,而不是看全部细节到函数实现的逻辑。

发现和改善的方法

  • 重构

  • 单元测试

  • code review

  • 业务讨论 https://github.com/qbox/gaea-admin/pull/917 让一个不熟悉你代码的人去读,然后提出意见。关键在这样的形式。这样做可以有效提高代码质量。

  • 可读性 https://github.com/qbox/pay/pull/2709

  • pair programing

  • 两个人一台电脑一个键盘,一个人写的时候会沉浸再里面,另一个人就可以看。然后讨论,这样意见统一后的代码可读性会非常好。

  • 工具(通过工具去发现和改善)

  • linter

  • 复杂度分析

额外内容——TDD 持续练习的方法

  • 识别坏味道的技巧

  • 重构的技巧

  • 小步快进的技巧

改善代码的过程是一个什么样的过程

  • 抽象领域模型,深入理解问题本质的过程-建模的过程

  • 将大问题拆分成小问题,解构问题的过程-拆分问题的过程

  • 高效表达和沟通的过程

  • 架构的过程——一个架构是一个过程,不是一个结果,架构在做的事情是定义函数的边界。这个服务,这个模块应该做什么东西。以小见大的话,在不停练习重构的过程中,其实是在不断地练习你架构的能力。

总结

  • 识别坏味道
  • 改善坏味道
  • 改善代码是架构的过程

——我们需要换个方式看代码,代码不是只要运行得好就可以了,要学会区分什么是给机器看的,什么是给人看的。

我们要注重代码的可读性和意图表达,要直接告诉别人这个代码要干什么。而不是用注释。这样的注释是坏注释。

注释应该写为什么要这么实现, 而不是这个函数在做什么。

方法背后的东西

ppt页面 5

  • 换一种看待代码的方式

  • 注重可读性和意图的表达

  • 区分给人看的和机器看的

  • 从关注运行正确到关注半年后能不能看懂

  • 从过程驱动的思维到数据驱动的思维——就像函数式编程:注重数据的映射,而不是过程。

Dijkstra说过:Quick and dirty,I would not like it.

我们不要做快而脏的事情,而是想办法去把这个事情去做好。

写出好代码不是一件小事,也不是小题大做。做好此项工作,开源有要求,学长有期盼,工作有抓手。主讲人在最后语重心长的表达对新成员的殷切希望:希望新成员要高度重视,坚定信心,将写出好代码落实到实处。

现场互动环节


Q:我们应当在什么时候开始重构?

A:坏味道不应该被保持,应该边写边重构。当然,实际上并不是这个样子的,一般是代码烂到一定程度了,绝大多数人都看不下去了,我们直接重写。重构是保持原有实现不变的情况下,对代码进行拆分,重组成可读性更好的代码。重写是知道了业务逻辑是这样了,然后我重新实现一下。这里要提到技术债务,它一般由团队的架构师进行评估,小的就原地加注释todo,大的就专门开个文档来记录。进行重构时机一般是引入需求/新功能的时候。这就像你写完大作业之后可能就不会再看了一样,重构代码也是这样,代码写完之后一般会在下一次需要修改的时候才被查看。现在一般公司里的操作是:每次加新功能的时候和架构师讨论一下这次是不是要重构了。当然重构也不是一次性完成的,一般重构会以不影响业务进展为前提进行。


Q:你对go中的Err逻辑怎么看?

A:Err是业务中必须处理的逻辑。对于Java来说这是需要try catch的。这是两种不同的设计,本质没有设计上的好坏(哲学层面)


Q:VPN是公司配?

A:是的,一般公司提供。不过我之前是用的自己的。


Q:VScode有个Vim插件,那么我该如何练习使用它?

A:其实这个问题不是特别好,我建议你百度。vim这有个习惯的过程。


Q:对于我们一般的人来说,像这样过度抽象会减慢写代码的效率,您是怎么做到的呢?

A:这个是可以通过TDD的方法来练习的。

Q:这个过程会不会很痛苦?

A:是的,比如我是用自己的业余时间练习的。你可以去网上找工程上的问题,把这个代码实现出来,然后去重构。此外,你们当年大一大二写的那些课程大作业的代码,本身就是非常优秀的练习材料。除了自己找素材练习以外,工作中也可以不时停下来想想这个代码哪里有点问题,去改了再提交。


Q:过度抽象会不会太复杂,感觉会想太多不该想的东西。

A:这其实不是一个纯粹的技术问题,而是你没想清楚你要做的东西是什么。你对这个模型理解不深刻才会过度优化。比如把3层for循环拆了三个函数出来。


Q:在公司里,你们实际生产是写go的吗?

A:是的,当年go进入中国市场就是由我们公司大力推广的。其实,现在做基础架构的公司都会用go。现在go被大量应用于云计算和高并发的场景中。以前我们一开始是打补丁,之后就往社区上面靠了。现在go项目——比如K8s、Docker,业界用的很多。当然go也有自己的使用场景,也存在着一些固有缺点。

Q:如果研究生想找工作的话,是要在毕业前就学会go吗?

A:其实学语言这个东西,我觉得一般学个一周,就可以用了。一门特定的语言如果要精通的话,大概要2年吧。在我看来,语言本身只是工具,它不是很重要的。重要的是各种语言不同的使用场景,自身的特性,还有生态。而不单单是“我要学会这个语言”这种技术上的问题。


开源社区和七牛云的往后合作

在长达六个月和七牛云的人力部门交流中,开源社区和七牛云联系了计算机学院的朱颖老师等,一同策划了这次的活动。

七牛云市场部表示愿意提供服务器给开源社区做学习使用,详细内容之后将开展深入合作。

马学长表示愿意以个人或公司名义再次给他曾经引领的上海大学开源社区做技术方面的分享。

此外,在分享结束后,社区的负责人和朱颖老师交流了学院内研究生和本科生的概况,希望可以在研究生中也推广这样一种精神,和上海大学开源社区。

PostScript:在分享结束后,目前社区的负责人和几位核心成员同马学长再次穿过校园,“回”新世纪门口的广场转悠了会,交谈了社区的发展和规划。马学长发现了这么多年还没有关门只是换了个位置的果汁店,甚是开心。