1. 如何編寫高質量的代碼
1. 打好基礎
寫出高質量代碼,並不是搭建空中樓閣,需要有一定的基礎,這里我重點強調與代碼質量密切相關的幾點:
掌握好開發語言,比如做Android就必須對Java足夠熟悉,《Effective Java》一書就是教授大家如何更好得掌握Java, 寫出高質量Java代碼。
熟悉開發平台, 不同的開發平台,有不同的API, 有不同的工作原理,同樣是Java代碼,在PC上寫與Android上寫很多地方不一樣,要去熟悉Android編程的一些特性,iOS編程的一些特性,了解清楚這些,才能寫出更加地道的代碼,充分發揮各自平台的優勢。
基礎的數據結構與演算法,掌握好這些在解決一些特定問題時,可以以更加優雅有效的方式處理。
基礎的設計原則,無需完全掌握23種經典設計模式,只需要了解一些常用的設計原則即可,甚至你也可以只了解什麼是低耦合,並在你的代碼中堅持實踐,也能寫出很不錯的代碼。
2. 代碼標准
代碼標准在團隊合作中尤為重要,誰也不希望一個項目中代碼風格各異,看得讓人糟心,即便是個人開發者,現在也需要跟各種開源項目打交道。標准怎麼定是一個老生常談的話題,我個人職業生涯中經歷過很多次的代碼標准討論會議,C++, C#, Java等等,大家有時會堅持自己的習慣不肯退讓。可現如今時代不一樣了,Google等大廠已經為我們制定好了各種標准,不用爭了,就用這些業界標准吧。
3. 想好再寫
除非你很清楚你要怎麼做,否則我不建議邊做邊想。
你真的搞清楚你要解決的問題是什麼了嗎?你的方案是否能有效?有沒有更優雅簡單的方案?准備怎麼設計它,必要的情況下,需要有設計文檔,復雜一些的設計需要有同行評審,寫代碼其實是很簡單的事情,前提是你得先想清楚。
4. 代碼重構
重構對於代碼質量的重要性不言而喻,反正我是很難一次把代碼寫得讓自己滿意、無可挑剔,《重構》這本書作為業內經典也理應人人必讀,也有其他類似的教授重構技巧的書,有些也非常不錯,遺憾的是我發現很多工作多年的同學甚至都沒有了解過重構的概念。
5. 技術債務
知乎上最近有個熱門問題《為什麼有些大公司技術弱爆了?》,其實裡面提到的很多歸根結底都是技術債務問題,這在一些大公司尤為常見。技術債務話題太大,但就代碼質量而言,我只想提一下不要因為這些債是前人留下的你就不去管,現實是沒有多少機會讓你從一個清爽清新的項目開始做起,你不得不去面對這些,你也沒法完全不跟這些所謂的爛代碼打交道。
因此我建議各位:當你負責一個小模塊時,除了把它做好之外,也要順便將與之糾纏在一起的技術債務還掉,因為這些債務最終將是整個團隊來共同承擔,任何一個人都別想獨善其身,如果你還對高質量代碼有追求的話。
作為團隊的技術負責人,也要頂住壓力,鼓勵大家勇於做出嘗試,引導大家不斷改進代碼質量,不要總是畏手畏腳,停滯不前,真要背鍋也得上,要有擔當。
6. 代碼審查
我曾經聽過一些較高級別的技術分享,竟然還不時聽到一些呼籲大家要做代碼審查的主題,我以為在這個級別的技術會議上,不應再討論代碼審查有什麼好,為什麼要做代碼審查之類的問題。同時我接觸過相當多所謂國內一線互聯網公司,竟有許多是不做代碼審查的,這一度讓我頗為意外。
這里也不想多談如何做好代碼審查,只是就代碼質量這點,不客氣地說:沒有過代碼審查經歷的同學,往往很難寫出高質量的代碼,尤其是在各種追求速度的糙快猛創業公司。
7. 靜態檢查
很多代碼上的問題,都可以通過一些工具來找到,某些場景下,它比人要靠譜得多,至少不會出現某些細節上的遺漏,同時也能有效幫助大家減少代碼審查的工作量。
Android開發中有Lint, Find bugs, PMD等優秀靜態檢查工具可用,通過改進這些工具找出的問題,就能對語法的細節,規范,編程的技巧有更多直觀了解。
建議最好與持續集成(CI),代碼審查環境配套使用, 每次提交的代碼都能自動驗證是否通過了工具的代碼檢查,通過才允許提交。
8. 單元測試
Android單元測試,一直備受爭議,主要還是原生的測試框架不夠方便,每跑一次用例需要在模擬器或者真機上運行,效率太低,也不方便在CI環境下自動構建單元測試,好在有Robolectric,能幫我們解決部分問題。
單元測試的一個非常顯著的優點是,當你需要修改大量代碼時,盡管放心修改,只需要保證單元測試用例通過即可,無需瞻前顧後。
9. 充分自測
有一種說法:程序員最害怕的是他自己寫的代碼,尤其是准備在眾人面前show自己的工作成果時,因此在寫完代碼後,需要至少跑一遍基本的場景,一些簡單的異常流。在把你的工作成果提交給測試或用戶前,充分自測是基本的職業素養,不要總想著讓測試幫你找問題,隨便用幾下就Crash的東西,你好意思拿給別人嗎?
10. 善用開源
並非開源的東西,質量就高,但至少關注度較高,使用人數較多,口碑較好的開源項目,質量是有一定保證的,這其中的道理很簡單。即便存在一些問題,也可以通過提交反饋,不斷改進。最重要的是,你自己花時間造的輪子,需要很多精力維護,而充分利用開源項目,能幫助你節省很多時間,把精力專注在最需要你關心的問題上。
2. 如何編寫高質量的python程序
寫出規范的代碼是寫出高質量代碼的第一步,並且有助於培養仔細的習慣。
為了培養規范寫代碼的習慣,可以安裝flake8這個工具,它不僅可以檢查代碼風格是否符合官方建議(PEP8),而且還能找出潛在的隱患(用Pyflakes做語法分析),更逆天的是還能檢測到你有些函數寫的太復雜(代碼圈復雜度)了,更更逆天的是可以設置git commit之前必須通過這些檢查。
當然具體操作需要根據自己的項目進行一些定製,比如可以忽略E501,W293。
空白項目模版
好的開始是成功的一半,寫python代碼就從pyempty開始吧。
在github上看一下那些經典的項目,web.py,flask, pep8,他們的項目目錄都很規范,綜合借鑒了一些項目的特點,我寫了這個pyempty項目。
1.README.md 這里寫你項目的簡介,quick start等信息,雖然distutils要求這個文件沒有後綴名,但github上如果後綴是.md的話可以直接轉換成html顯示。
2.ChangeLog.txt 該文件存放程序各版本的變更信息,也有一定的格式,參考web.py的ChangeLog.txt
3.LICENES.txt 這里存放你項目使用的協議,不要編寫自己的協議。
4.requirements.txt 如果你的項目需要依賴其它的python第三方庫,在這里一行一個寫出來,可能pip install的時候能自動幫你安裝
5.setup.py 安裝腳本,後面詳細介紹
6.docs 裡面存放你的項目文檔,如概要設計,詳細設計,維護文檔,pydoc自動生成的文檔等,強烈推薦大家使用MarkDown格式編寫文檔
7.src 這個目錄里存放項目模塊的主要代碼,盡量不要把模塊目錄直接放到根目錄,模塊代碼目錄可以在setup.py里指定的
8.tests 這個目錄存放所有單元測試,性能測試腳本,單元測試的文件確保以test_做前綴,這樣distutils會自動打包這些文件,並且用python -m unittest discover -s ./ -p 'test_*.py' -v 可以直接執行這些測試
單元測試
Martin Fowler:"在你不知道如何測試代碼之前,就不該編寫程序。而一旦你完成了程序,測試代碼也應該完成。除非測試成功,你不能認為你編寫出了可以工作的程序。"
我們有很多理由不寫單元測試,歸根結底是懶,雖然代碼大全上說:
大部分研究都發現,檢測比測試的成本更小。NASA軟體工程實驗室的一項研究發現,閱讀代碼每小時能夠檢測出來的缺陷要比測試高出80%左右(Basili and Selby 1987)。後來,IBM的一項研究又發現,檢查發現的一個錯誤只需要3.5個工作時,而測試則需要花費15-25個工作時(Kaplan 1995)。
但是單元測試還是讓別人相信你的代碼有很高質量的最有力證據。
好了,請詳細閱讀:
深入python3.0: 單元測試-2.x也適用
Unit testing framework 不完整中文版
文檔
敏捷開發不是提倡什麼文檔也不寫,沒有文檔就沒有傳承和積累,輪崗或新人接手任務就會遇到很大的麻煩,所以我決定每個項目最少要寫以下文檔:
1.nalysis.model.md 概要設計文檔,不同於README.md文件,該文檔應該寫於項目開發之前,把項目有哪些功能,大概分幾個模塊等項目整體概述信息寫一下。
2.design.model.md 詳細設計文檔,不用太詳細,至少把項目依賴哪些東西,誰依賴這個項目,重要演算法流程描述,代碼整體結構等寫出來。
3.maintain.md 維護文檔,這個我覺得最重要,你的服務都記錄哪些日誌,需要監控哪些業務指標,如何重啟,有哪些配置項等,沒這些東西,你的項目很難運維。
上面這些文檔都是項目全局性的文檔,不適合寫在docstring或注視里,所以要有單獨的文檔。
打包
python有專門的模塊打包系統distutils,你可以用這套機制把你的代碼打包並分發到Pypi上,這樣任何人都可以用pip或easy_install安裝你的模塊。
如果你開發的是內部項目,還可以用mypypi架設私有的pypi,然後把項目的大的版本更新發布到內部的pypi上,配置管理人員和運維人員可以很方便的從pypi上拉取代碼安裝到測試環境或生產環境。
發布大版本的時候要給版本命名及編寫ChangeList,可以參考Git Pro的相關章節,主要記住以下幾個命令。
git tag -a v0.1 -m 'my test tag' #給大版本命名,打Tag
git describe master #給小版本命名,Git將會返回一個字元串,由三部分組成:最近一次標定的版本號,加上自那次標定之後的提交次數,再加上一段SHA-1值
git shortlog --no-merges master --not v0.1 #生成版本簡報,ChangeList
python有自己的打包機制,所以一般不要用git archive命令。
當然大版本管理用pypi管理比較合適,小的bug fix,緊急上線等好多公司都是用git直接從生產環境拉代碼更新,因為git,svn等可以很方便的撤銷某次更新,回滾到某個位置。
如何管理好大版本上線和小的緊急上線,我還沒理清思路,歡迎大家參與討論。
關於打包,請閱讀如下鏈接:
Python 打包指南
深入Python3.0:打包 Python 類庫
python打包:分發指定文件
出自:http://developer.51cto.com/art/201209/356603.htm
3. 如何編寫高質量的VB代碼
1. 使用整數(Integer)和長整數(Long)
提高代碼運行速度最簡單的方法莫過於使用正確的數據類型了。也許你不相信,但是正確地選擇數據類型可以大幅度提升代碼的性能。在大多數情況下,程序員可以將Single,Double和Currency類型的變數替換為Integer或Long類型的變數,因為VB處理Integer和Long的能力遠遠高於處理其它幾種數據類型。
在大多數情況下,程序員選擇使用Single或Double的原因是因為它們能夠保存小數。但是小數也可以保存在Integer類型的變數中。例如程序中約定有三位小數,那麼只需要將保存在Integer變數中的數值除以1000就可以得到結果。根據我的經驗,使用Integer和Long替代Single,Double和Currency後,代碼的運行速度可以提高將近10倍。
2. 避免使用變體
對於一個VB程序員來說,這是再明顯不過的事情了。變體類型的變數需要16個位元組的空間來保存數據,而一個整數(Integer)只需要2個位元組。通常使用變體類型的目的是為了減少設計的工4作量和代碼量,也有的程序員圖個省事而使用它。但是如果一個軟體經過了嚴格設計和按照規范編碼的話,完全可以避免使用變體類型。
在這里順帶提一句,對於Object對象也存在同樣的問題。請看下面的代碼:
Dim FSO
Set FSO = New Scripting.FileSystemObject
或
Dim FSO as object
Set FSO = New Scripting.FileSystemObject
上面的代碼由於在申明的時候沒有指定數據類型,在賦值時將浪費內存和CPU時間。正確的代碼應該象下面這樣:
Dim FSO as New FileSystemObject
3. 盡量避免使用屬性
在平時的代碼中,最常見的比較低效的代碼就是在可以使用變數的情況下,反復使用屬性(Property),尤其是在循環中。要知道存取變數的速度是存取屬性的速度的20倍左右。下面這段代碼是很多程序員在程序中會使用到的:
Dim intCon as Integer
For intCon = 0 to Ubound(SomVar())
Text1.Text = Text1.Text & vbcrlf & SomeVar(intCon)
Next intCon
下面這段代碼的執行速度是上面代碼的20倍。
Dim intCon as Integer
Dim sOutput as String
For intCon = 0 to Ubound(SomeVar())
sOutput = sOutput & vbCrlf &
SomeVar(intCon)
Next
Text1.Text = sOutput
4. 盡量使用數組,避免使用集合
除非你必須使用集合(Collection),否則你應該盡量使用數組。據測試,數組的存取速度可以達到集合的100倍。這個數字聽起來有點駭人聽聞,但是如果你考慮到集合是一個對象,你就會明白為什麼差異會這么大。
5. 展開小的循環體
在編碼的時候,有可能遇到這種情況:一個循環體只會循環2到3次,而且循環體由幾行代碼組成。在這種情況下,你可以把循環展開。原因是循環會佔用額外的CPU時間。但是如果循環比較復雜,你就沒有必要這樣做了。
6. 避免使用很短的函數
和使用小的循環體相同,調用只有幾行代碼的函數也是不經濟的--調用函數所花費的時間或許比執行函數中的代碼需要更長的時間。在這種情況下,你可以把函數中的代碼拷貝到原來調用函數的地方。
7. 減少對子對象的引用
在VB中,通過使用.來實現對象的引用。例如:
Form1.Text1.Text
在上面的例子中,程序引用了兩個對象:Form1和Text1。利用這種方法引用效率很低。但遺憾的是,沒有辦法可以避免它。程序員唯一可以做就是使用With或者將用另一個對象保存子對象(Text1)。
注釋: 使用With
With frmMain.Text1
.Text = "Learn VB"
.Alignment = 0
.Tag = "Its my life"
.BackColor = vbBlack
.ForeColor = vbWhite
End With
或者
注釋: 使用另一個對象保存子對象
Dim txtTextBox as TextBox
Set txtTextBox = frmMain.Text1
TxtTextBox.Text = "Learn VB"
TxtTextBox.Alignment = 0
TxtTextBox.Tag = "Its my life"
TxtTextBox.BackColor = vbBlack
TxtTextBox.ForeColor = vbWhite 注意,上面提到的方法只適用於需要對一個對象的子對象進行操作的時候,下面這段代碼是不正確的:
With Text1
.Text = "Learn VB"
.Alignment = 0
.Tag = "Its my life"
.BackColor = vbBlack
.ForeColor = vbWhite
End With
很不幸的是,我們常常可以在實際的代碼中發現類似於上面的代碼。這樣做只會使代碼的執行速度更慢。原因是With塊編譯後會形成一個分枝,會增加了額外的處理工作。
8. 檢查字元串是否為空
大多數程序員在檢查字元串是否為空時會使用下面的方法:
If Text1.Text = "" then
注釋: 執行操作
End if
很不幸,進行字元串比較需要的處理量甚至比讀取屬性還要大。因此我建議大家使用下面的方法:
If Len(Text1.Text) = 0 then
注釋: 執行操作
End if
9. 去除Next關鍵字後的變數名
在Next關鍵字後加上變數名會導致代碼的效率下降。我也不知道為什麼會這樣,只是一個經驗而已。不過我想很少有程序員會這樣畫蛇添足,畢竟大多數程序員都是惜字如金的人。
注釋: 錯誤的代碼
For iCount = 1 to 10
注釋: 執行操作
Next iCount
注釋: 正確的代碼
For iCount = 1 to 10
注釋: 執行操作
Next
10. 使用數組,而不是多個變數
當你有多個保存類似數據的變數時,可以考慮將他們用一個數組代替。在VB中,數組是最高效的數據結構之一。
11. 使用動態數組,而不是靜態數組
使用動態數組對代碼的執行速度不會產生太大的影響,但是在某些情況下可以節約大量的資源。
12. 銷毀對象
無論編寫的是什麼軟體,程序員都需要考慮在用戶決定終止軟體運行後釋放軟體佔用的內存空間。但遺憾的是很多程序員對這一點好像並不是很在意。正確的做法是在退出程序前需要銷毀程序中使用的對象。例如:
Dim FSO as New FileSystemObject
' 執行操作
' 銷毀對象
Set FSO = Nothing
對於窗體,可以進行卸載:
Unload frmMain
或
Set frmMain = Nothing
13. 變長和定長字元串
從技術上來說,與變長字元串相比,定長字元串需要較少的處理時間和空間。但是定長字元串的缺點在於在很多情況下,你都需要調用Trim函數以去除字元串末的空字元,這樣反而會降低代碼效率。所以除非是字元串的長度不會變化,否則還是使用變長字元串。
14. 使用類模塊,而不是ActiveX控制項
除非ActiveX控制項涉及到用戶界面,否則盡量使用輕量的對象,例如類。這兩者之間的效率有很大差異。
15. 使用內部對象
在涉及到使用ActiveX控制項和DLL的時候,很多程序員喜歡將它們編譯好,然後再加入工程中。我建議你最好不要這樣做,因為從VB連接到一個外部對象需要耗費大量的CPU處理能力。每當你調用方法或存取屬性的時候,都會浪費大量的系統資源。如果你有ActiveX控制項或DLL的源代碼,將它們作為工程的私有對象。
16. 減少模塊的數量
有些人喜歡將通用的函數保存在模塊中,對於這一點我表示贊同。但是在一個模塊中只寫上二三十行代碼就有些可笑了。如果你不是非常需要模塊,盡量不要使用它。這樣做的原因是因為只有在模塊中的函數或變數被調用時,VB才將模塊載入到內存中;當VB應用程序退出時,才會從內存中卸載這些模塊。如果代碼中只有一個模塊,VB就只會進行一次載入操作,這樣代碼的效率就得到了提高;反之如果代碼中有多個模塊,VB會進行多次載入操作,代碼的效率會降低。
17. 使用對象數組
當設計用戶界面時,對於同樣類型的控制項,程序員應該盡量使用對象數組。你可以做一個實驗:在窗口上添加100個PictureBox,每個PictureBox都有不同的名稱,運行程序。然後創建一個新的工程,同樣在窗口上添加100個PictureBox,不過這一次使用對象數組,運行程序,你可以注意到兩個程序載入時間上的差別。
18. 使用Move方法
在改變對象的位置時,有些程序員喜歡使用Width,Height,Top和Left屬性。例如:
Image1.Width = 100
Image1.Height = 100
Image1.Top = 0
Image1.Left = 0
實際上這樣做效率很低,因為程序修改了四個屬性,而且每次修改之後,窗口都會被重繪。正確的做法是使用Move方法:
Image1.Move 0,0,100,100
19. 減少圖片的使用
圖片將佔用大量內存,而且處理圖片也需要佔用很多CPU資源。在軟體中,如果可能的話,可以考慮用背景色來替代圖片--當然這只是從技術人員的角度出發看這個問題。
20. 使用ActiveX DLL,而不是ActiveX控制項
如果你設計的ActiveX對象不涉及到用戶界面,使用ActiveX DLL。
編譯優化
我所見過的很多VB程序員從來沒有使用過編譯選項,也沒有試圖搞清楚各個選項之間的差別。下面讓我們來看一下各個選項的具體含義。
1. P-代碼(偽代碼)和本機代碼
你可以選擇將軟體編譯為P-代碼或是本機代碼。預設選項是本機代碼。那什麼是P-代碼和本機代碼呢?
P-代碼:當在VB中執行代碼時,VB首先是將代碼編譯為P-代碼,然後再解釋執行編譯好的P-代碼。在編譯環境下,使用這種代碼要比本機代碼快。選擇P-代碼後,編譯時VB將偽代碼放入一個EXE文件中。
本機代碼:本機代碼是VB6以後才推出的選項。當編譯為EXE文件後,本機代碼的執行速度比P-代碼快。選擇本機代碼後,編譯時VB使用機器指令生成EXE文件。
在使用本機代碼進行編譯時,我發現有時候會引入一些莫名其妙的錯誤。在編譯環境中我的代碼完全正確地被執行了,但是用本機代碼選項生成的EXE文件卻不能正確執行。通常這種情況是在卸載窗口或彈出列印窗口時發生的。我通過在代碼中加入DoEvent語句解決了這個問題。當然出現這種情況的幾率非常少,也許有些VB程序員從來沒有遇到過,但是它的確存在。
在本機代碼中還有幾個選項:
a) 代碼速度優化:該選項可以編譯出速度較快的執行文件,但執行文件比較大。推薦使用
b) 代碼大小優化:該選項可以編譯出比較小的執行文件,但是以犧牲速度為代價的,不推薦使用。
c) 無優化:該選項只是將P-代碼轉化為本機代碼,沒有做任何優化。在調試代碼時可以使用。
d) 針對Pentium Pro優化:雖然該項不是本機代碼中的預設選項,但是我通常會使用該選項。該選項編譯出的可執行程序在Pentium Pro和Pentium 2以上的機器上可以運行得更快,而在比較老的機器上要稍稍慢一些。考慮到現在用Pentium 2都是落伍,所以推薦大家使用該選項。
e) 產生符號化調試信息:該項在編譯過程中生成一些調試信息,使用戶可以利用Visual C++一類的工具來調試編譯好的代碼。使用該選項會生成一個.pdf文件,該文件記錄了可執行文件中的標志信息。當程序擁有API函數或DLL調用時,該選項還是比較有幫助的。
2. 高級優化
高級優化中的設置可以幫助你提高軟體的速度,但是有時候也會引入一些錯誤,因此我建議大家盡量小心地使用它們。如果在代碼中有比較大的循環體或者復雜的數學運算時,選中高級優化中的某些項會大幅度提升代碼的性能。如果你使用了高級優化功能,我建議你嚴格測試編譯好的文件。
a) 假定無別名:可以提高循環體中代碼的執行效率,但是在如果通過變數的引用改變變數值的情況下,例如調用一個方法,變數的引用作為方法的參數,在方法中改變了變數的值的話,就會引發錯誤。有可能只是返回的結果錯誤,也有可能是導致程序中斷運行的嚴重錯誤。
b) 取消數組綁定檢查、取消整數溢出檢查和取消浮點錯誤檢查:在程序運行時,如果通過這些檢查發現了錯誤,錯誤處理代碼會處理這些錯誤。但是如果取消了這些檢查,發生了錯誤程序就無法處理。只有當你確定你的代碼中不會出現上面的這些錯誤時,你才可以使用這些選項。它們將使軟體的性能得到很大的提升。
c) 允許不舍入的浮點操作:選擇該選項可以是編譯出來的程序更快地處理浮點操作。它唯一的缺點就是在比較兩個浮點數時可能會導致不正確的結果。
d) 取消Pentium FDIV安全檢查:該選項是針對一些老的Pentium晶元設置的,現在看來已經過時了。
4. 如何編寫高質量的代碼!
載選<編程思想>
非程序員 編 著
代碼永遠會有BUG,在這方面沒有最好只有更好。高效是程序員必須作到的事情,無錯是程序員一生的追求。復用、分而治之、折衷是代碼哲學的基本思想。模塊化與面向對象是實現高效無錯代碼的方法。高效無錯代碼需要思想與實踐的不斷反復。
1.2.1 命名約定
命令規范基本上採用了微軟推薦的匈牙利命名法,略有簡化。
1. 常量
常量由大寫字母和數字組成,中間可以下劃線分隔,如 CPU_8051。
2. 變數
變數由小寫(變數類型)字母開頭,中間以大寫字母分隔,可以添加變數域前綴(變數活動域前綴以下劃線分隔)。如: v_nAcVolMin(交流電壓最小值)。
變數域前綴見下表
局部變數,如果變數名的含義十分明顯,則不加前綴,避免煩瑣。如用於循環的int型變數 i,j,k ;float 型的三維坐標(x,y,z)等。
3. 函數名一般由大寫字母開頭,中間以大寫字母分隔,如SetSystemPara。函數命名採用動賓形式。如果函數為最底層,可以考慮用全部小寫,單詞間採用帶下劃線的形式。如底層圖形函數:pixel、lineto以及讀鍵盤函數get_key 等。
4. 符號名應該通用或者有具體含義,可讀性強。尤其是全局變數,靜態變數含義必須清晰。C++中的一些關鍵詞不能作為符號名使用,如class、new、friend等。符號名長度小於31個,與ANSI C 保持一致。命名只能用26個字母,10個數字,以及下劃線『_』來組成,不要使用『$』『@』等符號。下劃線『_』使用應該醒目,不能出現在符號的頭尾,只能出現在符號中間,且不要連續出現兩個。
5. 程序中少出現無意義的數字,常量盡量用宏替代。
1.2.2 使用斷言
程序一般分為Debug版本和Release版本,Debug版本用於內部調試,Release版本發行給用戶使用。
斷言assert是僅在Debug版本起作用的宏,它用於檢查「不應該」發生的情況。以下是一個內存復製程序,在運行過程中,如果assert的參數為假,那麼程序就會中止(一般地還會出現提示對話,說明在什麼地方引發了assert)。
//復制不重疊的內存塊
void memcpy(void *pvTo, void *pvFrom, size_t size)
{
void *pbTo = (byte *) pvTo;
void *pbFrom = (byte *) pvFrom;
assert( pvTo != NULL && pvFrom != NULL );
while(size - - > 0 )
*pbTo + + = *pbFrom + + ;
return (pvTo);
}
assert不是一個倉促拼湊起來的宏,為了不在程序的Debug版本和Release版本引起差別,assert不應該產生任何副作用。所以assert不是函數,而是宏。程序員可以把assert看成一個在任何系統狀態下都可以安全使用的無害測試手段。
以下是使用斷言的幾個原則:
1)使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況之間的區別,後者是必然存在的並且是一定要作出處理的。
2)使用斷言對函數的參數進行確認。
3)在編寫函數時,要進行反復的考查,並且自問:「我打算做哪些假定?」一旦確定了的假定,就要使用斷言對假定進行檢查。
4)一般教科書都鼓勵程序員們進行防錯性的程序設計,但要記住這種編程風格會隱瞞錯誤。當進行防錯性編程時,如果「不可能發生」的事情的確發生了,則要使用斷言進行報警。
1.2.3 優化/效率
規則一:對於在中斷函數/線程和外部函數中均使用的全局變數應用volatile定義。例如:
volatile int ticks;
void timer(void) interrupt 1 //中斷處理函數
{
ticks++
}
void wait(int interval)
{
tick=0;
while(tick<interval);
}
如果未用volatile,由於while循環是一個空循環,編譯器優化後(編譯器並不知道此變數在中斷中使用)將會把循環優化為空操作!這就顯然不對了。
規則二:不要編寫一條過分復雜的語句,緊湊的C++/C代碼並不見到能得到高效率的機器代碼,卻會降低程序的可理解性,程序出錯誤的幾率也會提高。
規則三:變數類型編程中應用原則:盡量採用小的類型(如果能夠不用「Float」就盡量不要去用)以及無符號Unsigned類型,因為符號運算耗費時間較長;同時函數返回值也盡量採用Unsigned類型,由此帶來另外一個好處:避免不同類型數據比較運算帶來的隱性錯誤。
1.2.4 其他
規則一:不要編寫集多種功能於一身的函數,在函數的返回值中,不要將正常值和錯誤標志混在一起。
規則二:不要將BOOL值TRUE和FALSE對應於1和0進行編程。大多數編程語言將FALSE定義為0,任何非0值都是TRUE。Visual C++將TRUE定義為1,而Visual Basic則將TRUE定義為-1。例如:
BOOL flag;
…
if(flag) { // do something } // 正確的用法
if(flag==TRUE) { // do something } // 危險的用法
if(flag==1) { // do something } // 危險的用法
if(!flag) { // do something } // 正確的用法
if(flag==FALSE) { // do something } // 不合理的用法
if(flag==0) { // do something } // 不合理的用法
規則三:小心不要將「= =」寫成「=」,編譯器不會自動發現這種錯誤。
規則四:建議統一函數返回值為無符號整形,0代表無錯誤,其他代表錯誤類型。
1.3 模塊化的C編程
C語言雖然不具備C++的面向對象的成分,但仍應該吸收面向對象的思想,採用模塊化編程思路。面向對象的思想與面向對象的語言是兩個概念。非面向對象的語言依然可以完成面向對象的編程,想想C++的誕生吧!
C++沒有理由對C存在傲慢與偏見,不是任何場合C++方法都是解決問題的良葯,譬如面對嵌入式系統效率和空間的雙重需求。注意我們談的是方法,而不是指編譯器。
C在軟體開發上存在的首要問題是缺乏對數據存取的控制(封裝),C編程者樂而不疲的使用著大量extern形式的全局變數在各模塊間交換著數據,「多方便啊」編程者樂曰,並傳授給下一個編程者。這樣多個變數出現在多個模塊中,剪不斷理還亂,直到有一天終於發現找一個「人」好難。一個東西好吃,智者淺嘗之改進之,而愚者只會直至撐死。
這世上本沒有什麼救世主,應在C上多下功夫,程序員和C締造者早就有過思考,相信野百合也有春天,還是看看C語言如何實現模塊化編程方法,在部分程度上具備了OO特性封裝與多態。
在具體闡述之前,需要明確生存期與可見性的概念。生存期指的是變數在內存的生存周期,可見性指的是變數在當前位置是否可用。兩者有緊密聯系,但不能混為一談。一個人存在但不可見只能解釋成上帝或靈魂,一個變數存在但不可見卻並非咄咄怪事,模塊化方法正是利用了靜態函數、靜態變數這些「精靈」們特殊的生存期與可見性。
最後需要明確一點的是這里的模塊是以一個.C文件為單位。
規則一:利用函數命名規則和靜態函數
模塊中不被其他模塊調用的內部函數採用以下命名規則:用全部小寫,單詞間採用帶下劃線的形式。如底層圖形函數:pixel、lineto以及讀鍵盤函數get_key等。這些函數應定義為static靜態函數,這樣在其他模塊錯誤地調用這些函數時編譯器能給出錯誤(如BC編譯器)。(注意:有些編譯器不能報告錯誤,但為了代碼風格一致和函數層次清晰,仍建議這樣作)。
規則二:利用靜態變數
模塊中不能被其他模塊讀寫的全局變數應採用static聲明,這樣在其他模塊錯誤地讀寫這些變數時編譯器能給出警告(C51編譯器)或錯誤(BC編譯器)。
規則三:引入OO介面概念和指針傳參
模塊間的數據介面(也就是函數)應該事先較充分考慮,需要哪些介面,通過介面需要操作哪些數據,盡量作到介面的不變性。
模塊間地數據交換盡量通過介面完成,方法是通過函數傳參數,為了保證程序高效和減少堆棧空間,傳大量參數(如結構)應採用傳址的方式,通過指針作為函數參數或函數返回指針,盡量杜絕extern形式的全局變數,請注意是extern形式的全局變數,模塊內部的全局變數是允許和必須的。
傳指針參數增加的開銷主要是作參數的指針和局部指針的數據空間(嵌入式系統(如C51)往往由於堆棧空間有限,函數參數會放到外部RAM的堆棧中),增加的代碼開銷僅是函數的調用,帶來的是良好的模塊化結構,而且使用介面函數會比在代碼中多處直接使用全局變數大大節約代碼空間。
需注意一點的事物總有他的兩面性,水能載舟,也能覆舟。對於需要頻繁訪問的變數如果仍採用介面傳遞,函數調用的開銷是巨大的,這時應考慮仍採用extern全局變數。
以下演示了兩個C模塊交換數據:
//Mole1.C
OneStruct* void GetOneStruct(void); //獲取模塊1數據介面
void SetOneStruct(OneStruct* pOneStruct); //寫模塊1數據介面
struct OneStruct
{
int m¬_imember;
//……
}t1; //模塊1的數據
//t1初始化代碼…..
OneStruct* void GetOneStruct(void)
{
OneStruct* pt1; //只需定義一個局部變數
t1.imember=15;
pt1=&t1;
return pt1;
}
void SetOneStruct(OneStruct* pOneStruct)
{
t1.imember=pOneStruct->imember;
//…….
}
//Mole2.C
void OperateOneStruct(void); //模塊2通過模塊1提供的介面操作模塊1的數據
OneStruct* void GetOneStruct(void);
void SetOneStruct(OneStruct* pOneStruct);
void OperateOneStruct(void)
{
OneStruct* pt2; //只需定義一個局部變數
pt2=GetOneStruct(); //讀取數據
SetOneStruct(pt2); //改寫數據
}
採用介面訪問數據可以避免一些錯誤,因為函數返回值只能作右值,全局變數則不然。
例如 cOneChar == 4; 可能被誤為cOneChar = 4;
規則四:有限的封裝與多態
不要忘記C++的class源於C的struct,C++的虛函數機制實質是函數指針。為了使數據、方法能夠封裝在一起,提高代碼的重用度,如對於一些與硬體相關的數據結構,建議採用在數據結構中將訪問該數據結構的函數定義為結構內部的函數指針。這樣當硬體變化,需要重寫訪問該硬體的函數,只要將重寫的函數地址賦給該函數指針,高層代碼由於使用的是函數指針,所以完全不用動,實現代碼重用。而且該函數指針可以通過傳參數或全局變數的方式傳給高層代碼,比較方便。例如:
struct OneStruct
{
int m¬_imember;
int (*func)(int,int);
//……
}t2;