今天小編給大家分享一下web開(kāi)發(fā)中怎么編寫(xiě)可讀代碼的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
遂川網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,遂川網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為遂川數(shù)千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)網(wǎng)站制作要多少錢(qián),請(qǐng)找那個(gè)售后服務(wù)好的遂川做網(wǎng)站的公司定做!
“Code should be written to minimize the time it would take for someone else to understand it.”
日常工作的事實(shí)是:
寫(xiě)代碼前的思考和看代碼的時(shí)間遠(yuǎn)大于真正寫(xiě)的時(shí)間
讀代碼是很平常的事情,不論是別人的,還是自己的,半年前寫(xiě)的可認(rèn)為是別人的代碼
代碼可讀性高,很快就可以理解程序的邏輯,進(jìn)入工作狀態(tài)
行數(shù)少的代碼不一定就容易理解
代碼的可讀性與程序的效率、架構(gòu)、易于測(cè)試一點(diǎn)也不沖突
整本書(shū)都圍繞“如何讓代碼的可讀性更高”這個(gè)目標(biāo)來(lái)寫(xiě)。這也是好代碼的重要標(biāo)準(zhǔn)之一。
使用含義明確的詞,比如用download而不是get,參考以下替換方案:
send -> deliver, dispatch, announce, distribute, route find -> search, extract, locate, recoverstart -> lanuch, create, begin, open make -> create,set up, build, generate, compose, add, new
像tmp
和retval
這樣詞,除了說(shuō)明是臨時(shí)變量和返回值之外,沒(méi)有任何意義。但是給他加一些有意義的詞,就會(huì)很明確:
tmp_file = tempfile.NamedTemporaryFile() ...SaveData(tmp_file, ...)
不使用retval而使用變量真正代表的意義:
sum_squares += v[i]; // Where's the "square" that we're summing? Bug!
嵌套的for循環(huán)中,i
、j
也有同樣讓人困惑的時(shí)候:
for (int i = 0; i < clubs.size(); i++) for (int j = 0; j < clubs[i].members.size(); j++) for (int k = 0; k < users.size(); k++) if (clubs[i].members[k] == users[j]) cout << "user[" << j << "] is in club[" << i << "]" << endl;
換一種寫(xiě)法就會(huì)清晰很多:
if (clubs[ci].members[mi] == users[ui]) # OK. First letters match.
所以,當(dāng)使用一些通用的詞,要有充分的理由才可以。
CanListenOnPort
就比ServerCanStart
好,can start比較含糊,而listen on port確切的說(shuō)明了這個(gè)方法將要做什么。
--run_locally
就不如--extra_logging
來(lái)的明確。
_ms
,對(duì)原始字符串加_raw
如果一個(gè)變量很重要,那么在名字上多加一些額外的字就會(huì)更加易讀,比如將string id; // Example: "af84ef845cd8"
換成string hex_id;
。
Start(int delay) --> delay → delay_secs CreateCache(int size) --> size → size_mbThrottleDownload(float limit) --> limit → max_kbps Rotate(float angle) --> angle → degrees_cw
更多例子:
password -> plaintext_password comment -> unescaped_comment html -> html_utf8 data -> data_urlenc
在比較小的作用域內(nèi),可以使用較短的變量名,在較大的作用域內(nèi)使用的變量,最好用長(zhǎng)一點(diǎn)的名字,編輯器的自動(dòng)補(bǔ)全都可以很好的減少鍵盤(pán)輸入。對(duì)于一些縮寫(xiě)前綴,盡量選擇眾所周知的(如str),一個(gè)判斷標(biāo)準(zhǔn)是,當(dāng)新成員加入時(shí),是否可以無(wú)需他人幫助而明白前綴代表什么。
_
、-
等符號(hào),比如對(duì)私有變量加_
前綴。var x = new DatePicker(); // DatePicker() 是類(lèi)的"構(gòu)造"函數(shù),大寫(xiě)開(kāi)始var y = pageHeight(); // pageHeight() 是一個(gè)普通函數(shù) var $all_images = $("img"); // $all_images 是jQuery對(duì)象var height = 250; // height不是 //id和class的寫(xiě)法分開(kāi)...命名不能有歧義
命名的時(shí)候可以先想一下,我要用的這個(gè)詞是否有別的含義。舉個(gè)例子:
results = Database.all_objects.filter("year <= 2011")現(xiàn)在的結(jié)果到底是包含2011年之前的呢還是不包含呢?
使用
min
、max
代替limit
CART_TOO_BIG_LIMIT = 10 if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT: Error("Too many items in cart.") MAX_ITEMS_IN_CART = 10 if shopping_cart.num_items() > MAX_ITEMS_IN_CART: Error("Too many items in cart.")對(duì)比上例中
CART_TOO_BIG_LIMIT
和MAX_ITEMS_IN_CART
,想想哪個(gè)更好呢?使用
first
和last
來(lái)表示閉區(qū)間print integer_range(start=2, stop=4)# Does this print [2,3] or [2,3,4] (or something else)? set.PrintKeys(first="Bart", last="Maggie")
first
和last
含義明確,適宜表示閉區(qū)間。使用
beigin
和end
表示前閉后開(kāi)(2,9))區(qū)間PrintEventsInRange("OCT 16 12:00am", "OCT 17 12:00am") PrintEventsInRange("OCT 16 12:00am", "OCT 16 11:59:59.9999pm")上面一種寫(xiě)法就比下面的舒服多了。
Boolean型變量命名
bool read_password = true;這是一個(gè)很危險(xiǎn)的命名,到底是需要讀取密碼呢,還是密碼已經(jīng)被讀取呢,不知道,所以這個(gè)變量可以使用
user_is_authenticated
代替。通常,給Boolean型變量添加is
、has
、can
、should
可以讓含義更清晰,比如:SpaceLeft() --> hasSpaceLeft()bool disable_ssl = false --> bool use_ssl = true符合預(yù)期
public class StatisticsCollector { public void addSample(double x) { ... } public double getMean() { // Iterate through all samples and return total / num_samples } ...}在這個(gè)例子中,
getMean
方法遍歷了所有的樣本,返回總額,所以并不是普通意義上輕量的get
方法,所以應(yīng)該取名computeMean
比較合適。漂亮的格式
寫(xiě)出來(lái)漂亮的格式,充滿(mǎn)美感,讀起來(lái)自然也會(huì)舒服很多,對(duì)比下面兩個(gè)例子:
class StatsKeeper { public: // A class for keeping track of a series of doubles void Add(double d); // and methods for quick statistics about them private: int count; /* how many so far */ public: double Average(); private: double minimum; listpast_items ;double maximum;}; 什么是充滿(mǎn)美感的呢:
// A class for keeping track of a series of doubles// and methods for quick statistics about them.class StatsKeeper { public: void Add(double d); double Average(); private: listpast_items; int count; // how many so far double minimum; double maximum;}; 考慮斷行的連續(xù)性和簡(jiǎn)潔
這段代碼需要斷行,來(lái)滿(mǎn)足不超過(guò)一行80個(gè)字符的要求,參數(shù)也需要注釋說(shuō)明:
public class PerformanceTester { public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator( 500, /* Kbps */ 80, /* millisecs latency */ 200, /* jitter */ 1 /* packet loss % */); public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator( 45000, /* Kbps */ 10, /* millisecs latency */ 0, /* jitter */ 0 /* packet loss % */); public static final TcpConnectionSimulator cell = new TcpConnectionSimulator( 100, /* Kbps */ 400, /* millisecs latency */ 250, /* jitter */ 5 /* packet loss % */);}考慮到代碼的連貫性,先優(yōu)化成這樣:
public class PerformanceTester { public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator( 500, /* Kbps */ 80, /* millisecs latency */ 200, /* jitter */ 1 /* packet loss % */); public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator( 45000, /* Kbps */ 10, /* millisecs latency */ 0, /* jitter */ 0 /* packet loss % */); public static final TcpConnectionSimulator cell = new TcpConnectionSimulator( 100, /* Kbps */ 400, /* millisecs latency */ 250, /* jitter */ 5 /* packet loss % */);}連貫性好一點(diǎn),但還是太羅嗦,額外占用很多空間:
public class PerformanceTester { // TcpConnectionSimulator(throughput, latency, jitter, packet_loss) // [Kbps] [ms] [ms] [percent] public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(500, 80, 200, 1); public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator(45000, 10, 0, 0); public static final TcpConnectionSimulator cell = new TcpConnectionSimulator(100, 400, 250, 5);}用函數(shù)封裝
// Turn a partial_name like "Doug Adams" into "Mr. Douglas Adams".// If not possible, 'error' is filled with an explanation.string ExpandFullName(DatabaseConnection dc, string partial_name, string* error); DatabaseConnection database_connection;string error;assert(ExpandFullName(database_connection, "Doug Adams", &error) == "Mr. Douglas Adams");assert(error == "");assert(ExpandFullName(database_connection, " Jake Brown ", &error) == "Mr. Jacob Brown III");assert(error == "");assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");assert(error == "no match found");assert(ExpandFullName(database_connection, "John", &error) == "");assert(error == "more than one result");上面這段代碼看起來(lái)很臟亂,很多重復(fù)性的東西,可以用函數(shù)封裝:
CheckFullName("Doug Adams", "Mr. Douglas Adams", "");CheckFullName(" Jake Brown ", "Mr. Jake Brown III", "");CheckFullName("No Such Guy", "", "no match found");CheckFullName("John", "", "more than one result"); void CheckFullName(string partial_name, string expected_full_name, string expected_error) { // database_connection is now a class member string error; string full_name = ExpandFullName(database_connection, partial_name, &error); assert(error == expected_error); assert(full_name == expected_full_name);}列對(duì)齊
列對(duì)齊可以讓代碼段看起來(lái)更舒適:
CheckFullName("Doug Adams" , "Mr. Douglas Adams" , "");CheckFullName(" Jake Brown ", "Mr. Jake Brown III", "");CheckFullName("No Such Guy" , "" , "no match found");CheckFullName("John" , "" , "more than one result"); commands[] = { ... { "timeout" , NULL , cmd_spec_timeout}, { "timestamping" , &opt.timestamping , cmd_boolean}, { "tries" , &opt.ntry , cmd_number_inf}, { "useproxy" , &opt.use_proxy , cmd_boolean}, { "useragent" , NULL , cmd_spec_useragent}, ...};代碼用塊區(qū)分
class FrontendServer { public: FrontendServer(); void ViewProfile(HttpRequest* request); void OpenDatabase(string location, string user); void SaveProfile(HttpRequest* request); string ExtractQueryParam(HttpRequest* request, string param); void ReplyOK(HttpRequest* request, string html); void FindFriends(HttpRequest* request); void ReplyNotFound(HttpRequest* request, string error); void CloseDatabase(string location); ~FrontendServer();};上面這一段雖然能看,不過(guò)還有優(yōu)化空間:
class FrontendServer { public: FrontendServer(); ~FrontendServer(); // Handlers void ViewProfile(HttpRequest* request); void SaveProfile(HttpRequest* request); void FindFriends(HttpRequest* request); // Request/Reply Utilities string ExtractQueryParam(HttpRequest* request, string param); void ReplyOK(HttpRequest* request, string html); void ReplyNotFound(HttpRequest* request, string error); // Database Helpers void OpenDatabase(string location, string user); void CloseDatabase(string location);};再來(lái)看一段代碼:
# Import the user's email contacts, and match them to users in our system.# Then display a list of those users that he/she isn't already friends with.def suggest_new_friends(user, email_password): friends = user.friends() friend_emails = set(f.email for f in friends) contacts = import_contacts(user.email, email_password) contact_emails = set(c.email for c in contacts) non_friend_emails = contact_emails - friend_emails suggested_friends = User.objects.select(email__in=non_friend_emails) display['user'] = user display['friends'] = friends display['suggested_friends'] = suggested_friends return render("suggested_friends.html", display)全都混在一起,視覺(jué)壓力相當(dāng)大,按功能化塊:
def suggest_new_friends(user, email_password): # Get the user's friends' email addresses. friends = user.friends() friend_emails = set(f.email for f in friends) # Import all email addresses from this user's email account. contacts = import_contacts(user.email, email_password) contact_emails = set(c.email for c in contacts) # Find matching users that they aren't already friends with. non_friend_emails = contact_emails - friend_emails suggested_friends = User.objects.select(email__in=non_friend_emails) # Display these lists on the page. display['user'] = user display['friends'] = friends display['suggested_friends'] = suggested_friends return render("suggested_friends.html", display)讓代碼看起來(lái)更舒服,需要在寫(xiě)的過(guò)程中多注意,培養(yǎng)一些好的習(xí)慣,尤其當(dāng)團(tuán)隊(duì)合作的時(shí)候,代碼風(fēng)格比如大括號(hào)的位置并沒(méi)有對(duì)錯(cuò),但是不遵循團(tuán)隊(duì)規(guī)范那就是錯(cuò)的。
如何寫(xiě)注釋
當(dāng)你寫(xiě)代碼的時(shí)候,你會(huì)思考很多,但是最終呈現(xiàn)給讀者的就只剩代碼本身了,額外的信息丟失了,所以注釋的目的就是讓讀者了解更多的信息。
應(yīng)該注釋什么
不應(yīng)該注釋什么
這樣的注釋毫無(wú)價(jià)值:
// The class definition for Accountclass Account { public: // Constructor Account(); // Set the profit member to a new value void SetProfit(double profit); // Return the profit from this Account double GetProfit();};不要像下面這樣為了注釋而注釋?zhuān)?/h5>
// Find a Node with the given 'name' or return NULL.// If depth <= 0, only 'subtree' is inspected.// If depth == N, only 'subtree' and N levels below are inspected.Node* FindNodeInSubtree(Node* subtree, string name, int depth);不要給爛取名注釋
// Enforce limits on the Reply as stated in the Request,// such as the number of items returned, or total byte size, etc. void CleanReply(Request request, Reply reply);注釋的大部分都在解釋clean是什么意思,那不如換個(gè)正確的名字:
// Make sure 'reply' meets the count/byte/etc. limits from the 'request' void EnforceLimitsFromRequest(Request request, Reply reply);記錄你的想法
我們討論了不該注釋什么,那么應(yīng)該注釋什么呢?注釋?xiě)?yīng)該記錄你思考代碼怎么寫(xiě)的結(jié)果,比如像下面這些:
// Surprisingly, a binary tree was 40% faster than a hash table for this data.// The cost of computing a hash was more than the left/right comparisons. // This heuristic might miss a few words. That's OK; solving this 100% is hard. // This class is getting messy. Maybe we should create a 'ResourceNode' subclass to// help organize things.也可以用來(lái)記錄流程和常量:
// TODO: use a faster algorithm// TODO(dustin): handle other image formats besides JPEG NUM_THREADS = 8 # as long as it's >= 2 * num_processors, that's good enough. // Impose a reasonable limit - no human can read that much anyway.const int MAX_RSS_SUBSCRIPTIONS = 1000;可用的詞有:
TODO : Stuff I haven't gotten around to yetFIXME : Known-broken code hereHACK : Adimittedly inelegant solution to a problemXXX : Danger! Major problem here站在讀者的角度去思考
當(dāng)別人讀你的代碼時(shí),讓他們產(chǎn)生疑問(wèn)的部分,就是你應(yīng)該注釋的地方。
struct Recorder { vectordata; ... void Clear() { vector ().swap(data); // Huh? Why not just data.clear()? }}; 很多C++的程序員啊看到這里,可能會(huì)想為什么不用
data.clear()
來(lái)代替vector.swap
,所以那個(gè)地方應(yīng)該加上注釋?zhuān)?/p>// Force vector to relinquish its memory (look up "STL swap trick")vector().swap(data); 說(shuō)明可能陷阱
你在寫(xiě)代碼的過(guò)程中,可能用到一些hack,或者有其他需要讀代碼的人知道的陷阱,這時(shí)候就應(yīng)該注釋?zhuān)?/p>
void SendEmail(string to, string subject, string body);而實(shí)際上這個(gè)發(fā)送郵件的函數(shù)是調(diào)用別的服務(wù),有超時(shí)設(shè)置,所以需要注釋?zhuān)?/p>
// Calls an external service to deliver email. (Times out after 1 minute.)void SendEmail(string to, string subject, string body);全景的注釋
有時(shí)候?yàn)榱烁宄f(shuō)明,需要給整個(gè)文件加注釋?zhuān)屪x者有個(gè)總體的概念:
// This file contains helper functions that provide a more convenient interface to our// file system. It handles file permissions and other nitty-gritty details.總結(jié)性的注釋
即使是在函數(shù)內(nèi)部,也可以有類(lèi)似文件注釋那樣的說(shuō)明注釋?zhuān)?/p>
# Find all the items that customers purchased for themselves.for customer_id in all_customers: for sale in all_sales[customer_id].sales: if sale.recipient == customer_id: ...或者按照函數(shù)的步進(jìn),寫(xiě)一些注釋?zhuān)?/p>
def GenerateUserReport(): # Acquire a lock for this user ... # Read user's info from the database ... # Write info to a file ... # Release the lock for this user很多人不愿意寫(xiě)注釋?zhuān)_實(shí),要寫(xiě)好注釋也不是一件簡(jiǎn)單的事情,也可以在文件專(zhuān)門(mén)的地方,留個(gè)寫(xiě)注釋的區(qū)域,可以寫(xiě)下你任何想說(shuō)的東西。
注釋?xiě)?yīng)簡(jiǎn)明準(zhǔn)確
前一個(gè)小節(jié)討論了注釋?xiě)?yīng)該寫(xiě)什么,這一節(jié)來(lái)討論應(yīng)該怎么寫(xiě),因?yàn)樽⑨尯苤匾?,所以要?xiě)的精確,注釋也占據(jù)屏幕空間,所以要簡(jiǎn)潔。
精簡(jiǎn)注釋
// The int is the CategoryType.// The first float in the inner pair is the 'score',// the second is the 'weight'.typedef hash_map> ScoreMap; 這樣寫(xiě)太羅嗦了,盡量精簡(jiǎn)壓縮成這樣:
// CategoryType -> (score, weight)typedef hash_map> ScoreMap; 避免有歧義的代詞
// Insert the data into the cache, but check if it's too big first.這里的
it's
有歧義,不知道所指的是data
還是cache
,改成如下:// Insert the data into the cache, but check if the data is too big first.還有更好的解決辦法,這里的
it
就有明確所指:// If the data is small enough, insert it into the cache.語(yǔ)句要精簡(jiǎn)準(zhǔn)確
# Depending on whether we've already crawled this URL before, give it a different priority.這句話理解起來(lái)太費(fèi)勁,改成如下就好理解很多:
# Give higher priority to URLs we've never crawled before.精確描述函數(shù)的目的
// Return the number of lines in this file.int CountLines(string filename) { ... }這樣的一個(gè)函數(shù),用起來(lái)可能會(huì)一頭霧水,因?yàn)樗梢杂泻芏嗥缌x:
”” 一個(gè)空文件,是0行還是1行?
“hello” 只有一行,那么返回值是0還是1?
“hello\n” 這種情況返回1還是2?
“hello\n world” 返回1還是2?
“hello\n\r cruel\n world\r” 返回2、3、4哪一個(gè)呢?
所以注釋?xiě)?yīng)該這樣寫(xiě):
// Count how many newline bytes ('\n') are in the file.int CountLines(string filename) { ... }用實(shí)例說(shuō)明邊界情況
// Rearrange 'v' so that elements < pivot come before those >= pivot;// Then return the largest 'i' for which v[i] < pivot (or -1 if none are < pivot)int Partition(vector* v, int pivot); 這個(gè)描述很精確,但是如果再加入一個(gè)例子,就更好了:
// ...// Example: Partition([8 5 9 8 2], 8) might result in [5 2 | 8 9 8] and return 1int Partition(vector* v, int pivot); 說(shuō)明你的代碼的真正目的
void DisplayProducts(listproducts) { products.sort(CompareProductByPrice); // Iterate through the list in reverse order for (list ::reverse_iterator it = products.rbegin(); it != products.rend(); ++it) DisplayPrice(it->price); ... } 這里的注釋說(shuō)明了倒序排列,單還不夠準(zhǔn)確,應(yīng)該改成這樣:
// Display each price, from highest to lowestfor (list::reverse_iterator it = products.rbegin(); ... ) 函數(shù)調(diào)用時(shí)的注釋
看見(jiàn)這樣的一個(gè)函數(shù)調(diào)用,肯定會(huì)一頭霧水:
Connect(10, false);如果加上這樣的注釋?zhuān)x起來(lái)就清楚多了:
def Connect(timeout, use_encryption): ... # Call the function using named parametersConnect(timeout = 10, use_encryption = False)使用信息含量豐富的詞
// This class contains a number of members that store the same information as in the// database, but are stored here for speed. When this class is read from later, those// members are checked first to see if they exist, and if so are returned; otherwise the// database is read from and that data stored in those fields for next time.上面這一大段注釋?zhuān)忉尩暮芮宄?,如果換一個(gè)詞來(lái)代替,也不會(huì)有什么疑惑:
// This class acts as a caching layer to the database.簡(jiǎn)化循環(huán)和邏輯
流程控制要簡(jiǎn)單
讓條件語(yǔ)句、循環(huán)以及其他控制流程的代碼盡可能自然,讓讀者在閱讀過(guò)程中不需要停頓思考或者在回頭查找,是這一節(jié)的目的。
條件語(yǔ)句中參數(shù)的位置
對(duì)比下面兩種條件的寫(xiě)法:
if (length >= 10)while (bytes_received < bytes_expected) if (10 <= length)while (bytes_expected > bytes_received)到底是應(yīng)該按照大于小于的順序來(lái)呢,還是有其他的準(zhǔn)則?是的,應(yīng)該按照參數(shù)的意義來(lái)
運(yùn)算符左邊:通常是需要被檢查的變量,也就是會(huì)經(jīng)常變化的
運(yùn)算符右邊:通常是被比對(duì)的樣本,一定程度上的常量
這就解釋了為什么
bytes_received < bytes_expected
比反過(guò)來(lái)更好理解。if/else的順序
通常,
if/else
的順序你可以自由選擇,下面這兩種都可以:if (a == b) { // Case One ...} else { // Case Two ...} if (a != b) { // Case Two ...} else { // Case One ...}或許對(duì)此你也沒(méi)有仔細(xì)斟酌過(guò),但在有些時(shí)候,一種順序確實(shí)好過(guò)另一種:
正向的邏輯在前,比如
if(debug)
就比if(!debug)
好簡(jiǎn)單邏輯的在前,這樣
if
和else
就可以在一個(gè)屏幕顯示 - 有趣、清晰的邏輯在前舉個(gè)例子來(lái)看:
if (!url.HasQueryParameter("expand_all")) { response.Render(items); ...} else { for (int i = 0; i < items.size(); i++) { items[i].Expand(); } ... }看到
if
你首先想到的是expand_all
,就好像告訴你“不要想大象”,你會(huì)忍不住去想它,所以產(chǎn)生了一點(diǎn)點(diǎn)迷惑,最好寫(xiě)成:if (url.HasQueryParameter("expand_all")) { for (int i = 0; i < items.size(); i++) { items[i].Expand(); } ... } else { response.Render(items); ... }三目運(yùn)算符(?:)
time_str += (hour >= 12) ? "pm" : "am"; Avoiding the ternary operator, you might write: if (hour >= 12) { time_str += "pm"; } else { time_str += "am";}使用三目運(yùn)算符可以減少代碼行數(shù),上例就是一個(gè)很好的例證,但是我們的真正目的是減少讀代碼的時(shí)間,所以下面的情況并不適合用三目運(yùn)算符:
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent); if (exponent >= 0) { return mantissa * (1 << exponent);} else { return mantissa / (1 << -exponent);}所以只在簡(jiǎn)單表達(dá)式的地方用。
避免使用do/while表達(dá)式
do { continue;} while (false);這段代碼會(huì)執(zhí)行幾遍呢,需要時(shí)間思考一下,
do/while
完全可以用別的方法代替,所以應(yīng)避免使用。盡早return
public boolean Contains(String str, String substr) { if (str == null || substr == null) return false; if (substr.equals("")) return true; ...}函數(shù)里面盡早的return,可以讓邏輯更加清晰。
減少嵌套
if (user_result == SUCCESS) { if (permission_result != SUCCESS) { reply.WriteErrors("error reading permissions"); reply.Done(); return; } reply.WriteErrors("");} else { reply.WriteErrors(user_result);}reply.Done();這樣一段代碼,有一層的嵌套,但是看起來(lái)也會(huì)稍有迷惑,想想自己的代碼,有沒(méi)有類(lèi)似的情況呢?可以換個(gè)思路去考慮這段代碼,并且用盡早return的原則修改,看起來(lái)就舒服很多:
if (user_result != SUCCESS) { reply.WriteErrors(user_result); reply.Done(); return;}if (permission_result != SUCCESS) { reply.WriteErrors(permission_result); reply.Done(); return;}reply.WriteErrors("");reply.Done();同樣的,對(duì)于有嵌套的循環(huán),可以采用同樣的辦法:
for (int i = 0; i < results.size(); i++) { if (results[i] != NULL) { non_null_count++; if (results[i]->name != "") { cout << "Considering candidate..." << endl; ... } }}換一種寫(xiě)法,盡早return,在循環(huán)中就用continue:
for (int i = 0; i < results.size(); i++) { if (results[i] == NULL) continue; non_null_count++; if (results[i]->name == "") continue; cout << "Considering candidate..." << endl; ... }拆分復(fù)雜表達(dá)式
很顯然的,越復(fù)雜的表達(dá)式,讀起來(lái)越費(fèi)勁,所以應(yīng)該把那些復(fù)雜而龐大的表達(dá)式,拆分成一個(gè)個(gè)易于理解的小式子。
用變量
將復(fù)雜表達(dá)式拆分最簡(jiǎn)單的辦法,就是增加一個(gè)變量:
if line.split(':')[0].strip() == "root": //用變量替換username = line.split(':')[0].strip() if username == "root": ...或者這個(gè)例子:
if (request.user.id == document.owner_id) { // user can edit this document...}...if (request.user.id != document.owner_id) {// document is read-only...} //用變量替換final boolean user_owns_document = (request.user.id == document.owner_id);if (user_owns_document) { // user can edit this document...}...if (!user_owns_document) { // document is read-only...}邏輯替換
1) not (a or b or c) <–> (not a) and (not b) and (not c)
2) not (a and b and c) <–> (not a) or (not b) or (not c)
所以,就可以這樣寫(xiě):
if (!(file_exists && !is_protected)) Error("Sorry, could not read file."); //替換if (!file_exists || is_protected) Error("Sorry, could not read file.");不要濫用邏輯表達(dá)式
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());這樣的代碼完全可以用下面這個(gè)替換,雖然有兩行,但是更易懂:
bucket = FindBucket(key);if (bucket != NULL) assert(!bucket->IsOccupied());像下面這樣的表達(dá)式,最好也不要寫(xiě),因?yàn)樵谟行┱Z(yǔ)言中,x會(huì)被賦予第一個(gè)為
true
的變量的值:x = a || b || c拆解大表達(dá)式
var update_highlight = function (message_num) { if ($("#vote_value" + message_num).html() === "Up") { $("#thumbs_up" + message_num).addClass("highlighted"); $("#thumbs_down" + message_num).removeClass("highlighted"); } else if ($("#vote_value" + message_num).html() === "Down") { $("#thumbs_up" + message_num).removeClass("highlighted"); $("#thumbs_down" + message_num).addClass("highlighted"); } else { $("#thumbs_up" + message_num).removeClass("highighted"); $("#thumbs_down" + message_num).removeClass("highlighted"); }};這里面有很多重復(fù)的語(yǔ)句,我們可以用變量還替換簡(jiǎn)化:
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).html(); var hi = "highlighted"; if (vote_value === "Up") { thumbs_up.addClass(hi); thumbs_down.removeClass(hi); } else if (vote_value === "Down") { thumbs_up.removeClass(hi); thumbs_down.addClass(hi); } else { thumbs_up.removeClass(hi); thumbs_down.removeClass(hi); }}變量與可讀性
消除變量
前一節(jié),講到利用變量來(lái)拆解大表達(dá)式,這一節(jié)來(lái)討論如何消除多余的變量。
沒(méi)用的臨時(shí)變量
now = datetime.datetime.now()root_message.last_view_time = now這里的
now
可以去掉,因?yàn)椋?/p>
并非用來(lái)拆分復(fù)雜的表達(dá)式
也沒(méi)有增加可讀性,因?yàn)閌datetime.datetime.now()`本就清晰
只用了一次
所以完全可以寫(xiě)作:
root_message.last_view_time = datetime.datetime.now()消除條件控制變量
boolean done = false;while (/* condition */ && !done) { ... if (...) { done = true; continue; }}這里的
done
可以用別的方式更好的完成:while (/* condition */) { ... if (...) { break; } }這個(gè)例子非常容易修改,如果是比較復(fù)雜的嵌套,
break
可能并不夠用,這時(shí)候就可以把代碼封裝到函數(shù)中。減少變量的作用域
我們都聽(tīng)過(guò)要避免使用全局變量這樣的忠告,是的,當(dāng)變量的作用域越大,就越難追蹤,所以要保持變量小的作用域。
class LargeClass { string str_; void Method1() { str_ = ...; Method2(); } void Method2() { // Uses str_ } // Lots of other methods that don't use str_ ... ;}這里的
str_
的作用域有些大,完全可以換一種方式:class LargeClass { void Method1() { string str = ...; Method2(str); } void Method2(string str) { // Uses str } // Now other methods can't see str.};將
str
通過(guò)變量函數(shù)參數(shù)傳遞,減小了作用域,也更易讀。同樣的道理也可以用在定義類(lèi)的時(shí)候,將大類(lèi)拆分成一個(gè)個(gè)小類(lèi)。不要使用嵌套的作用域
# No use of example_value up to this point.if request: for value in request.values: if value > 0: example_value = value break for logger in debug.loggers: logger.log("Example:", example_value)這個(gè)例子在運(yùn)行時(shí)候會(huì)報(bào)
example_value is undefined
的錯(cuò),修改起來(lái)不算難:example_value = Noneif request: for value in request.values: if value > 0: example_value = value break if example_value: for logger in debug.loggers: logger.log("Example:", example_value)但是參考前面的消除中間變量準(zhǔn)則,還有更好的辦法:
def LogExample(value): for logger in debug.loggers: logger.log("Example:", value) if request: for value in request.values: if value > 0: LogExample(value) # deal with 'value' immediately break用到了再聲明
在C語(yǔ)言中,要求將所有的變量事先聲明,這樣當(dāng)用到變量較多時(shí)候,讀者處理這些信息就會(huì)有難度,所以一開(kāi)始沒(méi)用到的變量,就暫緩聲明:
def ViewFilteredReplies(original_id): filtered_replies = [] root_message = Messages.objects.get(original_id) all_replies = Messages.objects.select(root_id=original_id) root_message.view_count += 1 root_message.last_view_time = datetime.datetime.now() root_message.save() for reply in all_replies: if reply.spam_votes <= MAX_SPAM_VOTES: filtered_replies.append(reply) return filtered_replies讀者一次處理變量太多,可以暫緩聲明:
def ViewFilteredReplies(original_id): root_message = Messages.objects.get(original_id) root_message.view_count += 1 root_message.last_view_time = datetime.datetime.now() root_message.save() all_replies = Messages.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變量最好只寫(xiě)一次
前面討論了過(guò)多的變量會(huì)讓讀者迷惑,同一個(gè)變量,不停的被賦值也會(huì)讓讀者頭暈,如果變量變化的次數(shù)少一些,代碼可讀性就更強(qiáng)。
一個(gè)例子
假設(shè)有一個(gè)頁(yè)面,如下,需要給第一個(gè)空的
input
賦值:...var setFirstEmptyInput = function (new_value) { var found = false; var i = 1; var elem = document.getElementById('input' + i); while (elem !== null) { if (elem.value === '') { found = true; break; } i++; elem = document.getElementById('input' + i); } if (found) elem.value = new_value; return elem;};這段代碼能工作,有三個(gè)變量,我們逐一去看如何優(yōu)化,
found
作為中間變量,完全可以消除:var setFirstEmptyInput = function (new_value) { var i = 1; var elem = document.getElementById('input' + i); while (elem !== null) { if (elem.value === '') { elem.value = new_value; return elem; } i++; elem = document.getElementById('input' + i); } return null;};再來(lái)看
elem
變量,只用來(lái)做循環(huán),調(diào)用了很多次,所以很難跟蹤他的值,i
也可以用for
來(lái)修改:var setFirstEmptyInput = function (new_value) { for (var i = 1; true; i++) { var elem = document.getElementById('input' + i); if (elem === null) return null; // Search Failed. No empty input found. if (elem.value === '') { elem.value = new_value; return elem; } }};重新組織你的代碼
分離不相關(guān)的子問(wèn)題
工程師就是將大問(wèn)題分解為一個(gè)個(gè)小問(wèn)題,然后逐個(gè)解決,這樣也易于保證程序的健壯性、可讀性。如何分解子問(wèn)題,下面給出一些準(zhǔn)則:
看看這個(gè)方法或代碼,問(wèn)問(wèn)你自己“這段代碼的最終目標(biāo)是什么?”
對(duì)于每一行代碼,要問(wèn)“它與目標(biāo)直接相關(guān),或者是不相關(guān)的子問(wèn)題?”
如果有足夠多行的代碼是處理與目標(biāo)不直接相關(guān)的問(wèn)題,那么抽離成子函數(shù)
來(lái)看一個(gè)例子:
ajax_post({ url: 'http://example.com/submit', data: data, on_success: function (response_data) { var str = "{\n"; for (var key in response_data) { str += " " + key + " = " + response_data[key] + "\n"; } alert(str + "}"); // Continue handling 'response_data' ... }});這段代碼的目標(biāo)是發(fā)送一個(gè)
ajax
請(qǐng)求,所以其中字符串處理的部分就可以抽離出來(lái):var format_pretty = function (obj) { var str = "{\n"; for (var key in obj) { str += " " + key + " = " + obj[key] + "\n"; } return str + "}";};意外收獲
有很多理由將
format_pretty
抽離出來(lái),這些獨(dú)立的函數(shù)可以很容易的添加feature,增強(qiáng)可靠性,處理邊界情況,等等。所以這里,可以將format_pretty
增強(qiáng),就會(huì)得到一個(gè)更強(qiáng)大的函數(shù):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(ob
文章名稱(chēng):web開(kāi)發(fā)中怎么編寫(xiě)可讀代碼
網(wǎng)頁(yè)網(wǎng)址:http://weahome.cn/article/piojsh.html