close

(文末有福利)

要寫出好代碼,首先需要提升品位。

很多軟件工程師寫不好代碼,在評審他人的代碼時也看不出問題,就是因為缺乏對好代碼標準的認識。

現在還有太多的軟件工程師認為,代碼只要可以正確執行就可以了。這是一種非常低的評價標準,很多重要的方面都被忽視了。

好代碼的特性

好代碼具有以下特性。

1. 魯棒(Solid and Robust)

代碼不僅要被正確執行,我們還要考慮對各種錯誤情況的處理,比如各種系統調用和函數調用的異常情況,系統相關組件的異常和錯誤。

對很多產品級的程序來說,異常和錯誤處理的邏輯占了很大比例。

2. 高效(Fast)

程序的運行應使用儘量少的資源。資源不僅僅包括CPU,還可能包括存儲、I/O等。

設計高效的程序,會運用到數據結構和算法方面的知識,同時要考慮到程序運行時的各種約束條件。

3. 簡潔(Maintainable and Simple)

代碼的邏輯要儘量簡明易懂,代碼要具有很好的可維護性。對於同樣的目標,能夠使用簡單清楚的方法達成,就不要使用複雜晦澀的方法。

「大道至簡」,能否把複雜的問題用簡單的方式實現出來,這是一種編程水平的體現。

4. 簡短(Small)

在某種意義上,代碼的複雜度和維護成本是和代碼的規模直接相關的。在實現同樣功能的時候,要儘量將代碼寫得簡短一些。

簡潔高於簡短。這裡要注意,某些人為了能把代碼寫得簡短,使用了一些晦澀難懂的描述方式,降低了代碼的可讀性。這種方式是不可取的。

5. 可測試(Testable)

代碼的正確性要通過測試來保證,尤其是在敏捷的場景下,更需要依賴可自動回歸執行的測試用例。

在代碼的設計中,要考慮如何使代碼可測、易測。一個比較好的實踐是使用TDD(Test-Driven Development,測試驅動開發)的方法,這樣在編寫測試用例的時候會很快發現代碼在可測試性方面的問題。

6. 共享(Re-Usable)

大量的程序實際上都使用了類似的框架或邏輯。由於目前開源代碼的大量普及,很多功能並不需要重複開發,只進行引用和使用即可。

在一個組織內部,應鼓勵共享和重用代碼,這樣可以有效降低代碼研發的成本,並提升代碼的質量。

實現代碼的共享,不僅需要在意識方面提升,還需要具有相關的能力(如編寫獨立、高質量的代碼庫)及相關基礎設施的支持(如代碼搜索、代碼引用機制)。

7. 可移植(Portable)

某些程序需要在多種操作系統下運行,在這種情況下,代碼的可移植性成為一種必需的能力。

要讓代碼具有可移植性,需要對所運行的各種操作系統底層有充分的理解和統一抽象。一般會使用一個適配層來屏蔽操作系統底層的差異。

一些編程語言也提供了多操作系統的可移植性,如很多基於Python語言、Java語言、Go語言編寫的程序,都可以跨平台運行。

8. 可觀測(Observable)/ 可監控(Monitorable)

面對目前大量存在的在線服務(Online Service)程序,需要具備對程序的運行狀態進行細緻而持續監控的能力。

這要求在程序設計時就提供相關的機制,包括程序狀態的收集、保存和對外輸出。

9. 可運維(Operational)

可運維已經成為軟件研發活動的重要組成部分,可運維重點關注成本、效率和穩定性三個方面。

程序的可運維性和程序的設計、編寫緊密相關,如果在程序設計階段就沒有考慮可運維性,那麼程序運行的運維目標則難以達成。

10. 可擴展(Scalable andExtensible)

可擴展包含「容量可擴展」(Scalable)和「功能可擴展」(Extensible)兩方面。

在互聯網公司的系統設計中,「容量可擴展」是重要的設計目標之一。系統要儘量支持通過增加資源來實現容量的線性提高。

快速響應需求的變化,是互聯網公司的另外一個重要挑戰。可考慮使用插件式的程序設計方式,以容納未來可能新增的功能,也可考慮使用類似Protocol Buffer 這樣的工具,支持對協議新增字段。

以上十條標準,如果要記住,可能有些困難。我們可以把它們歸納為四個方面,見表1。

表1對一流代碼特性的匯總分類

壞代碼的例子

關於好代碼,上面介紹了一些特性,本節也給出壞代碼(Bad Code)的幾個例子。關於壞代碼,本書沒有做系統性總結,只是希望通過以下這些例子的展示讓讀者對壞代碼有直觀的感覺。

1.不好的函數名稱(Bad Function Name)

