分享讓人折服的優秀代碼基因

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大叔!

相關文章
相關標籤/搜索