举一个很有趣的例子,假如世界上只有两种烤鸭,在北京的叫做北京烤鸭,不在北京的叫做非北京烤鸭,写烂代码的程序员思路如下:
如果烤鸭不在地球,它一定不是北京烤鸭;
如果烤鸭不在陆地,它一定不是北京烤鸭;
如果烤鸭不在亚洲,它一定不是北京烤鸭;
如果烤鸭不在中国,它一定不是北京烤鸭;
如果烤鸭不在北京,它一定不是北京烤鸭;
否则,这是北京烤鸭。
伪代码如下:
if(!烤鸭在地球){
输出非北京烤鸭。
}else{
if(!烤鸭在陆地){
输出非北京烤鸭。
}else{
if(!烤鸭在亚洲){
输出非北京烤鸭。
}else{
if(!烤鸭在亚洲){
输出非北京烤鸭。
}else{
if(!烤鸭在中国){
输出非北京烤鸭。
}else{
if(!烤鸭在北京){
输出非北京烤鸭。
}else{
输出北京烤鸭。
}
}
}
}
}
}
好把,如果真有哪个程序员是这样写代码的,那么我真的很想一巴掌把它拍到火星去。不要这样嵌套好吗?就算你真的是这么想,也可以这样写:
if(!烤鸭在地球){
输出非北京烤鸭。
}else if(!烤鸭在陆地){
输出非北京烤鸭。
}else if(!烤鸭在亚洲){
输出非北京烤鸭。
}else if(!烤鸭在中国){
输出非北京烤鸭。
}else if(!烤鸭在北京){
输出非北京烤鸭。
}else{
输出北京烤鸭。
}
这个时候你可以发现原来前四个条件的内容都是相同的,那么可以这样写:
if(!烤鸭在地球
||!烤鸭在陆地
||!烤鸭在亚洲
||!烤鸭在中国
||!烤鸭在北京){
输出非北京烤鸭。
}else{
输出北京烤鸭。
}
当你发现前面if的条件过分臃肿的时候,可以考虑反过来会不会更容易
最终变成:
if(烤鸭在北京){
输出北京烤鸭。
}else{
输出非北京烤鸭。
}
可见,逻辑简化可弥补思维的不足。
这里有两个公式:
公式1:
if(条件1){
//做事情A
}else if(条件2){
//做事情A
}
可以转换为:
if(条件1 || 条件2){
//做事情A
}
if(条件1){
if(条件2){
//做事情A
}
}
可以转换为:
if(条件1 && 条件2){
//做事情A
}
如果条件太多,且实在不知道怎么合并,可以把条件单独放到一个函数:
public void doSomething(){
if(isRoastDuckNotInBeijing()){
输出非北京烤鸭。
}else{
输出北京烤鸭。
}
}
public boolean isRoastDuckNotInBeijing(){
return !烤鸭在地球
||!烤鸭在陆地
||!烤鸭在亚洲
||!烤鸭在中国
||!烤鸭在北京;
}
注意,如果条件可读性强:比如sex == “man”就不要把条件放到单独函数。
public void doSomething(){
if(firstNum!=0){
if(secondNum!=0){
//doSomething
}
}
}
如果改为:
public void doSomething(){
if(firstNum == 0)
return;
if(secondNum == 0)
return;
//doSomething
}
看起来好多了。
我是在上大学的时候学会写代码的。有一点解不开的疑惑:为什么要写函数?我把代码从头写到尾逻辑不是更加通顺吗?每个教代码的老师,总是在开始的时候给我们讲什么是变量,什么是函数,怎么用?我好想说一句:喂,我还没决定要用函数呢!
讲这部分之前,我先讲讲这个为什么要写函数,讲为什么要写函数前,我不得不提提二分查找法。
举个例子:
有十六张扑克牌按照编号从小的顺序排列,我让你找出编号排名第11的,你怎么找呢?
按照顺序一本本找,你要找11次,如果我找的是最后一张,需要找16次(当然这里我们不讨论倒着找)
使用二分查找:分成一半,前面是1-8,后面是9-16,然后再分成一半9-12和13-16,然后再分成一半9-10和11-12,然后我找到了。我找了4次。发现了吗?速度比顺序查找要快,而且我最多只需要找4次。当查找的范围越大,二分查找相对顺序查找的优势就越明显,平均速度也更快。
好了,懂了二分查找,我们再来弄清楚为什么要写函数
举个例子:假设我要写100行代码,伪代码如下:
public void 写100行代码(){
写1-50行代码();
写51-100行代码();
}
public void 写1-50行代码(){
写1-25行代码();
写26-50行代码();
}
public void 写51-100行代码(){
写51-75行代码();
写75-100行代码();
}
这样如果我找写第70行代码写什么,只要使用二分法的原理:先找写100行代码(),然后找写51-100行代码();以此类推。
所以我们写函数的目的就在此:把代码分割成小的部分,更容易查找。
这样做有个前提:就是你的函数名必须有意义并且表达准确,否则就更浪费时间,如上文的写1-50行代码()和写51-100行代码()函数名调换,内容不变,识别起来难度就会变大,你先找写1-50行代码(),发现它其实是写后面50行代码,这会让你在逻辑上纠结一段时间,所以再次强调:函数名一定要有意义。
另外还有一点,分割项目不要一次分的太细比如上面的例子,我也可以这样写(为了显示效果,我没写省略号,就一行行写了):
public void 写100行代码(){
写1-5行代码();
写6-10行代码();
写11-15行代码();
写16-20行代码();
写21-25行代码();
写26-30行代码();
写31-35行代码();
写35-40行代码();
写41-45行代码();
写46-50行代码();
写51-55行代码();
写56-60行代码();
写61-65行代码();
写66-70行代码();
写71-75行代码();
写76-80行代码();
写81-85行代码();
写86-90行代码();
写91-95行代码();
写96-100行代码();
}
发现了吗?这段代码的可读性明显变差了。所以一般情况下这种分割函数(里面每一句都调用其他函数),分割函数代码不要超过10句,最好不要超过5句。
和分割函数相对应的,还有一种我称作逻辑函数,这种函数都是做具体的工作,一般不要超过20行,如下:
public void 写1-5行代码(){
写第一行
写第二行
写第三行
写第四行
写第五行
}
特殊情况1:有一堆的参数赋值/传递:
比如录入个人信息:有身高,体重,爱好,优点,缺点,性别,父名,母名…这种可能会有很多数据,可能远远超过20行,那么,就把这部分代码单独分割:
public void outputUserInfo(){
System.out.println(getUserInfo());
}
public UserInfo getUserInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setHigh(10);
userInfo.setHeavy(1000);
userInfo.setHobby("泡妞");
userInfo.setAdvantage("很帅");
userInfo.setDisadvantage("我是世界上最帅的!");
userInfo.setFatherName("爸爸");
userInfo.setMotherName("妈妈");
//...此处省略万字
return userInfo;
}
大概是这样,其实真的无法分割吗?并没有这样说法。上面的内容可分为:身体信息,偏好信息和亲友信息,于是上面的一个UserInfo就可以分割为BodyInfo,LoveInfo和RelativesInfo,于是又可以保持在20行之内了
ヾ(◍°∇°◍)ノ゙
特殊情况2:逻辑分支,回调和异常处理
逻辑分支例子:
if(!烤鸭在地球){
输出非北京烤鸭。
输出当前地点。
}else if(!烤鸭在陆地){
输出非北京烤鸭。
输出当前地点。
}else if(!烤鸭在亚洲){
输出非北京烤鸭。
输出当前地点。
}else if(!烤鸭在中国){
输出非北京烤鸭。
输出当前地点。
}else if(!烤鸭在北京){
输出非北京烤鸭。
输出当前地点。
}else{
输出北京烤鸭。
}
回调例子:
public void clickButton(){
button.setOnClickListener(
new OnClickListener(){
@Overriade
public void onClick(View v){
输出当前地点。
输出北京烤鸭。
}
}
);
}
异常处理例子:
public void methodName(){
try{
输出北京烤鸭。
输出当前地点。
}catch(Exception e){
e.printStackTrace();
}
}
上面三种情况:选择分支,回调和异常处理都会导致代码臃肿,可读性变差,我通常都会把里面的部分单独列到新的:
第一种:
合成为:输出结果(boolean isRoastDuckInBeijing,String address)
因为条件分支往往好起名字,所以我通常会这样写
第二种:
clickButtonEvent(){}
因为我不能保证除了输出北京烤鸭信息外是否会做些别的什么,通常这部分的修改频率较高
第三种:
public void methodNameThrowException() throw Exception{}
通常我会在原名称后加上ThrowException提醒自己抛出异常,这样既显示了区别也降低层次,看起来更舒服了:
public void methodName(){
try{
methodNameThrowException();
}catch(Exception e){
e.printStackTrace();
}
}
public void methodNameThrowException() throw Exception{
输出北京烤鸭。
输出当前地点。
}
总结:
1.学会用if…else if代替多层次if…else
2.学会用逻辑运算符&&和||代替多层if和多分支if … else if
3.学会用return降低层次
4.函数参数要尽量少,太多的把它合成一个类
5.函数名要易读
6.函数要尽量短小
7.有多层次或多分支的,使用新的函数代替代码块。