還重構(gòu)?就這代碼只能鏟了重寫!
往期熱門文章:
1、是怎么樣的SQL優(yōu)化能做到 900W+數(shù)據(jù),從17s到300ms? 2、再見(jiàn) BeanUtils!對(duì)比 12 種 Bean 自動(dòng)映射工具,就它性能最拉跨! 3、暴力拒絕白嫖,著名開(kāi)源項(xiàng)目作者刪庫(kù)跑路,數(shù)千個(gè)應(yīng)用程序無(wú)限輸出亂碼 4、兩天兩夜,1M圖片優(yōu)化到100kb! 5、12 個(gè)頂級(jí) Bug 跟蹤工具
一、前言
我們不一樣,就你沒(méi)對(duì)象!?對(duì),你是面向過(guò)程編程的!業(yè)務(wù)價(jià)值!可維護(hù)、易擴(kuò)展、好交接的特點(diǎn)。
二、代碼優(yōu)化
1. 約定規(guī)范
#?提交:主要 type
feat:?????增加新功能
fix:??????修復(fù)bug
#?提交:特殊 type
docs:?????只改動(dòng)了文檔相關(guān)的內(nèi)容
style:????不影響代碼含義的改動(dòng),例如去掉空格、改變縮進(jìn)、增刪分號(hào)
build:????構(gòu)造工具的或者外部依賴的改動(dòng),例如webpack,npm
refactor:?代碼重構(gòu)時(shí)使用
revert:???執(zhí)行g(shù)it?revert打印的message
#?提交:暫不使用type
test:?????添加測(cè)試或者修改現(xiàn)有測(cè)試
perf:?????提高性能的改動(dòng)
ci:???????與CI(持續(xù)集成服務(wù))有關(guān)的改動(dòng)
chore:????不修改src或者test的其余修改,例如構(gòu)建過(guò)程或輔助工具的變動(dòng)
#?注釋:類注釋配置
/**
*?@description:?
*?@author:?${USER}
*?@date:?${DATE}
*/
分支:開(kāi)發(fā)前提前約定好拉分支的規(guī)范,比如 日期_用戶_用途,210905_xfg_updateRuleLogic提交: 作者,type: desc?如:小傅哥,fix:更新規(guī)則邏輯問(wèn)題?參考Commit message 規(guī)范注釋:包括類注釋、方法注釋、屬性注釋,在 IDEA 中可以設(shè)置類注釋的頭信息? Editor -> File and Code Templates -> File Header?推薦下載安裝 IDEA P3C 插件?Alibaba Java Coding Guidelines,統(tǒng)一標(biāo)準(zhǔn)化編碼方式。
2. 接口標(biāo)準(zhǔn)
Code碼和Info描述,否則使用方很難知道這個(gè)接口是否調(diào)用成功還是異常,以及是什么情況的異常。public?class?Result?implements?java.io.Serializable?{
????private?static?final?long?serialVersionUID?=?752386055478765987L;
????/**?返回結(jié)果碼?*/
????private?String?code;
????/**?返回結(jié)果信息?*/
????private?String?info;
????public?Result()?{
????}
????public?Result(String?code,?String?info)?{
????????this.code?=?code;
????????this.info?=?info;
????}
????public?static?Result?buildSuccessResult()?{
????????Result?result?=?new?Result();
????????result.setCode(Constants.ResponseCode.SUCCESS.getCode());
????????result.setInfo(Constants.ResponseCode.SUCCESS.getInfo());
????????return?result;
????}
????
????//?...get/set
}
public?class?RuleResult?extends?Result?{
????private?String?ruleId;
????private?String?ruleDesc;
????public?RuleResult(String?code,?String?info)?{
????????super(code,?info);
????}
?
?//?...get/set
}
//?使用
public?RuleResult?execRule(DecisionMatter?request)?{
????return?new?RuleResult(Constants.ResponseCode.SUCCESS.getCode(),?Constants.ResponseCode.SUCCESS.getInfo());
}
public?class?ResultData<T>?implements?Serializable?{
????private?Result?result;
????private?T?data;
????public?ResultData(Result?result,?T?data)?{
????????this.result?=?result;
????????this.data?=?data;
????}???
?
?//?...get/set
}??
//?使用
public?ResultData?execRule(DecisionMatter?request)? {
????return?new?ResultData(Result.buildSuccessResult(),?new?Rule());
}
兩種接口返回結(jié)果的包裝定義,都可以規(guī)范返回結(jié)果。在這樣的方式包裝后,使用方就可以用統(tǒng)一的方式來(lái)判斷 Code碼并做出相應(yīng)的處理。
3. 庫(kù)表設(shè)計(jì)
0NF

