代码书写过程中一些不好的习惯,也可能导致语法错误,代码丢失等问题,使得编码和维护的效率低下。下面几点虽然都是一些很细节的地方,但细节决定命运,编程中从细节处完善,养成好的习惯,也可以长久受益。
修改前手动备份
修改代码时经常碰到这种情况,对某模块做修改,改了一半发现原来构想错误,想退回原点,却不幸忘记刚才修改了哪些地方。辛苦大半天,回不到解放前。即使现在有代码管理系统,一些小规模的调试修改前还是应自己备份。
匹配符号成对写
C语言中一些符号如:{}, (), [], “”,/**/要求有始有终,保证前后匹配。如果按习惯从左到右顺序写,一些复杂嵌套表达式的语法检查就比较麻烦。好办法是先成对写好符号模板,再往里填内容。如表达式:if(((a > 100) && (a < 200 )) || ( b % 2 )),式子很长,嵌套层次多,检查括号匹配让人头晕,再复杂更不用说了。从左到右顺序写,写到右边后半部分还要不断回头数着要加几个另一半括号,写完还得检查,效率低又易出错。改为下面书写步骤:
1. if()
2. if(() || ())
3. if((()&&())||(b%2))
4. if(((a>100)&&(a<200))||(b%2))
这种方式写完后不用检查就一定正确。同样,匹配函数成对写。C中一些资源管理类函数需要成对出现,比如:malloc/free, init/uninit, fopen/fclose等,开始就写好也可以防止因忘记释放而产生资源泄漏。
先写;号
下面程序片段中:
struct x{
int a
}
f() { ... }
紧挨着函数f()前的}号后遗漏了;分号,结果就变成声明函数f(),返回值类型是struct x。如果f()前有分号,f()默认返回整型值,两种情况意义完全不同。为防止类似bug,定义struct时先完成包括;在内的模板,再写结构体内容。即:struct x {…… };//一次写全。
严格按模板,不省略{}和()
省略{}和()不是好习惯,多重if else或switch case语句中,省略{}后往往语法正确,但功能与预想相差很远,导致代码能通过编译器检查却引入了bug。如悬挂else:
if (x == 0)
if (y == 0) error();
else
{
z = x + y;
}
本意:x分为0和非0两种情况,x为0时,如果y也等0,调用函数error,否则不处理;x不等0时,把xy的和赋给z。而上面代码实现的功能与设想完全不同。C中else始终与最近的未匹配if结合,实际逻辑就成为:
if (x == 0) {
if (y == 0) error();
else { z = x + y; }
}
这里else和第二个if结合了,程序逻辑偏离原意,要体现原本意图,应:
if (x == 0) {
if (y == 0) error();
} else {
z = x + y;
}
这样第二个if被括号封装,else才会与第一个if结合。之前就少写个{},结果因小失大。江湖传言,类似错误曾导致NASA的一次航天事故。即使语法语义正确,省略{}还会增加代码修改时引入错误的风险,如:
if(cMychar <= ‘Z’)
printf(“This is a letter \n”);
else
printf(“This is not a letter \n”);
后来者觉得要在else后增加额外功能,于是顺手添加了一个函数调用,变成:
if(cMychar <= ‘Z’)
printf(“is a letter \n”);
else
printf(“not a letter \n”);
added_function();
受原代码误导,直接插入新增语句,误认为它们仍属同一模块。但缺少{}束缚,新增语句会把added_function();挤出else范围。错误根源还是省略了{}。
总之,按正规流程书写代码有助于在开始就把一些小问题消灭于无形之中。