做程序員的時間里, 伴隨自己的熱愛走到今天嗽桩,總結(jié)出一些經(jīng)驗(yàn)岳守,供大家參考,也希望和更多人找到共鳴碌冶,僅此而已湿痢,也歡迎大家在技術(shù)的范疇盡情討論。獨(dú)立思考扑庞,敢于質(zhì)疑譬重,搞懂為什么,怎么做罐氨。
一臀规、設(shè)計篇
按功能聚合代碼
受傳統(tǒng)MVC思想的影響,大多數(shù)java web軟件的分包結(jié)構(gòu)是按controller
栅隐,dao
, service
等這樣的結(jié)構(gòu)塔嬉,很多沒什么關(guān)聯(lián)性的controller、dao租悄、service都放在了同一個包下谨究,這樣的分包結(jié)構(gòu),會使得程序員在關(guān)注某一個功能時泣棋,在IDE中來回的尋找他需要的service胶哲,dao等。特別是類的數(shù)量特別多時潭辈,頻繁的找類纪吮,是一件浪費(fèi)心力的事情。
事實(shí)上mvc只是技術(shù)上的分層萎胰,軟件的設(shè)計思想碾盟;按照功能來聚合代碼和分包更加的科學(xué),這使得程序員在關(guān)注處理一個功能模塊的時候不用來回的尋找技竟,且有助于設(shè)計時模塊化這些代碼冰肴。可以在一個功能包下,再去劃分controller熙尉,service等這些技術(shù)包联逻。
用字典還是枚舉硬編碼?
字典通常設(shè)計為單獨(dú)的表, 可以在UI上靈活增減检痰,枚舉一般硬編碼在代碼中包归。
- 當(dāng)系統(tǒng)并不關(guān)心這個數(shù)據(jù)是什么,而只是需要一個數(shù)據(jù)范圍的約束時铅歼,使用字典公壤,且由于字典表可以靈活增減,所以最好存儲文本不存儲字典表的ID(或者設(shè)置為系統(tǒng)字典椎椰,不可隨意刪除)厦幅,前端一般可設(shè)計為自動完成組件
- 當(dāng)每個數(shù)據(jù)類型涉及到不同的邏輯,在系統(tǒng)中需要判斷處理慨飘,且每增加一種類型确憨,都需要編碼實(shí)現(xiàn)一部分功能時,使用枚舉寫死更好瓤的。通常這樣的功能邏輯中也會使用到這些數(shù)據(jù)休弃,使用枚舉也可以保持類型安全(這里如果再使用字典實(shí)現(xiàn),還需保持程序和數(shù)據(jù)的一致性圈膏,徒增復(fù)雜度)
- 當(dāng)只有少部分?jǐn)?shù)據(jù)需要系統(tǒng)處理不同的邏輯塔猾,其他數(shù)據(jù)都是一樣的邏輯,且會有增減本辐,這時可以將數(shù)據(jù)分為系統(tǒng)數(shù)據(jù)和非系統(tǒng)數(shù)據(jù),系統(tǒng)數(shù)據(jù)硬編碼程序中寫死ID医增。如默認(rèn)的管理員角色慎皱,和普通角色,這時如果有不同的環(huán)境叶骨,應(yīng)當(dāng)統(tǒng)一ID茫多,并在系統(tǒng)啟動階段校驗(yàn)這些數(shù)據(jù)的合法性,系統(tǒng)初始化階段自動或手動初始化這些數(shù)據(jù)忽刽。
不為了抽象而抽象
有一些系統(tǒng)中天揖,為了實(shí)現(xiàn)“面向接口編程”,每個Service類都去抽象了接口出來跪帝,實(shí)際上這些接口的實(shí)現(xiàn)類只有一個今膊,整個項(xiàng)目連一個多實(shí)現(xiàn)的Service類都沒找到,以為這樣就是面向接口編程伞剑,其實(shí)是自欺欺人斑唬。相反不寫接口,直接寫實(shí)現(xiàn)類,需要的接口的時候再去抽象接口恕刘,減少復(fù)雜度和無用編碼缤谎。有一個理由是為了使用JDK動態(tài)代理來提高性能,事實(shí)上JDK和CGLIB在性能上的差異根本沒到我們必須選擇某一個的程度褐着,而且坷澡,云原生時代無狀應(yīng)用擴(kuò)容伸縮非常便利,完全可以補(bǔ)足這一點(diǎn)含蓉。
優(yōu)化代碼频敛,哪怕只是一點(diǎn)點(diǎn)
有時候費(fèi)了很大功夫,才發(fā)現(xiàn)只是少寫了一行代碼谴餐,或者只是簡單了一點(diǎn)點(diǎn)姻政,到底值得嗎?
我問過自己這個問題岂嗓,是不是有點(diǎn)小題大做汁展,我想不是的。每一個小的優(yōu)化點(diǎn)都降低了系統(tǒng)的一點(diǎn)點(diǎn)復(fù)雜度厌殉,別小看這一點(diǎn)點(diǎn)復(fù)雜度食绿,這大大降低了程序員閱讀代碼時的心智損耗,把一個屋子里的東西分類整理好公罕,把他們放到各自的盒子里器紧,尋找起來比亂糟糟的屋子好找多了對吧。當(dāng)一個系統(tǒng)的每個模塊都復(fù)雜一點(diǎn)點(diǎn)楼眷,那你維護(hù)這個系統(tǒng)的過程時铲汪,大腦考慮的東西就多了很多,出現(xiàn)BUG的可能性就多了一些罐柳,寫一個hello world出BUG的幾率比寫一個cms小得多掌腰。想想這樣的場景:老板問你這個改動需要幾天?你思考了半天覺得改動的東西很多還怕出BUG张吉。需求浮在水面齿梁,需求背后的實(shí)現(xiàn)卻是一座冰山藏在水下!而這座冰山需要在工程的各個角落小心敲打讓它變小!
模型轉(zhuǎn)換代碼不要寫在Service中
DO DTO VO PO BO肮蛹,各種模型類之間需要進(jìn)行轉(zhuǎn)換時勺择,往往應(yīng)在模型類中提供of, valueOf等這樣的方法,在這些方法中時間模型轉(zhuǎn)換的邏輯伦忠,避免service中出現(xiàn)冗長的轉(zhuǎn)換代碼省核,影響閱讀,service中主要體現(xiàn)業(yè)務(wù)的邏輯昆码,如出現(xiàn)長篇幅的類型轉(zhuǎn)換代碼芳撒,在閱讀起來會耗費(fèi)更多心力邓深。且of,valueOf這樣的方法亦可被spring的類型轉(zhuǎn)換系統(tǒng)識別(ObjectToObjectConverter
)笔刹,在一些地方可以使用spring的類型轉(zhuǎn)換系統(tǒng)來轉(zhuǎn)換模型對象芥备。
像新人一樣寫注釋
在編寫代碼注釋時,往往是最了解程序運(yùn)行邏輯的時候舌菜,這時容易因?yàn)橹R的遮蔽性導(dǎo)致只描述了當(dāng)時編寫人認(rèn)為需要描述的內(nèi)容萌壳,一個好的辦法是以一個剛接手這個項(xiàng)目的新人的角度來思考,到底應(yīng)該將注釋寫到什么程度來幫助之后維護(hù)這段代碼的程序員日月。
考慮3方接口失敗
在功能設(shè)計中袱瓮,如果和第三方系統(tǒng)對接,最好要考慮調(diào)用第三方接口失敗的情況(墨菲定律爱咬,請相信他一定會失敗)尺借,三方的失敗是否需要影響本來的業(yè)務(wù)流程,還是降級處理精拟,先把自己的業(yè)務(wù)處理成功燎斩,之后采用人工/定時任務(wù)補(bǔ)償?shù)谌浇涌凇_@通常需要在功能上多設(shè)計幾個功能蜂绎。
例如微信支付回調(diào)沒收到時栅表,系統(tǒng)主動拉取待支付訂單的支付狀態(tài)或手動點(diǎn)擊同步按鈕。
另外一個例子是新增數(shù)據(jù)要同步給其他系統(tǒng)時师枣,考慮同步失敗時要不要影響自身系統(tǒng)的新增數(shù)據(jù)流程?有沒有后續(xù)的補(bǔ)償流程?
并不是所有的三方對接都是需要允許3方失敗怪瓶,根據(jù)業(yè)務(wù)具體情況考慮。
追求性能還是可讀性?
性能和可讀性都是判斷一個程序好壞的指標(biāo)践美,但在實(shí)際中并不是所有的優(yōu)化都可以做到帕累托優(yōu)化
洗贰,大多數(shù)時候性能和可讀性是成反比,提高性能的同時需要進(jìn)行一部分可讀性的犧牲陨倡。在評估一個優(yōu)化的價值時應(yīng)當(dāng)從以下幾個方面考慮
- 應(yīng)用響應(yīng)速度提高多少?
- 用戶體驗(yàn)是否得到提高?
- 目前這一功能在性能上是否是或?qū)⒁蔀槠款i?
- 該優(yōu)化導(dǎo)致可讀性或設(shè)計的降低程度?其他的副作用?
考慮這些問題能讓自己更客觀的考慮問題而不堅(jiān)持于自己追求某一方面的執(zhí)念敛滋。
例如:曾遇到一位高級安卓開發(fā)工程師,為了提高性能堅(jiān)持在各種業(yè)務(wù)場景中都不使用包裝數(shù)據(jù)類型Integer而使用int玫膀,int不能存儲null值矛缨,在一些業(yè)務(wù)場景下并不能完整表達(dá)業(yè)務(wù)的意義爹脾,當(dāng)我不理解為什么使用int可以提高性能時帖旨,他給我看一篇關(guān)于包裝數(shù)據(jù)類型性能測試的博文,文中大概意思是在使用了大量的(可能有幾百萬)包裝數(shù)據(jù)類型時灵妨,性能是下降的(可能有幾十毫秒解阅,記不清了),所以得出結(jié)論:盡可能的使用基本數(shù)據(jù)類型以提高性能泌霍。在這個場景中货抄,接口返回的數(shù)據(jù)是分頁的述召,最多100個int類型的數(shù)據(jù)都沒有,實(shí)際能提高的性能聊勝于無蟹地,而且app中也沒有大量計算的場景积暖,堅(jiān)持這一點(diǎn)來提高性能簡直就是撿了芝麻丟了西瓜。
如果完全的追求性能怪与,大可以直接寫C語言夺刑,沒有面向?qū)ο蟮姆庋b,會比JAVA快很多分别。
如果多注意項(xiàng)目的可讀性遍愿,諸如“這里改起來可能會有很多問題”,“我不知道修改這里會不會出問題”這樣的聲音會少很多耘斩,畢竟程序運(yùn)行花的是CPU的時間沼填,維護(hù)代碼花的是程序員的時間。
盡可能無狀態(tài)
這里的標(biāo)題事實(shí)上是一個縮寫括授,完整的標(biāo)題應(yīng)當(dāng)是“盡可能保證應(yīng)用的無狀態(tài)”坞笙,狀態(tài)是可以存儲到各個地方的,認(rèn)證使用jwt替換session刽脖,就是將狀態(tài)放置在了客戶端羞海,將session外置到redis,就是移動到了redis曲管,保證后端應(yīng)用的無狀態(tài)却邓,可以在容器平臺的加持下非常方便的伸縮擴(kuò)容,通常有狀態(tài)的應(yīng)用是不容易擴(kuò)容的院水。判斷一個程序有無狀態(tài)最簡單的辦法就是腊徙,當(dāng)啟動兩個實(shí)例時,用戶訪問其中一個實(shí)例1做了任何操作檬某,再訪問另一個實(shí)例2時撬腾,實(shí)例2的表現(xiàn)和訪問實(shí)例1是一致的。
盡量的使用類型安全
JAVA是編譯型語言恢恼,盡可能的將錯誤暴露在編譯器是非常好的避免BUG的做法民傻,特別是配置類的地方,能使用類型安全的配置就不使用字符串配置场斑,在重構(gòu)代碼時漓踢,IDE可以更好的同時更改這些地方。如spring在一些掃描配置注解中同時提供了basePackages
和basePackageClasses
,basePackageClasses
就是類型安全的配置漏隐。
安全的暴露接口
最容易產(chǎn)生安全問題的喧半,就是系統(tǒng)的輸入,對于前端的傳參應(yīng)當(dāng)做充分的校驗(yàn)青责,例如一個狀態(tài)字段一般只能在某幾個狀態(tài)之間進(jìn)行變化挺据,阿里編碼規(guī)約中也指出3個以上的狀態(tài)就要畫狀態(tài)圖幫助梳理業(yè)務(wù)邏輯了取具,因?yàn)楹芏鄷r候看似線性的狀態(tài)變化中隱藏了很多回溯,實(shí)際的狀態(tài)變化是比較復(fù)雜的(跑題了)扁耐,我要舉的例子是暇检,例如狀態(tài)字段可能的值是(1,2,3),接口設(shè)計時不應(yīng)該將這個狀態(tài)字段的值直接傳入婉称,而應(yīng)該提供操作接口去處理占哟,待支付->已支付不是簡單的0變成1,而是經(jīng)過支付這個操作酿矢,已支付->已取消也不是1變成2榨乎,而是經(jīng)過取消訂單這個操作,接口的設(shè)計應(yīng)當(dāng)是支付接口或者是取消接口瘫筐,而不是updateStatus(int status)蜜暑,沒有復(fù)雜業(yè)務(wù)邏輯的狀態(tài)字段也不建議這么做,參數(shù)范圍太大策肝,再加上如果沒做入?yún)⑿r?yàn)肛捍,一旦被滲透后果可想而知。
二之众、安全篇
數(shù)據(jù)輸入
分頁大小限制
在安全方面拙毫,分頁參數(shù)常常容易被忽略,分頁的頁大小一定要進(jìn)行限制棺禾,尤其是C端缀蹄,可以想象pageSize=999999
,發(fā)送一萬個請求是什么樣子的膘婶。
@生產(chǎn)服務(wù)器安全規(guī)范
三缺前、思考篇
你可以讓自己更懶一點(diǎn)
我喜歡做程序員的一個原因就是,解決一個問題之后悬襟,以后可以一直享受這個問題的便利衅码,這種勞動一次享受終生的感覺很讓人著迷。于是脊岳,任何讓我更多勞動的地方我都會想辦法讓機(jī)器來代替逝段,提高效率,把自己解脫出來割捅,然后去享受或者做更多這樣的事奶躯。人不就是這樣進(jìn)步的嗎?
四、流程篇
TODO