编写自文档化代码
版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/YhL_Leo/article/details/50545047
?
本系列文章由 @yhl_leo 出品,轉載請注明出處。
文章鏈接: http://blog.csdn.net/yhl_leo/article/details/50545047
?
嚴肅是寫作必備的兩個因素之一。另一個,很不幸,是天分。 —— 歐內斯特??海明威
個人覺得可以把一個人編寫程序所處的階段可以分為四個:
- 識字:接觸和掌握了一些基本的編程知識(變量,類型,函數,……)和語法(運算,循環,判斷,……);
- 造句:可以根據基本的知識和語法模仿甚至自主實現一些小的算法等;
- 自由表達:熟悉掌握編程語言后,只要不太復雜的算法,似乎只要知道算法原理,就能實現;
- 妙筆生花:此時,你可能意識到代碼實現不再是你所追求的目標,創建優秀的代碼對你而言更具吸引力和挑戰性。
創建優秀的代碼意味著創建良好的文檔化的代碼。我們編寫代碼的原因是要表達一套清晰的指令——不僅僅是對電腦,也是對那些以后需要維護或拓展這些指令的可憐傻瓜們。所以,千萬不要去膜拜把原本簡單易懂的代碼寫得反人類的所謂大神!現實世界中的代碼從來沒有在編寫完成后就被遺忘掉。在軟件產品的生命周期內,這些代碼將不斷地被修改,拓展和維護。想要做到這一點,我們需要說明指導,即一個文檔化的用戶指南。
對于代碼的文檔化,人們通常的做法是編寫大量的關于代碼的文檔或者在代碼中添加大量的注釋,這兩種方式都是明智的。
實際上,這兩種方法都是無稽之談。大多數程序員對文字編輯器唯恐避之不及,對于編寫太多注釋也是相當頭疼。編寫代碼是意見艱苦的工作,而將代碼文檔化更是艱苦異常。
對于支持文檔的系統,常常面臨以下挑戰:
- 我們不需要做額外的工作。編寫文檔非常耗費時間,閱讀文檔也是這樣。程序員們更愿意將時間花在編寫程序上。
- 所有的獨立文檔都必須隨著代碼的更新而不斷更新。在大型項目中,這將是一項可怕的工作。而不更新任何文檔將會導致危險的錯誤和產生錯誤的信息。
- 大量的文檔是很難管理的。在大量的文檔中查找正確的文檔,或者尋找在一個文檔中可能多次出現的信息是不易的。就像代碼一樣,文檔必須接受版本的控制,必須確保所閱讀的文檔的版本與所處理的代碼的版本相對應。
- 分布在不同文檔中的重要信息很容易被錯過。
建議不要編寫需要外部文檔支持的代碼。這樣的代碼是脆弱的。要確保你的代碼本身讀起來就很清晰。
對于另一種方案——使用詳細的代碼注釋來使代碼文檔化——即使不是最糟的方法,也不是很好。一大堆毫無創造性的逐條注釋,會妨礙好代碼的產生。
避免這些,最好的方法應該就是編寫自文檔化的代碼吧。
要知道唯一能完整并正確地描述代碼的文檔就是代碼本身。這并不自然而然地意味著代碼本身就是最佳的描述,但是在通常情況下,這是我們所能獲得的唯一文檔。
因此,你應該想盡辦法使代碼成為良好的文檔,一種人人都可以讀懂的文檔。除了編寫代碼的作者外,必須還有更多的讀者可以理解代碼。編程語言是我們交流的媒介,清晰的交流至關重要。清晰的代碼就會獲得更高的質量,因為你犯錯誤的可能性會大大降低(錯誤變得顯而易見),而且這樣的代碼維護成本也比較低。
自文檔化的代碼是可讀性很強的代碼。它本身就易于理解,而不需要依賴外部文檔。我們可以通過很多方式提高代碼的清晰度。其中一些技巧是非常基礎的,而且在我們學習編程之初就被訓練加以掌握,而其他一些技巧則是從經驗中獲得。
編寫易于閱讀的代碼。人性化。簡單易懂。編譯器可以處理。
先看一段簡單的函數例子:
int fval(int i) {int ret=2;for (int n1=1, n2=1, i2=i-3; i2>=0; --i2){n1=n2; n2=ret; ret=n2+n1;}return (i<2) ? 1 : ret; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
這個示例非常接近現實。很多代碼看上去就是這個樣子的,雖然運行沒有問題,結果也能輸出準確,但是著實讓戰斗在前線的程序員們深受其害。再看一段自文檔化的代碼:
int fibonacci(int position) {if (position < 2){return 1;}int previouseButOne = 1;int previous = 1;int answer = 2;for (int n = 2; n < position; ++n){previouseButOne = previous;previous = answer;answer = previous + previouseButOne;}return answer; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
你或許只要讀第一行代碼,就可以知道這段程序在做什么了~它本身沒有注釋,卻非常容易理解。一定要記住:注釋只會增加人們的閱讀量。不必要的注釋,往往使代碼變得毫無必要地煩人,而且將來會使函數的維護變得更加艱難。了解這一點非常重要,因為即便是最小、最優美的函數也需要日后進行維護。
傳統的觀念認為,編寫自文檔化的代碼需要添加大量的注釋。良好的注釋編寫當然是一項重要的技能,但是除此以外,還有很多重要的技能。事實上,我們應該通過編寫不需要注釋的清晰代碼來主動避免注釋。
優秀的代碼許多特征都會重復出現,一種技巧的好處可以在代碼質量的若干方面得到體現。下面羅列出一些編寫自文檔化代碼的技術。
使用好的樣式編寫簡單的代碼
樣式極大地影響著代碼的清晰度。考慮周密的版面排布表達了代碼的結構,它使得函數、循環和田間語句更加明確。
- 讓“正常”的流程明顯地貫穿你的代碼。也就是說保持結構的一致性,例如,你的if-then-else結構的順序應該前后一致(總是將“正常”情況放在“錯誤”情況之前,或者反過來)。
- 避免過多地嵌套語句。這些語句會導致復雜的代碼,并且需要冗長的解釋說明。人們通常認為每個函數都應該有且僅有一個出口,這杯成為“單入口單出口”(Single Entry, Single Exit, SESE)代碼。但是,這要求對于可讀性代碼來說太嚴格了,并且可能會造成深層的嵌套。與下面這個SESE變體相比,我們更加喜歡前面看到的斐波拉契的例子:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
為了一條額外的return語句,我寧愿避免哪個不必要的嵌套——它使得函數更難以讀懂。函數邏輯深處的return語句,其作用值得質疑,但頂端簡短的循環卻極大地提高了函數的可讀性。
- 要謹慎地優化代碼,防止它不再清晰地表達基礎的算法。除非你已經非常明確某段代碼是可接受的程序函數的一個瓶頸,否則不要對代碼進行優化,即便優化了,也要清晰地注釋這段代碼發生了哪些變化。
選擇有意義的名稱
關于這一點,可以參考閱讀:C/C++ 名正則言順。
分解為原子函數
- 一個函數,一種操作。無需編寫非常復雜的函數,在一個函數中,只需進行一種操作,選擇一個毫無歧義的說明這種操作的名稱,既有利于代碼的利用效率,也便于移植和維護。
- 減少任何出人意料的副作用,不管他們看上去多么無害。
- 保持簡短。短小的函數易于理解,如果能分解為帶有描述性名稱的小代碼塊,那么即便是一個復雜的算法你也可以輕易地弄明白。
選擇有描述型的類型
盡可能使用現有語言的功能來描述約束或行為:
- 如果你要定義一個永遠都不變得值,那么就強制將它定義為常量類型(C/C++中使用const);
- 如果一個變量不應包含負值,那么就使用無符號類型;
- 使用枚舉emun來描述一組相關的值;
- 選擇適當的類型。
命名常量
看到類似if(count == 76)的代碼時往往令人困惑,數字76到底是什么意思?這句話的含義又是什么?這樣的數字都隱藏了其含義,不利于閱讀和理解,但是使用下面的方法:
const size_t bananas_per_cake = 76; ... if (count == bananas_per_cake) {// make banana cake }- 1
- 2
- 3
- 4
- 5
- 6
就會清晰很多(也有很多人習慣使用宏定義的放法#define BANANAS_PER_CAKE 76,但是高效C++的做法里,往往建議使用const來取而代之)。
強調重要的代碼
突出重要的代碼,把讀者的注意力吸引到正確的地方。編程時有很多機會可以做到這一點:
- 在類中按照一定的順序進行聲明。公共信息應該被放在首位,然后將私有的實現細節放在最后。
- 盡可能隱藏所有不重要的信息。不要以無用的垃圾信息塞滿全局命名空間。
- 不要隱藏重要的代碼。每行只寫一條語句,并保持每條語句都簡單。
- 限制嵌套的條件語句的數量。如果不加以限制,重要條件的處理就會被層層嵌套的if語句和括號隱藏起來。
提供頭文件
在文件的頂部放置一個注釋塊,以描述文件的內容以及該文件所屬的項目(甚至提供代碼創建和編輯人的名單及其郵箱等)。這并不需要花費太多精力,但是卻有明顯地效果。當有人維護這個文件時,他們就能馬上掌握關于該文件的信息。
這個頭文件非常重要,大多數公司處于法律方面的考慮,要求在每個源文件都必須包含一個可見的版權聲明。以cv.h(OpenCV 2.4.10版本的)為例:
/*M/// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this license. // If you do not agree to this license, do not download, install, // copy or use the software. // // // License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. // Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of the copyright holders may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, // indirect, incidental, special, exemplary, or consequential damages // (including, but not limited to, procurement of substitute goods or services; // loss of use, data, or profits; or business interruption) however caused // and on any theory of liability, whether in contract, strict liability, // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // //M*/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
或許你寫不了這么清晰,詳盡的頭文件,但是也應該提供一些最基本的描述:
/********************************************************** File: Foo.h* Purpose: Foo class implementation* Notice: (c) 1066 Foo industries. All rights reserved.*********************************************************/- 1
- 2
- 3
- 4
- 5
恰當地處理錯誤
在最恰當的上下文中處理錯誤。如果磁盤I/O有問題,你應該在訪問磁盤的代碼中處理這個問題。處理這個錯誤可能會引起另一個更高層的錯誤(如無法加載文件異常)。這意味著在程序的每一層上,一個錯誤就是此上下文中的問題是什么的一個精確描述。自文檔化代碼能幫助讀者了解錯誤的起因,錯誤意味著什么,以及對程序在這一點的暗示。
編寫有意義的注釋
前面已經講述了如何嘗試使用隱代碼文檔化技術來避免編寫注釋。不過,在你編寫出所能編寫的最清晰的代碼之后,其余信息仍然需要使用注釋來說明。
只有在你無法以任何其他方式來提高代碼清晰度的情況下,再添加注釋。
最后,總結一下。關于寫作技巧的提高,有一個簡單的原則:讀的書越多,寫得就越好。用批判的眼光閱讀著名作者的作品,可以是你學會如何分辨好壞。你會從中學到新的技巧和習慣用法,為你的知識庫添磚加瓦。類似地,如果你閱讀大量的代碼,那么你將會成為更出色的程序員。如果你將自己沉浸在優秀的代碼中,你很快就會具備在千里之外就能聞到糟糕代碼的能力。有了這些經驗,你自然而然地會發現自己在編寫代碼時能運用好的技巧,當你編寫出糟糕代碼時,你會馬上有所警覺,讓你渾身不自在。
--------------------- 本文來自 yhl_leo 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/yhl_leo/article/details/50545047?utm_source=copy
總結
- 上一篇: linux中断的上半部和下半部
- 下一篇: 深入理解C语言变量和内存——整理篇