几个编程准则
参考书籍: 《代码整洁之道》,《重构》,《程序员修炼之道》
这三本书里有许多优雅的编程建议
总结几个我认为日常使用最多的,供参考
目录:
- 命名清晰
- 一个函数只做一件事
- 合理注释
- 处理好异常逻辑
- 边界清晰
- 不要容忍“破窗户”
准则一: 命名清晰
回想一下:你大多时候是在读代码,还是在写代码?
有经常用下面单词命名变量, 类名吗?
obj, flag, list, list1, map, xmap, tmp, params, info, json, result, i, j, k, x, y, z...
Manager, Data, Info, Processor
比如下面这个函数:
public List<JSONObject> getInfos(){
List<JSONObject> list = getList();
List<JSONObject> list1 = new ArrayList();
for(JSONObject item: list){
if(item.get("age")< 10){
list1.add(item);
}
}
return list1
}
info是什么东西的info?
list是什么东西的list?
list1又是什么?
item是什么?
“小于数字10”代表什么?
JSONObject里面有什么?
下面是不是更好一些?
public List<User> getChildren():
List<User> users = getUserListFromDB();
children = new ArrayList<User>();
for(User user : users ){
if(user.isChilden()){
children.add(user)
}
}
return children;
}
class User {
private static final int childen_age_limit = 10;
String age;
boolean isChildren(){
if(this.getAge()<childen_age_limit) return true;
return false;
}
}
【这块代码能写的更好,这里不赘述】
切记:
- 要清晰, 不要模糊
- 要准确: 能叫“苹果”的时候, 别起名叫“对象”
- 不要“创造”单词:YYMMDD你打算怎么读它?peop是什么的缩写?(people)
- 简洁更重要
- 不要废话:能叫Product, 就别叫:ProductInfo, ProductData,这是无意义后缀
- 不要重复:Api模块里的类,没必要带前缀ApiUser,ApiService,成员变量也没必要加m前缀,这是无意义重复
【更多命名规范请参考:《重构》《代码整洁之道》这类书】
准则二: 一个函数只做一件事
函数有三大规则:
- 要短小
- 要更短小
- 还要更短小
比如"小明起床去上学"的函数
可能缺乏经验的程序员会这么写:
def 小明上学(){
小明睁眼;
小明看闹钟;
if 时间还早:
return;//小明接着睡觉;
else:
if 剩半小时:
小明起床;
小明穿裤子;
小明穿上衣;
小明刷牙;
小明洗脸;
小明吃煎饼;
小明喝豆浆;
小明跑着去上学;
else if 剩十分钟:
小明起床;
小明穿衣;
小明跑着去上学;
}
如果逻辑更复杂, 他能把这个函数写的和解八元七次方程一样长
如何做到“短小”?
拆函数!
怎么拆?
记住一句话:“每个函数一个抽象层级”
优化后:
def 小明上学(){ //第一层抽象
if 到起床时间没() == false:
return;
if 剩半小时:
缓慢起床();
else if 剩十分钟:
快速起床();
}
def 到起床时间没(){ //第二层抽象
小明睁眼;
小明看闹钟;
if 时间还早:
return false
else:
return true
}
def 缓慢起床(){ //第二层抽象
小明穿衣();
小明洗漱();
小明吃早餐();
小明去上学();
}
def 快速起床(){ //第二层抽象
小明穿衣();
小明去上学();
}
def 小明穿衣(){ //第三层抽象, 底层细节,无需再拆
先穿裤子;
再穿上衣;
再穿袜子;
}
def 小明洗漱(){ //第三层抽象, 底层细节,无需再拆
先刷牙;
再洗脸;
}
。。。
原来的1个函数拆成了6+个函数,值得吗?
值得!!!
原因:
- 更清晰
- 更灵活
如果小明周末去打球,优化后的函数直接复用:
def 小明今天不上学(){
小明睡到自然醒();
小明穿衣(); //直接重用
小明洗漱(); //直接重用
小明吃早餐(); //直接重用
小明去玩();
}
你用没拆前的函数复用试试?
另一点:函数不要有副作用
反例:
def 起床没(小明){
小明睁眼;
小明看闹钟;
小明穿上了裤子;
if 时间还早:
return false
else:
return true
}
这是一个有副作用的函数:
作用一:判断小明是否起床
作用二:偷偷给小明穿了裤子
这么写函数的人很多!!!
人们总想着在一个函数里做更多的事情,吃喝拉撒都在里面
后来要改bug的时候,才发现自己写的代码变量太多,变化太多,自己都读不懂,改不动!
只有拼图足够小的时候,它才能
- 想拼啥就拼啥!
- 想拆哪就拆哪!
- 想改哪就改哪!
不知道怎么拆的时候,记住那句话:
“每个函数一个抽象层级”
准则三:合理注释
注释是一种必须的恶!
注释是一种失败! 说明你已经无法用代码表述意图
你有用注释掩盖你对代码设计的无力,或懒惰吗?
举两个典型的例子:
案例1:可用代码替换的注释
def 小明玩游戏(){
//只有在小明父母都不在家的时候,并且满足小明考了60分,或得到同意才能玩游戏
if((parents out)&&(count>60 || permission==true)){
小明开始玩游戏();
}
}
我相信你遇到过if里面一堆条件,因为写的人自己都看不明白,在上方加了一些注释
这些注释很糟糕:
- 因为if写的太糟糕,才不得不加注释
- 如果条件变了,变成考了90分才能玩游戏,注释也得跟着变,增加了维护成本
处理方式:
- 删掉注释
- 用“函数替换表达式“完成重构【来自书籍《重构》】
def 小明玩游戏(){
if(小明满足玩游戏的条件吗() == true){
小明开始玩游戏();
}
}
def 小明满足玩游戏的条件吗(){
if(parents out ==false) return false;
if (count > 60 || permission == true){
return true;
}
return false;
}
案例2:废话注释
// name
private String name;
/**
*default xxx
*/
private void getUser(){
//dosomething
}
能用函数和命名自解释的,不要添加重复的注释
好的注释:
- //TODO
- 专有名称,不方便用英文、代码直接表述
【TODO:这个准则写的敷衍,后面补全】
准则四:处理好异常逻辑
那一天,程序员终于回想起了被空指针支配的恐惧!
我相信你一定写过类似下面的代码:
def getUserPhoneNumber(){
user = getUser();
if(user != null){
phone = user.getPhone();
if(phone!=null){
return phone.getNumber();
}
}
return null;
}
调这个函数的地方很绝望
def getOtherDetail(){
phoneNumber = getUserPhoneNumber();
if(phoneNumber==null){
//非正常逻辑
}else{
//正常逻辑
}
return something;
}
如果something中包含phoneNumber,再外层的函数在使用phoneNumber的时候还得做判空。。。
你的代码里充斥着: if(XXX != null)
或者,你经常遇到:NullPointerException
怎么解?
从根源处杜绝!
user = getUser(); //保证这里面的phoneNumber不为空
实现方式:
- 底层判断为null,直接抛出异常,终止后续操作
- 使用“空对象”模式,底层获取对象为空时,用空对象替换 【细节可搜索:空对象设计模式】,这样上层任何地方拿到的对象绝不为空
【TODO】
准则五:划清边界
【TODO】
准则六: 不要容忍破窗户
【TODO】