Sky's blog

          我和我追逐的夢

          常用鏈接

          統(tǒng)計(jì)

          其他鏈接

          友情鏈接

          最新評論

          一個(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對象。但是現(xiàn)在在我們的代碼中,為了得到key,我們進(jìn)行了一個(gè)從pk -> serviceCapabilityProfile 對象的數(shù)據(jù)庫查詢,而在使用這個(gè)serviceCapabilityProfile  對象的函數(shù)中,很驚訝的發(fā)現(xiàn),其實(shí)這里真正用到的不過是一個(gè)pk而且,而這個(gè)pk我們本來就持有,何須去數(shù)據(jù)庫里跑一回?
           
          pk ---->  get serviceCapabilityProfile from database by pk ---> get pk by serviceCapabilityProfile.getServiceCapabilityPrimaryKey();

              讓我們來看看為什么會犯下如此可笑的錯(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 的對象。

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

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

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

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

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

              前面提到說這個(gè)案例有點(diǎn)"極端",這里的極端指的是buildTrafficProfileCacheKey()方法本身就在這個(gè)類之中,代碼量也非常少,意圖非常明確,本來應(yīng)該很容易被發(fā)現(xiàn)的。因此犯錯(cuò)的情況顯得比較可笑,但是我們推開來想一想,問題似乎沒有這么簡單了:如果buildTrafficProfileCacheKey()中的代碼比較復(fù)雜,可能還通過調(diào)用其他的類從而將對serviceCapabilityProfile對象的時(shí)候的代碼邏輯轉(zhuǎn)移,惡劣的情況下可能還有多層調(diào)用,甚至出現(xiàn)接口抽象實(shí)際代碼運(yùn)行時(shí)注入等復(fù)雜場景,再假設(shè)我們沒有辦法直接看到最終的使用代碼,我們無法知道原來底層只是需要一個(gè)pk而已!那么這個(gè)問題就一點(diǎn)都不可笑,上面這個(gè)白白訪問一次數(shù)據(jù)庫的錯(cuò)誤一定會再次發(fā)生,因?yàn)樯蠈诱{(diào)用者不知道到底需要什么數(shù)據(jù),只好整個(gè)對象全給!何況通常上層都有良好的代碼封裝,通過一個(gè)pk獲取一個(gè)對象這種事情,可能只需要一兩行代碼調(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),對調(diào)用者來說足夠清晰明確。但是我們考慮一下復(fù)雜場景:如果底層的實(shí)現(xiàn)邏輯沒有這么簡單明確,底層代碼的實(shí)現(xiàn)者可能擔(dān)心未來的實(shí)現(xiàn)邏輯會發(fā)生更改,比如需要serviceCapabilityProfile的其他數(shù)據(jù),因此為了保持接口穩(wěn)定,底層代碼的實(shí)現(xiàn)者一定會傾向于使用serviceCapabilityProfile對象作為參數(shù)從而保留未來不需要修改接口/函數(shù)定義就可以擴(kuò)展的自由。不經(jīng)意間,挖了一個(gè)坑...

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


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

          評論

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

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

          # 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ù)  更多評論   

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

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

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

          主站蜘蛛池模板: 陕西省| 治县。| 皮山县| 资讯 | 静乐县| 五大连池市| 介休市| 花莲市| 山东省| 襄汾县| 镇远县| 台中县| 乐陵市| 蕉岭县| 乌鲁木齐县| 平原县| 舒城县| 巴东县| 彭山县| 南雄市| 忻城县| 平武县| 永州市| 峡江县| 五寨县| 马龙县| 邳州市| 游戏| 辰溪县| 大城县| 四川省| 绥芬河市| 宁晋县| 宁远县| 白沙| 丹东市| 海阳市| 始兴县| 阳山县| 广元市| 区。|