如do(),這樣的函數名稱沒有多少信息量;又如myFunc(),這樣的函數名稱,個人色彩過於強烈,也沒有足夠的信息量。

2.不好的變量名稱(Bad Variable Name)

如a、b、c、i、j、k、temp,這樣的變量名稱在很多教科書中經常出現,很多人在上學期間寫代碼時也會經常這樣用。如果作為局部變量,這樣的名稱有時是可以接受的;但如果作為作用域稍微大的變量,這樣的名稱就非常不可取了。

3.沒有注釋(No Comments)

有寫注釋習慣的軟件工程師很少,很多軟件工程師認為寫注釋是浪費時間,是「額外」的工作。但是沒有注釋的代碼,閱讀的成本會比較高。

4.函數不是單一目的(The Function has No Single Purpose)

如LoadFromFileAndCalculate()。這個例子是我編造的,但現實中這樣的函數其實不少。很多函數在首次寫出來的時候,就很難表述清楚其用途;還有一些函數隨着功能的擴展,變得越來越龐雜,也就慢慢地說不清它的目的了。

這方面的問題可能很多人都沒有充分地認識到——非單一目的的函數難以維護,也難以復用。

5.不好的排版(Bad Layout)

不少人認為,程序可以正常執行就行了,所以一些軟件工程師不重視對代碼的排版,認為這僅僅是一種「形式」。

沒有排好版的程序,在閱讀效率方面會帶來嚴重問題。這裡舉一個極端的例子:對於C語言來說,「;」可作為語句的分割符,而「縮進」和「換行」對於編譯器來說是無用的,所以完全可以把一段C語言程序都「壓縮」在一行內。這樣的程序是可以運行的,但是對人來說,可讀性非常差。這樣的程序肯定是我們非常不希望看到的。

6.無法測試(None Testable)

程序的正確性要依賴測試來保證(雖然測試並不能保證程序完全無錯)。無法或不好為之編寫測試用例的程序,是很難有質量保證的。

好代碼從哪裡來

上一節說明了好代碼的特性,本節來分析好代碼是如何產出的。

▊ 好代碼不止於編碼

好代碼從哪裡來?

對於這個問題,很多讀者肯定會說:「好代碼肯定是寫出來的呀。」

我曾做過多次調研,發現很多軟件工程師日常所讀的書確實是和「寫代碼」緊密相關的。

但是,這裡要告訴讀者的是,代碼不只是「寫」出來的。在很多年前,我所讀的軟件工程方面的教科書就告訴我,編碼的時間一般只占一個項目所花時間的 10%。我曾說過一句比較有趣的話:

「如果一個從業者告訴你,他的大部分時間都在寫代碼,那麼他大概率不是一個高級軟件工程師。」

那麼,軟件工程師的時間都花到哪裡去了呢?軟件工程師的時間應該花在哪裡呢?

好的代碼是多個工作環節的綜合結果。

(1)在編碼前,需要做好需求分析和系統設計。而這兩項工作是經常被大量軟件工程師忽略或輕視的環節。

(2)在編碼時,需要編寫代碼和編寫單元測試。對於「編寫代碼」,讀者都了解;而對於「編寫單元測試」,有些軟件工程師就不認同了,甚至還有人誤以為單元測試是由測試工程師來編寫的。

(3)在編碼後,要做集成測試、上線,以及持續運營/迭代改進。這幾件事情都是要花費不少精力的,比如上線,不僅僅要做程序部署,而且要考慮程序是如何被監控的。有時,為了一段程序的上線,設計和實施監控的方案要花費好幾天才能完成。

因此,一個好的系統或產品是以上這些環節持續循環執行的結果。

▊ 需求分析和系統設計

1.幾種常見的錯誤現象

相對於編碼工作,需求分析和系統設計是兩個經常被忽視的環節。在現實工作中,我們經常會看到以下這些現象。

(1)很多人錯誤地認為,寫代碼才是最重要的事情。不少軟件工程師如果一天沒有寫出幾行代碼,就會認為工作沒有進展;很多管理者也會以代碼的產出量作為衡量工作結果的主要標準,催促軟件工程師儘早開始寫代碼。

(2)有太多的從業者,在沒有搞清楚項目目標之前就已經開始編碼了。在很多時候,項目目標都是通過並不準確的口頭溝通來確定的。例如:

「需要做什麼?」

「就按照×××網站的做一個吧。」

(3)有太多的從業者,在代碼編寫基本完成後,才發現設計思路是有問題的。他們在很多項目上花費很少(甚至沒有花費)時間進行系統設計,對於在設計中所隱藏的問題並沒有仔細思考和求證。基於這樣的設計投入和設計質量,項目出現設計失誤也是很難避免的。而面對一個已經完成了基本編碼的項目,如果要「動大手術」來修改它,相信每個有過類似經歷的人都一定深知那種感受——越改越亂,越改越着急。

