分享让人折服的优秀代码基因

1 背景css

 

近来参与一个较大团队的项目实施,项目的金额两千万,人数近百。可是,项目实施后,暴露出如下几个问题:python

(1)质量不佳,团队成员水平良莠不齐,软件外部质量、内部质量一致性差;程序员

(2)需求不肯定,时间很是紧,代码频繁修改,愈来愈丑,效率变低。数据库

 

为了保证项目按时按质交付,质量改善刻不容缓。所以,在项目的初中期开始,作了如下三件事情:编程

(1)制定统一的界面规范,制定了统一参考实例,为全部成员进行按期界面规范的培训和评审;服务器

(2)制定统一的代码规范,制定了《评审文化构建》、《代码之丑》PPT,培养团队的质量文化和评审文化,实施评审;架构

(3)引入管理工具ReviewBoard。框架

 

项目统一构建了标准、规范,有效保证了界面的一致性,可是代码质量的提升却不是那么简单的事情。在这里,我暂不分享项目的状况,将来会经过更加详细的文章来介绍标准、规范化软件开发方法对于项目的重要性。数据库设计

 

2 ReviewBoard介绍函数

 

这里却是引出了一款优秀工具ReviewBoard。这款工具简单易用,功能强大。经过它来实施代码评审很是有效。代码评审分为提交前评审(Pre-Review)和提交后评审(Post-Review)两种方式。提交前评审,即开发人员代码变动后,须要提交到ReviewBoard,通过评审经过后,才能提交到SVN源码服务器,若是没有经过评审,则代码提交会失败,一行都没法提交;提交后评审,即开发人员先提交代码,而后再提交变动到ReviewBoard,若是评审未经过,则修改代码,更新评审直到经过为止。这两种方式,各有优劣,咱们采用的是后一种,它不阻塞开发人员提交代码,没法100%控制全部质量,可是能够达到80%以上。

 

ReviewBoard设计之初更多考虑的是支持Pre-Review方式,所以,存在如下问题:(1)提交经过后,Review没法自动进行状态变动为关闭,会与全部经过评审的ReviewRequest混在一块儿;(2)没法看出未经过评审的请求进行再次更新,由于若是再次更新后,意味着要进行第二次评审。在管理过程当中,会查询哪些请求没有经过评审,按期给开发人员发送通知。

 

所以,我决定对ReviewBoard进行订制来支持以上功能。不过,ReviewBoard是基于Python语言和基于Django框架开发,我历来没有学习过Python,更没有学习过Django,那如何来修改?因此,我开始对ReviewBoard作研究。ReviewBoard最新版本只能在非Windows运行,我在Ubuntu 14.04安装部署,同时使用MySQL数据库。所以,我开始来尝试作修改。

 

3 尝试修改ReviewBoard

3.1 探索第一步:数据库

 

首先,研究数据库,惊讶发现其数据库设计很是的整齐,压根不须要查看任何文档便可知道数据库的表、字段的意义。我找到了几个关键的表,分别是reviews_reviewrequest,看看其字段,与界面显示几乎一致,一会儿就明白什么意思,顺着字段意思,找到了另外的两个关键表diffviewer_diffsethistory、diffviewer_diffset。查询一下这些表的数据,我很容易来解决第一个问题,经过一条SQL语句便可将经过评审的ReviewRequest关闭,这样就不须要查看到这些评审请求了。

 

3.2 学习Python并修改代码

 

接下来的第二个问题,须要修改代码,我花了3个小时学习了Python,经过在线的英文帮助,直接在控制台作实验,学习基本语法结构、学习类与对象编程、学习了Python模块与编译,对Python初步熟悉以后,我决定开始来进行代码修改。

 

所以,我先经过类似列“Submitter”对全部文件进行查询,经过它很快找到了columns.py和grids.py这两个文件,看了代码以后,发现ReviewBoard的评审请求经过这两个文件来实现,进行再次查询名称没有再发现关联文件了。经过这两个文件,能够发现,columns.py用于定义要显示的全部的列,grids.py进行列的组合和显示。那么接下来能够简单的进行模仿添加一个列了。该列定义为Diffs。

 

class DiffSetCountColumn(Column):
    """Shows the diff set count of published reviews for a review request."""
    def __init__(self, *args, **kwargs):
        super(DiffSetCountColumn, self).__init__(
            label=_('Diffs'),
            detailed_label=_('Number of Diffs'),
            shrink=True,
            sortable=True,
            link=False,
            *kwargs, **kwargs)

    def render_data(self, state, review_request):
        if review_request.mydiffsetcount > 1:
            return ('<span class="issue-count">'
                    ' <span class="issue-icon">!</span> %s'
                    '</span>'
                    % review_request.mydiffsetcount)
        else:
            return ''

    def augment_queryset(self, state, queryset):
        return queryset.extra(select={
            'mydiffsetcount': """
                SELECT COUNT(*)
                  FROM diffviewer_diffset
                  WHERE diffviewer_diffset.history_id =
                        reviews_reviewrequest.diffset_history_id
            """
        })

  

简单说明一下,我定义了DiffSetCountColumn类,继承于Column类,类初始化方法为__init__,至关于构造器。这里的self是之前常用的this,定义了两个方法,一个是augment_queryset,用于当前列的数据显示,定义名称为mydiffsetcount,另外定义一个方法render_data,即在表格中展示数据。这里参考了其它列定义。

 

