<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Sky's blog

    我和我追逐的夢(mèng)

    常用鏈接

    統(tǒng)計(jì)

    其他鏈接

    友情鏈接

    最新評(píng)論

    一個(gè)因參數(shù)定義不合理造成的滑稽錯(cuò)誤引發(fā)的思考

        這是一個(gè)真實(shí)案例,本周在工作中發(fā)現(xiàn)的,案例情況比較極端,因此顯得很滑稽很搞笑。但是深入一下,還是有些東西值得思考。

        先來看這個(gè)案例,在性能優(yōu)化的過程中,通過thread dump發(fā)現(xiàn)有非常多的線程都在執(zhí)行同一個(gè)數(shù)據(jù)庫訪問。而按照分析,在cache開啟的情況下應(yīng)該只訪問一次才是,后面的數(shù)據(jù)庫訪問都是不應(yīng)該的。

        隨即跟蹤到問題代碼:

        //1. get pk as method parameter
        public TrafficProfile createTrafficProfile(
            
    long serviceCapabilityPrimaryKey, String serviceProviderId,                             
            String applicationId) throws NotFoundException {
     
            // 2. do database query to get serviceCapabilityProfile by pk
            ServiceCapabilityProfile serviceCapabilityProfile = new ServiceCapabilityProfilePreLoadFullSerializableImpl(getContext(),
                    serviceCapabilityPrimaryKey);
           
    // 3. generate key using obj serviceCapabilityProfile
            String key = buildTrafficProfileCacheKey(serviceProviderId, applicationId, serviceCapabilityProfile);
            TrafficProfile trafficProfile = (TrafficProfile) trafficProfileCache.get(key);
     
            //5. found in cache and return
            if ((trafficProfile != null)) {                                                            
                return trafficProfile;
            }
     
            trafficProfile 
    = new TrafficProfilePreLoadFullSerializableImpl(getContext(), serviceCapabilityProfile,
                    serviceProviderId, applicationId);
            trafficProfileCache.put(key, trafficProfile);
            
    return trafficProfile;
        }
     
        //4. notice: in fact only pk is used 
        private String buildTrafficProfileCacheKey(String serviceProviderId, String applicationId,
                ServiceCapabilityProfile serviceCapabilityProfile) {
            
    return serviceCapabilityProfile.getServiceCapabilityPrimaryKey() + "," + serviceProviderId + ","               
                    + applicationId;
        }

        因此可以看到,如果cache有效,我們其實(shí)只需要一個(gè)pk就可以組合出key從而從cache中得到保存的trafficProfile對(duì)象。但是現(xiàn)在在我們的代碼中,為了得到key,我們進(jìn)行了一個(gè)從pk -> serviceCapabilityProfile 對(duì)象的數(shù)據(jù)庫查詢,而在使用這個(gè)serviceCapabilityProfile  對(duì)象的函數(shù)中,很驚訝的發(fā)現(xiàn),其實(shí)這里真正用到的不過是一個(gè)pk而且,而這個(gè)pk我們本來就持有,何須去數(shù)據(jù)庫里跑一回?
     
    pk ---->  get serviceCapabilityProfile from database by pk ---> get pk by serviceCapabilityProfile.getServiceCapabilityPrimaryKey();

        讓我們來看看為什么會(huì)犯下如此可笑的錯(cuò)誤,隨即在這個(gè)類中我們找到了另外一個(gè)createTrafficProfile():

    // parameter is serviceCapabilityProfile obj
    public TrafficProfile createTrafficProfile(
            ServiceCapabilityProfile serviceCapabilityProfile,                                                                     

            String serviceProviderId, String applicationId)
            
    throws NotFoundException {
     
            // pass to buildTrafficProfileCacheKey() is obj, not pk
            String key = buildTrafficProfileCacheKey(serviceProviderId, applicationId, serviceCapabilityProfile);                   


        現(xiàn)在原因就很清楚了:在方法buildTrafficProfileCacheKey()中,實(shí)際只需要一個(gè)long類型的pk值,但是在它的方法參數(shù)定義中,它卻要求傳入一個(gè)serviceCapabilityProfile 的對(duì)象。

        可以想象一下這個(gè)代碼開發(fā)的過程:

    1. 第一個(gè)人先增加了以serviceCapabilityProfile對(duì)象為參數(shù)的createTrafficProfile()方法
    2. 他創(chuàng)建了buildTrafficProfileCacheKey()方法,因?yàn)槭诸^就有serviceCapabilityProfile對(duì)象,因此他選擇了將整個(gè)對(duì)象傳入
    3. 這兩個(gè)函數(shù)工作正常,雖然這個(gè)參數(shù)傳遞的有點(diǎn)感覺不大好,但至少?zèng)]有造成問題

    4. 后來,另外一個(gè)人來修改這個(gè)代碼,他添加了使用long serviceCapabilityPrimaryKey的createTrafficProfile()方法
    5. 他試圖調(diào)用buildTrafficProfileCacheKey()方法,然后發(fā)現(xiàn)這個(gè)方法需要一個(gè)serviceCapabilityProfile 對(duì)象
    6. 他不得不進(jìn)行一次數(shù)據(jù)庫訪問來獲取整個(gè)對(duì)象數(shù)據(jù)......
     
        從這個(gè)案例中,我們可以看到,一個(gè)含糊的參數(shù)是如何導(dǎo)致我們最終犯錯(cuò)的 ^0^

        這個(gè)錯(cuò)誤的修改當(dāng)然非常簡單,將buildTrafficProfileCacheKey()方法的參數(shù)調(diào)整為傳入long類型的pk就解決了問題。
     
        在日常代碼中,我們有非常多的大對(duì)象諸如“****DTO/context/profile”,而它們經(jīng)常被作為參數(shù)在代碼之間傳遞。因此需要小心:
     
    1. 當(dāng)定義一個(gè)類似buildTrafficProfileCacheKey()的方法時(shí)
        盡量將接口的參數(shù)簡單化,如果我們確認(rèn)只是需要使用到某個(gè)大對(duì)象的一兩個(gè)簡單屬性,請(qǐng)將方法定義為簡單類型,不需要傳入整個(gè)對(duì)象。
        或者在方法上通過javadoc說明我們只需要這個(gè)對(duì)象的某個(gè)或某幾個(gè)屬性。

    2. 當(dāng)調(diào)用類似buildTrafficProfileCacheKey()的方法時(shí)
        需要稍微謹(jǐn)慎一些,進(jìn)去目標(biāo)方法,看看代碼實(shí)現(xiàn),到底是需要什么數(shù)據(jù),是否真的需要整個(gè)對(duì)象從而導(dǎo)致我們需要進(jìn)行數(shù)據(jù)庫查詢這種的重量級(jí)操作。
        例如上面的例子,如果原有buildTrafficProfileCacheKey()的方法不容許修改,那么我們大可以new 一個(gè)serviceCapabilityProfile 對(duì)象,然后setPK()來解決,比訪問數(shù)據(jù)庫快捷多了。

        前面提到說這個(gè)案例有點(diǎn)"極端",這里的極端指的是buildTrafficProfileCacheKey()方法本身就在這個(gè)類之中,代碼量也非常少,意圖非常明確,本來應(yīng)該很容易被發(fā)現(xiàn)的。因此犯錯(cuò)的情況顯得比較可笑,但是我們推開來想一想,問題似乎沒有這么簡單了:如果buildTrafficProfileCacheKey()中的代碼比較復(fù)雜,可能還通過調(diào)用其他的類從而將對(duì)serviceCapabilityProfile對(duì)象的時(shí)候的代碼邏輯轉(zhuǎn)移,惡劣的情況下可能還有多層調(diào)用,甚至出現(xiàn)接口抽象實(shí)際代碼運(yùn)行時(shí)注入等復(fù)雜場(chǎng)景,再假設(shè)我們沒有辦法直接看到最終的使用代碼,我們無法知道原來底層只是需要一個(gè)pk而已!那么這個(gè)問題就一點(diǎn)都不可笑,上面這個(gè)白白訪問一次數(shù)據(jù)庫的錯(cuò)誤一定會(huì)再次發(fā)生,因?yàn)樯蠈诱{(diào)用者不知道到底需要什么數(shù)據(jù),只好整個(gè)對(duì)象全給!何況通常上層都有良好的代碼封裝,通過一個(gè)pk獲取一個(gè)對(duì)象這種事情,可能只需要一兩行代碼調(diào)用就搞定,于是我們很可能輕松自如的,一腳踩進(jìn)坑里!

        所以說想復(fù)雜點(diǎn)問題就變得嚴(yán)峻起來:底層代碼的實(shí)現(xiàn)者,需要如何設(shè)計(jì)接口參數(shù),才能準(zhǔn)確的告知上層調(diào)用者,到底哪些數(shù)據(jù)是真實(shí)需要的?上面的案例中將參數(shù)簡單的簡化為只傳入一個(gè)pk值就明確的達(dá)到了目標(biāo),對(duì)調(diào)用者來說足夠清晰明確。但是我們考慮一下復(fù)雜場(chǎng)景:如果底層的實(shí)現(xiàn)邏輯沒有這么簡單明確,底層代碼的實(shí)現(xiàn)者可能擔(dān)心未來的實(shí)現(xiàn)邏輯會(huì)發(fā)生更改,比如需要serviceCapabilityProfile的其他數(shù)據(jù),因此為了保持接口穩(wěn)定,底層代碼的實(shí)現(xiàn)者一定會(huì)傾向于使用serviceCapabilityProfile對(duì)象作為參數(shù)從而保留未來不需要修改接口/函數(shù)定義就可以擴(kuò)展的自由。不經(jīng)意間,挖了一個(gè)坑...

        我們似乎又回到了原來犯錯(cuò)的軌道中,那個(gè)看似搞笑的錯(cuò)誤似乎又在對(duì)我們揮手微笑......
        只是現(xiàn)在,我頗有點(diǎn)笑不起來了:下一次,如果我面對(duì)一個(gè)函數(shù)/接口,要求傳入一個(gè)大對(duì)象,我手頭只有一個(gè)pk,還有一個(gè)現(xiàn)成的函數(shù)可以一行代碼就搞定查詢,我要如何才能擋住誘惑?


    posted on 2010-04-17 10:22 sky ao 閱讀(1992) 評(píng)論(3)  編輯  收藏 所屬分類: 雜談

    評(píng)論

    # re: 一個(gè)因參數(shù)定義不合理造成的滑稽錯(cuò)誤引發(fā)的思考 2010-04-17 11:32 俏物悄語

    我們似乎又回到了原來犯錯(cuò)的軌道中,那個(gè)看似搞笑的錯(cuò)誤似乎又在對(duì)我們揮手微笑  回復(fù)  更多評(píng)論   

    # re: 一個(gè)因參數(shù)定義不合理造成的滑稽錯(cuò)誤引發(fā)的思考 2010-04-19 17:20 wueddie

    我在項(xiàng)目中看到了相同的代碼,當(dāng)時(shí)我只是以為作者可能覺得傳遞DDO參數(shù)比用long primaryKey 更加OO。

    從我的角度看,沒有辦法理解為什么不用long primaryKey,而要去用大而無當(dāng)?shù)腄DO參數(shù)。  回復(fù)  更多評(píng)論   

    # re: 一個(gè)因參數(shù)定義不合理造成的滑稽錯(cuò)誤引發(fā)的思考 2010-04-19 17:28 sky ao

    帖子中的原話:"為了保持接口穩(wěn)定,底層代碼的實(shí)現(xiàn)者一定會(huì)傾向于使用serviceCapabilityProfile對(duì)象作為參數(shù)從而保留未來不需要修改接口/函數(shù)定義就可以擴(kuò)展的自由"

    我是這么猜測(cè)的,從我自己的角度考慮,讓我不傳簡單的pk而是傳遞一個(gè)object,我只能想到上面這個(gè)理由。  回復(fù)  更多評(píng)論   

    主站蜘蛛池模板: 西西人体免费视频| 亚洲综合色区中文字幕| 国产中文在线亚洲精品官网| 四虎影院永久免费观看| 国产男女猛烈无遮挡免费视频| 青青免费在线视频| 男女猛烈xx00免费视频试看| 精品亚洲麻豆1区2区3区| 亚洲国产老鸭窝一区二区三区| 四虎永久免费影院| 亚洲视频在线精品| 在线亚洲97se亚洲综合在线| 亚洲色爱图小说专区| 久久久久亚洲AV成人无码| 深夜国产福利99亚洲视频| www.亚洲精品| 久久久青草青青亚洲国产免观| 免费大黄网站在线观| 精品在线免费视频| 男女作爱免费网站| 亚洲AV无码专区在线电影成人| 91嫩草私人成人亚洲影院| 亚洲国产成人久久99精品| 国产日本亚洲一区二区三区 | 国产无遮挡色视频免费观看性色| wwwxxx亚洲| 亚洲大尺度无码无码专线一区| 亚洲国产高清在线精品一区| 久久精品亚洲综合| 久久精品国产亚洲麻豆| 亚洲人成色7777在线观看不卡| 手机看片久久国产免费| 国产亚洲成人久久| 久久精品国产亚洲香蕉| 精品久久亚洲中文无码| 精品一区二区三区免费毛片| 国产精品免费视频观看拍拍 | 午夜亚洲国产成人不卡在线| 亚洲综合日韩久久成人AV| 亚洲黄网在线观看| 国产亚洲欧美在线观看|