@DevWiki
2015-07-02T21:11:12.000000Z
字数 3790
阅读 1103
CleanCode
更多内容详见:CleanCode笔记
函数第一原则要短小,第二原则还是要短小
如果函数需要翻页,就有拆分的必要了.翻页往往会导致看着下面忘了上面.
一个函数不要太长,一般每行不超过100个字符,行数不超过30行.
if, else, while, case语句中代码应只有一行,该行要描述特定条件下要做的事情.
函数也应该遵守单一职责原则,即一个函数制作一件事情.比如一个检查登陆账户的函数:
public boolean checkAccount() {
boolean isAccountAccess = false;
if(userName.isAccess() && password.isAcces()){
isAccountAccess = true;
login();
} else {
isAccountAccess = false;
}
}
函数名称为checkAccount,意为检查账户;而里面当账户可用是却请求登录.
如果开发人员未仔细检查代码,只看函数名,很容易出现问题.检查账户的函数就应该只检查账户,而不应该在做其他事情.
一个函数不应该既有抽象又有细节!
我们经常写Android的Activity时这样:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ToastUtil.init(this);
startButton = findViewById(R.id.start_button);
resultView= findViewById(R.id.result_view);
startButton.setOnClickListener(this);
resultView.setText("结果为:");
}
抛去2,3两行不说.第5行为抽象,而第7~10行为细节,这样读下来,突然发生了变化,会误入到细节之中.
不如改为下面这样:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ToastUtil.init(this);
initView();
}
private void initView(){
startButton = findViewById(R.id.start_button);
resultView= findViewById(R.id.result_view);
startButton.setOnClickListener(this);
resultView.setText("结果为:");
}
这样只需阅读在onCreate中做了哪些事情,而不需要去知道具体怎么做的.所以一个函数应保持同一抽象层次.
我们一般写switch语句往往如下:
switch (type) {
case 0:
//
break;
case 1:
//
break;
case 2:
//
break;
default:
break;
}
当有多个分支时,switch语句往往会很长.可以通过以下来改善:
1. 每个case下只有一行代码.
2. 使用策略模式分解.
函数的名称应该看到名字就知道该函数要做什么事情
比如上面的checkAccount(),看到改名称就应该知道该函数做的事情是检查账户,其返回值为boolean类型,则返回值应该是账户合法或者不合法.
关于函数的参数个数:
零个最佳,其次是一参,再次是二参,尽量避免三参或更过
最理想的函数是没有参数,直接调用,不考虑其需要的条件.因为函数自己已经拥有相应的条件.
比如Android中的Activity的onStart(), onResume()等
一个参数是最普遍的形式.
比如常用的setter和getter方法.一个参数可以明确函数运行必要的条件.
两个参数已经可以出现混淆了.
比如String类有个方法replace(String oldChar, String newChar)如果不是参数的名称,很容易混淆是用谁代替谁.
所以当函数出现两个参数时,务必给参数取个好的名称,以区分参数的意义.
举个返利,放我们给一个对象集合排序时,会实现Comparator接口,如下:
class MyCompar implements Comparator<String>{
@Override
public int compare(String lhs, String rhs) {
// TODO Auto-generated method stub
return 0;
}
}
你知道lhs与rhs的含义么?他们两个比较的结果是什么意思?
三个参数更容易出现问题.特别是三个参数类型一样时,往往会顺序错误.
比如求梯形的面积,我们都知道梯形面积S = (上底a + 下底b) * 高h /2;
public float calculateArea(int a, int b, int c){
return (a + b)*c/2;
}
如果不看细节,你能一下子就看出参数的含义么?
有需要三个或者更多参数时就应该抽象为一个类了.
上面的方法可以改为:
public float calculateArea(Trapezoid trapezoid){
return (trapezoid.getTopWidth() + trapezoid.getBottomWidth()) * trapezoid.getHeight / 2;
}
不要使用boolean类型的参数!!!
传入boolean类型的参数,那么该函数必定违反单一职责原则!
为何不拆分为两个函数呢?
上面有个checkAccount()函数其实就是一个有副作用的函数!
方法名为checkAccount(),且函数值也是boolean类型的.但是它却在账户可用时做了登录操作!!!
如果不知道该函数的细节,多次调用会导致重复登陆!!!
所以函数内容一定要和函数名称相同.
函数要么做什么事,要么回答什么事情,但二者不可兼得.
函数应该修改某对象的状态或者返回该对象的有关信息.
例子:
public boolean set(String attribute, String value);
该函数用于修改属性,如果成功返回true,如果不存在那个属性则返回false.这样会导致:
if(set("username", "jack"))..
这句是什么意思呢?意思是设置username为jack成功呢?还是设置username之前检查username是否为jack呢?
所以要分割指令和查询:
public void setUserName(String name);
public boolean isAttrExists(String attr);
我们经常这样写:
public int deletePage(Page page);
if(deletePage(page) == E_OK)...
但是这样会导致深层次的嵌套:
if(deletePage() == E_OK){
if(registy.deleteReference(page.name) == E_OK){
if(configKeys.deleteKey(page.name.makeKey()) == E_OK) {
logger.log("page deleted");
} else {
logger.log("configkey not delete");
}
} else {
logger.log(delteReference from registry failed);
}
} else {
logger.log("delete failed");
return E_ERROR;
}
如果改为返回异常,错误处理就能从主路径中分离出来:
try{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
代码处理直接放在try..catch中,要抽离出来,另外形成函数:
public void deletePage(){
try{
deletePageAndReferences(page);
} catch (Exception e){
logError(e);
}
}
一个函数只做一件事,处理错误就是一件事,所以错误处理要抽离单独的函数.
使用异常代替错误码,新的异常可以从异常类中派生,无需重新编译或重新部署.
代码混乱最直接的原因就是代码重复.重复可能是软件中一切邪恶的根源.所以别重复自己!!!
结构化编程规范:
每个函数,函数中的每个代码块应该只有一个入口,一个出口.
函数应遵守此规范,但是对于小函数,这些规则助益不大,只有在大函数中才有明显的好处.
只要函数短小,偶尔出现的return, break或 continue语句就没坏处,甚至比单入单出原则更有表达力.