聪明和规范

        最近维护了一个“腐烂”的驱动项目,虽然烂,但实现了功能,作为windows平台的驱动程序也通过了HCK,编译后的驱动放到微软的更新中,为世界各地购买了硬件的用户服务。查看源代码,我深深为作者的智商折服,到处是全局变量,在一个函数内改变值,在另一个函数内做判断;冗余的逻辑泛滥,随意的变量和函数命名,网上取得的代码片断,注释也不修改就引用;代码中经常突然用一对大括号括起来,然后开始定义变量实现功能。这样腐烂的结构,作者竟然能控制住正确的功能,以我的智商来看,简直是奇迹。我丝毫不会贬低原作者水平,反而有高山仰止的感觉,不论什么原因,搭建这样的架构完成任务,我是无法做到的。在程序员圈子里混了很久,我属于比较笨的类型,记忆力也不好(自认为不能算合格的程序员)。想象中的程序员,是那些一个周末就完成了某个浏览器框架开发,或者一个晚上就理解了一个协议,甚至轻松的进入别人的系统窥探秘密。学习新东西,我一般要经过无数的试错,才能掌握基本框架,遇到细节的时候还需要查资料,因为记不住那么多东西。如果遇到知识领域以外的问题,我会花费比别人更多的时间才能解决。工作中解决问题时没有任何一次是灵光一现,潇洒的解掉问题,总是历经无数次尝试才终于找到问题的起因。修改问题和寻找问题花费的时间不成比例。但是既然走上了这条路,不论什么原因,总要继续走下去,所以在长时间的工作中,我也总结了一些规则,以弥补缺陷,我称之为用规范的做法弥补智商的硬伤:-)。规则如下:

    1 函数和变量命名时,首先要长期遵守一种规则,除非找到了更好的规则才改变。具体来说,我现在遵守的规则是,函数名和变量名全部小写,不缩写,单词间用下划线分隔。这个规则在windows平台上和微软的参数定义稍微有点冲突,不过暂时无伤大雅。

    2 命名时候避免不必要的前缀,这样可以提高移植性。

        例如:驱动编程中的一个回调定义:ioQueueConfig.EvtIoRead = EvtIoRead;比起 ioQueueConfig.EvtIoRead = XXXEvtIoRead;的定义方式降低了耦合,提高了可移植性,因为驱动本身就叫XXX,这里再写一遍XXX没有任何意义。没有XXX前缀,写下一个驱动时,这些代码可以直接拷贝。

    3 对于代码中的常用功能,比如循环,二分法,IO操作等,使用的变量名要保持一致。比如:循环索引用idx,循环的最大值用count,寄存器用reg,寄存器的值用value,文件操作的读、写、控制句柄固定为handle_read, handle_write, handle_io_ctrl,按照这样的规则写好的代码不仅有利于编码和阅读,并且方便移植,因为这些代码不大可能和业务代码冲突。按照命名规则,业务变量也不会和这些常用变量重名。

    4 避免使用直接量,而应该使用常量。

        在IO操作中经常会有位操作,如果用直接量,比如 value &= 0x8000000FF或者 value = 0x00000001,相信过段时间,就看不懂这些代码的业务意义了。如果改为 value &= START_DMA_WRITE和value=SET_DMA_WRITE_ENABLE业务就很清晰了。

    5 函数定义要符合业务意义,避免不恰当的参数类型和返回值。这里不恰当的参数类型,包括不正确的类型,或者应该传递整个结构体还是传递结构体中的一个变量,甚至包括参数的顺序。返回值应该放到参数中返回还是作为函数的返回值返回。

以下是从某个项目中摘取的一段代码,作为说明。

原始代码:

VOID spi_write_byte(IN PFDO_DATA fdoData, UINT8 value)
{
    UINT32 regValue;

	regValue = 0x80000000 | value;
	WriteRegisterUlong(&fdoData->Reg->FLASH_ADDR_CONTROL, regValue);

	while (1) {
		regValue = ReadRegisterUlong(&fdoData->Reg->FLASH_ADDR_CONTROL);
		if(regValue & 0x40000000)
		{
			break;
		}
	}
}
按照以上的原则修改后的代码:

//
// 向FLASH写入一个字节
//
VOID write_byte_to_flash(
	IN PULONG reg,
	IN UINT8 data
)
{
	FLASH_ADDR_CONTROL control;
	
	// 清零
	control.value = 0;
	// 设置启动写标记
	control.u.write_start = 1;
	// 设置写数据
	control.u.data = data;
	WriteRegisterUlong(reg, reg_value);
    
   	// 等待写完成标记
    do {
    	reg_value = ReadRegisterUlong(reg);
	} while (reg_value.u.write_done == 0);
}

可以看出,修改后的代码可移植和可维护性都有提升,当然,实际工程中0和1这样的常数也会定义为常量。这样的代码,过几个月后其他人阅读仍然很清晰,移植到其他平台也没有什么问题(旧代码的fdoData参数是windows平台的特定参数,其实函数的本意是需要访问一个寄存器,这里随意引入一个大范围的变量提高了耦合性,降低了可移植性)。当然,这段代码移植到不同的平台以后,考虑到cpu的不同,例如x86和powerpc的字节序和位序不同,FLASH_ADDR_CONTROL的定义会用宏来控制。

    对于参数类型,还要注意随意扩大参数类型可能引入BUG。这也是C语言开发中需要注意的基本点之一。

    例如:对于参数类型该使用无符号还是有符号,似乎很多人不会关注,随手一写,如果遇到编译器警告,就强制转换类型,实际上,任何时候,平台类型是首选,除非平台类型的存储范围不能满足需求,才会使用无符号类型。强制类型转换往往会滋生BUG,malloc例程现在的返回值是void ×,早已不需要强制类型转换了,但是我们顺手一写,仍然是(int *)malloc(...)。最常见的,参数中表示长度的类型会被定义为无符号的,这样就失去了对传入负值做检查的机会,因为传入负值会变成很大的正值。不恰当的类型选择往往会在边界测试中被命中,在正常测试中不会发现问题,所以这种类型的BUG多在产品中出现。

    其实,这些规则早就有了,UNIX中有一句话,降低耦合,避免二义性;我自己总结为一句话,冗余逻辑是滋生BUG的温床。最后,运用以上原则时,如果实在不能决定怎样写,就参考windows或linux的源代码,这些都是工程代码,大概率而言是不会错的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值