• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-341  評(píng)論-2670  文章-0  trackbacks-0

            上次有人來(lái)要求我寫一篇文章談?wù)勈裁创a才是好代碼,是誰(shuí)我已經(jīng)忘記了,好像是AutoHotkey還是啥的專欄的作者。撇開那些奇怪的條款不談,靠譜的 代碼有一個(gè)共同的特點(diǎn),就是DRY。DRY就是Don't Repeat Yourself,其實(shí)已經(jīng)被人談了好多年了,但是幾乎所有人都會(huì)忘記。

            什么是DRY(Don't Repeat Yourself)

            DRY 并不是指你不能復(fù)制代碼這么簡(jiǎn)單的。不能repeat的其實(shí)是信息,不是代碼。要分析一段代碼里面的什么東西時(shí)信息,就跟給物理題做受力分析一樣,想每次 都做對(duì)其實(shí)不太容易。但是一份代碼總是要不斷的修補(bǔ)的,所以在這之前大家要先做好TDD,也就是Test Driven Development。這里我對(duì)自己的要求是覆蓋率要高達(dá)95%,不管用什么手段,總之95%的代碼的輸出都要受到檢驗(yàn)。當(dāng)有了足夠多的測(cè)試做后盾的時(shí) 候,不管你以后發(fā)生了什么,譬如說(shuō)你發(fā)現(xiàn)你Repeat了什么東西要改,你才能放心大膽的去改。而且從長(zhǎng)遠(yuǎn)的角度來(lái)看,做好TDD可以將開發(fā)出相同質(zhì)量的代碼的時(shí)間縮短到30%左右(這是我自己的經(jīng)驗(yàn)值) 。

            什么是信息

            信息這個(gè)詞不太好用語(yǔ)言下定義,不過(guò)我可以舉個(gè)例子。譬如說(shuō)你要把一個(gè)配置文件里面的字符串按照分隔符分解成幾個(gè)字符串,你大概就會(huì)寫出這樣的代碼:

            // name;parent;description
            void ReadConfig(const wchar_t* config)
            {
                auto p 
            = wcschr(config, L';');                            // 1
                if(!p) throw ArgumentException(L"Illegal config string"); // 2
                DoName(wstring(config, p));                               // 3
                auto q = wcschr(p + 1, L';');                             // 4
                if(!q) throw ArgumentException(L"Illegal config string"); // 5
                DoParent(wstring(p + 1, q);                               // 6
                auto r = wcschr(q + 1, L';');                             // 7
                if(r) throw ArgumentException(L"Illegal config string");  // 8
                DoDescription(q + 1);                                     // 9
            }

            這段短短的代碼重復(fù)了多少信息?

            • 分隔符用的是分號(hào)(1、4、7)
            • 第二/三個(gè)片段的第一個(gè)字符位于第一/二個(gè)分號(hào)的后面(4、6、7、9)
            • 格式檢查(2、5、8)
            • 異常內(nèi)容(2、5、8)

            除了DRY以外還有一個(gè)問(wèn)題,就是處理description的方法跟name和parent不一樣,因?yàn)樗竺嬖僖矝](méi)有分號(hào)了。

            那這段代碼要怎么改呢?有些人可能會(huì)想到,那把重復(fù)的代碼抽取出一個(gè)函數(shù)就好了:

            wstring Parse(const wchar_t& config, bool end)
            {
                auto next 
            = wcschr(config, L';');
                ArgumentException up(L
            "Illegal config string");
                
            if (next)
                {
                    
            if (end) throw up;
                    wstring result(config, next);
                    config 
            = next + 1;
                    
            return result;
                }
                
            else
                {
                    
            if (!end) throw up;
                    wstring result(config);
                    config 
            += result.size();
                    
            return result;
                }
            }

            // name;parent;description
            void ReadConfig(const wchar_t* config)
            {
                DoName(Parse(config, 
            false));
                DoParent(Parse(config, 
            false));
                DoDescription(Parse(config, 
            true));
            }

            是不是看起來(lái)還很別扭,好像把代碼修改了之后只把事情搞得更亂了,而且就算config對(duì)了我們也會(huì)創(chuàng)建那個(gè)up變量,就僅僅是為了不 重復(fù)代碼。而且這份代碼還散發(fā)出了一些不好的味道,因?yàn)閷?duì)于Name、Parent和Description的處理方法還是不能統(tǒng)一,Parse里面針對(duì) end變量的處理看起來(lái)也是很重復(fù),但實(shí)際上這是無(wú)法在這樣設(shè)計(jì)的前提下消除的。所以這個(gè)代碼也是不好的,充其量只是比第一份代碼強(qiáng)一點(diǎn)點(diǎn)。

            實(shí) 際上,代碼之所以要寫的好,之所以不能repeat東西,是因?yàn)楫a(chǎn)品狗總是要改需求,不改代碼你就要死,改代碼你就要加班,所以為了減少修改代碼的痛苦, 我們不能repeat任何信息。舉個(gè)例子,有一天產(chǎn)品狗說(shuō),要把分隔符從分號(hào)改成空格!一下子就要改兩個(gè)地方了。description后面要加tag! 這樣你處理description的方法又要改了因?yàn)樗且钥崭窠Y(jié)尾不是0結(jié)尾。

            因此針對(duì)這個(gè)片段,我們需要把它改成這樣:

            vector<wstring> SplitString(const wchar_t* config, wchar_t delimiter)
            {
                vector
            <wstring> fragments;
                
            while(auto next = wcschr(config, delimiter))
                {
                    fragments.push_back(wstring(config, next));
                    config 
            = next + 1;
                }
                fragments.push_back(wstring(config));
                
            return fragments; // C++11就是好!
            }

            void ReadConfig(const wchar_t* config)
            {
                auto fragments 
            = SplitString(config, L';');
                
            if(fragments.size() != 3)
                {
                    
            throw ArgumentException(L"Illegal config string");
                }
                DoName(fragments[
            0]);
                DoParent(fragments[
            1]);
                DoDescription(fragments[
            2]);
            }

            我們可以發(fā)現(xiàn),分號(hào)(L';')在這里只出現(xiàn)了一次,異常內(nèi)容也只出現(xiàn)了一次,而且處理name、parent和 description的代碼也沒(méi)有什么區(qū)別了,檢查錯(cuò)誤也更簡(jiǎn)單了。你在這里還給你的Library增加了一個(gè)SplitString函數(shù),說(shuō)不定在以 后什么地方就用上了,比Parse這種專門的函數(shù)要強(qiáng)很多倍。

            大家可以發(fā)現(xiàn),在這里重復(fù)的東西并不僅僅是復(fù)制了代碼,而是由于你把 同一個(gè)信息散播在了代碼的各個(gè)部分導(dǎo)致了有很多相近的代碼也散播在各個(gè)地方,而且還不是那么好通過(guò)抽成函數(shù)的方法來(lái)解決。因?yàn)樵谶@種情況下,就算你把重復(fù) 的代碼抽成了Parse函數(shù),你把函數(shù)調(diào)用了幾次實(shí)際上也等于重復(fù)了信息。因此正確的方法就是把做事情的方法變一下,寫成SplitString。這個(gè) SplitString函數(shù)并不是通過(guò)把重復(fù)的代碼簡(jiǎn)單的抽取成函數(shù)而做出來(lái)的。去掉重復(fù)的信息會(huì)讓你的代碼的結(jié)構(gòu)發(fā)生本質(zhì)的變化

            這個(gè)問(wèn)題其實(shí)也有很多變體:

            • 不能有Magic Number。L';'出現(xiàn)了很多遍,其實(shí)就是個(gè)Magic Number。所以我們要給他個(gè)名字,譬如說(shuō)delimiter。
            • 不要復(fù)制代碼。這個(gè)應(yīng)該不用我講了。
            • 解耦要做成正交的。SplitString雖然不是直接沖著讀config來(lái)寫的,但是它反映了一個(gè)在其它地方也會(huì)遇到的常見的問(wèn)題。如果用Parse的那個(gè)版本,顯然只是看起來(lái)解決了問(wèn)題而已,并沒(méi)有給你帶來(lái)任何額外的效益。

            信息一旦被你repeat了,你的代碼就會(huì)不同程度的出現(xiàn)各種腐爛或者破窗,上面那三條其實(shí)只是我能想到的比較常見的表現(xiàn)形式。這件事情也告訴我們,當(dāng)高手告訴你什么什么不能做的時(shí)候,得想一想背后的原因,不然跟封建迷信有什么區(qū)別。

            posted on 2014-07-15 06:44 陳梓瀚(vczh) 閱讀(15872) 評(píng)論(9)  編輯 收藏 引用 所屬分類: 啟示

            評(píng)論:
            # re: 靠譜的代碼和DRY[未登錄](méi) 2014-07-16 04:23 | 煙圈
            vally vally god   回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY[未登錄](méi) 2014-07-18 02:02 | korall
            提供機(jī)制優(yōu)先于提供功能  回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2014-07-20 10:33 | cymheart
            當(dāng)需求不同時(shí),實(shí)現(xiàn)同樣功能的代碼算法也可能是不同的。
            如果需求上一開始就強(qiáng)調(diào)config文件中哪些字符會(huì)變動(dòng),算法設(shè)計(jì)上肯定要做到對(duì)應(yīng)。如果需求不明,也只能簡(jiǎn)單的用博主所說(shuō)的第一段代碼了。

              回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2014-08-10 19:33 | 飛奔吧少年
            各種都真不錯(cuò)!  回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2014-08-10 19:34 | 飛奔吧少年
            主頁(yè)真不錯(cuò)!  回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2014-08-31 22:51 | 清醒瘋子利炳根
            反復(fù)看了幾遍,看明白了:)集中在一個(gè)地方調(diào)用處理,優(yōu)于分散在不同的地方來(lái)調(diào)用。

            不單單是把代碼抽取出來(lái)建立子程,子程里的實(shí)現(xiàn)也要不重復(fù):)

            第三個(gè)例程的代碼,確實(shí)要比第二個(gè)那樣的把子程作為參數(shù)調(diào)用的,好很多  回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2015-01-13 06:08 | 男人沒(méi)錢
            DRY的說(shuō)法很靠譜,我的看法是“做任何事情之前,先減少心智負(fù)載”,就是把那些可以簡(jiǎn)化掉的行為變成一個(gè)接口,以后來(lái)了類似的需求,只需要往這個(gè)接口里一扔就好了。希望vczh多給我們普及一些簡(jiǎn)單易懂的東西。。  回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2015-03-01 02:51 | ivy
            感覺(jué)是借用了vb的概念呀  回復(fù)  更多評(píng)論
              
            # re: 靠譜的代碼和DRY 2015-03-05 22:38 | sptt
            感同身受~  回復(fù)  更多評(píng)論
              
            一本色综合久久| 国内精品久久久久久不卡影院 | 久久精品国产清高在天天线| av午夜福利一片免费看久久| 国产精品无码久久久久| 久久久久亚洲av综合波多野结衣 | 成人午夜精品无码区久久 | 亚洲欧美一区二区三区久久| 国产V综合V亚洲欧美久久| 欧美粉嫩小泬久久久久久久| 久久99精品国产麻豆| 久久亚洲欧洲国产综合| 国产精品一久久香蕉产线看| 久久久网中文字幕| 国产精品久久久久久福利漫画| 色播久久人人爽人人爽人人片AV| 久久99精品国产| 18岁日韩内射颜射午夜久久成人| 久久99久久成人免费播放| 99久久成人国产精品免费 | 久久综合狠狠综合久久激情 | 久久婷婷国产剧情内射白浆| 久久影院亚洲一区| 7国产欧美日韩综合天堂中文久久久久 | 潮喷大喷水系列无码久久精品| 7777精品久久久大香线蕉| 久久精品国产亚洲αv忘忧草| 久久精品夜色噜噜亚洲A∨ | 久久综合狠狠综合久久激情 | 久久偷看各类wc女厕嘘嘘| 国产成人综合久久精品红| 久久综合视频网| 东方aⅴ免费观看久久av| 久久SE精品一区二区| 麻豆av久久av盛宴av| 亚洲午夜久久久影院伊人| 久久亚洲中文字幕精品一区| 热re99久久精品国99热| 久久本道伊人久久| 久久综合色之久久综合| 久久九九久精品国产免费直播|