我發現你們都喜歡在正文前嘮嗑幾句,多是作技術的日常自己就比較沉悶,跟周邊的人也不能無所顧忌的交流,畢竟國人都比較講究含蓄。在網上就不一樣了,能夠暢所欲言,因此每次除了分享知識,還順帶交待下本身,讓技術也就不那麼」冰冷「了。html
前幾天看本身的爲知,發現每一年都有個讀書計劃列表,不過大多都沒有完成,今年甚至連計劃都尚未。心裏以爲不安,正好從深圳回來的時候,公司把以前購買的書也一併帶回來了,從書架上挑了一本《編寫可讀代碼的藝術》來看,做爲今年的讀書開篇,順便也把讀書計劃繼續完成。node
這本書很薄,花了兩天時間就看完了,內容通俗易懂;jquery
它是根據做者在實際中碰到的問題,由淺入深,從幾個方面來敘述如何編寫可讀的代碼。程序員
書中指正的幾個地方,在我本身作過的項目中是真實存在,有頓悟的感受。web
實現一樣的功能,有的代碼讀起來味同嚼蠟,有的代碼讀起來妙不可言,耐人尋味。ajax
我想每一個coder所追求的無非都是那種書寫代碼時行雲流水般的感受,別人看到時爲之一嘆的知足感。json
本書對想提高本身代碼可讀性有必定幫助,想讀的朋友能夠搜一下。windows
每個變量名,每一段註釋,每一組分塊多會對你的可讀性產生影響。若是不想忍受重構時的苦難,那就從基本作起。緩存
1.選擇專業的詞 服務器
在不一樣場景中綜合考慮詞所表達的意義,這要求對項目所對應的業務很是熟悉。 其實在項目需求之時,有些專業詞語就應該整理成字典,讓項目組人員熟悉這些詞彙,對項目溝通頗有幫助。
編碼時花點時間去想一個合適的詞,雖然耽誤一些編碼的時間,可是對維護時有極大的好處。
2.找到更有表現力的詞
send --- deliver,dispatch,announce,distribute,route
find --- search,extract,locate,recover
start --- lunch,create,begin,open
make --- create,set up,build,generate,compose,add,new
*英語的同義詞有不少,合適的詞能讓讀代碼的人一眼明白,而不用去猜想。
3.retval,tmp,i,j,k 等臨時變量
retval,tmp除非代碼很短,意義清晰才用。
在循環迭代中,i,j,k能夠適當加上前綴,多重循環中更容易發現錯誤。
4.具體代替抽象
hex_id->id,若是須要注意id的格式
CanListenOnPort()優於ServerCanStart(),直接描述方法要作什麼。
5.爲名字附帶更多信息
id->hex_id
6.帶單位的值
start_ms->start
delay_secs->delay
size_mb->size
讀代碼的人不須要進入方法就知道應該使用什麼樣的值
7.關鍵信息附加
html_utf8->html 若是編碼很重要
pLast->Last 指針last
8.名字長度
小做用域中使用簡短的名字
儘可能不要本身創造縮略,衆所周知的縮略使用沒有問題。
丟掉不須要的詞
ToString->ConvertToString
9.名字格式傳遞含義
static const int kMaxOpenFiles = 100; class LogReader{ public: void OpenFile(string local_file); private: int offset_; DISALLOW_COPY_AND_ASSIGN(LogReader); }
*用下劃線來表示私有變量,確實讓人一眼就明白。
10.格式規範
js中,構造函數使用大寫,普通函數首字母小寫。
jquery庫中,jquery返回的結果也加上$符號。
var $all_image = $('img');
1.Filter()
究竟是「挑出」仍是「篩選」
results = Database.Objects.Filter("year<=2011");
2.Clip(text,length)
是尾部刪除length長度,仍是裁掉最大長度的length一段
Truncate(text,length)->Clip(text,length);
Truncate(text,max_chars)->Truncate(text,max_length);
3.使用min和max來表示(包含)極限
MAX_ITEMS_IN_CART->CART_TOO_BIG_LIMT;
4.使用first和last表示包含範圍
integer_range(first,last)->integer_range(start,stop);
5.使用begin和end表示包含-排除範圍
PrintEventInRange("OCT 16 12:00am","OCT 17 12:00am")->
PrintEventInRange("OCT 16 12:00am","OCT 16 11:59:59pm")->
6.給布爾值命名
need_password(user_is_authenticated)->read_password
is,has,can,should使單詞更明確
避免使用反義詞
bool use_ssl = false ->disable_ssl = true;
7.與指望匹配
get*如今看成」輕量級訪問器「,若是getXX過於複雜,使用者過於輕率使用。
8.list:size()
void ShrinkList(list<Node>& list,int max_size){ while(list.size()>max_size){ FreeNode(list.back()); list.pop_back(); } }
list.size()不是事先算好的數,而是一個O(n)操做。致使ShinkList變爲O(n2)操做。
*C++標準庫list.size()已經改成O(1)操做
9.權衡備選名字
1.使代碼排版優美
如今通常編輯器提供了自動排版。
2.一目瞭然的對齊
經過額外引入行來使得排版緊湊。
3.用方法來整理不規則的代碼
4.使代碼整齊。
5.選有意義的排序。
details,location,phone,email,url.
代碼組織使應該有必定的順序,尤爲是大類。
一旦固定,不要輕易修改,不然會讓人迷惑。
6.把聲明按塊組織起來
C#中有#region 段落 #endregion
7.把代碼分紅「段落」
8.保持我的風格一致。
1.不要爲那些從代碼自己就能快速推斷的事實寫註釋。
2.不要爲了註釋而註釋
3.改進名字的意義比增長註釋更好。
ReleaseRegistryHandle(RegistryKey* key)->
//This doesn't modify the actual registry.
DeleteRegistry(RegistryKey* key);
4.加入"導演評論「
//二叉樹比用哈希錶快40%
//哈希運算的代價比左/右比較大得多。
*這樣能夠避免了維護人作無謂的優化。
5.爲代碼瑕疵註釋
TODO:尚未處理的事情
FIXME:已知的沒法運行代碼
HACK:比較粗糙的解決方案
XXX:重要問題
6.給常量加註釋
NUM_THREADS = 8; #as long as it's >=2 * num_processors,that's good enough.
7.意料之中的提問
void Clear(){ //Force vector to relinquish its memory(look up "STL swap trick") vector<float>().swap(data); }
8.未雨綢繆
//調用外部服務來發送郵件.(1分鐘後超時) void SendEmail(string to,string subject,string body);
9.全局觀註釋
//這個文件包含一些輔助函數,爲咱們文件系統提供了更便利的接口.
//它處理了文件權限以及其餘基本細節.
10.總結性註釋
#find all the itmes that customers purchased for themselves. for customer_id in all_customers: for sale in all_sales[customer_id].sales: if sale.recipient==customer_id
1.讓註釋緊湊
//CategoryType->(socre,weight)
typedef has_map<int,pair<float,float>> ScoreMap;
2.避免使用不明確的代詞.而使用具體的指代.
the data too big -> it's too big.
3.潤色粗糙的句子
#give higher priority to URLs we've never crawled before.->
#depending on whether we've already crawled this url before,give it a different priority.
4.精確描述函數的行爲
//return the number of lines in this file. × //Count how many newline bytes('\n') are in the file. √ int CountLines(string filename){}
5.用輸入/輸出列子來講明特別狀況
//remove the suffix/prefix of 'chars' from the input 'src'. + //Example:Strip("abba/a/ba","ab") returns "/a". √ String Strip(string src,string chars){ }
6.聲明代碼的意圖
//iterate through the list in reverse order × //display each price,from highest to lowest √ for(list<Product>::reverse_iterator it = products.rbegin();...)
7.具名函數參數的註釋
Connect(/* timeout_ms = */ 10,/* use_encryption */ false);
8.採用信息量較高的詞
//this class acts as a chahing layer to the database. //Canonicalize the street address(remove extra spaces,"avenue"->"ave",etc)
1.循環參數的順序
length>10 -> 10<=length
比較左側:「被問詢的」表達式,它的值更傾向於不斷變化。
比較右側:用來作比較的表達式,它的值更傾向於常量。
"尤達表示法":null==obj -> obj==null (尤達大師口氣)
*現代編譯器已不需如此
2.if/else語句塊
首先處理正邏輯而不是負邏輯的狀況。
先處理掉簡單的狀況。
首先處理有趣或可疑的狀況。
3.?:條件表達式
簡單直觀的狀況下使用,追求最小化人們理解的時間,而不是最小化行數。
4.避免do/whiile
public bool ListHasNode(Node node,string name,int max_length) { while(node!=null && max_length-->0) { if(node.name().equals(name)) return true; } return false; }
*爲了省略do而重複body也是愚蠢的。
5.從函數中提早返回
public bool Contains(string str,string substr) { if(str==null || substr==null) return false; if(substr.equals("")) return true; ... }
若是不用「保護語句」(guard clause)會很不天然。
6.重名昭著的goto
if(p==NULL) goto exit; ... exit: fclose(file1); fclose(file2);
只有這種形式的goto值得推薦。
7.最小化嵌套
當改動代碼時,從全新的角度審視它,做爲總體看待。
if(user_result==success){ if(permission_result!=success){ reply.WrieErrors("error reading permissions"); reply.Done(); return; } reply.WriteErrors(""); } else { reply.WriteErrors(user_result); } reply.Done();
permission_result!=success條件是後來添加,但這樣看起來不直觀。
if(user_result!=SUCCESS) { reply.WriteErrors(user_result); reply.Done(); return; } if(permission_result!=SUCCESS){ reply.WrieErrors(permission_result); reply.Done(); return; } reply.WriteErrors(""); reply.Done();
用函數的當即返回來減小嵌套。
8.減小循環內的嵌套
for(int i=0;i<result.size();i++){ if(results[i]!=NULL) non_null_count++; if(results[i]->name!=""){ count<<"considering candidate..."<<endl; } } }
能夠利用continue來作保護
for(int i=0;i<result.size();i++){ if(results[i]==NULL) continue; non_null_count++; if(results[i]->name=="") continue; count<<"considering candidate..."<<endl; }
9.代碼執行流程
線程:不清楚什麼時間執行代碼
信號量/中斷處理:有些代碼隨時有可能執行
異常:可能會從多個函數調用中向上冒泡執行
函數指針和匿名函數:很難知道到底會執行什麼,由於在編譯時還沒決定
虛方法:object.virtualMethod()可能會調用一個未知子類代碼
1.用解釋變量
if line.split(':')[0].strip()=="root"; ... var username = line.split(';')[0].strip(); if username=="root";
2.總結變量
bool user_owns_document = (request.user.id==document.owner_id); if(user_owns_document){ //user can edit this document ... } if(!user_owns_document){ ... }
3.使用德摩根定理
not(a or b or c) <=> (not a) and (not b) and (not c)
not(a and b and c) <=> (not a) or (not b) or (not c)
if(!(file_exists && !is_protected)) Error("could not read file"). if(!file_exists || is_protected) Error("could not read file");
4.不要濫用短路邏輯
assert((!(bucket=FindBucket(key))) || !bucket->IsOccupied());
bucket = FindBucket(key);
if(bucket!=NULL) assert(!bucket->IsOccupied());
不要讓代碼成爲思惟的減速帶。
達到簡明整潔的代碼可使用短路邏輯。
if(object && object->method()) ... x = a || b || c;
5.與複雜邏輯爭鬥
struct Range { int begin; int end; //for example:[0,5) overlaps with [3,8) bool OverlapsWidth(Range other); } bool Range::OverlapsWith(Range other){ return (begin>=other.begin && begin<other.end) || (end > other.begin && end <=other.end) || (begin <= other.begin && end >= other.end);} bool Range::OverlapsWith(Range other) { if(other.end<=begin) return false; //they end before we begin if(other.begin>=end) return false; //they begin after we end return true; //only possibility left:they overlap }
從「反方向」解決問題,這裏的反向是「不重疊」。只有兩種可能:
另外一個範圍在這個範圍開始前結束。
另外一個範圍在這個範圍結束前開始。
6.拆分巨大的語句
var update_highlight = function ( message_num ){ var thumbs_up = $("#thumbs_up"+message_num); var thumbs_down = $("#thumbs_down"+message_num); var vote_value = $("#vote_value"+message_num); var hi = "ighlighted"; if(vote_value == "up"){ thumbs_up.addClass(hi); } else if(vote_value=="Down"){ ... } else{ ... } }
7.簡化表達式創意
void AddStats(const Stats& add_from,Stats* add_to){ #define ADD_FIELD(field) add_to->set_##field(add_from.field()+add_to->field()) ADD_FIELD(total_memory); ... #undef ADD_FIELD }
利用宏的特性來簡化重複的工做,可是不鼓吹常用宏。
變量越多,越難所有跟蹤它們的動向。
變量的做用域越大,就須要跟蹤它的動向越久。
變量改變越頻繁,就越難以跟蹤它的當前值。
1.減小變量
var now = datetime.datetime.now(); root_message.last_view_time = now;
沒有拆分任何複雜表達式 。
沒有更多的澄清。
只用過一次,沒有壓縮任何冗餘代碼。
這種變量能夠刪除掉。
2.減小中間結果
var remove_one = function(array,value_to_remove){ var index_to_remove = null; for(var i =0;i<array.length;i+=1){ if(array[i]==value_to_remove){ index_to_remove=i; break; } } if(index_to_remove!=null){ array.splice(index_to_remove,1); } } var remove_one = function(array,value_to_remove){ for(var i =0;i<array.length;i+=1){ if(array[i]==value_to_remove){ array.splice(i,1); return; } } }
3.減小控制流變量
bool done = false; while(/* condition */ && !done){ ... if(..){ done = true; continue; } } while(/* condition */){ ... if(..){ break; } }
4.縮小變量的做用域
class LaregeClass{ string str_; void Method1(){ str_ = ...; Method2(); } void Method2(){ //use str_ } //lots of other methods that don't use str_ } class LaregeClass{ void Method1(){ string str = ...; Method2(str); } void Method2(string str){ //use str } //other method can't see str. }
將類的成員變量「降格」爲局部變量。
5.C++中的if語句
if(PaymentInfo* info = database.ReadPaymentInfo()){ count << "user paid;"<< info->amount()<<endl; }
*相似C#中的using語句做用域。
6.JavaScript中建立「私有」變量
submitted = false;//note:global variable var submit_form = function(form_name){ if(submitted){ return; //dont't double-submit the form } ... submitted = true; } var submit_form = (function(){ var submitted = false;//note:global variable return function(form_name){ if(submitted){ return; //dont't double-submit the form } ... submitted = true; } }(());
最後的圓括號會使得外層這個匿名函數當即執行,返回內層的函數。
7.JavaScript全局做用域
js中若是定義變量省略了var,這個變量會放在全局做用域中。
var f = function (){ //DANGER:"i" is not declared with 'var'! for(i = 0;i<10;i++) ... } f(); alert(i); //alerts '10'.'i' is global variable!
8.JavaScript中麼有嵌套做用域
if(...){ int x =1; } x++;//compile-error!'x' is undefined.
可是在Python和JS中,在語句塊中定義的變量會「溢出」到整個函數。這種規則下會變量有一些不一樣的寫法。
9.把定義向下移
不要一開始定義全部變量,把定義移到它的使用以前
def ViewFilterReplies(original_id){ root_message = Messages.objects.get(original_id) root_message.view_count+=1 root_message.save() all_replies = Mesages.objects.select(root_id=original_id) filtered_replies =[] for reply in all_replies: if reply.spam_votes <= MAX_SPAM_VOTES: filtered_replies.append(reply) return filtered_replies }
10.只寫一次變量
var setFirstEmptyInput = function (new_value){ for(var i=1;true;i++){ var elem = document.getElementById('input'+i); if(elm = null) return null; if(elem.value === ''){ elem.value = new_value; return elem; } } }
看看某個函數或代碼塊:這段代碼高層次目標是什麼?
對於每一行代碼:它是直接爲了目標而工做嗎?這段代碼的高層次的目標是什麼?
若是足夠的行數在解決不相關的子問題,抽取代碼到獨立函數中。
1.findClosestLocation()
抽取spherical_distnace()用來「計算兩個經緯度座標點之間的球面距離」。
其實就是重構代碼。
2.純代碼工具
當你在想「我但願咱們的庫裏面有xxx()函數」, 那麼就動手寫一個。慢慢會積累很多工具代碼。
3.多用途代碼
ajax_post({ ... success:function(response_data){ var str = "{\n"; for(var key in oj){ str += "" + key + " = " + obj[key] +"\n"; } return str+"}"; } }); var format_pretty = function(obj){ var str = "{\n"; for(var key in oj){ str += "" + key + " = " + obj[key] +"\n"; } return str+"}"; };
抽取format_pretty.當須要處理更多狀況時,能夠對format_pretty進行擴展
var format_pretty = function(obj,indent){ //handle null,undefined,strings,and non-objects. if(obj == null) return "null"; if(obj === undefined) return "undefined"; if(typeof obj === "string") return '"'+obj+'"'; if(typeof obj === "object") return string(obj); if(indent === undefined) indent =""; //hand(non-null) objects. var str = "{\n"; for(var key in oj){ str += "" + key + " = "; str += format_pretty(obj[key],indent+"") +"\n"; } return str + indent + "}"; };
4.項目專有功能
CHARS_TO_REMOVE = re.complie(r"[\.]+") CHARS_TO_DASH = re.compile(r"[^a-z0-9]+") def make_url_friendly(text): text = text.lower() text = CHARS_TO_REMOVE.sub('',text) text = CHARS_TO_DASH.sub('-',text) return text.strip("-") business = Business() business.name = request.POST["name"] business.url = "/biz/"+make_url_friendly(business.name) business.date_created = datetime.datetime.utcnow() business.save_to_database()
5.簡化已有接口
js中操做cookie比較不理想,因此須要本身重寫。
永遠都不要安於使用不理想的接口。老是能夠本身建立包裝函數來隱藏接口的粗陋細節。
def url_safe_encrypt(obj): obj_str = json.dumps(obj) cipher = Cipher("aes_128_cbc",key=PRIVATE_KEY,init_vector=INIT_VECTOR,op=ENCODE) encrypted_bytes = cipher.update(obj_str) encrypted_bytes += cipher.final() #flush out the current 128 bit block return base64.urlsafe_b64encode(encrypted_bytes) user_info = { "username":"...","password":"..." } url = "http://.../?user_info="+url_safe_encrypt(user_info)
6.過猶不及
若是上一個問題進一步拆分
def url_safe_encrypt(obj): obj_str = json.dumps(obj) return url_safe_encrypt_str(obj_str) def url_safe_encrypt_str(data): encrypted_bytes = encrypt(data) return base64.urlsafe_b64encode(encrypted_bytes) def encrypt(data): cipher = make_chiper() encrypted_bytes = cipher.update(obj_str) encrypted_bytes += cipher.final() #flush out the current 128 bit block return encrypted_bytes def make_cipher() return Cipher("aes_128_cbc",key=PRIVATE_KEY,init_vector=INIT_VECTOR,op=ENCODE)
引入衆多小函數對可讀性不利,付出代價卻沒有獲得實質價值。
1.任務能夠很小
var vote_changed = function(old_vote,new_vote){ var score = get_score(); if(new_vote!==old_vote){ if(new_vote==='Up') score += (old_vote === 'Down' ? 2:1); else if(new_vote==='Down') score -= (old_vote === 'Up'?2:1); else if(new_vote==='') score +=(old_vote==='Up'?-1:1); } }; var vote_value = function(vote){ if(vote ==='Up') return +1; if(vote === 'Down') return -1; return 0; }; var vote_changed = function(old_vote,new_vote){ var score = get_score(); score -= vote_value(old_vote);//remove the old vote socre += vote_value(new_vote);//add the new vote set_score(score); };
2.從對象中抽取值
當須要抽取對象中的值時,能夠優先將其提取。
var town = location_info["LocalityName"]; var city = location_info["SubAdministrativeAreaName"]; var state = location_info["AdministativeAreaName"]; var country = location_info["CoountryName"]; var first_half = "Middleof_nowhere"; if(state&&country!="USA") first_half = state; if(city) first_half = city; if(town) first_half = town; return first_half+", "+second_half;
3.JavaScript另外一種作法
var fisrt_half,second_half; if(country === "USA"){ first_half = town || city || "middle-of-nowhere"; second_half = state || "USA"; }else { first_half = town || city || state || "middle-of-nowhere"; second_half = country || "planet earth"; } return first_half + ", " + second_half;
4.大例子
void UpdateCounts(HttpDownload hd){ counts["exit state"][ExitState(hd)]++; counts["http response"][HttpResponse(hd)]++; counts["content-type"][ContentType(hd)]++; } string ExitState(HttpDownload hd){ if(hd.has_event_log() && hd.event_log().has_exit_state()){ return ExitStateTypeName(hd.event_log().exit_state()); } else{ return "unknow"; } }
1.清楚描述邏輯
使用天然語言來敘述一個邏輯
受權你有兩種方式:
你是管理
你擁有當前文檔
不然,沒法受權你。
if(is_admin_request()){ //authorized }else if($document && ($document['username']==$_SESSION['username'])){ //authorized }else{ return not_authorized(); }
2.瞭解函數庫
找到當前可見的提示並隱藏它
而後找到它的下一個提示並顯示。
若是沒有更多提示,循環回第一個提示。
var show_next_tip = function(){ var cur_tip = $('.tip:visible').hide();//find the currently visible tip and hide it; var next_tip = cur_tip.next('.tip'); //find the next tip after it if(next_tip.size()===0) // if we're run out of tips next_tip = $('.tip:first'); //cycle back to the first tip next_tip.show(); //show the new tip }
3.更大的問題
並行讀取三個迭代行
只要這些行不匹配,向前找知道它們匹配。
而後輸出匹配的行。
一直作到沒有匹配的行。
def PrintStockTransactions(): while True: time = AdvanceToMatchingTime(stock_iter,price_iter,num_shares_iter) if time is None: return #print the aligned rows. print "@",time, print stock_iter.ticker_symbol, print prince_iter.price, print num_shares_iter.number_of_shares stock_iter.NextRow() price_iter.NextRow() num_shares_iter.NextRow() AdvanceToMatchingTime:
看一下每一個當前行:若是它們匹配,那麼就完成。
不然,向前移動任何」落後「的行。
一直這樣作知道全部行匹配(或者迭代結束)。
def AdvanceToMatchingTime(row_iter1,row_iter2,row_iter3){ t1 = row_iter1.time t2 = row_iter2.time t3 = row_iter3.time if t1==t2==t3 return t1; tmax = max(t1,t2,t3) #if any row is "behind," advance it . #eventually,this while loop will align them all. if t1 < tmax:row_iter1.NextRow() if t2 < tmax:row_iter2.NextRow() if t3 < tmax:row_iter3.NextRow() }
知道何時不寫代碼對於一個程序員來講是他所學習的最重要技巧。所寫的每一行代碼都要測試和維護。經過重用和減小功能,能夠節省時間並保持代碼簡潔。
1.商店定位器
不須要去考慮南極北極,計算地表曲面的狀況。
2.增長緩存
對於特殊狀況,不須要去實現LRU(最近最少使用)緩存,僅僅須要一個條目緩存:
DiskObjet lastUsed; DiskObject LookUp(string key){ if(lastUsed == null || !lastUsed.key().equals(key)){ lastUsed = loadDiskObject(key); }
return lastUsed;
}
3.保持小代碼庫
建立越多越好的「工具」代碼來減小重複代碼
減小無用代碼或沒有用的功能
讓項目保持分開的子項目狀態
當心代碼的「重量」,保持代碼輕盈
4.熟悉周邊的庫
每隔一段時間,花點時間閱讀標準庫中的全部函數/模塊/類型的名字。
不是須要記住全部庫函數,而是「靈機一動」發現代碼中存在的相似東西。
1.使測試易於閱讀和維護
咱們有一個文檔列表,它們的分數爲[-5,1,4,-99998.7,3].
在SortAndFilterDocs()以後,剩下的文檔應該有的分數是[4,3,1],並且順序也是這樣
CheckScoresBeforeAfter("-5,1,4,-99998.7,3","4,3,1');
對於這樣的輸入/情形,指望有這樣的行爲/輸出.
void CheckScoresBeforeAfter(string input,string expected_output){ vector<ScoredDocument> docs = ScoredDocsFromString(input); SortAndFilterDocs(&docs); string output = ScoredDocsToString(docs); assert(output == expected_output); } vector<ScoredDocument> ScoredDocsFromString(string scores){ vector<ScoredDocument> docs; replace(scores.begin(),scores.end(),',',' '); //popluate 'docs' from a string of space-separeated scores. istringstream stream(socres); double score; while(stream >> score){ AddScoredDoc(docs,score); } return docs; } string ScoredDocsToString(vector<ScoredDocument> docs){ ostringstream stream; for(int i=0;i<docs.size();i++){ if(i>0) stream << ", "; stream<<doc[i].score; } return stream.str(); }
2.讓錯誤消息可讀
看語言庫/框架能給你提供何種幫助。
編寫程序來產生測試數據/代碼。
3.爲測試函數命名
被測試的類
唄測試的函數
唄測試的情形或bug
void Test_SortAndFilterDocs(){...}
void Test_SortAndFilterDocs_basicSorting(){...}
void Test_SortAndFilterDocs_NegativeValues(){...}
4.測試驅動開發(TDD)
可測試性差的代碼特徵
使用全局變量:
對於每一個測試都要重置全部的全局狀態。
很難理解哪些函數有什麼反作用。沒辦法獨立考慮每一個函數,要考慮整個程序才能理解是否是全部代碼都能工做。
對外部組件有大量依賴的代碼:
很難給它寫出任何測試,由於要先搭建太多的腳手架。寫測試比較無趣,沒人願意寫測試。
系統可能會由於某一依賴失敗而失敗。對於改動來說很難知道會產生什麼樣的影響。很難重構類。系統會有更多失敗模式,而且要考慮更多恢復路徑。
代碼有不肯定的行爲:
測試會很古怪,並且不可靠。常常失敗的測試會被忽略。
這種程序可能會有競爭或其餘難以重現的bug。很難推理。產品中的bug很難跟蹤和改正。
可測試性好的代碼特徵
類中只有少或者沒有內部狀態:
容易測試,由於一個方法只要較少的設置。而且較少的隱藏狀態須要檢查。
有較少狀態的類更簡單,更容易理解。
類/函數只作一件事:
要測試它只須要較少的測試用例。
較小/較簡單的組件更加模塊化,而且通常來說系統有更少的耦合。
依賴少;耦合低:
每一個類獨立的測試。
系統能夠並行開發。能夠容易修改或者刪除類,而不會影響系統的其餘部分
函數的接口簡單,定義明確:
有明確的行爲能夠測試。測試簡單接口所需的工做量較少。
接口更容易學習,而且重用可能性較大。
每一個測試的最高一層應該越簡明越好。最好每一個測試的輸入/輸出能夠用一行代碼描述。
若是測試失敗了,它所發出的錯誤消息應該能讓你容易跟蹤並修復這個bug。
使用最簡單的而且可以完整運用代碼的測試輸入。
給測試函數其一個完整描述性的名字,使得每一個測試很明確。
設計跟蹤在過去的一分鐘和一小時裏web服務器傳輸了多少字節。
//Track the cumulative counts over the past minute and over the past hour. //useful,for exmaple,to track recent bandwidth usage. class MinuteHourCounter{ //Add a new data point(count>=0) //For the next minute,MinuteCount() will be larger by +count. //For the next hour,HourCount() will be larger by +count. void Add(int count); //return the accumulated count over the past 60 seconds. int MinuteCount(); //Return the accumulated count over the past 3600 seconds. int HourCount(); }; class MinuteHourCounter{ list<Event> minute_events; list<Event> hour_events; //only contians elements not in minute_events int minute_count; int hour_count; //counts all events over past hour,including past minute void Add(int count){ const time_t now_secs = time(); ShiftOldEvents(now_secs); //feed into the minute list(not into the hour list--that will happedn laster) minute_events.push_back(Event(count,now_secs)); miniute_count+=count; hour_count+=count; } int MinuteCount{ ShiftOldEvents(time()); return minute_count; }; int HourCount(){ ShiftOldEvents(time()); return hour_count; } //Find and delete old eents,and decrease hour_count and minute_count accordingly. void ShiftOldEvents(time_t now_secs){ const int minute_ago = now_secs-60; const int minute_ago = noew_secs-3600; //move events more than one minute old from 'minute_events' into 'hour_events' //(events older than one hour will be removed in the second loop.) while(!minute_events.empty() && minute_events.front().time<=minute_ago){ hour_events.push_back(minute_events.front()); minute_count -= minute_events.front().count; minute_events.pop_front(); } //remove events more than one hour old from 'hour_events' while(!hour_events.empty() && hour_events.front().time<=hour_ago){ hour_count -= hour_events.front().count; hour_events.pop_front(); } } }
使用時間桶優化:
//a class that keeps counts for the past N buckets of time. class TrailingBucketCounter{ public: //example:trailingbucketcounter(30,60)tracks the last 30 minute-buckets of time. TrailingBucketCounter(int num_buckets,int secs_per_bucket); void Add(int count,time_t now); //return the total count over the last num_buckets worth of time int TrailingCount(time_t now); } class MinuteHourCounter{ TrailingBucketCounter minute_counts; TrailingBucketCounter hour_counts; public: MinuteHourCounter(): minute_count(/* num_buckets = */60,/* secs_per_bucket=*/1), hour_counts(/* num_buckets = */60,/* secs_per_bucket=*/60){ } void Add(int count){ time_t now = time(); minute_counts.Add(count,now); hour_counts.Add(count,now); } int MinuteCount(){ time_t now = time(); return minute_counts.TrailingCount(now); } int HourCount(){ time_t now = time(); return hour_counts.TrailingCount(now); } } class TrailingBucketCounter{ ConveyorQueue buckets; const int secs_per_bucket; time_t last_update_time; //the last time update() was called //calculate how many buckets of time have passed and shift() accordingly. void Update(time_t now){ int current_bucket = now / secs_per_bucket; int last_update_bucket = last_update_time / secs_per_bucket; buckets.Shift(current_bucket - last_update_bucket); last_update_time = now; } public: TrailingBucketCounter(int num_buckets,int secs_per_bucket): buckets(num_buckets); secs_per_bucket(secs_per_bucket){} void Add(int count,time_t now){ Update(now); buckets.AddToBack(count); } int TrailingCount(time_t now){ Update(now); return bucktesTotalSum(); } }; //A queue with a maximum number of slots,where old data gets shifted off the end. class ConveryorQueue{ queue<int> q; int max_items; int total_sum ; //sum of all items in q public: ConveyorQueue(int max_itmes):max_items(max_items),total_sum(0){} int TotalSum(){ return total_sum; } void Shift(int num_shifted){ //in case too many items shifted,just clear the queue. if(num_shifted>=max_items){ q = queue<int>(); //clear the queue total_sum = 0; return; } //push all the needed zeros. while(num_shifted>0){ q.push(0); num_shifted--; } //let all the excess items fall off. while(q.size()>max_items){ total_sum -= q.front(); q.pop(); } } void AddToBack(int count){ if(q.empty()) Shift(1); //make sure q has at least 1 item. q.back() += count; total_sum += count; } }
《代碼質量》
《代碼整潔之道》
《代碼之美》
《代碼之殤》
《重構-改善既有代碼的設計》
《程序員修煉之道 從小工到專家》
寫個博客真是不容易,我在wiz下寫的筆記,而後挪到windows live writer下,整個代碼全亂了,用wiz去發佈到博客內容又更亂。心累,之後慢慢改吧,你們多包含。