傻瓜都能寫出計(jì)算機(jī)可以讀懂的代碼监嗜,只有優(yōu)秀的程序員才能寫出人能讀懂的代碼蒋畜!
在我看來(lái)饶号,編寫簡(jiǎn)單的函數(shù)是一件簡(jiǎn)單又困難的事情。簡(jiǎn)單是因?yàn)檫@沒(méi)有什么技術(shù)難點(diǎn)咳燕,困難是因?yàn)檫@是一種思維習(xí)慣勿决,很難養(yǎng)成,不寫個(gè)幾年代碼招盲,很難寫出像樣的代碼低缩。
大部分的程序員寫的都是CRUD、一些業(yè)務(wù)邏輯的代碼曹货,誰(shuí)實(shí)現(xiàn)不了咆繁?對(duì)于我來(lái)說(shuō),如果業(yè)務(wù)邏輯的代碼評(píng)審顶籽,需要人來(lái)講每一個(gè)代碼做了什么玩般,這樣的代碼就是不合格的,合格的代碼寫出來(lái)應(yīng)該像人說(shuō)話那么簡(jiǎn)單有條理礼饱,基本上是業(yè)務(wù)怎么樣描述需求坏为,寫出來(lái)的代碼就是怎么樣的究驴。編寫出非開(kāi)發(fā)人員都能看懂的代碼,才是我們追求的目標(biāo)匀伏。不要以寫出了一些非常復(fù)雜的代碼而沾沾自喜洒忧。好的代碼應(yīng)該是看起來(lái)平淡無(wú)奇覺(jué)得很簡(jiǎn)單自然,而不是看得人云里霧里的覺(jué)得很高深很有技術(shù)含量够颠。
如果你做好了我前面幾篇文章的要求熙侍,編寫簡(jiǎn)單的函數(shù)就容易的多,如果你覺(jué)得我之前說(shuō)的去掉local履磨,去掉用戶參數(shù)這些沒(méi)有什么必要是小題大做蛉抓,那么我覺(jué)得你寫不出簡(jiǎn)單的函數(shù)。從個(gè)人經(jīng)驗(yàn)來(lái)說(shuō)蹬耘,函數(shù)編寫的建議有以下幾點(diǎn):
1 不要出現(xiàn)和業(yè)務(wù)無(wú)關(guān)的參數(shù)
參考我之前的帖子芝雪,參數(shù)校驗(yàn)和國(guó)際化規(guī)范,函數(shù)參數(shù)里面不要出現(xiàn)local综苔,messagesource惩系,request,Response這些參數(shù)如筛,第一非常干擾閱讀堡牡,一堆無(wú)關(guān)的參數(shù)把業(yè)務(wù)代碼都遮掩住了,第二導(dǎo)致你的函數(shù)不好測(cè)試杨刨,如你要構(gòu)建一個(gè)request參數(shù)來(lái)測(cè)試晤柄,還是有一定難度的。
2 避免使用Map妖胀,Json這些復(fù)雜的數(shù)據(jù)對(duì)象作為參數(shù)和結(jié)果
這類參數(shù)看著靈活方便芥颈,但是靈活的同義詞(代價(jià))就是復(fù)雜,最終的結(jié)果是可變數(shù)多赚抡、bug多爬坑、質(zhì)量差。就好比刻板的同義詞就是嚴(yán)謹(jǐn)涂臣,最終的結(jié)果就是高質(zhì)量盾计。千萬(wàn)不要為了偷懶少幾行代碼,就到處把map赁遗,json傳來(lái)傳去署辉。其實(shí)定義一個(gè)bean也相當(dāng)簡(jiǎn)單,加上lombok之后岩四,代碼量也沒(méi)有幾行哭尝,但代碼可讀性就不可同日而語(yǔ)了。做過(guò)開(kāi)發(fā)的人應(yīng)該很容易體會(huì)炫乓,你如果接手一個(gè)項(xiàng)目刚夺,到處的輸入輸出都是map的話献丑,request從頭傳到尾,看到這樣的代碼你會(huì)哭的侠姑,我相信你會(huì)馬上崩潰很快離職的创橄。
還有人說(shuō)用bean的話后面加字段改起來(lái)麻煩,你用map還不是一樣要加一個(gè)key莽红,不是更加麻煩嗎妥畏?說(shuō)到底就是懶!
如果一個(gè)項(xiàng)目的所有代碼都如下面這樣安吁,我是會(huì)崩潰的醉蚁!
/**
* !9淼辍网棍!錯(cuò)誤代碼示例
* 1. 和業(yè)務(wù)無(wú)關(guān)的參數(shù)locale,messagesource
* 2. 輸入輸出都是map妇智,根本不知道輸入了什么滥玷,返回了什么
*
* @param params
* @param local
* @param messageSource
* @return
*/
public Map<String, Object> addConfig(Map<String, Object> params,
Locale locale, MessageSource messageSource) {
Map<String, Object> data = new HashMap<String, Object>();
try {
String name = (String) params.get("name");
String value = (String) params.get("value");
//示例代碼,省略其他代碼
}
catch (Exception e) {
logger.error("add config error", e);
data.put("code", 99);
data.put("msg", messageSource.getMessage("SYSTEMERROR", null, locale));
}
return data;
}
3 有明確的輸入輸出和方法名
盡量有清晰的輸入輸出參數(shù)巍棱,使人一看就知道函數(shù)做了啥惑畴。舉例:
public void updateUser(Map<String, Object> params){
long userId = (Long) params.get("id");
String nickname = (String) params.get("nickname");
//更新代碼
}
上面的函數(shù),看函數(shù)定義你只知道更新了用戶對(duì)象航徙,但你不知道更新了用戶的什么信息如贷。建議寫成下面這樣:
public void updateUserNickName(long userId, String nickname){
//更新代碼
}
你就算不看方法名,只看參數(shù)就能知道這個(gè)函數(shù)只更新了nickname一個(gè)字段到踏。多好案芨ぁ!
這點(diǎn)只可意會(huì)不可言傳窝稿。
4 把可能變化的地方封裝成函數(shù)
編寫函數(shù)的總體指導(dǎo)思想是抽象和封裝霞掺,你要把代碼的邏輯抽象出來(lái)封裝成為一個(gè)函數(shù),以應(yīng)對(duì)將來(lái)可能的變化讹躯。以后代碼邏輯有變更的時(shí)候,單獨(dú)修改和測(cè)試這個(gè)函數(shù)即可缠劝。
這一點(diǎn)相當(dāng)重要潮梯,否則你會(huì)覺(jué)得怎么需求老變?改代碼煩死了惨恭。
如何識(shí)別可能變的地方秉馏,多思考一下就知道了,工作久了就知道了脱羡。比如萝究,開(kāi)發(fā)初期免都,業(yè)務(wù)說(shuō)只有管理員才可以刪除某個(gè)對(duì)象,你就應(yīng)該考慮到后面可能除了管理員帆竹,其他角色也可能可以刪除绕娘,或者說(shuō)對(duì)象的創(chuàng)建者也可以刪除,這就是將來(lái)潛在的變化栽连,你寫代碼的時(shí)候就要埋下伏筆险领,把是否能刪除做成一個(gè)函數(shù)。后面需求變更的時(shí)候秒紧,你就只需要改一個(gè)函數(shù)绢陌。
舉例,刪除配置項(xiàng)的邏輯熔恢,判斷一下只有是自己創(chuàng)建的配置項(xiàng)才可以刪除脐湾,一開(kāi)始代碼是這樣的:
/**
* 刪除配置項(xiàng)
*/
@Override
public boolean delete(long id) {
Config config = configs.get(id);
if(config == null){
return false;
}
// 只有自己創(chuàng)建的可以刪除
if (UserUtil.getUser().equals(config.getCreator())) {
return configs.remove(id) != null;
}
return false;
}
這里我會(huì)識(shí)別一下,是否可以刪除這個(gè)地方就有可能會(huì)變化叙淌,很有可能以后管理員就可以刪除任何人的秤掌,那么這里就抽成一個(gè)函數(shù):
/**
* 刪除配置項(xiàng)
*/
@Override
public boolean delete(long id) {
Config config = configs.get(id);
if(config == null){
return false;
}
// 判斷是否可以刪除
if (canDelete(config)) {
return configs.remove(id) != null;
}
return false;
}
/**
* 判斷邏輯變化可能性大,抽取一個(gè)函數(shù)
*
* @param config
* @return
*/
private boolean canDelete(Config config) {
return UserUtil.getUser().equals(config.getCreator());
}
后來(lái)想了一下凿菩,沒(méi)有權(quán)限應(yīng)該拋出異常机杜,再次修改為:
/**
* 刪除配置項(xiàng)
*/
@Override
public boolean delete(long id) {
Config config = configs.get(id);
if (config == null) {
return false;
}
// 判斷是否可以刪除
check(canDelete(config), "no.permission");
return configs.remove(id) != null;
}
這就是簡(jiǎn)單的抽象和封裝的藝術(shù)⌒乒龋看這些代碼椒拗,參數(shù)多么的簡(jiǎn)單,很容易理解吧获黔。
這一點(diǎn)非常重要蚀苛,做好了這點(diǎn),大部分的小的需求變更對(duì)程序員的傷害就會(huì)降到最低了玷氏!畢竟需求變更大部分都是這些小邏輯的變更堵未。
5 編寫能測(cè)試的函數(shù)
程序猿不招妹子們喜愛(ài)的根本原因在于追求了錯(cuò)誤的目標(biāo):更短、更小盏触、更快渗蟹。
這個(gè)非常重要,當(dāng)然很難實(shí)現(xiàn)赞辩,很多人做技術(shù)之前都覺(jué)得代碼都會(huì)做單元測(cè)試雌芽,實(shí)際上和業(yè)務(wù)相關(guān)的代碼單元測(cè)試是很難做的。
我覺(jué)得要編寫能測(cè)試的函數(shù)主要有以下幾點(diǎn):
第一不要出現(xiàn)亂七八糟的參數(shù)辨嗽,如參數(shù)里面有request世落,response就不好測(cè)試,
第二你要把函數(shù)寫小一點(diǎn)糟需。如果一個(gè)功能你service代碼只有一個(gè)函數(shù)屉佳,那么你想做單元測(cè)試是很難做到的谷朝。我的習(xí)慣是盡量寫小一點(diǎn),力求每一個(gè)函數(shù)都可以單獨(dú)測(cè)試(用junit測(cè)試或者main函數(shù)測(cè)試都沒(méi)有關(guān)系)武花。這樣會(huì)節(jié)約大量的時(shí)間圆凰,尤其是代碼頻繁改動(dòng)的時(shí)候。我們應(yīng)用重啟一次需要15分鐘以上髓堪。新手可以寫一個(gè)功能可能需要重啟10幾次送朱,我可能只需要重啟幾次,節(jié)約的時(shí)候的很可觀的干旁。
第三你要有單獨(dú)測(cè)試每一個(gè)函數(shù)的習(xí)慣驶沼。不要一上來(lái)就測(cè)試整個(gè)功能,應(yīng)該一行一行代碼争群、一個(gè)一個(gè)函數(shù)測(cè)試回怜,有了這個(gè)習(xí)慣,自然就會(huì)寫出能測(cè)試的小函數(shù)换薄。所以說(shuō)玉雾,只有喜歡編碼的人才能寫出好代碼。
如 配置規(guī)范這篇文章了轻要,我的配置相關(guān)代碼复旬,都是可以單獨(dú)測(cè)試的,所以配置項(xiàng)的改動(dòng)不需要測(cè)試業(yè)務(wù)功能冲泥,應(yīng)用都不需要重啟驹碍。
====================總結(jié)======================
一句話總結(jié),使用簡(jiǎn)單參數(shù)凡恍,不要出現(xiàn)和業(yè)務(wù)無(wú)關(guān)的參數(shù)志秃,把可能變化的地方都寫出獨(dú)立的函數(shù),力求每一個(gè)函數(shù)都可以單獨(dú)測(cè)試嚼酝。
上一篇:《參數(shù)校驗(yàn)和國(guó)際化規(guī)范》
下一篇:《未完成》
本文原著作者:曉風(fēng)輕
原文鏈接:https://zhuanlan.zhihu.com/p/29129469
版權(quán)歸作者所有浮还,轉(zhuǎn)載請(qǐng)注明出處