垃圾代码重构-《代码整洁之道》-读后感

根据作者的说法,该书大致上分为3个部分。第一部分主要介绍编写整洁代码的原则、模式和实践。第二部分结合实际的例子教导我们如何使用这些原则。第三部分则重点介绍得到的启示与灵感。目前的话我应该是读完了第一部分以及第二部分的一半左右,但是仅仅这部分的内容感觉就已经获益匪浅,在此记录一下。

有意义的命名

这一点,在我不长的编程生涯中,体会不可谓不深。截取一段自己在大学阶段项目里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class LoginServiceImpl implements LoginService{

@Autowired
private UserMapper userDao;

@Autowired
private UserInfoMapper userInfoDao;

public UserDto login(String username, String password) {
// TODO Auto-generated method stub
UserDto userDto=new UserDto();
User user=userDao.LoginService(username, password);
UserInfo userInfo=null;
if(user!=null){
userInfo=userInfoDao.selectByUserName(user.getUserName());
userDto.setUserName(user.getUserName());
userDto.setRole(user.getRole());
userDto.setDepartment(userInfo.getDepartment());
userDto.setEmail(userInfo.getEmail());
userDto.setName(userInfo.getName());
userDto.setCreateTime(userInfo.getCreateTime());
userDto.setUserId(userInfo.getUserId());
return userDto;
}else{
return null;
}


}
}

这是自己当年上学时某个项目中的一个实现用户登录的逻辑,以现在的眼光或者说现在的经验来看,感觉命名糟糕的一踏糊涂。首先是类的命名,类的命名应该是一个名词,因为一个对象其方法才是具体的行为,在这里我用login作为命名一眼看过去就好像这整个类都在做登录这个动作,但实际上类代表的不是一个动作,这很不面向对象。其次是对象UserDto和userDao也意义不明,一眼看去根本不知道这个类到底是用来干嘛的。所以正如书中说的那样变量、函数或类的名称应该已经答复了所有的大问题。它应该告诉你,它为什么会存在,它做什么事情,应该怎么用
还有一点避免使用编码,没有理由要求每位新人都在弄清楚要应付的代码之外,还要再搞懂另一种编码“语言”。感觉自己刚刚进公司就常常犯这种错误,比如xxxList,xxxSet之类的带有鲜明语言特色的东西,其实对阅读代码的人毫无帮助反而徒增了很多负担。在看看下面的一个排序代码:

1
2
3
4
5
6
7
8
int main(void){
int array[5] ={4,2,1,4,5};
selectSort(array,5);
for(int i=0;i<5;j++){
printf("%d",array[i]);
}
return 0;
}

初学编程我们每个人应该都写过这样的排序代码,那在命名层面其有什么不妥呢?首先在代码开头定义的array变量就很意义不明,数组这一单词带有明显的编码色彩。其次是selectSort的函数命名,看起来是要排序,但是select是什么鬼,取出排序?对于函数命名应该言简意赅,意义明确,使用动词表达清其要做的事情,所以这里单纯用sort其实更好。最后是for循环的终止条件5,5这个数字在这段代码中表示什么?它在循环中表示什么的终止?显然这些问题仅仅靠单纯一个数字是没办法回答的。虽然这个程序现在很短小我们结合上下文阅读立马就可以反应过来,这个5是需要排序数组的大小,是待排数据的极值。但是如果程序变得很长很长呢,想象一下,你已经读了100来行代码了,现在又要求你回过头去结合上下文理解这个5的含义,任谁都会吐槽诅咒写的人吧。所以在编码中我们需要将这类意义不明的数字“5”换作一个具有意义的变量来表示其作用职责。
对于命名书中还涉及到了很多原则,在此就不做过多展开了,有兴趣可以去翻翻原作。由此看以看出,就单纯一项命名已经很考验编程功底了,所以千万不要胡乱命名给自己他人造成麻烦。

函数

短小,只做好一件事情。这是我对书中关于函数部分的最大印象。

  • 短小
    即函数尽量要短,其实这个准则应该最大的好处就是读起来很很简单吧。按照作者的话说就是,每个函数都依序把你带到下一个函数。这就是函数应该达到的短小程度!!!即当我们面对一个长长的函数时,应该对其进行拆分将里面过长的内容拆分成一个个的小函数。这样不仅仅读的人赏心悦目,修改起来也很方便。
  • 只做好一件事
    个人感觉这条是最难以把握的,因为往往分不清这件事是不是应该这个函数做还是需要单独拆分。比如getStudentInfoById这个函数,根据名称来看应该是通过id获取学生信息,听起来貌似只需要从数据库中拿出学生信息并返回就可以了,但是学生信息还需要各种处理,比如需要有顺序的返回,或者说某些信息需要转换后返回,这些操作看起来与拿这个动作无关,但是确实是属于这个函数的附属品,不可能在函数名称中体现到这么多操作。其实,只要确保函数中的内容都在同一抽象级就好了,刚才说的各种对信息的处理其实可以拆分成使用formatXXX之类的函数完成,那么我们这个getStudentInfoById的函数就一共分为了两步,第一步从数据库拿出学生信息,第二部对其进行格式化。格式化与从数据库拿出这两个操作均在函数的同一抽象层,所以这两个操作都可以看作是为了完成拿出学生准确信息这一操作。

判断函数是否不止做了一件事,就是看是否能再拆岀一个函数,该函数不仅只是单纯地重新诠释其实现

注释

这里我就记住一句话,唯一真正好的注释是你想办法不去写的注释
尽量用代码去解释函数、类的行为,带有少量注释的整洁而有表达力的代码,要比大量注释的零碎而复杂的代码像样的多。

格式

代码格式很重要,感觉作者再书中不断强调这一点。尤其是一个项目团队大家都应当按照相同的格式去编写代码,这样有利于维护代码。从上往下的垂直格式总是必要的,还有最外层的文件的抽象概念由外到里依次变得细节。
运算符之间加空格区分:

1
a = b + 2

不同的区块也应该分割下,函数开头一般都是变量声明初始化之类的,所以就应该在初始化变量完成后,多一个换行之类的与接下来的代码区分,以此利于阅读。总之不管用什么风格,同一个团队应该保持一致,这样在后续开发中项目整体才比较容易维护,不同人不同的风格就会造成巨大的混乱,最终导致源代码不可读,不可维护。

对象和数据结构

当功能点需要常常添加新的结构时使用面向对象,需要常常添加新的方法行为使用面向过程
所以说无论是面向对象或者是面向过程都是方法论,业务才是一切,没有永远适合的,只有当前适合的。
一切都是对象只是一个传说

错误处理

这个仁者见仁智者见智,我只想说书中关于只使用异常的做法以前我可能会很认同,但最近在使用了go语言之后,对于没有异常处理机制的语言来说只使用异常就不再可能了,所以说看待问题还是不能太死板,没有什么是一成不变的,也没有绝对的真理。

类应该短小,且遵循单一权责原则,即类或者模块应有且只有一条加以修改的理由。
类应该只有少量实体变量,类中的每个方法都应该操作一个或多个这种变量,即类要保持内聚性。
如果说有很多方法并没有黏聚到类上,又或者为了保持函数和参数列表短小的策略,有时会导致为一组子集方法所用的实体变量数量增加。出现这种情况,就要拆分岀新的类了。