• <noscript id="eom2a"><optgroup id="eom2a"></optgroup></noscript>
    <tt id="eom2a"><small id="eom2a"></small></tt>
    <input id="eom2a"></input>
  • <div id="eom2a"><small id="eom2a"></small></div>
    <td id="eom2a"><small id="eom2a"></small></td>
  • 您的位置:知識庫 ? Web前端

    前端要給力之:代碼可以有多爛?

    作者: 周愛民  發布時間: 2010-12-06 10:09  閱讀: 1157 次  推薦: 0   原文鏈接   [收藏]  

      1、爛代碼是怎么定義的?

      !KissyUI是淘寶Kissy這個前端項目的一個群,龍藏同學在看完我在公司內網的“讀爛代碼系列”之后就在群里問呵:爛代碼是怎么定義的?

      是呵,到底什么才算爛代碼呢?這讓我想到一件事,是另一個網友在gtalk上問我的一個問題:他需要a,b,c三個條件全真時為假,全假時也為假,請問如何判斷。

      接下來KissyUI群里的同學給出了很多答案:

    1. if( a&&b&&c || !a&&!b&&!c){
    2. return false
    3. }
    4. // 2. 龍藏
    5. (a ^ b) & c
    6. // 3. 愚公(我給gtalk上的提問者)的答案
    7. (a xor b) or (a xor c)
    8. // 4. 提問者自己的想法
    9. (a + b + c) % 3
    10. // 5. 云謙對答案4的改進版本
    11. (!!a+!!b+!!c)%n
    12. // 6. 拔赤
    13. a ? (b?c:b) : (b?!b:!c)
    14. // 7. 吳英杰
    15. (a != b || b != c)
    16. 或
    17. (!a != !b || !b != !c)
    18. // 8. 姬光
    19. var v = a&&b&&c;
    20. if(!v){
    21. return false;
    22. }else if(v){
    23. return false;
    24. }else{
    25. return true;
    26. }

      確實,我沒有完全驗證上面的全面答案的有效性。因為如同龍藏后來強調的:“貌似我們是要討論什么是爛代碼?”的確,我們怎么才能把代碼寫爛呢?上面出現了種種奇異代碼,包括原來提問者的那個取巧的:

    1. // 4. 提問者自己的想法
    2. (a + b + c) % 3

      因為這個問題出現在js里面,存在弱類型的問題,即a、b、c可能是整數,或字符串等等,因此(a+b+c)%3這個路子就行不通了,所以才有了:

    1. // 5. 云謙對答案4的改進版本
    2. (!!a+!!b+!!c)%n

      2、問題的泛化與求解:普通級別

      如果把上面的問題改變一下:

       - 如果不是a、b、c三個條件,而是兩個以上條件呢?

       - 如果強調a、b、c本身不一定是布爾值呢?

      那么這個問題的基本抽象就是:

    1. // v0,對任意多個運算元求xor
    2. function e_xor() { ... }
    3.
    4. 對于這個e_xor()來說,最直接的代碼寫法是:
    5. // v1,掃描所有參數,發現不同的即返回true,全部相同則返回false。
    6. function e_xor() {
    7. var args=arguments, argn=args.length;
    8. args[0] = !args[0];
    9. for (var i=1; i<argn; i++) {
    10. if (args[0] != !args[i]) return true;
    11. }
    12. return false;
    13. }

      接下來,我們考慮一個問題,既然arguments就是一個數組,那么可否使用數組方式呢?事實上,據說在某些js環境中,直接存取arguments[x]的效率是較差的。因此,上面的v1版本可以有一個改版:

    1. // v1.1,對v1的改版
    2. function e_xor() {
    3. var args=[].slice.call(arguments,0), argn=args.length;
    4. ...
    5. }

      這段小小的代碼涉及到splice/slice的使用問題。因為操作的是arguments,因此splice可能導致函數入口的“奇異”變化,在不同的引擎中的表現效果并不一致,而slice則又可能導致多出一倍的數據復制。在這里仍然選用slice()的原因是:這里畢竟只是函數參數,不會是“極大量的”數組,因此無需過度考慮存儲問題。

      3、問題的泛化與求解:專業級別

      接下來,我們既然在args中得到的是一個數組,那么再用for循環就實在不那么摩登了。正確的、流行風格的、不被前端鄙視做法是:

    1. // v2,使用js1.6+的數組方法的實現
    2. function e_xor(a) {
    3. return ([].slice.call(arguments,1)).some(function(b) { if (!b != !a) return true });
    4. }

      為了向一些不太了解js1.6+新特性的同學解釋v2這個版本,下面的代碼分解了上述這個實現:

    1. // v2.1,對v2的詳細分解
    2. function e_xor(a) {
    3. var args = [].slice.call(arguments,1);
    4. var callback = function(b) {
    5. if (!b != !a) return true
    6. }
    7. return args.some(callback);
    8. }

      some()這個方法會將數組args中的每一個元素作為參數b傳給callback函數。some()有一項特性正是與我們的原始需求一致的:

        - 當callback()返回true的時候,some()會中斷args的列舉然后返回true值;否則,

        - 當列舉完全部元素且callback()未返回true的情況下,some()返回false值。

      現在再讀v2版本的e_xor(),是不是就清晰了?

      當然,僅僅出于減少!a運算的必要,v2版本也可以有如下的一個改版:

    1. // v2.2,對v2的優化以減少!a運算次數
    2. function e_xor(a) {
    3. return (a=!a, [].slice.call(arguments,1)).some(function(b) { if (!b != a) return true });
    4. }

      在這行代碼里,使用了連續運算:

    (a=!a, [].slice.call(arguments,1))

      而連續運算返回最后一個子表達式的值,即slice()后的數組。這樣的寫法,主要是要將代碼控制在“一個表達式”。

      4、問題的泛化與求解:Guy入門級別

      好了,現在我們開始v3版本的寫法了。為什么呢?因為v2版本仍然不夠酷,v2版本使用的是Array.some(),這個在js1.6中擴展的特既不是那么的“函數式”,還有些面向對象的痕跡。作為一個函數式語言的死忠,我認為,類似于“列舉一個數組”這樣的問題的最正常解法是:遞歸。

      為什么呢?因為erlang這樣的純函數式語言就不會搞出個Array.some()的思路來——當然也是有這樣的方法的,只是從“更純正”的角度上講,我們得自己寫一個。呵呵。這種“純正的遞歸”在js里面又怎么搞呢?大概的原型會是這樣子:

    1. // v3,采用純函數式的、遞歸方案的框架
    2. function e_xor(a, b) { ... }

      在這個框架里,我們設e_xor()有無數個參數,但每次我們只處理a,b兩個,如果a,b相等,則我們將其中之任一,與后續的n-2個參數遞歸比較。為了實現“遞歸處理后續n-2個參數”,我們需要借用函數式語言中的一個重要概念:連續/延續(continuous)。這個東東月影曾經出專題來講過,在這里:http://bbs.51js.com/viewthread.php?tid=85325

      簡單地說,延續就是對函數參數進行連續的回調。這個東東呢,在較新的函數式語言范式中都是支持的。為了本文中的這個例子,我單獨地寫個版本來分析之。我稱之為tail()方法,意思是指定函數參數的尾部,它被設計為函數Function上的一個原型方法。

    1. Function.prototype.tail = function() {
    2. return this.apply(this, [].slice.call(arguments,0).concat([].slice.call(this.arguments, this.length)));
    3. }

      注意這個tail()方法的有趣之處:它用到了this.length。在javascript中的函數有兩個length值,一個是foo.length,它表明foo函數在聲明時的形式參數的個數;另一個是arguments.length,它表明在函數調用時,傳入的實際參數的個數。也就是說,對于函數foo()來說:

    1. function foo(a, b) {
    2. alert([arguments.length, arguments.callee.length]);
    3. }
    4. foo(x);
    5. foo(x,y,z);

      第一次調用將顯示[1,2],第二次則會顯示[3,2]。無論如何,聲明時的參數a,b總是兩個,所以foo.length == arguments.callee.length == 2。

      回到tail()方法。它的意思是說:

    1. Function.prototype.tail = function() {
    2. return this.apply( // 重新調用函數自身
    3. this, // 以函數foo自身作為this Object
    4. [].slice.call(arguments,0) // 取調用tail時的全部參數,轉換為數組
    5. .concat( // 數組連接
    6. [].slice.call(this.arguments, // 取本次函數foo調用時的參數,由于tail()總在 foo()中調用,因此實際是取最近一次foo()的實際參數
    7. this.length) // 按照foo()聲明時的形式參數個數,截取foo()函數參數的尾部
    8. )
    9. );
    10. }

      那么tail()在本例中如何使用呢?

    1. // v3.1,使用tail()的版本
    2. function e_xor(a, b) {
    3. if (arguments.length == arguments.callee.length) return !a != !b;
    4. return (!a == !b ? arguments.callee.tail(b) : true);
    5. }

      這里又用到了arguments.callee.length來判斷形式參數個數。也就是說,遞歸的結束條件是:只剩下a,b兩個參數,無需再掃描tail()部分。當然,return中三元表達式(?:)右半部分也會中止遞歸,這種情況下,是已經找到了一個不相同的條件。

      在這個例子中,我們將e_xor()寫成了一個尾遞歸的函數,這個尾遞歸是函數式的精髓了,只可惜在js里面不支持它的優化。WUWU~~ 回頭我查查資源,看看新的chrome v8是不是支持了。v8同學,尚V5否?:)

      5、問題的泛化與求解:Guy進階級別

      從上一個小節中,我們看到了Guy解決問題的思路。但是在這個級別上,第一步的抽象通常是最關鍵的。簡單地說,V3里認為:

    1. // v3,采用純函數式的、遞歸方案的框架
    2. function e_xor(a, b) { ... }

      這個框架抽象本身可能是有問題。正確的理解不是“a,b求異或”,而是“a跟其它元素求異或”。由此,v4的框架抽象是:

    1. // v4,更優的函數式框架抽象,對接口的思考
    2. function e_xor(a) { ... }

      在v3中,由于每次要向后續部分傳入b值,因此我們需要在tail()中做數組拼接concat()。但是,當我們使用v4的框架時,b值本身就隱含在后續部分中,因此無需拼接。這樣一來,tail()就有了新的寫法——事實上,這更符合tail()的原意,如果真的存在拼接過程,那它更應由foo()來處理,而不是由tail()來處理。

    1. // 更符合原始抽象含義的tail方法
    2. Function.prototype.tail = function() {
    3. return this.apply(this, [].slice.call(this.arguments, this.length));
    4. }

      在v4這個版本中的代碼寫法,會變得更為簡單:

    # // v4.1,相較于v3更為簡單的實現
    # function e_xor(a) {
    #
    if (arguments.length < 2) return false;
    #
    return (!a == !arguments[1] ? arguments.callee.tail() : true);
    # }
    #
    // v4.1.1,一個不使用三元表達式的簡潔版本
    # function e_xor(a) {
    #
    if (arguments.length < 2) return false;
    #
    if (!arguments[1] != !a) return true;
    #
    return arguments.callee.tail();
    # }

      6、問題的泛化與求解:Guy無階級別

      所謂無階級別,就是你知道他是Guy,但不知道可以Guy到什么程度。例如,我們可以在v4.1版本的e_xor()中發現一個模式,即:

        - 真正的處理邏輯只有第二行。

      由于其它都是框架部分,所以我們可以考慮一種編程范式,它是對tail的擴展,目的是對在tail調用e_xor——就好象對數組調用sort()方法一樣。tail的含義是取數據,而新擴展的含義是數組與邏輯都作為整體。例如:

    1. // 在函數原型上擴展的tailed方法,用于作參數的尾部化處理
    2. Function.prototype.tailed = function() {
    3. return function(f) { // 將函數this通過參數f保留在閉包上
    4. return function() { // tailed()之后的、可調用的e_xor()函數
    5. if (arguments.length < f.length+1) return false;
    6. if (f.apply(this, arguments)) return true; // 調用tailed()之前的函數 f
    7. return arguments.callee.apply(this, [].slice.call(arguments, f.length));
    8. }
    9. }(this)
    10. }
    tailed()的用法很簡單:
    e_xor = function(a){
    if (!arguments[1] != !a) return true;
    }.tailed();

      簡單的來看,我們可以將xor函數作為tailed()的運算元,這樣一樣,我們可以公開一個名為tailed的公共庫,它的核心就是暴露一組類似于xor的函數,開發者可以使用下面的編程范式來實現運算。例如:

    /* tiny tailed library, v0.0.0.1 alpha. by aimingoo. */
    Function.prototype.tailed
    = ....;
    // 對參數a及其后的所有參數求異或
    function xor(a) {
    if (!arguments[1] != !a) return true;
    }
    // ...更多類似的庫函數

      那么,這個所謂的tailed庫該如何用呢?很簡單,一行代碼:

    // 求任意多個參數的xor值
    xor.tailed()(a,b,c,d,e,f,g);

      現在我們得到了一個半成熟的、名為tailed的開放庫。所謂半成熟,是因為我們的tailed()還有一個小小缺陷,下面這行代碼:

    if (arguments.length < f.length+1) return false;

      中間的f.length+1的這個“1”,是一個有條件的參數,它與xor處理數據的方式有關。簡單的說,正是因為要比較a與arguments[1],所這里要+1,如果某種算法要比較 多個運算元,則tailed()就不通用了。所以正確的、完善的tailed應該允許調用者指定終止條件。例如:

    # // less_one()作為tailed庫函數中的全局常量,以及缺省的closed條件
    # // 當less_one返回true時,表明遞歸應該終止
    # function less_one(args, f) {
    #
    if (args.length < f.length+1) return true;
    # }
    #
    // 在函數原型上擴展的tailed方法,用于作參數的尾部化處理
    # Function.prototype.tailed = function(closed) {
    #
    return function(f) { // 將函數this通過參數f保留在閉包上
    # return function() { // tailed()之后的、可調用的e_xor()函數
    # if ((closed||less_one).apply(this, [arguments,f])) return false;
    #
    if (f.apply(this, arguments)) return true; // 調用tailed()之前的函數 f
    # return arguments.callee.apply(this, [].slice.call(arguments, f.length));
    # }
    # }(
    this)
    # }

      使用的方法仍然是:

    xor.tailed()(a,b,c,d,e,f,g);
    // 或者
    xor.tailed(less_one)(a,b,c,d,e,f,g);

      在不同的運算中,less_one()可以是其它的終止條件。

      現在,在這個方案——我的意思是tailed library這個庫夠Guy了嗎?不。所謂意淫無止盡,淫人們自有不同的淫法。比如,在上面的代碼中我們可以看到一個問題,就是tailed()中有很多層次的函數閉包,這意味著調用時效率與存儲空間都存在著無謂的消耗。那么,有什么辦法呢?比如說?哈哈,我們可以搞搞范型編程,弄個模板出來:

    # /* tiny tailed library with templet framework, v0.0.0.1 beta. by aimingoo. */
    # Function.prototype.templeted
    = function(args) {
    #
    var buff = ['[', ,'][0]'];
    # buff[
    1] = this.toString().replace(/_([^_]*)_/g, function($0,$1) { return args[$1]||'_'});
    #
    return eval(buff.join(''));
    # }
    #
    function tailed() {
    #
    var f = _execute_;
    #
    if (_closed_(arguments, f)) return false;
    #
    if (f.apply(this, arguments)) return true;
    #
    return arguments.callee.apply(this, [].slice.call(arguments, f.length));
    # }
    #
    function less_one(args, f) {
    #
    if (args.length < f.length+1) return true;
    # }
    #
    function xor(a) {
    #
    if (!arguments[1] != !a) return true;
    # }
    # e_xor
    = tailed.templeted({
    # closed: less_one,
    # execute: xor
    # })

      當然,我們仍然可以做得更多。例如這個templet引擎相當的粗糙,使用eval()的方法也不如new Function來得理想等等。關于這個部分,可以再參考QoBean對元語言的處理方式,因為事實上,這后面的部分已經在逼近meta language編程了。

      7、Guy?

      我們在做什么?我們已經離真相越來越遠了。或者說,我故意地帶大家兜著一個又一個看似有趣,卻又漸漸遠離真相的圈子。

      我們不是要找一段“不那么爛的代碼”嗎?如果是這樣,那么對于a,b,c三個運算條件的判斷,最好的方法大概是:

    (a!=b || a!=c)

      或者,如果考慮到a,b,c的類型問題:

    (!a!=!b || !a!=!c)

      如果考慮對一組運算元進行判斷的情況,那么就把它當成數組,寫成:

    function e_xor(a) {
    for (var na=!a,i=1; i<arguments.length; i++) {
    if (!arguments[i] != na) return true
    }
    return false;
    }

      對于這段代碼,我們使用JS默認對arguments的存取規則,有優化就優化,沒有就算了,因為我們的應用環境并沒有提出“這里的arguments有成千上萬個”或“e_xor()調用極為頻繁”這樣的需求。如果沒有需求,我們在這方面所做的優化,就是白費功能——除了技術上的完美之外,對應用環境毫無意義。

      夠用了。我們的所學,在應用環境中已經足夠,不要讓技巧在你的代碼中泛濫。所謂技術,是控制代碼復雜性、讓代碼變得優美的一種能力,而不是讓技術本身變得強大或完美。

      所以,我此前在“讀爛代碼”系統中討論時,強調的其實是三個過程:

       - 先把業務的需求想清楚,

       - 設計好清晰明確的調用接口,

       - 用最簡單的、最短距離的代碼實現。

      其它神馬滴,都系浮云。

      注:本文從第2小節,至第6小節,僅供對架構、框架、庫等方面有興趣的同學學習研究,有志于在語言設計、架構抽象等,或基礎項目中使用相關技術的,歡迎探討,切勿濫用于一般應用項目。

    0
    0
    標簽:前端 WEB

    Web前端熱門文章

      Web前端最新文章

        最新新聞

          熱門新聞

            黄色网_免费在线黄色电影_黄色成人快播电影_伦理电影_黄色片