傻瓜都能寫出計算機可以讀懂的代碼,只有優(yōu)秀的程序員才能寫出人能讀懂的代碼二跋!
函數(shù)編寫木人,可讀性放在第一位麸锉。而函數(shù)可讀性的最關(guān)鍵點在于函數(shù)的輸入?yún)?shù)钠绍。
1. 不要出現(xiàn)和業(yè)務(wù)無關(guān)的參數(shù)
函數(shù)參數(shù)里面不要出現(xiàn)local,messagesource花沉,request柳爽,response這些參數(shù),第一非常干擾閱讀碱屁,一堆無關(guān)的參數(shù)把業(yè)務(wù)代碼都遮掩住了磷脯,第二導致你的函數(shù)不好測試,如你要構(gòu)建一個request參數(shù)來測試娩脾,還是有一定難度的赵誓。
干凈清爽的參數(shù),寫測試代碼非常舒服柿赊,如我們編寫一些Service的測試代碼:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml" })
@WebAppConfiguration
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class CongfigServiceTest {
@Autowired
ConfigService configService;
/**
* 初始化信息
*/
@Before
public void init() {
System.out.println("------------init-----------");
UserUtil.setLocale("en");
UserUtil.setUser("測試的用戶");
}
@Test
public void test01Full() {
Config config = new Config();
config.setName("配置項名稱");
config.setValue("配置項值");
// 新增測試
long newId = configService.add(config);
assertTrue(newId > 1);
// 查詢測試
Collection<Config> all = configService.getAll();
assertTrue(all.size() == 1);
// 刪除測試
boolean result = configService.delete(newId);
assertTrue(result);
}
}
2. 避免使用復雜的數(shù)據(jù)對象作為參數(shù)和結(jié)果
輸入輸出參數(shù)都應該盡量避免出現(xiàn)Map俩功,Json這種“黑箱子”參數(shù)。這種參數(shù)碰声,你只有通讀代碼诡蜓,才知道里面究竟放了什么。
錯誤代碼范例:
/**
* R忍簟B!椿肩!錯誤代碼示例
* 1. 和業(yè)務(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ù)定義你只知道更新了用戶對象团南,但你不知道更新了用戶的什么信息。建議寫成下面這樣:
public void updateUserNickName(long userId, String nickname){
//更新代碼
}
就算不看方法名炼彪,只看參數(shù)就能知道這個函數(shù)只更新了nickname一個字段吐根。
4. 把可能變化的地方封裝成函數(shù)
編寫函數(shù)的總體指導思想是抽象和封裝,需要把代碼的邏輯抽象出來封裝成為一個函數(shù)辐马,以應對將來可能的變化拷橘。以后代碼邏輯有變更的時候,單獨修改和測試這個函數(shù)即可喜爷。
如何識別可能變的地方冗疮,多思考一下就知道了,隨著工作經(jīng)驗的增加識別起來會越來越容易檩帐。比如术幔,開發(fā)初期,業(yè)務(wù)說只有管理員才可以刪除某個對象湃密,你就應該考慮到后面可能除了管理員诅挑,其他角色也可能可以刪除,或者說對象的創(chuàng)建者也可以刪除泛源,這就是將來潛在的變化拔妥,你寫代碼的時候就要埋下伏筆,把是否能刪除做成一個函數(shù)达箍。后面需求變更的時候没龙,你就只需要改一個函數(shù)。
舉例幻梯,刪除配置項的邏輯兜畸,判斷一下只有是自己創(chuà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;
}
這里我們會識別一下碘梢,是否可以刪除這個地方就有可能會變化咬摇,很有可能以后管理員就可以刪除任何人的,那么這里就抽成一個函數(shù):
/**
* 刪除配置項
*/
@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;
}
/**
* 判斷邏輯變化可能性大煞躬,抽取一個函數(shù)
*
* @param config
* @return
*/
private boolean canDelete(Config config) {
return UserUtil.getUser().equals(config.getCreator());
}
后來想了一下肛鹏,沒有權(quán)限應該拋出異常逸邦,再次修改為:
/**
* 刪除配置項
*/
@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;
}
這就是簡單的抽象和封裝的思想。把可能變化的點封裝成可以獨立測試的函數(shù)在扰,我們編碼過程中就會少了很多“需求變更”缕减。