java開(kāi)發(fā)技巧代碼寫(xiě)的快且bug少的原因分析
前言
讀者諸君,今日我們適當(dāng)放松一下,不鉆研枯燥的知識(shí)和源碼,分享一套高效的摸魚(yú)絕活。
我有一位程序員朋友,當(dāng)時(shí)在一個(gè)團(tuán)隊(duì)中開(kāi)發(fā)Android應(yīng)用,歷經(jīng)多次考核后發(fā)現(xiàn):
在組內(nèi)以及與iOS團(tuán)隊(duì)的對(duì)比中:
- 他的任務(wù)量略多
- 但他的bug數(shù)量和嚴(yán)重度均低
- 但他加班的時(shí)間又少于其他人
不禁令人產(chǎn)生好奇,他是如何做到代碼別的又快,質(zhì)量又高的
經(jīng)過(guò)多次研究我終于發(fā)現(xiàn)了奧秘。
為了行文方便我用"老L"來(lái)代指這位朋友。
最常見(jiàn)的客戶端bug
- "老L,聽(tīng)說(shuō)昨晚上線,你又坐那摸魚(yú)看測(cè)試薅別人,有什么秘訣嗎?"
- 老L:"秘訣?倒也談不上,你這么說(shuō),我倒是有個(gè)問(wèn)題,你覺(jué)得平日里最常見(jiàn)的bug有哪些?"
- "emm,編碼上不健壯的地方,例如NPE,IndexOutOfBoundsException,UI上的可就海了去了,文本長(zhǎng)度不一導(dǎo)致顯示不下,間距問(wèn)題,亂七八糟的一大堆"
- 老L:"哈哈,都是些看起來(lái)很幼稚、愚蠢的問(wèn)題吧?是不是測(cè)試掛嘴邊的那句:' 你就不能跑一跑嗎,你又不瞎,跑兩下不就看到了,這么明顯!??!' "
- 我突然來(lái)了興致,"你是說(shuō)我們有必要上 TDD(test-driven-develop),按照DevOps思想,在CI(Continuous Integration)的時(shí)候,順帶跑自動(dòng)化測(cè)試用例發(fā)現(xiàn)問(wèn)題?"
- 老L突然打斷了我:"不要拽你那些詞了,記住了,事情是要人干的,機(jī)器只能替代可重復(fù)勞動(dòng),現(xiàn)在還不能替代人的主觀能動(dòng)性,拽詞并不能解決問(wèn)題。我們已經(jīng)找到了第一個(gè)問(wèn)題的答案,現(xiàn)在換個(gè)角度"
平日里最常見(jiàn)的bug有哪些?
- 編碼不健壯, 例如NPE,IndexOutOfBoundsException
- UI細(xì)節(jié)問(wèn)題, 例如文本長(zhǎng)度不一導(dǎo)致顯示不下,間距,等
為什么很淺顯的問(wèn)題沒(méi)有被發(fā)現(xiàn)
老L:"那么問(wèn)題來(lái)了,為什么這些淺顯的問(wèn)題,在交測(cè)前沒(méi)有被發(fā)現(xiàn)呢?"
我陷入了思考...
是開(kāi)發(fā)們都很懶嗎?也不至于??!
是時(shí)間很緊來(lái)不及嗎?確實(shí)節(jié)奏緊張,但也不至于不給調(diào)試就拿去測(cè)了!
"emm, 可能是迭代的節(jié)奏的太頻繁,壓力較大,并沒(méi)有整塊的時(shí)間用來(lái)自測(cè)聯(lián)調(diào)"
老L接過(guò)話茬,"假定你說(shuō)的是正確的,那么就有兩種可能。"
"第一種,自測(cè)與聯(lián)調(diào)要比開(kāi)發(fā)還要耗費(fèi)心思的一件事情。但實(shí)際上,你我都知道,這一點(diǎn)并站不住腳!"
"而第二種,就是在開(kāi)發(fā)階段無(wú)法及時(shí)測(cè)試,拖到開(kāi)發(fā)完,簡(jiǎn)單測(cè)測(cè)甚至被催促著就交差了"
仔細(xì)的思考后
- 業(yè)務(wù)逐步展開(kāi),無(wú)法在任意時(shí)間自由地進(jìn)行有效的集成測(cè)試
- 后端節(jié)奏并不比前端快多少,在前端的開(kāi)發(fā)階段,難以借助后端接口測(cè)試,也許接口也有問(wèn)題
"確實(shí),這是一個(gè)挺麻煩的問(wèn)題,聽(tīng)你一說(shuō),我感覺(jué)除了多給幾天,開(kāi)發(fā)完集中自測(cè)一波才行" 我如是說(shuō)到。
"NO NO NO",老L又打斷了我:"你想的過(guò)多了,你想借助一個(gè)可靠的、已經(jīng)完備的后端系統(tǒng)來(lái)進(jìn)行自測(cè)。對(duì)于你的需求來(lái)說(shuō),這個(gè)要求過(guò)高了,你這是準(zhǔn)備干QA的活"
"我?guī)湍懔信e一下情況"
- 一些數(shù)據(jù)處理的算法,這種沒(méi)有辦法,老老實(shí)實(shí)寫(xiě)單元測(cè)試,在開(kāi)發(fā)階段就可以做好,保障可靠性
- UI呢,我們現(xiàn)在寫(xiě)的代碼,基本都做到了UI與邏輯分層,只要能模擬數(shù)據(jù),就能跑起來(lái)看頁(yè)面
- 業(yè)務(wù)層,后端邏輯我們無(wú)法控制,但 Web-API 調(diào)用的情況可以分析下并做一下測(cè)試,而對(duì)于返回?cái)?shù)據(jù)的JSON結(jié)構(gòu)校驗(yàn)、約束性校驗(yàn)也可以考慮做一下測(cè)試
總而言之,我們只需要先排除掉淺顯的錯(cuò)誤。而這些淺顯的錯(cuò)誤,屬于情況2、3
老L接著說(shuō)道:"你先歇歇吧,我來(lái)說(shuō),你再插嘴這文章就太長(zhǎng)了!"
接下來(lái)就可以實(shí)現(xiàn)矛盾轉(zhuǎn)移:"如何模擬數(shù)據(jù)進(jìn)行測(cè)試",準(zhǔn)確的說(shuō),問(wèn)題分成兩個(gè)子問(wèn)題:
- 如何生成模擬數(shù)據(jù)
- 如何從接縫中塞入數(shù)據(jù),讓系統(tǒng)得以使用
可能存在的接縫
先看問(wèn)題2:"如何從接縫中塞入數(shù)據(jù),讓系統(tǒng)得以使用"
腦暴一下,可以得出結(jié)論:
應(yīng)用內(nèi)部
- 替換調(diào)用web-api的業(yè)務(wù)模塊,使用假數(shù)據(jù)調(diào)用業(yè)務(wù)鏈,一般替換Presenter、Controller實(shí)例
- 替換Model層,不調(diào)用web-api,返回假數(shù)據(jù)或用假數(shù)據(jù)調(diào)用回調(diào)鏈
- 侵入網(wǎng)絡(luò)層實(shí)現(xiàn),不進(jìn)行實(shí)際網(wǎng)絡(luò)層交互,直接使用假數(shù)據(jù)
- 遵循切面,向緩存等機(jī)制模塊中植入假數(shù)據(jù)
應(yīng)用外部
- 使用代理,返回假數(shù)據(jù)
- 假數(shù)據(jù)服務(wù)器
簡(jiǎn)單分析:
- "假數(shù)據(jù)服務(wù)器" ,并且使用邏輯編造假數(shù)據(jù)的代價(jià)太大,過(guò)。
- "使用代理,返回假數(shù)據(jù)",可以用于特定問(wèn)題的調(diào)試,不適用廣泛情況,過(guò)。
- "替換調(diào)用web-api的業(yè)務(wù)模塊",成本過(guò)大,過(guò)。
- "替換Model層",對(duì)項(xiàng)目的依賴(lài)注入管理具有較大挑戰(zhàn),備選,可能帶來(lái)很多冗余代碼。
- "侵入網(wǎng)絡(luò)層實(shí)現(xiàn)",優(yōu)選。
- "向緩存等機(jī)制模塊中植入假數(shù)據(jù)",操作真實(shí)的緩存較復(fù)雜,但可以考慮增加一個(gè) Mock緩存實(shí)現(xiàn)模塊,基于SPI等機(jī)制,可以解決冗余代碼問(wèn)題,備選。
得出結(jié)論:
- 方案1:"侵入網(wǎng)絡(luò)層實(shí)現(xiàn)",優(yōu)選
- 方案2:"替換Model層",(項(xiàng)目的依賴(lài)注入做得很好時(shí))作為備選,可能帶來(lái)冗余代碼
- 方案3:"向緩存等機(jī)制模塊中植入假數(shù)據(jù)",增加一個(gè) Mock緩存實(shí)現(xiàn)模塊,備選。(基于SPI等機(jī)制,可以解決冗余代碼問(wèn)題)
再仔細(xì)分析: 方案1和方案3可以合并,形成一個(gè)完整的方案,但未必需要限定在緩存機(jī)制中
OK 我們先擱置一下這個(gè)問(wèn)題,看前一個(gè)問(wèn)題。
創(chuàng)造假數(shù)據(jù)
簡(jiǎn)單腦暴一下,無(wú)非三種:
人工介入,手動(dòng)編寫(xiě) -- 成本過(guò)大
- 可能在前期準(zhǔn)備好,基本是純文本
- 可能使用一個(gè)交互工具,在需要數(shù)據(jù)時(shí)介入,通過(guò)圖形化操作和輸入產(chǎn)生數(shù)據(jù)
人工介入,邏輯編碼
- 基于反射等自省機(jī)制,并完全隨機(jī)或者基于限制生成數(shù)據(jù)
"第一種代價(jià)過(guò)大,暫且拋棄"
"第二種可以采用,但是人力成本不容忽視! 一個(gè)可以說(shuō)服我使用它的理由是:"可以精心設(shè)計(jì)單測(cè)數(shù)據(jù),針對(duì)性的發(fā)現(xiàn)問(wèn)題"
"第三種很輕松,例如使用Mockito,但生成合適的數(shù)據(jù)需要花費(fèi)一定的精力"
我們來(lái)扒一扒第三種方式,其核心思想為:
獲取類(lèi)信息,得到屬性集
遍歷屬性填充 >
- 基礎(chǔ)類(lèi)型、箱體類(lèi)型,枚舉,確定取值范圍,使用Random取值,賦值
- 普通類(lèi)、泛型類(lèi),創(chuàng)建實(shí)例,回歸步驟1
- 集合、數(shù)組等,創(chuàng)建實(shí)例,回歸步驟1,收集填充
不難得出結(jié)論,這一方法雖然很強(qiáng)大,但 創(chuàng)建高度定制化的數(shù)據(jù) 是一件有挑戰(zhàn)的事情。
舉個(gè)例子,模擬字符串時(shí),一般會(huì)使用語(yǔ)料集作為枚舉,進(jìn)行取值。要得到“地址”、“郵箱”等特定風(fēng)格的數(shù)據(jù),需要結(jié)合框架做配置,客觀上存在較高地學(xué)習(xí)、使用門(mén)檻。
你也知道,前幾年我圖好玩,寫(xiě)了個(gè) mock庫(kù) 。
必須強(qiáng)調(diào)的一點(diǎn):“我并不認(rèn)為我寫(xiě)的庫(kù)比Mockito等庫(kù)強(qiáng)大,僅僅是在我們開(kāi)發(fā)人員夠用的基礎(chǔ)上,做到盡可能簡(jiǎn)單!”
所以,我能使用它便捷的生成合適的假數(shù)據(jù),在開(kāi)發(fā)階段及時(shí)的進(jìn)行 “偽集成”
此刻,我再也忍不住要發(fā)言了:“且慢,老L,你這個(gè)做法有一定的侵入性吧。而且,如果數(shù)據(jù)類(lèi)在不同業(yè)務(wù)下復(fù)用的話,是否存在問(wèn)題呢?”
老L頓了頓,“確實(shí),google的annotations是源碼級(jí)注解,并不是運(yùn)行時(shí),我為了保持簡(jiǎn)單,使用了運(yùn)行時(shí)反射而非代碼生成。所以確實(shí)存在一定的代碼侵入性”。
但是,我們可以基于此建立一套簡(jiǎn)單的MOCK-API,這樣就不存在代碼侵入了。
另外,也可以增加一套Annotation-Processor 實(shí)現(xiàn)方案,這樣就可以適當(dāng)沿用項(xiàng)目中的注解約束了,但我個(gè)人認(rèn)為華而不實(shí)。
看你的第二個(gè)問(wèn)題,Mocker一開(kāi)始確實(shí)存在這個(gè)問(wèn)題,有一次從Spring的JSR380中得到靈感,我優(yōu)化了注解規(guī)則,這個(gè)問(wèn)題已經(jīng)被解決了。得空你可以順著這個(gè)圖看看:

