需求變更,代碼改的像辣雞 - 論代碼質(zhì)量
一句注釋引發(fā)的思考
接到一個有雞毛信般的緊急需求(當(dāng)然,002的需求向來是如此緊急的):大屏展示原來只有二個品牌數(shù)據(jù),現(xiàn)增加到三個品牌的數(shù)據(jù)。一句話的需求,且沒有業(yè)務(wù)邏輯變更,我認(rèn)為可以迅雷不及掩耳之勢,2小時收拾干凈交差。當(dāng)我滿腔激情的定位的核心邏輯部分時,這樣一句注釋(見下圖),讓我頓時思緒天馬行空:
這個作者經(jīng)歷了什么樣一個撕心裂肺的過程?但是可以肯點(diǎn)的是這一定是一個有想法的作者,不由得心中肅然起敬。
這段代碼經(jīng)歷了多少次的蹂躪,才會讓作者的心潮有如此波瀾?
抑或,這到底提出了怎么樣一個需求,讓作者需要通過這樣的注釋來宣泄心中怨氣?

巴拉開代碼修改記錄,作者已經(jīng)去別的地方高就了,要不是留了這些代碼,實(shí)在想不起有這樣的一個同事存在過;代碼提交記錄比較整潔,大部分代碼是在5月29號提交,5月30大概是修復(fù)bug提交了小部分代碼。如此看來,代碼沒有經(jīng)歷過什么苦難,這里的需求僅僅是每個品牌的門店按訂單數(shù)量排序(如下圖),想想怎么也鬧出什么大動靜... 再細(xì)讀作者留下的代碼,只能說作者給自己設(shè)置了難度系數(shù)(這說法太含蓄了),稍微有一點(diǎn)改動,便是牽一發(fā)而動全身,于是留了這樣一個不太成熟且不太有價(jià)值的注釋,想起了最近在讀的一本書,Bob大叔的《代碼整潔之道》,頗有一點(diǎn)不成熟的感觸。
如何衡量代碼質(zhì)量
《代碼整潔之道》一書開篇第一句話就是一個著名的論斷:衡量代碼質(zhì)量的唯一有效標(biāo)準(zhǔn):WTF/min。 一看到WTF這樣的簡稱就不明覺厲,一頓搜索居然沒有找到“標(biāo)準(zhǔn)”的解釋,更覺得高深莫測。 也是在后來一個偶然的時間看到原來是 What-The-Fuck 的縮寫,看重看這張經(jīng)典的圖,一下子恍然頓悟:原來如此,就該如此。
原本學(xué)得這是一本好書,甚至覺得大部分程序都應(yīng)該去閱讀這類的書籍一遍(比如 馬丁·福勒出品的《重構(gòu)-改善既有代碼的設(shè)計(jì)》)。 看到這樣的論述,更覺此書接地氣,仿佛與大師拉近了距離了。回想起過往種種,以及最近修改歷史代碼時的反應(yīng),沒什么比WTF更有表達(dá)力了。

