一次代碼評(píng)審,差點(diǎn)過(guò)不了試用期!
好的代碼往往也很好看!代碼是給機(jī)器運(yùn)行的,但同樣也是給人看的,并且隨著上線還需要由人來(lái)運(yùn)維。那么寫(xiě)出可擴(kuò)展、易維護(hù)、好讀懂的代碼就顯得非常重要。
對(duì)于新人來(lái)說(shuō),互聯(lián)網(wǎng)大廠項(xiàng)目開(kāi)發(fā)與平常自己學(xué)習(xí)的代碼還是有很大的差別的。日常學(xué)習(xí)時(shí)候通常只要能運(yùn)行出結(jié)果即可,并不會(huì)有其他的要求。
也不會(huì)說(shuō)有;PRD 評(píng)審、研發(fā)設(shè)計(jì)評(píng)審、代碼開(kāi)發(fā)、代碼評(píng)審以及中間一些列的提交物,直到測(cè)試完成,上線驗(yàn)證,開(kāi)量對(duì)外等等。
所以很多新人剛從學(xué)校畢業(yè)或者從小公司進(jìn)入大廠,在規(guī)范制約下會(huì)有一些不習(xí)慣,甚至犯錯(cuò)誤。
那么為了讓大家更好的知曉這些問(wèn)題,我特意整理了一些例子,歡迎參考。
會(huì)議室
謝飛機(jī),剛剛?cè)肼殯](méi)多久,興奮的寫(xiě)著 Leader 給的需求,碼的飛快。恰巧組長(zhǎng)走過(guò)來(lái):“飛機(jī),帶著你的電腦,跟我來(lái)會(huì)議室,做下代碼評(píng)審。”
Leader:飛機(jī),你這代碼咋這么粗魯!
飛機(jī):啊?😱
Leader:我要不攔著你,我感覺(jué)你這代碼都能飛。
Leader:你看哈,就說(shuō)這行,這日志打的,上線后出了問(wèn)題,你能查到原因嗎?
飛機(jī):好像...
Leader:還有這,這 idea 都提示你了,都報(bào)黃色了,你怎么不看看。還有,這代碼也不格式化,一個(gè)月后它認(rèn)識(shí)你,你還認(rèn)識(shí)它嗎。
Leader:給你發(fā)的入職編碼規(guī)范看了?
飛機(jī):哦,看一些,寫(xiě)的時(shí)候忘了。
Leader:先別著急寫(xiě),看會(huì)了再寫(xiě)代碼,這還有一個(gè)不錯(cuò)的工程,可以參考。
寫(xiě)代碼不是以完成功能就算完事,還需要寫(xiě)的漂亮。評(píng)審后,飛機(jī),坐回工位,收起了躁動(dòng)的心,安心熟讀手冊(cè)并練習(xí)。
代碼評(píng)審
①日志規(guī)范
日志是整個(gè)代碼開(kāi)發(fā)過(guò)程中非常重要的環(huán)節(jié),如果日志打的不好,那么遇到的線上 Bug 就沒(méi)法快速定位,定位不了問(wèn)題也就沒(méi)法快速解決問(wèn)題。直接帶來(lái)的結(jié)果可能包括;客訴更多、資損更大、修復(fù)更慢。
- public Result execRule(RuleReq req) {
- try {
- logger.info("執(zhí)行服務(wù)規(guī)則 req:{}", JSON.toJSONString(req));
- // 業(yè)務(wù)流程
- return Result.buildSuccess();
- } catch (Exception e) {
- logger.error("執(zhí)行服務(wù)規(guī)則失敗", e);
- return Result.buildError(e);
- }
- }
就像上面這段代碼中的日志:
- 看似沒(méi)什么問(wèn)題,但在這段異常代碼中,沒(méi)有打方法的入?yún)⑿畔ⅰH绻椒ó惓r(shí)只是拋出一些異常棧信息,那么是很難定位具體的由次調(diào)用觸發(fā)的。
- 另外如果你的系統(tǒng)監(jiān)控服務(wù),沒(méi)有類似方法跟蹤 ID 的功能,最好還需要在日志中把本次調(diào)用具有標(biāo)識(shí)性的 id,作為查詢條件打到日志中。
- public Result execRule(RuleReq req) {
- try {
- logger.info("執(zhí)行服務(wù)規(guī)則{}開(kāi)始 req:{}", req.getrId(), JSON.toJSONString(req));
- // 業(yè)務(wù)流程
- logger.info("執(zhí)行服務(wù)規(guī)則{}完成 res:{}", req.getrId(), "業(yè)務(wù)流程,必要的結(jié)果信息");
- return Result.buildSuccess();
- } catch (Exception e) {
- logger.error("執(zhí)行服務(wù)規(guī)則{}失敗 req:{}", req.getrId(), JSON.toJSONString(req), e);
- return Result.buildError(e);
- }
- }
修改后的日志如上:
- 那么現(xiàn)在這樣改成這樣打日志,就可以非常方便的查詢問(wèn)題,例如搜索;執(zhí)行服務(wù)規(guī)則 100098921,那么它的一整串關(guān)于這次調(diào)用的信息就可以都搜索出來(lái)了,方便排查問(wèn)題。
- 在異常中打印入?yún)⑹菫榱烁臃奖愕亩ㄎ粏?wèn)題,不需要比對(duì)上下文。
- 打日志還有很多技巧,但所有打的日志目的都為了在出問(wèn)題時(shí)可以快速定位問(wèn)題,但也注意不要打太多日志,精簡(jiǎn)好用即可。
②IDEA 提示
很多時(shí)候因?yàn)槟悖呱?、疏忽、手滑,?xiě)出來(lái)的錯(cuò)誤代碼,IntelliJ IDEA,都會(huì)給你警告⚠提示,只是你,沒(méi)有去看、沒(méi)有去看、沒(méi)有去看!
來(lái)自 idea 的警告:
idea 警告
idea 在警告提示這方面非常優(yōu)秀,只要你能看得見(jiàn),按照它的提示修改,就可以減少很多的錯(cuò)誤。如果你還希望有更強(qiáng)的提示,那么你可以按照 p3c 插件,幫你檢查代碼錯(cuò)誤。
③代碼格式
可能這并不是一個(gè)致命的問(wèn)題,但代碼格式化最大的好處是,提升可讀性、規(guī)整性、以及可以讓整組人都在一個(gè)標(biāo)準(zhǔn)下執(zhí)行。
因?yàn)楹芏鄷r(shí)候一個(gè)組的程序員,會(huì)在一個(gè)類下開(kāi)發(fā),有人格式化、有人不格式化除了不好看以外,合并代碼有時(shí)候也會(huì)遇到麻煩。
不格式化的代碼缺少靈魂:
代碼格式化
對(duì)于嚴(yán)格要自己的程序員來(lái)說(shuō),代碼沒(méi)有格式化還是很難受的??匆欢未a,只要發(fā)現(xiàn)差一個(gè)空格位置,都知道這是格式化還是沒(méi)格式化。
④單元測(cè)試
單測(cè)?覆蓋率?寫(xiě)代碼不是寫(xiě)完就可以了嗎?
當(dāng)然不是,你寫(xiě)的代碼你需要保證它能你跑通你所有的流程節(jié)點(diǎn),確保這份功能是沒(méi)有問(wèn)題的,才能提交給測(cè)試,否則來(lái)回反復(fù),耗時(shí)耗力。
這也就是寫(xiě)單測(cè)的目的!甚至好一點(diǎn)的研發(fā)可以通過(guò)單測(cè)驅(qū)動(dòng)開(kāi)發(fā),在這個(gè)階段能把一些共用的方法合并、抽離,避免過(guò)多的冗余方法。
單測(cè)長(zhǎng)什么樣,如上圖:
- 單測(cè)完整基本也就是代碼的健壯性更好,能把單測(cè)寫(xiě)好,基本提交的代碼就不會(huì)有那么多測(cè)試妹子找你聊天。
- 在很多公司中一般都會(huì)要求單測(cè)覆蓋率超過(guò)多少,否則是不允許編譯提交的,這有插件可以和 Jenkins 配合使用。
⑤分支規(guī)范
可能有些人看到分支規(guī)范根本沒(méi)有感覺(jué),因?yàn)樗麄冮_(kāi)發(fā)的項(xiàng)目較小,沒(méi)有多人開(kāi)發(fā),上線周期也短,也不會(huì)開(kāi)發(fā)中添加需求。
但在互聯(lián)網(wǎng)中并不是這樣,往往一個(gè)系統(tǒng)需要幾個(gè)人維護(hù),并同時(shí)進(jìn)行開(kāi)發(fā)。一般這里會(huì)包括 master 分支、test 分支、本次需求的分支。
有這么多分支怎么用呢,如下:
- master 分支,是主分支,也是上線分支,不允許在上面直接修改代碼。
- test 分支,是測(cè)試環(huán)境分支,每個(gè)人都需要把自己開(kāi)發(fā)完的分支,提測(cè)后合并到 test 分支,交由測(cè)試驗(yàn)證。
- 需求分支,也是個(gè)人開(kāi)發(fā)的分支,同一個(gè)需求下,大家在這個(gè)分支寫(xiě)代碼,當(dāng)然也可能這個(gè)系統(tǒng)模塊的分支就一個(gè)人在開(kāi)發(fā)。
重點(diǎn),如果有人不遵守分支規(guī)范或者壓根沒(méi)概念,把自己的需求代碼寫(xiě)在 test 分支上,并且是多次修改提交都在 test 分支寫(xiě)。那么就危險(xiǎn)了,嚴(yán)重會(huì)耽誤上線。
為什么?
- test 分支,是由大家把自己的代碼合并過(guò)來(lái)共用的,那么這個(gè)分支就會(huì)包含 2 個(gè)或者更多的并行需求,當(dāng)你需要上線的時(shí)候,需要把自己的代碼合并到 master,但 test 分支代碼是不能合并到 master 的,那么多未知的內(nèi)容,根本沒(méi)有在上線范圍。
- 那么你又想上線,又不能避開(kāi) test 分支,就需要把你寫(xiě)的代碼,重新粘貼過(guò)去,這個(gè)時(shí)間成本非常大。
- test 分支,還隨時(shí)有刪除重新拉的可能,如果有人通知大家刪除重新拉,那你的代碼就會(huì)丟失。
⑥夾帶需求
提交測(cè)試,但還藏一個(gè)需求。
研發(fā)開(kāi)發(fā)需求代碼時(shí)候,有時(shí)候會(huì)額外加一些其他代碼,而且這些代碼可能跟本次需求并沒(méi)有關(guān)系。
那為什么會(huì)這樣呢?
- 以前留下來(lái)的 Bug,想修復(fù)下,但忘記告知測(cè)試。
- 在開(kāi)發(fā)這個(gè)需求時(shí),其他產(chǎn)品又找過(guò)來(lái)讓加功能,并說(shuō)功能很小,沒(méi)有發(fā)郵件通知相關(guān)測(cè)試人員。
- 看到某塊以前寫(xiě)的代碼太亂了,就想著優(yōu)化下,自信心很高,不必告訴測(cè)試。
那這時(shí)候你提交的代碼,如果不在測(cè)試范圍又出了問(wèn)題,只能研發(fā)自己抗。并且在所有的研發(fā)團(tuán)隊(duì),幾乎是不會(huì)讓夾帶需求上線的,這樣的做完了不算功勞,做出了問(wèn)題還會(huì)被罵。
所以,千萬(wàn)不要私自?shī)A帶!哪怕你是好心!
⑦異常流程
擦屁屁的紙,80% 的面積都是保護(hù)手的!
這句話是我經(jīng)常用的,因?yàn)槲覀兙幊毯芏鄷r(shí)候都是在處理異常流程,正常流程往往并不難,難的是分析出這段開(kāi)發(fā)的代碼有多少異常流程有沒(méi)有處理。
那么,會(huì)有哪些異常呢?
- 支付成功 MQ 消息發(fā)送失敗,需要 worker 補(bǔ)償。
- PRC 接口調(diào)用失敗,網(wǎng)絡(luò)超時(shí),實(shí)際成功。
- 接口冪等性,多次調(diào)用結(jié)果一致性。
等等,這些都是異常流程,尤其在一些交易提現(xiàn)環(huán)節(jié),會(huì)出現(xiàn)各種異常,那么不可能把這些異常都反饋用戶展示到界面。
而是要有一些非常友好的提示,并且在服務(wù)端的流程里,有一定的補(bǔ)償機(jī)制,來(lái)保證最終的調(diào)用成功,或者逆反。
⑧代碼成坨
代碼成坨
CRUD 往往可能是因?yàn)槟愕脑O(shè)計(jì),換個(gè)人寫(xiě)也許不同。
很多時(shí)候研發(fā)寫(xiě)代碼,根本不考慮是否要擴(kuò)展,總之一個(gè)類+幾十行 if else,能搞定所有需求。等下次在開(kāi)發(fā)類似的,就粘貼過(guò)去再修修補(bǔ)補(bǔ),能用就行。
缺少寫(xiě)出良好代碼的研發(fā),一方面是經(jīng)歷有限,另外一方面是學(xué)了很多理論但是不好落地。比如設(shè)計(jì)模式,但自己實(shí)際寫(xiě)代碼的時(shí)候還是很暈。
⑨SQL 性能
- select * from table where status = 1 limit 200;
這是一段定時(shí)任務(wù)掃描庫(kù)表的 SQL,這段 SQL 會(huì)定時(shí)掃庫(kù),將庫(kù)表中狀態(tài)是 1 的掃描出來(lái)進(jìn)行處理,每次掃描 200 行。
你發(fā)現(xiàn)有什么問(wèn)題了嗎?
- 掃描必要字段即可,不需要全部字段。
- 這段 SQL 會(huì)越來(lái)越慢,即使?fàn)顟B(tài)字段加了索引。因?yàn)?status 并不能大量排掉其他狀態(tài)字段,隨著數(shù)據(jù)越來(lái)越多依然是全表掃描。
那么怎么優(yōu)化呢,其實(shí)優(yōu)化也比較簡(jiǎn)單,需要先根據(jù)狀態(tài)查詢到符合條件的最小的 id,之后再 SQL 的查詢條件中添加 id>xx,即可。
另外如果你的任務(wù)需要多個(gè) worker 掃描,增加效率,可以增加門(mén)牌號(hào)設(shè)計(jì),提升掃描效率,如下:
門(mén)牌號(hào)掃描
⑩結(jié)伴編程
評(píng)審代碼最后這點(diǎn)想說(shuō)說(shuō),陪伴式開(kāi)發(fā),可能這不是結(jié)伴編程,不是共同合作,而是一個(gè)研發(fā)需要另外一個(gè)研發(fā)不斷的提供幫助。有時(shí)候可能就是很簡(jiǎn)單的問(wèn)題,也不想查,或者說(shuō)沒(méi)有意識(shí)去查,只是問(wèn)。
業(yè)務(wù)開(kāi)發(fā)的過(guò)程,只要把流程定下來(lái),研發(fā)設(shè)計(jì)評(píng)審?fù)?,其他的開(kāi)發(fā)過(guò)程中遇到的小點(diǎn)并不難,只要查一查就可以搞定。
當(dāng)日也不是說(shuō)完全不能問(wèn),只不過(guò)特別普遍,簡(jiǎn)單的代碼問(wèn)題,自己搞定就可以了,但這個(gè)時(shí)候還像保姆似的陪伴,就會(huì)拖累整個(gè)團(tuán)隊(duì)的進(jìn)展,最終大家都需要扛起那個(gè)慢的。
所以,如果你是那個(gè)需要陪伴的,要及早斷奶,學(xué)會(huì)自己攻克,快速成長(zhǎng)。而如果你是那個(gè)卷紙,可哪擦屁股的,要把卷紙傳遞給他。一個(gè)人擦一次是能力體現(xiàn),反反復(fù)復(fù)擦一個(gè)人,就惹屎上身了。
總結(jié)
總結(jié)如下:
- 以上介紹了代碼評(píng)審中涉及到的比較常見(jiàn)的點(diǎn),基本也是很多研發(fā)容易忽略和犯錯(cuò)誤的地方。這些問(wèn)題點(diǎn)拿出哪一個(gè)看,都不大,但運(yùn)行在代碼中,確都有可能發(fā)生致命或者麻煩的事情。
- 想讓自己能把代碼寫(xiě)好,就不只面試時(shí)候造飛機(jī)的回答,什么時(shí)間復(fù)雜度、什么可重入鎖、什么紅黑樹(shù),什么 DDD,只要你不能正確的落地和運(yùn)用這些技術(shù),說(shuō)的再多都是空談。
- 多學(xué)一些、多看一些、多問(wèn)一些,沒(méi)有壞處,但要自己能成長(zhǎng),把吸取到的經(jīng)驗(yàn)心得,運(yùn)用到業(yè)務(wù)開(kāi)發(fā)中,寫(xiě)出可擴(kuò)展、可維護(hù)的代碼,才能讓自己真的升職加薪。也能讓既有留下的本事,也有出去的能力。
作者:小傅哥
簡(jiǎn)介:多年從事一線互聯(lián)網(wǎng) Java 開(kāi)發(fā),從 19 年開(kāi)始編寫(xiě)工作和學(xué)習(xí)歷程的技術(shù)匯總,旨在為大家提供一個(gè)較清晰詳細(xì)的核心技能學(xué)習(xí)文檔。
編輯:陶家龍
出處:轉(zhuǎn)載自公眾號(hào) bugstack 蟲(chóng)洞棧(ID:bugstack)