前端要给力之:代码可以有多烂?
1、爛代碼是怎么定義的?
!KissyUI是淘寶Kissy這個前端項目的一個群,龍藏同學在看完我在公司內網的“讀爛代碼系列”之后就在群里問呵:爛代碼是怎么定義的?
是呵,到底什么才算爛代碼呢?這讓我想到一件事,是另一個網友在gtalk上問我的一個問題:他需要a,b,c三個條件全真時為假,全假時也為假,請問如何判斷。
接下來KissyUI群里的同學給出了很多答案:
en... 確實,我沒有完全驗證上面的全面答案的有效性。因為如同龍藏后來強調的:“貌似我們是要討論什么是爛代碼?”的確,我們怎么才能把代碼寫爛呢?上面出現了種種奇異代碼,包括原來提問者的那個取巧的:
[javascript]?view plain?copy
因為這個問題出現在js里面,存在弱類型的問題,即a、b、c可能是整數,或字符串等等,因此(a+b+c)%3這個路子就行不通了,所以才有了
[javascript]?view plain?copy
2、問題的泛化與求解:普通級別
如果把上面的問題改變一下:
?- 如果不是a、b、c三個條件,而是兩個以上條件呢?
?- 如果強調a、b、c本身不一定是布爾值呢?
那么這個問題的基本抽象就是:
[c-sharp]?view plain?copy
接下來,我們考慮一個問題,既然arguments就是一個數組,那么可否使用數組方式呢?事實上,據說在某些js環境中,直接存取arguments[x]的效率是較差的。因此,上面的v1版本可以有一個改版:
[javascript]?view plain?copy
這段小小的代碼涉及到splice/slice的使用問題。因為操作的是arguments,因此splice可能導致函數入口的“奇異”變化,在不同的引擎中的表現效果并不一致,而slice則又可能導致多出一倍的數據復制。在這里仍然選用slice()的原因是:這里畢竟只是函數參數,不會是“極大量的”數組,因此無需過度考慮存儲問題。
?
3、問題的泛化與求解:專業級別
接下來,我們既然在args中得到的是一個數組,那么再用for循環就實在不那么摩登了。正確的、流行風格的、不被前端鄙視做法是:
[javascript]?view plain?copy
為了向一些不太了解js1.6+新特性的同學解釋v2這個版本,下面的代碼分解了上述這個實現:
[javascript]?view plain?copy
some()這個方法會將數組args中的每一個元素作為參數b傳給callback函數。some()有一項特性正是與我們的原始需求一致的:
? - 當callback()返回true的時候,some()會中斷args的列舉然后返回true值;否則,
? - 當列舉完全部元素且callback()未返回true的情況下,some()返回false值。
現在再讀v2版本的e_xor(),是不是就清晰了?
?
當然,僅僅出于減少!a運算的必要,v2版本也可以有如下的一個改版:
[javascript]?view plain?copy
在這行代碼里,使用了連續運算:
[javascript]?view plain?copy
而連續運算返回最后一個子表達式的值,即slice()后的數組。這樣的寫法,主要是要將代碼控制在“一個表達式”。
?
?
4、問題的泛化與求解:Guy入門級別
好了,現在我們開始v3版本的寫法了。為什么呢?因為v2版本仍然不夠酷,v2版本使用的是Array.some(),這個在js1.6中擴展的特既不是那么的“函數式”,還有些面向對象的痕跡。作為一個函數式語言的死忠,我認為,類似于“列舉一個數組”這樣的問題的最正常解法是:遞歸。
為什么呢?因為erlang這樣的純函數式語言就不會搞出個Array.some()的思路來——當然也是有這樣的方法的,只是從“更純正”的角度上講,我們得自己寫一個。呵呵。這種“純正的遞歸”在js里面又怎么搞呢?大概的原型會是這樣子:
[javascript]?view plain?copy
在這個框架里,我們設e_xor()有無數個參數,但每次我們只處理a,b兩個,如果a,b相等,則我們將其中之任一,與后續的n-2個參數遞歸比較。為了實現“遞歸處理后續n-2個參數”,我們需要借用函數式語言中的一個重要概念:連續/延續(continuous)。這個東東月影曾經出專題來講過,在這里:
http://bbs.51js.com/viewthread.php?tid=85325
簡單地說,延續就是對函數參數進行連續的回調。這個東東呢,在較新的函數式語言范式中都是支持的。為了本文中的這個例子,我單獨地寫個版本來分析之。我稱之為tail()方法,意思是指定函數參數的尾部,它被設計為函數Function上的一個原型方法。
[javascript]?view plain?copy
注意這個tail()方法的有趣之處:它用到了this.length。在javascript中的函數有兩個length值,一個是foo.length,它表明foo函數在聲明時的形式參數的個數;另一個是arguments.length,它表明在函數調用時,傳入的實際參數的個數。也就是說,對于函數foo()來說:
[javascript]?view plain?copy
第一次調用將顯示[1,2],第二次則會顯示[3,2]。無論如何,聲明時的參數a,b總是兩個,所以foo.length == arguments.callee.length == 2。
回到tail()方法。它的意思是說:
[javascript]?view plain?copy
那么tail()在本例中如何使用呢?
[javascript]?view plain?copy
這里又用到了arguments.callee.length來判斷形式參數個數。也就是說,遞歸的結束條件是:只剩下a,b兩個參數,無需再掃描tail()部分。當然,return中三元表達式(?:)右半部分也會中止遞歸,這種情況下,是已經找到了一個不相同的條件。
在這個例子中,我們將e_xor()寫成了一個尾遞歸的函數,這個尾遞歸是函數式的精髓了,只可惜在js里面不支持它的優化。WUWU~~ 回頭我查查資源,看看新的chrome v8是不是支持了。v8同學,尚V5否?:)
?
5、問題的泛化與求解:Guy進階級別
從上一個小節中,我們看到了Guy解決問題的思路。但是在這個級別上,第一步的抽象通常是最關鍵的。簡單地說,V3里認為:
[javascript]?view plain?copy
這個框架抽象本身可能是有問題。正確的理解不是“a,b求異或”,而是“a跟其它元素求異或”。由此,v4的框架抽象是:
[javascript]?view plain?copy
在v3中,由于每次要向后續部分傳入b值,因此我們需要在tail()中做數組拼接concat()。但是,當我們使用v4的框架時,b值本身就隱含在后續部分中,因此無需拼接。這樣一來,tail()就有了新的寫法——事實上,這更符合tail()的原意,如果真的存在拼接過程,那它更應由foo()來處理,而不是由tail()來處理。
[javascript]?view plain?copy
在v4這個版本中的代碼寫法,會變得更為簡單:
[javascript]?view plain?copy
?
6、問題的泛化與求解:Guy無階級別
所謂無階級別,就是你知道他是Guy,但不知道可以Guy到什么程度。例如,我們可以在v4.1版本的e_xor()中發現一個模式,即:
? - 真正的處理邏輯只有第二行。
由于其它都是框架部分,所以我們可以考慮一種編程范式,它是對tail的擴展,目的是對在tail調用e_xor——就好象對數組調用sort()方法一樣。tail的含義是取數據,而新擴展的含義是數組與邏輯都作為整體。例如:
[javascript]?view plain?copy
tailed()的用法很簡單:
[javascript]?view plain?copy
簡單的來看,我們可以將xor函數作為tailed()的運算元,這樣一樣,我們可以公開一個名為tailed的公共庫,它的核心就是暴露一組類似于xor的函數,開發者可以使用下面的編程范式來實現運算。例如:
[javascript]?view plain?copy
那么,這個所謂的tailed庫該如何用呢?很簡單,一行代碼:
[javascript]?view plain?copy
?
現在我們得到了一個半成熟的、名為tailed的開放庫。所謂半成熟,是因為我們的tailed()還有一個小小缺陷,下面這行代碼:
[javascript]?view plain?copy
?
中間的f.length+1的這個“1”,是一個有條件的參數,它與xor處理數據的方式有關。簡單的說,正是因為要比較a與arguments[1],所這里要+1,如果某種算法要比較 多個運算元,則tailed()就不通用了。所以正確的、完善的tailed應該允許調用者指定終止條件。例如:
[javascript]?view plain?copy
使用的方法仍然是:
[javascript]?view plain?copy
?
在不同的運算中,less_one()可以是其它的終止條件。
?
現在,在這個方案——我的意思是tailed library這個庫夠Guy了嗎?不。所謂意淫無止盡,淫人們自有不同的淫法。比如,在上面的代碼中我們可以看到一個問題,就是tailed()中有很多層次的函數閉包,這意味著調用時效率與存儲空間都存在著無謂的消耗。那么,有什么辦法呢?比如說?哈哈,我們可以搞搞范型編程,弄個模板出來:
[javascript]?view plain?copy
當然,我們仍然可以做得更多。例如這個templet引擎相當的粗糙,使用eval()的方法也不如new Function來得理想等等。關于這個部分,可以再參考QoBean對元語言的處理方式,因為事實上,這后面的部分已經在逼近meta language編程了。
?
7、Guy?
我們在做什么?我們已經離真相越來越遠了。或者說,我故意地帶大家兜著一個又一個看似有趣,卻又漸漸遠離真相的圈子。
我們不是要找一段“不那么爛的代碼”嗎?如果是這樣,那么對于a,b,c三個運算條件的判斷,最好的方法大概是:
[javascript]?view plain?copy
或者,如果考慮到a,b,c的類型問題:
[javascript]?view plain?copy
如果考慮對一組運算元進行判斷的情況,那么就把它當成數組,寫成:
[javascript]?view plain?copy
對于這段代碼,我們使用JS默認對arguments的存取規則,有優化就優化,沒有就算了,因為我們的應用環境并沒有提出“這里的arguments有成千上萬個”或“e_xor()調用極為頻繁”這樣的需求。如果沒有需求,我們在這方面所做的優化,就是白費功能——除了技術上的完美之外,對應用環境毫無意義。
?
夠用了。我們的所學,在應用環境中已經足夠,不要讓技巧在你的代碼中泛濫。所謂技術,是控制代碼復雜性、讓代碼變得優美的一種能力,而不是讓技術本身變得強大或完美。
?
所以,我此前在“讀爛代碼”系統中討論時,強調的其實是三個過程:
?- 先把業務的需求想清楚,
?- 設計好清晰明確的調用接口,
?- 用最簡單的、最短距離的代碼實現。
?
其它神馬滴,都系浮云。
?
=====
注:本文從第2小節,至第6小節,僅供對架構、框架、庫等方面有興趣的同學學習研究,有志于在語言設計、架構抽象等,或基礎項目中使用相關技術的,歡迎探討,切勿濫用于一般應用項目。
from:?http://blog.csdn.net/aimingoo/article/details/6036574
總結
以上是生活随笔為你收集整理的前端要给力之:代码可以有多烂?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: excel批量转换日期格式,将yyyym
- 下一篇: JavaScript类型总览(图)