再回到開篇講的注釋,當(dāng)時的作者必然也是有類似的反應(yīng),只是他的吐嘲對象是他自己,或者他只會認(rèn)為這是需求的而不是代碼的問題。所以 WTF/min作為衡量代碼質(zhì)量的唯一有效標(biāo)準(zhǔn),還得加一個定語:優(yōu)秀的Coder喊出的WTF次數(shù),才是真正的標(biāo)準(zhǔn)。 至于如何為優(yōu)秀的代碼,在《代碼整潔之道》,《重構(gòu)-改善既有代碼的設(shè)計(jì)》等經(jīng)典書籍里都有詳細(xì)的描述:
- 從變量命名,到注釋,到方法,到類,到模塊都有非常詳細(xì)的規(guī)范;
- 從抽象到邊界,到依賴也有完整的方法論;
- 從SOLID設(shè)計(jì)原則到,組件相關(guān)的 CRP,CCP,CRP等原則都有從理論到落地的解說。
坦白講,讀完這些書后,我感覺自己原來寫的代碼,很多都缺乏這樣的思考,也有不少代碼相當(dāng)丑陋,甚至覺得:在code這件事讓,只能算得上初窺門徑。于是越時讀書,越覺得自己無知。最近讀技術(shù)書籍的間歇,順便翻讀了幾本名人傳記:奧本海默為了讓人覺得他是天才,總是"偷偷"的讀書學(xué)習(xí);特斯拉甚至在病危之際也是一心讀書;讓我們有在劍手的錢學(xué)森利用所有空閑時間閱讀方能一年完成碩士學(xué)位,并獲得航空和數(shù)學(xué)雙博士,成本最年輕的終生教授...(省略好多榮譽(yù))。他們無一不是通過自己努力讀書,思考,實(shí)踐終成我等學(xué)習(xí)的楷模。最近招聘的經(jīng)歷中,大部分應(yīng)聘的人幾乎不看技術(shù)書籍的了,也是讓我捉摸不透。
讀技術(shù)書籍沒用了嗎
最近兩月一直忙于面試,溝通了沒有100個也有80個候選人,大部分人都沒有了讀書習(xí)慣,更不用說技術(shù)書籍了,倒是有部分說覺得blog比書籍有用多了。有些說工作太忙,有得說沒有用....他們中有的在傳統(tǒng)公司朝9晚5,有的在互聯(lián)網(wǎng)企業(yè)996,有從小企業(yè)過來的,也有從阿里,快手過來的... 其中一個人的話,讓我記憶非常深刻,最后環(huán)節(jié),我問他現(xiàn)在還讀書不,他說為了進(jìn)阿里,他非常努力了2年,把《深入理解java虛擬機(jī)》讀了2遍,《高性能mysql》,《深入理解kafka》... 都仔細(xì)閱讀了,后來進(jìn)了阿里,覺得這些書都沒有沒啥用了,也不在閱讀其他技術(shù)書籍了,最后我不知道應(yīng)該說啥,畢竟我沒去過阿里,于是結(jié)束了面試。就我自己而言,早些年面試也是被問得體無完膚,也有過這樣的心態(tài)閱讀了大量類似 《深入理解java虛擬機(jī)》,《MySQL技術(shù)內(nèi)幕:InnoDB存儲引擎》,《RocketMQ技術(shù)內(nèi)幕:RocketMQ架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)原理》,《從Paxos到Zookeeper:分布式一致性原理與實(shí)踐》等書籍,確實(shí)也讓自己在后來的面試中可以從容面對。但越是閱讀,越是感覺自己不知道的東西越多,越是想要通過閱讀來充實(shí)自己。于是又開始閱讀系統(tǒng)設(shè)計(jì),架構(gòu)一類的書籍。時至今日,讀到《代碼整潔之道》時,依然覺得即使在做了10年的編碼這件事上,不懂的依然有非常之多(哈哈,也許是悟性不夠)。 從前面的名人,到我身邊認(rèn)識的人,大凡優(yōu)秀的人的,獨(dú)有閱讀的習(xí)慣,并且有大量閱讀自己專業(yè)相關(guān)的書籍。
最近和媳婦探討讀書這件事兒,說我周末在家除了溜娃就是刷新聞,現(xiàn)如今的新聞包括熱搜又大部分是沒有“營養(yǎng)”的,也聊到目下短視頻盛行,下到3歲,上到70,地鐵上,公園里,城市里,老家里... 到處都是,當(dāng)然也包括我們自己的爸媽,談及此,心中不覺升起一股名族憂心(哈哈,操心有點(diǎn)多了)。于是我放下了新聞,當(dāng)然了周5晚上,等娃睡著后,我們買些宵夜,找一部金典電影還是保持著。 一段時間后,發(fā)現(xiàn)一個周閱讀10幾個小時好像也挺正常的,閱讀成生活的一部分。也沒有了過去那種讀了多久了,要休息下的,看看新聞,看看電視的想法了。現(xiàn)在想來,讀書也好,新聞,短視頻也罷,本質(zhì)且沒啥差異,內(nèi)心富足就好。
再回到前面的面試,也不知道,我在面試過程中,把讀書這一塊看得如此重,是否合適,但是我相信:喜歡閱讀技術(shù)書籍的人,應(yīng)該都不會太差。
回到前面的代碼
回家開篇的注釋問題,想和大家一直分享下代碼重構(gòu)過程,如果不幸被作者看到,希望不要介懷,就如Bob大叔所講,每個程序員都應(yīng)該接專業(yè)眼光的檢視(哈哈也許我也不是那么專業(yè))。 需求比較簡單,就是兩個品牌下的門店根據(jù)訂單數(shù)排序。 現(xiàn)在的需求是增加了第三個品牌,門店信息有品牌屬性。
如果作者閱讀了Clean Code ,他就會明白代碼走向整潔的4原則:
- 運(yùn)行所有測試;
- 不可重復(fù);
- 表達(dá)了程序員的意圖;
- 盡可能減少類和方法的數(shù)量。
就會把排序算法抽離出來,與業(yè)務(wù)邏輯分離,避免大量重復(fù);
如果他深刻喊出了 Don't Repeat YourSelf, 就不會有么多 ConsultationOrderRank 對象的出事化,甚至不會單獨(dú)處理有數(shù)據(jù)與沒數(shù)據(jù)的情況。
如果作者閱讀了Clean Architecture,他就會明白要面向抽象,而不是具體去編程,
他就會面向品牌這個概念去編程,而不是面向具體的品牌1,品牌2去實(shí)現(xiàn)。
//需求變更,改的像辣雞。
if (CollectionUtils.isEmpty(orderList)) { List<CfgStore> allStoreList = cfgStoreService.getStoresBLAndBabyBL(); List<CfgStore> bellaList = allStoreList.stream().filter(st -> { return st.getType() == 0; }).sorted(Comparator.comparingInt(CfgStore::getStoreId)).collect(Collectors.toList()); ArrayList<ConsultationOrderRank> ballaResult = new ArrayList<>(); int bellaIndex = 0; for (CfgStore store : bellaList) { ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank(); consultationOrderRank.setStoreName(store.getNameAlias()); consultationOrderRank.setStoreId(store.getStoreId()); consultationOrderRank.setOrderNum(0); consultationOrderRank.setSort(bellaIndex); ballaResult.add(consultationOrderRank); bellaIndex++; } List<ConsultationOrderRank> blRankResult = ballaResult.stream() .sorted(Comparator.comparing(ConsultationOrderRank::getSort)).collect(Collectors.toList()); List<CfgStore> babyBellaList = storeList.stream().filter(st -> { return st.getType() == 1; }).sorted(Comparator.comparingInt(CfgStore::getStoreId)).collect(Collectors.toList()); ArrayList<ConsultationOrderRank> babyBallaResult = new ArrayList<>(); int babyIndex = 0; for (CfgStore store : babyBellaList) { ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank(); consultationOrderRank.setStoreName(store.getNameAlias()); consultationOrderRank.setStoreId(store.getStoreId()); consultationOrderRank.setOrderNum(0); consultationOrderRank.setSort(babyIndex); babyBallaResult.add(consultationOrderRank); babyIndex++; } List<ConsultationOrderRank> babyRankResult = babyBallaResult.stream() .sorted(Comparator.comparing(ConsultationOrderRank::getSort)) .collect(Collectors.toList()); Order order = Order.builder().consultationOrderRankStBellaList(blRankResult) .consultationOrderRankBabyBellaList(babyRankResult).build(); return order; } List<CfgStore> others = storeList.stream().filter(store -> { return !Arrays.stream(storeIdArr).collect(Collectors.toList()).contains(store.getStoreId()); }).collect(Collectors.toList()); Map<Integer, CfgStore> storeMap = storeList.stream().collect(Collectors.toMap(CfgStore::getStoreId, store -> { return store; })); //品牌1門店ID List<Integer> blIdList = storeList.stream().filter(st -> st.getType().equals(0)) .map(CfgStore::getStoreId).collect(Collectors.toList()); //品牌2門店ID List<Integer> babyblIdList = storeList.stream().filter(st -> st.getType().equals(1)) .map(CfgStore::getStoreId).collect(Collectors.toList()); //品牌2分組數(shù)據(jù) Map<Integer, List<HeOrder>> babyblMap = orderList.stream() .filter(order -> babyblIdList.contains(order.getStoreId())) .collect(Collectors.groupingBy(HeOrder::getStoreId)); //品牌2分組數(shù)據(jù) Map<Integer, List<HeOrder>> blMap = orderList.stream() .filter(order -> blIdList.contains(order.getStoreId())) .collect(Collectors.groupingBy(HeOrder::getStoreId)); //品牌1排行數(shù)據(jù) List<ConsultationOrderRank> bellaList = new ArrayList<>(); //品牌2排行數(shù)據(jù) List<ConsultationOrderRank> babyBellaList = new ArrayList<>(); //品牌1 for (Entry<Integer, List<HeOrder>> entry : babyblMap.entrySet()) { CfgStore cfgStore = storeMap.get(entry.getKey()); String storeName = cfgStore.getNameAlias(); if (Strings.isNotBlank(storeName)) { List<HeOrder> orderNum = entry.getValue(); ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank(); consultationOrderRank.setStoreName(storeName); consultationOrderRank.setStoreId(entry.getKey()); consultationOrderRank.setOrderNum(orderNum == null ? 0 : orderNum.size()); babyBellaList.add(consultationOrderRank); } } List<CfgStore> otherbabyBlList = others.stream().filter(store -> { return store.getType() == 1; }).collect(Collectors.toList()); for (CfgStore store : otherbabyBlList) { ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank(); consultationOrderRank.setStoreName(store.getNameAlias()); consultationOrderRank.setStoreId(store.getStoreId()); consultationOrderRank.setOrderNum(0); babyBellaList.add(consultationOrderRank); } //品牌2 for (Entry<Integer, List<HeOrder>> entry : blMap.entrySet()) { CfgStore cfgStore = storeMap.get(entry.getKey()); String storeName = cfgStore.getNameAlias(); if (Strings.isNotBlank(storeName)) { List<HeOrder> orderNum = entry.getValue(); ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank(); consultationOrderRank.setStoreName(storeName); consultationOrderRank.setStoreId(entry.getKey()); consultationOrderRank.setOrderNum(orderNum == null ? 0 : orderNum.size()); bellaList.add(consultationOrderRank); } } List<CfgStore> otherBellaList = others.stream().filter(store -> { return store.getType() == 0; }).collect(Collectors.toList()); for (CfgStore store : otherBellaList) { ConsultationOrderRank consultationOrderRank = new ConsultationOrderRank(); consultationOrderRank.setStoreName(store.getNameAlias()); consultationOrderRank.setStoreId(store.getStoreId()); consultationOrderRank.setOrderNum(0); bellaList.add(consultationOrderRank); } //品牌1排序 List<ConsultationOrderRank> blRank = bellaList.stream().sorted(Comparator.comparing(ConsultationOrderRank::getOrderNum).reversed()) .collect(Collectors.toList()); int blSort = 0; int blIndexCounter = 0; for (int i = 0; i < blRank.size(); i++) { //訂單=0, 訂單值不同, 遞增 boolean flag = blRank.get(i).getOrderNum() == 0 || (i != 0 && blRank.get(i).getOrderNum() != blRank.get(i - 1).getOrderNum()); if (flag) { blSort = blSort + blIndexCounter + 1; blIndexCounter = 0; } else { if (i != 0) { blIndexCounter++; } } blRank.get(i).setSort(blSort); } //品牌2排序 List<ConsultationOrderRank> babyBlRank = babyBellaList.stream().sorted(Comparator.comparing(ConsultationOrderRank::getOrderNum).reversed()) .collect(Collectors.toList()); int babySort = 0; int babyIndexCounter = 0; for (int i = 0; i < babyBlRank.size(); i++) { //訂單=0, 訂單值不同, 遞增 boolean flag = babyBlRank.get(i).getOrderNum() == 0 || (i != 0 && babyBlRank.get(i).getOrderNum() != babyBlRank.get(i - 1).getOrderNum()); if (flag) { babySort = babySort + babyIndexCounter + 1; babyIndexCounter = 0; } else { if (i != 0) { babyIndexCounter++; } } babyBlRank.get(i).setSort(babySort); }
我相信作者如果經(jīng)常閱讀技術(shù)書籍,寫出的代碼應(yīng)該是這樣的。
//統(tǒng)計(jì)每個品牌每個門店訂單數(shù)量 for (Integer brandType : brandTypeList){ Map<Integer, Long> theBrandStoreOrderCount = orderList.stream().filter(order -> brandType.longValue() == order.getBrandType()).collect(Collectors.groupingBy(HeOrder::getStoreId, Collectors.counting())); List<CfgStore> brandStores = storeList.stream().filter(store -> store.getType().equals(brandType)).collect(Collectors.toList()); List<ConsultationOrderRank> storeOrderRank = new ArrayList<>(); brandStores.forEach(store ->{ Long orderCount = 0L; if (theBrandStoreOrderCount.containsKey(store.getStoreId())){ orderCount = theBrandStoreOrderCount.get(store.getStoreId()); } ConsultationOrderRank storeOrder = ConsultationOrderRank.builder() .storeId(store.getStoreId()) .orderNum(orderCount.intValue()) .storeName(store.getStoreName()) .sort(0).build(); storeOrderRank.add(storeOrder); }); List<ConsultationOrderRank> sortedStoreRank = storeOrderRank.stream().sorted(Comparator.comparing(ConsultationOrderRank::getOrderNum).reversed()).collect(Collectors.toList()); setSortWithSameRankNum(sortedStoreRank); BrandOrderStatistic statistic = BrandOrderStatistic.builder() .name(CfgStoreEnum.getValueByCode(brandType)) .storeOrderRank(sortedStoreRank) .brandType(brandType).build(); brandOrderStatistics.add(statistic); }
如此這般,我們當(dāng)時踐行了編碼里的童子軍規(guī):當(dāng)你離開營地時候,要讓它比你來的時候更整潔干凈。
成為一名優(yōu)秀的程序員!
作者:J2

編輯:妞妞
妞妞主頁
出處:http://www.rzrgm.cn/jijunjian/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,大家好,才是真的好!

最近招聘讀書打代碼的平靜,被一句不經(jīng)意的注釋打亂了。
浙公網(wǎng)安備 33010602011771號