第零范式是指沒(méi)有使用任何范式,數(shù)據(jù)存放冗余大量表字段,而且這樣的表結(jié)構(gòu)非常難以維護(hù)。
1NF

第一范式是在第零范式冗余字段上的改進(jìn),把重復(fù)字段抽離出來(lái),設(shè)計(jì)成一個(gè)冗余數(shù)據(jù)較少便于存儲(chǔ)和讀取的表結(jié)構(gòu)。 同時(shí)在第一范式中也指出,表中的所有字段都應(yīng)該是原子的、不可再分割的,例如:你不能把公司雇員表的部門名稱和職責(zé)存放到一個(gè)字段。需要確保每列保持原子性
2NF

滿足1NF后,要求表中的列,都必須依賴主鍵,確保每個(gè)列都和主鍵列之間聯(lián)系,而不能間接聯(lián)系,也就是一個(gè)表只能描述一件事情。需要確保表中的每列都和主鍵相關(guān)。
3NF

不能存在依賴關(guān)系,學(xué)號(hào)、姓名,到院系,院系到宿舍,需要確保每列都和主鍵列直接相關(guān),而不是間接相關(guān)。
反三范式
有時(shí)候?yàn)榱吮阌诓樵儯瑫?huì)在如訂單表冗余上當(dāng)時(shí)用戶的快照信息,比如用戶下單時(shí)候的一些設(shè)置信息。 單列列表數(shù)據(jù)匯總到總表中一個(gè)數(shù)量值,便于查詢的時(shí)候可以避免列表匯總操作。 可以在設(shè)計(jì)表的時(shí)候冗余一些字段,避免因業(yè)務(wù)發(fā)展情況多變,考慮不周導(dǎo)致該表繁瑣的問(wèn)題。
4. 算法邏輯

背景:這個(gè)一個(gè)商品活動(dòng)秒殺的實(shí)現(xiàn)方案,最開(kāi)始的設(shè)計(jì)是基于一個(gè)活動(dòng)號(hào)ID進(jìn)行鎖定,秒殺時(shí)鎖定這個(gè)ID,用戶購(gòu)買完后就進(jìn)行釋放。但在大量用戶搶購(gòu)時(shí),出現(xiàn)了秒殺分布式 獨(dú)占鎖后的業(yè)務(wù)邏輯處理中發(fā)生異常,釋放鎖失敗。導(dǎo)致所有的用戶都不能再拿到鎖,也就造成了有商品但不能下單的問(wèn)題。優(yōu)化:優(yōu)化獨(dú)占競(jìng)態(tài)為分段靜態(tài),將活動(dòng)ID+庫(kù)存編號(hào)作為動(dòng)態(tài)鎖標(biāo)識(shí)。當(dāng)前秒殺的用戶如果發(fā)生鎖失敗那么后面的用戶可以繼續(xù)秒殺不受影響。而失敗的鎖會(huì)有worker進(jìn)行補(bǔ)償恢復(fù),那么最終會(huì)避免超賣以及不能售賣。