接下来在grids.py中声明对该列的使用,很是简单。

 

25行新增:

DiffSetCountColumn,

99行新增:

diffset_count = DiffSetCountColumn()

 

接着重启Apache服务器,能够简单测试,惊讶发现能够工做了。

 

3.3 再进一步添加功能

 

有了此次尝试,我接下来想再进一步来定制。目前各个小组提交到不一样的SVN地址,经过SVN能够区分各个组提交状况。可是ReviewBoard没有针对SVN地址的过滤。

 

首先修改columns.py,修改Repository列。

 

class RepositoryColumn(Column):
    """Shows the name of the repository the review request's change is on."""
    def __init__(self, *args, **kwargs):
        super(RepositoryColumn, self).__init__(
            label=_('Repository'),
            db_field='repository__name',
            shrink=True,
            sortable=True,
            link=True,
            link_func=self.link_to_object,
            css_class='repository-column',
            *args, **kwargs)

    def augment_queryset(self, state, queryset):
        return queryset.select_related('repository')

    def render_data(self, state, obj):
        return super(RepositoryColumn, self).render_data(state, obj) or ''

    def link_to_object(self, state, obj, value):
        import urllib
        return local_site_reverse('all-review-requests',
                                  request=state.datagrid.request) + '?' +
               urllib.urlencode({'repository':obj.repository_id})

  

 在这里更改该列支持Link,接着定义link_to_object函数,这里找了一段时间,发现local_site_reverse能够用于获取须要的url,而后再与url组合,一开始没有使用urlencode,一直没法正常呈现。因为原来在ASP.NET中,使用过urlencode,知道这里面有坑,就网上查询了Python的urlencode,而后解决之,发现能够工做了。不过,下一个问题来了,即连接后,如何进行过滤?这里发现其“show closed”过滤功能,经过代码查询,知道在grids.py中,如何工做,那么,添加过滤也就不难了。以下。

 

grids.py 第116行

 

self.repository_query = ‘'

 

grids.py 第133行

 

        self.repository_query = self.request.GET.get('repository', '')

        

        if len(self.repository_query) > 0:

             self.queryset = self.queryset.filter(repository_id=int(self.repository_query))

 

4 ReviewBoard使人震惊的优秀代码基因

 

顺利修改以后,我大为震惊,为ReviewBoard这款优秀的软件说叹服。为何可以在1天时间里面,从Python学习到对一个彻底黑盒子的软件进行修改?答案就是ReviewBoard具备很是整洁的代码。也就是说,ReviewBoard拥有很是优秀的内外部质量。

 

数据库设计,简单清晰,结构很是清晰,可读性很强,根本不须要任何数据库文档。文档是多余的!!!

 

代码风格很是一致,咱们发现ReviewBoard的注释很是少,Bob大叔的《代码整洁之道》强调了注释是代码意图表现失败的补充,最好的代码是一行注释都不要。ReviewBoard的命名,从前到后都很是一致,大小写、下划线、类名、方法名规则统一,命名准确无误,没有不专业的命名,没有不专业的缩写。ReviewBoard的类都很是简单,在columns.py这个文件,你能够发现每个Column基本都在50行之内,每个方法也很是简单。ReviewBoard很是完美的践行了单一职责,清晰的告诉我,要完成什么任务能够经过哪些类、哪些方法、写什么样代码来完成。ReviewBoard代码风格很是优秀,没有丑陋的缩进,没有丑陋的拥挤,在该有空格的时候空格,在该有空行的时候空行,在该缩进的时候缩进。这里,我看不出任何代码腐败的味道。跟着这种优秀的软件“混”,我也很难写出差的代码。

 

在写代码这件小事上,可能不少人眼里只有架构、框架这类东西,但是实际上,不少工做多年的程序员,代码依然是狗屎同样,没有规范的风格,没有良好的编码习惯。不少人可以去完成一件工做,可是,不漂亮。在没有意识的状况下,随着时间推移,只是作的丑事更多而已。从个人编码经验,能够总结出一个程序员现状,1~3年的开发人员,处于混乱状态,他们能完成功能就不错了;3~5年的开发人员,有些抽象意识,可是,一种是走向黑客,代码抽象的只有本身和上帝看得懂,一种是变得专业,倾向编写一些让团队其余人员看得懂的代码,还有一种就是保持现状;5年以后,开发人员又是三类,一类走向管理,也是不错选择,一类走向更高的技术路线如架构师或者专家,还有一类,就是变成废物,随着时间的推移,他们愈来愈没有空间,愈来愈没有价值。通常而言,经过培养,年纪越轻的人,成长越快,越有前途,要提高他们的水平,代码评审是最有效的方法,经过评审和培训让他们知道什么是美、什么是丑,什么是优秀、什么是低劣。

 

5 致敬

 

再次致敬ReviewBoard!ReviewBoard既是提高咱们总体实力的有效工具,也是一款值得学习的软件,感谢ReviewBoard!(我激动的想给它捐款时,居然找不到按钮~~)

 

另外,咱们使用了TaoReviewBoard、FindBugs、JSHint这些工具,感谢大家~~。

 

在代码评审,我大量引用Bob大叔的两本著名书籍《代码整洁之道》、《程序员的职业素养》,很是好的书,感谢Bob大叔!

相关文章
相关标签/搜索