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