前言
程序本身就是逻辑的表示,程序第一服务对象是人,第二服务对象是机器。对于软件而言,可用性重要,可读性可扩展性也是一样。为了后续接手的人可以更容易通过代码理解所要实现的目的,就需要在编码时候更有规划地编码。
高质量的设计包括如下特征:
- 最小复杂度。做出简单且易于理解的设计。
- 易于维护。设计出逻辑自洽的代码。
- 富有层次,在任意层面观察系统,都可以理解这一层所要实现的功能。
本文章大部分内容参考自《代码大全》。
指针的使用
指针表示的是内存地址,而要解析这个地址所表示的内容,还需要指针的“基类型”,取决于指针的类型,可以是int的,也可以是char的,类型不同,解析出的数据也不同。
使用标记字段来检查内存改写。在一个结构体中,加入一个仅仅用于检测错误的字段。一旦这个字段与预期不相符,那么这个数据就被破坏了。对于网络的缓存buf,使用这个就很有效。多个应用会使用到缓存的buf,可能会发生异常改写。通过这个标记,即可发现数据被改写了,再在此刻回溯是哪个应用改写了内存。
简化复杂的指针。如果发现调用了复杂的只恨表达式,思考是否能够增加变量,让代码变得更容易理解。
难以理解的代码
for (rate_index =0; rate_index < num_rates; rate_index++) {
new_rate[rate_index] = base_rate[rate_index] * rates->discounts->factors->net;
}
易于理解的代码
quantity_discount = rates->discounts->factors->net;
for (rate_index =0; rate_index < num_rates; rate_index++) {
new_rate[rate_index] = base_rate[rate_index] * quantity_discount;
}
指针出错是很难调试,能难确定什么时候指针会变为非法的了。一种方式是在释放指针的时候给这个区域添加一些垃圾数据,即如果意外调用到的话可以看出问题。另外常用的就是free指针后设置为null。free后,还是有可能读取这个指针的数据,写入null会保证读取的时候能发现这个错误。
全局变量的使用
全局变量会使代码重用变得复杂。同时全局变量会使得代码管理变得复杂,在阅读模块代码的时候,需要考虑这个全局变量是否会在其他位置发生变化。因此在编码设计时候,首先将每个变量设置为局部的,只有万不得已的时候才设置为全局变量。
尽量使用子程序来访问全局数据,而不是直接访问。这会给代码带来更好的可读性,同时防止在代码编写过程修改到实现细节。
不合适的代码
node = node.next;
node = node.next;
node =node.next;
合适的代码
account = next_account(account);
employee = next_employee(employee);
rate_level = next_rate_level(rate_level);
判断语句的使用
对于顺序无关的代码,显式地表现出来。对于有依赖关系的数据,利用输入参数来显式依赖关系。
没有显示表示依赖关系
initialize_expense_data(expense_data);
computer_market_expense(expense_data);
computer_sales_expense(expense_data);
显式表示依赖关系
expense_data = initialize_expense_data(expense_data);
expense_data = computer_market_expense(expense_data);
expense_data = computer_sales_expense(expense_data);
对于判断语句,尽量将正常情况和出错情况隔离。先关注正常处理的流程,然后再根据每个正常流程的异常情况,理解异常逻辑的处理。
然而当出错情况过多的时候,会造成if嵌套过多,正常逻辑的判断位置和出错逻辑的判断间隔过远,对于代码理解也不是好事。可以选择先初始化判断完所有异常后,才进入正常逻辑。
case语句尽量把default当做检测错误的语句,而不是把default当成其他未考虑的情况。因为case语句在修订的时候,会引入一个新的情况,如果default确实是真正的默认情况,无需修订。如果是假的话,则需要将原来的default修改为普通的情况。
循环语句的使用
for循环是为了简单的用途,更复杂的循环最好用while去实现。for循环在初始化完数据后,不要在内部通过下标变更的方式让for循环终止。而且for循环需要关注下标,对于结束条件不那么清晰。
将循环看成是一个黑盒子,外围程序只关心输入参数、结束条件,不需要了解内部实现过程。为了这一目标,将初始化代码放在循环前面。让循环的入口、出口变得一目了然。
减少goto的使用,goto的使用会增加代码理解复杂度,且很容易出错,经常会出现,跳转到goto的语句前,有些逻辑没有考虑全
循环的退出要明显,让循环的终止条件看起来很明显,避免在for循环中出现依赖下标值的代码。同时小心有很多break散布的循环。
循环的编写应该是由内向外,从最小的、最简单的逻辑创建循环,再往外扩展。