@Test
public?void?test_idx_hashMap()?{
????Map?map?=?new?HashMap<>(64);
????map.put("alderney",?"未實(shí)現(xiàn)服務(wù)");
????map.put("luminance",?"未實(shí)現(xiàn)服務(wù)");
????map.put("chorology",?"未實(shí)現(xiàn)服務(wù)");
????map.put("carline",?"未實(shí)現(xiàn)服務(wù)");
????map.put("fluorosis",?"未實(shí)現(xiàn)服務(wù)");
????map.put("angora",?"未實(shí)現(xiàn)服務(wù)");
????map.put("insititious",?"未實(shí)現(xiàn)服務(wù)");
????map.put("insincere",?"已實(shí)現(xiàn)服務(wù)");
????
????long?startTime?=?System.currentTimeMillis();
????for?(int?i?=?0;?i?100000000;?i++)?{
????????map.get("insincere");
????}
????System.out.println("耗時(shí)(initialCapacity):"?+?(System.currentTimeMillis()?-?startTime));
}
背景:HashMap 數(shù)據(jù)獲取時(shí)間復(fù)雜度在 O(1) -> O(logn) -> O(n),但經(jīng)過(guò)特殊操作,可以把這個(gè)時(shí)間復(fù)雜度,拉到O(n) 操作:這是一個(gè)定義 HashMap存放業(yè)務(wù)實(shí)現(xiàn)key,通過(guò)key調(diào)用服務(wù)的功能。但這里的key,只有insincere有用,其他的都是未實(shí)現(xiàn)服務(wù)。那你看到有啥問(wèn)題了嗎?這點(diǎn)代碼乍一看沒(méi)什么問(wèn)題,看明白了就是代碼里下砒霜!它的目的就一個(gè),要讓所有的key成一個(gè)鏈表放到HashMap中,而且把有用的key放到鏈表的最后,增加get時(shí)的耗時(shí)! 首先, new HashMap<>(64);為啥默認(rèn)初始化64個(gè)長(zhǎng)度?因?yàn)槟J(rèn)長(zhǎng)度是8,插入元素時(shí),當(dāng)鏈表長(zhǎng)度為8時(shí)候會(huì)進(jìn)行擴(kuò)容和鏈表樹(shù)化判斷,此時(shí)就會(huì)把原有的key散列了,不能讓所有key構(gòu)成一個(gè)時(shí)間復(fù)雜度較高的鏈表。其次,所有的? key?都是刻意選出來(lái)的,因?yàn)樗麄冊(cè)?HashMap?計(jì)算下標(biāo)時(shí),下標(biāo)值都為0,idx =?(size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16)),這樣就能讓所有?key?都散列到同一個(gè)位置進(jìn)行碰撞。而且單詞?insincere?的意思是;不誠(chéng)懇的、不真誠(chéng)的!最后,前7個(gè)key其實(shí)都是廢? key,不起任何作用,只有最后一個(gè) key 有服務(wù)。那么這樣就可以在HashMap中建出來(lái)很多這樣耗時(shí)的碰撞鏈表,當(dāng)然要滿足0.75的負(fù)載因子,不要讓HashMap擴(kuò)容。
5. 職責(zé)分離
public?interface?IRuleExec?{
????void?doRuleExec(String?req);
}
public?class?RuleConfig?{
????protected?Map?configGroup?=?new?ConcurrentHashMap<>();
????static?{
????????//?...
????}
}
public?class?RuleDataSupport?extends?RuleConfig{
????protected?String?queryRuleConfig(String?ruleId){
????????return?"xxx";
????}
}
public?abstract?class?AbstractRuleBase?extends?RuleDataSupport?implements?IRuleExec{
????@Override
????public?void?doRuleExec(String?req)?{
????????//?1.?查詢配置
????????String?ruleConfig?=?super.queryRuleConfig("10001");
????????//?2.?校驗(yàn)信息
????????checkRuleConfig(ruleConfig);
????????//?3.?執(zhí)行規(guī)則{含業(yè)務(wù)邏輯,交給業(yè)務(wù)自己處理}
????????this.doLogic(configGroup.get(ruleConfig));
????}
????/**
?????*?執(zhí)行規(guī)則{含業(yè)務(wù)邏輯,交給業(yè)務(wù)自己處理}
?????*/
????protected?abstract?void?doLogic(String?req);
????private?void?checkRuleConfig(String?ruleConfig)?{
????????//?...?校驗(yàn)配置
????}
}
public?class?RuleExec?extends?AbstractRuleBase?{
????@Override
????protected?void?doLogic(String?req)?{
????????//?封裝自身業(yè)務(wù)邏輯
????}
}

這是一種模版模式結(jié)構(gòu)的定義,使用到了接口實(shí)現(xiàn)、抽象類繼承,同時(shí)可以看到在? AbstractRuleBase?抽象類中,是負(fù)責(zé)完成整個(gè)邏輯調(diào)用的定義,并且這個(gè)抽象類把一些通用的配置和數(shù)據(jù)使用單獨(dú)隔離出去,而公用的簡(jiǎn)單方法放到自身實(shí)現(xiàn),最后是關(guān)于抽象方法的定義和調(diào)用,而業(yè)務(wù)類?RuleExec?就可以按需實(shí)現(xiàn)自己的邏輯功能了。
6. 邏輯縝密

