傻瓜都能寫出計算機可以讀懂的代碼,只有優秀的程序員才能寫出人能讀懂的代碼!
函數編寫,可讀性放在第一位。而函數可讀性的最關鍵點在于函數的輸入參數。
1. 不要出現和業務無關的參數
函數參數里面不要出現local,messagesource,request,response這些參數,第一非常干擾閱讀,一堆無關的參數把業務代碼都遮掩住了,第二導致你的函數不好測試,如你要構建一個request參數來測試,還是有一定難度的。
干凈清爽的參數,寫測試代碼非常舒服,如我們編寫一些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. 避免使用復雜的數據對象作為參數和結果
輸入輸出參數都應該盡量避免出現Map,Json這種“黑箱子”參數。這種參數,你只有通讀代碼,才知道里面究竟放了什么。
錯誤代碼范例:
/**
* !!!錯誤代碼示例
* 1. 和業務無關的參數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. 有明確的輸入輸出和方法名
盡量有清晰的輸入輸出參數,使人一看就知道函數做了啥。舉例:
public void updateUser(Map<String, Object> params){
long userId = (Long) params.get("id");
String nickname = (String) params.get("nickname");
//更新代碼
}
上面的函數,看函數定義你只知道更新了用戶對象,但你不知道更新了用戶的什么信息。建議寫成下面這樣:
public void updateUserNickName(long userId, String nickname){
//更新代碼
}
就算不看方法名,只看參數就能知道這個函數只更新了nickname一個字段。
4. 把可能變化的地方封裝成函數
編寫函數的總體指導思想是抽象和封裝,需要把代碼的邏輯抽象出來封裝成為一個函數,以應對將來可能的變化。以后代碼邏輯有變更的時候,單獨修改和測試這個函數即可。
如何識別可能變的地方,多思考一下就知道了,隨著工作經驗的增加識別起來會越來越容易。比如,開發初期,業務說只有管理員才可以刪除某個對象,你就應該考慮到后面可能除了管理員,其他角色也可能可以刪除,或者說對象的創建者也可以刪除,這就是將來潛在的變化,你寫代碼的時候就要埋下伏筆,把是否能刪除做成一個函數。后面需求變更的時候,你就只需要改一個函數。
舉例,刪除配置項的邏輯,判斷一下只有是自己創建的配置項才可以刪除,一開始代碼是這樣的:
/**
* 刪除配置項
*/
@Override
public boolean delete(long id) {
Config config = configs.get(id);
if(config == null){
return false;
}
// 只有自己創建的可以刪除
if (UserUtil.getUser().equals(config.getCreator())) {
return configs.remove(id) != null;
}
return false;
}
這里我們會識別一下,是否可以刪除這個地方就有可能會變化,很有可能以后管理員就可以刪除任何人的,那么這里就抽成一個函數:
/**
* 刪除配置項
*/
@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;
}
/**
* 判斷邏輯變化可能性大,抽取一個函數
*
* @param config
* @return
*/
private boolean canDelete(Config config) {
return UserUtil.getUser().equals(config.getCreator());
}
后來想了一下,沒有權限應該拋出異常,再次修改為:
/**
* 刪除配置項
*/
@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;
}
這就是簡單的抽象和封裝的思想。把可能變化的點封裝成可以獨立測試的函數,我們編碼過程中就會少了很多“需求變更”。