或者去看看代碼和使用說(shuō)明:github.com/leobert-lan…
再次審視如何處理接縫
此時(shí)我已經(jīng)有點(diǎn)云里霧里,雖然聽(tīng)起來(lái)很牛,如何用起來(lái)呢?我還是很茫然,簡(jiǎn)直人麻了!不得不再次請(qǐng)教。
老L笑著說(shuō):“你問(wèn)的是一個(gè)實(shí)踐方案的問(wèn)題,而這類(lèi)問(wèn)題沒(méi)有銀彈.不同的項(xiàng)目、不同的習(xí)慣都有最適宜的方法,我只能分享一下我的想法和做法,僅做參考”
在之前的項(xiàng)目中,我自己建了一個(gè)Mock-API,利用我的Mocker庫(kù),寫(xiě)一個(gè)假數(shù)據(jù)接口就是分分鐘的事情。
測(cè)試機(jī)掛上charles代理,有需要的接口直接進(jìn)行mapping,所以在客戶端代碼中,你看不到我做了啥。
當(dāng)然,這個(gè)做法是在軟件外部。
如果要在軟件內(nèi)部做,我個(gè)人認(rèn)為這也是一個(gè)華而不實(shí)的事情。不過(guò)不得不承認(rèn)是一件好玩的事情,那就提一些思路。
基于Retrofit的CallAdapter
public interface CallAdapter<R, T> {
Type responseType();
T adapt(Call<R> call);
abstract class Factory {
public abstract @Nullable
CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
protected static Type getParameterUpperBound(int index,
ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
}
}
很明顯,我們可以追加注解,用以區(qū)分是否需要考慮mock;
可選:對(duì)于有可能需要mock的接口,可以繼續(xù)追加切面,實(shí)現(xiàn)在軟件外部控制使用 mock數(shù)據(jù) 或 真實(shí)數(shù)據(jù)
而Retrofit已經(jīng)使用反射確定了方法的 return Type ,在Mocker中也有適應(yīng)的API直接生成假數(shù)據(jù)
基于Retrofit的Interceptor
相比于上一種,攔截器已經(jīng)在Retrofit處理流程中靠后,此時(shí)在 Chain 中能夠得到的內(nèi)容已經(jīng)屬于Okhttp庫(kù)的范疇。
所以需要一定的前置措施用于確定 "return Type"、"是否需要Mock" 等信息??梢越柚鶷ag機(jī)制:
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Tag {
}
@GET("/")
Call<ResponseBody> foo(@Tag String tag);
最終從 Request#tag(type: Class<out T>): T? 方式獲取,并接入mock,并生成 Response
其他針對(duì)Okhttp的封裝
思路基本類(lèi)似,不再展開(kāi)。
寫(xiě)在最后
聽(tīng)完老L的思路,我若有所思,若有所悟。他的方案似乎很有效,而且直覺(jué)告訴我,這些方案中還有很多留白空間,例如:
- 借用SPI等技術(shù)思路,可以輕易的解決 "Mock 模塊集成與移除" 的問(wèn)題
- 提前外部控制是否Mock的接縫,可以在加一個(gè)工具APP、或者Socket+網(wǎng)頁(yè)端工具 用以實(shí)現(xiàn)控制
但我似乎遺漏了問(wèn)題的開(kāi)始
是否原意做 用于約束假數(shù)據(jù)生成規(guī)則的基礎(chǔ)建設(shè)工作呢??? 例如維護(hù)注解
事情終究是人干的,人原意做,辦法總比困難多。
以上就是java開(kāi)發(fā)中代碼寫(xiě)的快且bug少的原因分析的詳細(xì)內(nèi)容,更多關(guān)于java開(kāi)發(fā)代碼快bug少的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于Spring源碼深度解析(AOP功能源碼解析)
這篇文章主要介紹了關(guān)于Spring源碼深度解析(AOP功能源碼解析),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
利用IDEA社區(qū)版創(chuàng)建SpringBoot項(xiàng)目的詳細(xì)圖文教程
大家應(yīng)該都知道Idea社區(qū)版本,默認(rèn)是不能創(chuàng)建SpringBoot項(xiàng)目的,下面這篇文章主要給大家介紹了關(guān)于利用IDEA社區(qū)版創(chuàng)建SpringBoot項(xiàng)目的詳細(xì)圖文教程,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04
Java中的@Conditional條件注解詳細(xì)解析
這篇文章主要介紹了Java中的@Conditional條件注解詳細(xì)解析,@Conditional是Spring4新提供的注解,它的作用是按照一定的條件進(jìn)行判斷,滿足條件給容器注冊(cè)bean,需要的朋友可以參考下2023-11-11
Java中處理金額計(jì)算之使用Long還是BigDecimal詳解
在Java后端開(kāi)發(fā)中處理與錢(qián)有關(guān)的業(yè)務(wù)時(shí),確保金額計(jì)算的準(zhǔn)確性和避免錯(cuò)誤非常重要,這篇文章主要給大家介紹了關(guān)于Java中處理金額計(jì)算之使用Long還是BigDecimal的相關(guān)資料,需要的朋友可以參考下2024-07-07
利用Java代碼寫(xiě)一個(gè)并行調(diào)用模板
這篇文章主要介紹了利用Java代碼寫(xiě)一個(gè)并行調(diào)用模板,文章基于Java的相關(guān)內(nèi)容展開(kāi)寫(xiě)一個(gè)并行調(diào)用模板的詳細(xì)介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05
java 中Executor, ExecutorService 和 Executors 間的不同
這篇文章主要介紹了java 中Executor, ExecutorService 和 Executors 間的不同的相關(guān)資料,需要的朋友可以參考下2017-06-06
jQuery.event.trigger()的簡(jiǎn)單解釋
今天小編就為大家分享一篇關(guān)于jQuery.event.trigger()的簡(jiǎn)單解釋?zhuān)【幱X(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
SpringBoot?Profiles?多環(huán)境配置及切換
大部分情況下,我們開(kāi)發(fā)的產(chǎn)品應(yīng)用都會(huì)根據(jù)不同的目的,所以需要支持不同的環(huán)境,本文主要介紹了SpringBoot?Profiles?多環(huán)境配置及切換,感興趣的可以了解一下2021-12-12
Java使用poi導(dǎo)出ppt文件的實(shí)現(xiàn)代碼
Apache POI 是用Java編寫(xiě)的免費(fèi)開(kāi)源的跨平臺(tái)的 Java API,Apache POI提供API給Java對(duì)Microsoft Office格式檔案讀和寫(xiě)的功能。本文給大家介紹Java使用poi導(dǎo)出ppt文件的實(shí)現(xiàn)代碼,需要的朋友參考下吧2021-06-06