背景:這個(gè)產(chǎn)品功能的背景可能很大一部分研發(fā)都參與開(kāi)發(fā)過(guò),簡(jiǎn)單說(shuō)就是滿足用戶使用積分抽獎(jiǎng)的一個(gè)需求。上圖左側(cè)就是研發(fā)最開(kāi)始設(shè)計(jì)的流程,通過(guò)RPC接口扣減用戶積分,扣減成功后進(jìn)行抽獎(jiǎng)。但由于當(dāng)天RPC服務(wù)不穩(wěn)定,造成RPC實(shí)際調(diào)用成功,但返回超時(shí)失敗。而調(diào)用RPC接口的uuid是每次自動(dòng)生成的,不具備調(diào)用冪等性。所以造成了用戶積分多支付現(xiàn)象。 處理:事故后修改抽獎(jiǎng)流程,先生成待抽獎(jiǎng)的抽獎(jiǎng)單,由抽獎(jiǎng)單ID調(diào)用RPC接口,保證接口冪等性。在RPC接口失敗時(shí)由定時(shí)任務(wù)補(bǔ)償?shù)姆绞綀?zhí)行抽獎(jiǎng)。流程整改后發(fā)現(xiàn),補(bǔ)償任務(wù)每周發(fā)生1~3次,那么也就是證明了RPC接口確實(shí)有可用率問(wèn)題,同時(shí)也說(shuō)明很久之前就有流程問(wèn)題,但由于用戶客訴較少,所以沒(méi)有反饋。
7. 領(lǐng)域聚合

依靠領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的設(shè)計(jì)思想,通過(guò)事件風(fēng)暴建立領(lǐng)域模型,合理劃分領(lǐng)域邏輯和物理邊界,建立領(lǐng)域?qū)ο蠹胺?wù)矩陣和服務(wù)架構(gòu)圖,定義符合DDD分層架構(gòu)思想的代碼結(jié)構(gòu)模型,保證業(yè)務(wù)模型與代碼模型的一致性。通過(guò)上述設(shè)計(jì)思想、方法和過(guò)程,指導(dǎo)團(tuán)隊(duì)按照DDD設(shè)計(jì)思想完成微服務(wù)設(shè)計(jì)和開(kāi)發(fā)。 拒絕泥球小單體、拒絕污染功能與服務(wù)、拒絕一加功能排期一個(gè)月 架構(gòu)出高可用極易符合互聯(lián)網(wǎng)高速迭代的應(yīng)用服務(wù) 物料化、組裝化、可編排的服務(wù),提高人效
8. 服務(wù)分層

這是一個(gè)簡(jiǎn)化的分層邏輯結(jié)構(gòu),有聚合的領(lǐng)域、SDK組件、中間件和代碼編排,并提供一些通用共性凝練出的服務(wù)治理功能。通過(guò)這樣的分層和各個(gè)層級(jí)的實(shí)現(xiàn)方式,就可以更加靈活的承接需求了。
9. 并發(fā)優(yōu)化

所以通常情況下更需要做去集中化處理,使用MQ消除峰,降低耦合,讓數(shù)據(jù)可以最終一致性,也更要考慮在 Redis 下的使用,減少對(duì)數(shù)據(jù)庫(kù)的大量鎖處理。 合理的運(yùn)用MQ、RPC、分布式任務(wù)、Redis、分庫(kù)分表以及分布式事務(wù)只有這樣的操作你才可能讓自己的程序代碼可以支撐起更大的業(yè)務(wù)體量。
10. 源碼能力