以上這幾種情況,很多讀者是不是都有過類似經歷?

2.研發前期多投入,收益更大

關於軟件研發,首先我們需要建立一個非常重要的觀念。

在研發前期(需求分析和系統設計)多投入資源,相對於把資源都投入在研發後期(編碼、測試等),其收益更大。

這是為什麼呢?

要回答這個問題,需要從軟件研發全生命周期的角度來考量軟件研發的成本。除編碼外,軟件測試、上線、調試等都需要很高成本。如果我們把需求搞錯了,那麼與錯誤需求有關的設計、編碼、測試、上線等成本就都浪費了;如果我們把設計搞錯了,那麼與錯誤設計相關的編碼、測試、上線的成本也就浪費了。

如果仔細考量那些低效的項目,會發現有非常多的類似於上面提到的「浪費」的地方。軟件工程師似乎都很忙,但是在錯誤方向上所做的所有努力並不會產生任何價值,而大部分的加班實際上是在做錯誤的事情,或者是為了補救錯誤而努力。在這種情況下,將更多的資源和注意力向研發前期傾斜會立刻收到良好的效果。

3.修改代碼和修改文檔,哪個成本更高

很多軟件工程師不願意做需求分析和系統設計,是因為對「寫文檔」有着根深蒂固的偏見。這裡問大家一個問題,如果大家對這個問題能給出正確的回答,那麼在「寫文檔」的意識方面,一定會有很大的轉變。

任何人都不是神仙,無法一次就把所有事情做對。對於一段程序來說,它一定要經過一定周期的修改和迭代。這時有兩種選擇:

選擇一:修改文檔。在設計文檔時完成迭代調整,待沒有大問題後再開始編碼。

選擇二:修改代碼。只有粗略的設計文檔,或者沒有設計文檔,直接開始編碼,所有的迭代調整都在代碼上完成。

請大家判斷,修改代碼和修改文檔,哪個成本更高?

在之前的一些分享交流會上,對於這個問題,有人會說,修改文檔的成本更高。因為在修改文檔後還要修改代碼,多了一道手續。而直接修改代碼,只需要做一次,這樣更直接。

這個回答說明了回答者沒有充分理解「先寫文檔,後寫代碼」的設計方法。如果沒有充分重視設計文檔的工作,在輸出的設計文檔質量不高的情況下就開始編碼,確實會出現以上提到的問題。但是,如果在設計文檔階段就已經做了充分考慮,會減少對代碼的迭代和反覆。

對於同樣的設計修改,「修改代碼」的成本遠高於「修改文檔」。這是因為,在設計文檔中只會涉及主要的邏輯,那些細小的、顯而易見的邏輯不會在設計文檔中出現。在修改設計文檔時,也只會影響到這些主要邏輯。而如果在代碼中做修改,不僅會涉及這些主要邏輯,而且會涉及那些在文檔中不會出現的細小邏輯。對於一段程序來說,任何一個邏輯出現問題,程序都是無法正常運行的。

4.需求分析和系統設計之間的差別

很多讀者無法清楚地區分「需求分析」和「系統設計」之間的差別,於是會發現,在寫出的文檔中,有些需求分析文檔里出現了系統設計的內容,而有些系統設計文檔里又混雜了需求分析的內容。

我們用幾句話可以非常明確地給出二者的差異。

(1)需求分析:定義系統/軟件的黑盒的行為,它是從外部(External)看到的,在說明「是什麼」(What)。

(2)系統設計:設計系統/軟件的白盒的機制,它是從內部(Internal)看到的,要說明「怎麼做」(How)和「為什麼」(Why)。

比如,對一輛汽車來說,首先使用者從外部可以看到車廂、車輪,坐在車裡可以看到方向盤、剎車踏板、油門踏板等;操作方向盤可以改變汽車的行駛方向,腳踩剎車踏板、油門踏板可用於減速和加速。以上這些是對汽車的「需求分析」。

然後,我們想象汽車外殼和內部變成了透明的,可以看到汽車內部的發動機、變速箱、傳動杆、與剎車相關的內部裝置等。而這些對駕駛者來說是不可見的,它們是對汽車的「系統設計」。

本文節選自《代碼的藝術:用工程思維驅動軟件開發》一書,想要了解更多相關內容,歡迎閱讀本書!

福利:本文下方評論區說說你心中的「好代碼」和「壞代碼」。
抽出點讚前三名及兩條有價值的評論可免費獲贈一本《代碼的藝術》,截止時間:2022年3月24日20點)

現在下單即減50,快搶!

如果喜歡本文
歡迎在看丨留言丨分享至朋友圈三連

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()