@Around("aopPoint()?&&?@annotation(dbRouter)")
public?Object?doRouter(ProceedingJoinPoint?jp,?DBRouter?dbRouter)?throws?Throwable?{
????String?dbKey?=?dbRouter.key();
????if?(StringUtils.isBlank(dbKey))?throw?new?RuntimeException("annotation DBRouter key is null!");
????//?計(jì)算路由
????String?dbKeyAttr?=?getAttrValue(dbKey,?jp.getArgs());
????int?size?=?dbRouterConfig.getDbCount()?*?dbRouterConfig.getTbCount();
????//?擾動(dòng)函數(shù)
????int?idx?=?(size?-?1)?&?(dbKeyAttr.hashCode()?^?(dbKeyAttr.hashCode()?>>>?16));
????//?庫(kù)表索引
????int?dbIdx?=?idx?/?dbRouterConfig.getTbCount()?+?1;
????int?tbIdx?=?idx?-?dbRouterConfig.getTbCount()?*?(dbIdx?-?1);???
????//?設(shè)置到?ThreadLocal
????DBContextHolder.setDBKey(String.format("%02d",?dbIdx));
????DBContextHolder.setTBKey(String.format("%02d",?tbIdx));
????logger.info("數(shù)據(jù)庫(kù)路由 method:{} dbIdx:{} tbIdx:{}",?getMethod(jp).getName(),?dbIdx,?tbIdx);
???
????//?返回結(jié)果
????try?{
????????return?jp.proceed();
????}?finally?{
????????DBContextHolder.clearDBKey();
????????DBContextHolder.clearTBKey();
????}
}
這是 HashMap 哈希桶數(shù)組 + 鏈表 + 紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu),通過(guò)擾動(dòng)函數(shù)? (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16));?解決數(shù)據(jù)碰撞嚴(yán)重的問(wèn)題。但其實(shí)這樣的散列算法、尋址方式都可以運(yùn)用到數(shù)據(jù)庫(kù)路由的設(shè)計(jì)實(shí)現(xiàn)中,還有整個(gè)數(shù)組+鏈表的方式其實(shí)庫(kù)+表的方式也有類似之處。 數(shù)據(jù)庫(kù)路由簡(jiǎn)化的核心邏輯實(shí)現(xiàn)代碼如上,首先我們提取了庫(kù)表乘積的數(shù)量,把它當(dāng)成 HashMap 一樣的長(zhǎng)度進(jìn)行使用。 當(dāng) idx 計(jì)算完總長(zhǎng)度上的一個(gè)索引位置后,還需要把這個(gè)位置折算到庫(kù)表中,看看總體長(zhǎng)度的索引因?yàn)槁涞侥膫€(gè)庫(kù)哪個(gè)表。 最后是把這個(gè)計(jì)算的索引信息存放到 ThreadLocal 中,用于傳遞在方法調(diào)用過(guò)程中可以提取到索引信息。
三、總結(jié)
講道理,你幾乎不太可能把一堆已經(jīng)爛的不行的代碼,通過(guò)重構(gòu)的方式把他處理干凈。細(xì)了說(shuō),你要改變代碼結(jié)構(gòu)分層、屬性對(duì)象整合、調(diào)用邏輯封裝,但任何一步的操作都可能會(huì)對(duì)原有的接口定義和調(diào)用造成風(fēng)險(xiǎn)影響,而且外部現(xiàn)有調(diào)用你的接口還需要隨著你的改動(dòng)而升級(jí),可能你會(huì)想著在包裝一層,但這一層包裝仍需要較大的時(shí)間成本和幾乎沒(méi)有價(jià)值的適配。 所以我們?cè)趯?shí)際開(kāi)發(fā)中,如果能讓這些代碼具有重構(gòu)的可能,幾乎就是要實(shí)時(shí)重構(gòu),每當(dāng)你在添加新的功能、新的邏輯、修復(fù)異常時(shí),就要考慮是否可以通過(guò)代碼結(jié)構(gòu)、實(shí)現(xiàn)方式、設(shè)計(jì)模式等手段的使用,改變不合理的功能實(shí)現(xiàn)。每一次,一點(diǎn)的優(yōu)化和改變,也不會(huì)有那么難。 當(dāng)你在接需求的時(shí)候,認(rèn)真思考承接這樣的業(yè)務(wù)訴求,都需要建設(shè)怎樣的數(shù)據(jù)結(jié)構(gòu)、算法邏輯、設(shè)計(jì)模式、領(lǐng)域聚合、服務(wù)編排、系統(tǒng)架構(gòu)等,才能更合理的搭建出良好的具有易維護(hù)、可擴(kuò)展的系統(tǒng)服務(wù)。如果你對(duì)這些還沒(méi)有什么感覺(jué),可以閱讀?設(shè)計(jì)模式?和?手寫Spring,這些內(nèi)容可以幫助你提升不少的編程邏輯設(shè)計(jì)。
往期熱門文章:
1、《歷史文章分類導(dǎo)讀列表!精選優(yōu)秀博文都在這里了!》 2、程序員裸辭全職接單一個(gè)月的感觸 3、Java8 Stream:2萬(wàn)字20個(gè)實(shí)例,玩轉(zhuǎn)集合的篩選、歸約、分組、聚合 4、字節(jié)終面:兩個(gè)文件的公共URL怎么找? 5、留在一線,逃離一線?我從上海舉家回成都的生活經(jīng)歷告訴你 6、公司規(guī)定所有接口都用 POST請(qǐng)求,這是為什么? 7、我被這個(gè)瀏覽了 746000 次的問(wèn)題驚住了! 8、騰訊三面:40億個(gè)QQ號(hào)碼如何去重? 9、自從用完Gradle后,有點(diǎn)嫌棄Maven了!速度賊快! 10、一個(gè)員工的離職成本有多恐怖!
評(píng)論
圖片
表情
