走近专业程序员

0

Posted by conan | Posted in 读书笔记 | Posted on 19-03-2011

最近开始看李先静的《系统程序员成长计划》,算是对长久以来缺乏编码的一种补偿把。使用这本书顺便还为了养成编码的习惯,免得见了代码就头疼。养成一个习惯有三个阶段,第一个阶段是需要一直提醒自己,而且觉得很别扭,第二个阶段是需要一直提醒自己,但是已经比较习惯了,第三个阶段是不再需要提醒自己,而且习惯了。纵观以往的努力,中断于第二阶段的情况太多了,因为已经比较习惯了,所以总会去放松,不小心就中断了。这个时候中断太可惜了,既浪费了前面的努力,也浪费了来之不易的良好状态。这次引以为戒,坚决不能再次中断。
目前进度,两周,第一章结束,通用双向链表写了五遍,虽说还不能将双向链表烂熟于心,也算有了一定的经验了。

专业程序员最需要的是什么?是专业态度。

专业态度一:风格态度。良好的代码风格是严谨的一种表现,“傻瓜都能写出机器能读懂的代码,但只有专业程序员才能写出人能读懂的代码。”

稳定的代码风格:
文件名:单词小写,多个单词用下划线分隔。如:dlist.c
函数名:单词小写,多个单词用下划线分隔。如:node_find。一个函数只完成单一功能。一个函数最好在80*24的范围内完成,函数内的临时变量不要超过10个(此句来自linux kernel coding style)。
结构/枚举/联合名:下划线开头,首字母大写,多个单词连写。如:struct _DListNode;
宏名:单词大写,多个单词下划线分隔。如:#define MAX_PATH 260
变量名:单词小写,多个单词下划线分隔。如:DList *node = NULL(此处习惯*与node相连沿用于K&R C,表明*node是DList,这种写法有利于同时定义多个变量。)

面向对象的命名方式:
1.以对象为中心,采用主谓形式(对象+动作)来命名,取代传统的动宾形式,如:dlist_append
2.第一个参数为对象,并用thiz命名,如:dlist_append(DList *thiz, void *value)
3.对象有自己的声明周期,所以有对应的创建和销毁函数。

排版:
使用空行:1.函数体之间。2.结构/联合/枚举声明。3.不同功能的代码块之间。4.功能类似的代码放在一起,和其他部分用可能空行分隔。5.用一行就够了。
使用空格:1.等号两边。2.运算符两边。3.参数之间(for,函数等)
使用括号:
1.分隔子表达式,让人更容易看明白。如:((a && b) || (c && d))。
2.条件判断和循环,即使是一句也要加括号。如:

1
2
3
4
5
if (a > b) {
return c;
} else if (a == b) {
...
}

缩进:使用tab。缩进超过三层的话说明设计有问题

保护隐私:封装。可以隔离变化,降低复杂度。
隐藏数据结构:若为内部数据结构,则直接放在C文件中,不要放在头文件里。若为外部也要使用,则对外暴露名字而封装实现细节。做法为:
在头文件中声明该数据结构。
在C文件中定义该数据结构。
提供操作改数据结构的函数,避免直接访问其成员。
提供创建和销毁函数。
不过是否封装还是要看具体情况。
隐藏内部函数:在头文件中只放最少的接口函数声明,在C文件中所有内部函数都加上static关键字(将函数private化)。
禁用全局变量。

通用化
通用链表的实现方式有两种:
深拷贝,存值,以void *来保存数据首地址,size_t length来保存数据长度。进行深拷贝的时候使用memcpy。C语言没有构造函数,实现深拷贝比较复杂,且复制数据带来性能开销,不符合C语言的风格,较少见。
浅拷贝,存指针,只用void *保存对象的指针。存放整数时,可以把void *强制转换成整数使用。
(注:是否要进行深拷贝要根据实际情况来决定,在需要的时候,比如防火墙处理数据包,还是需要做深拷贝的。另外,据说公司前辈说自己实现的拷贝函数性能比memcpy好。)
让C++可以调用:在头文件中防止编译器对函数名重新编码,加入如下内容:

1
2
3
4
5
6
7
#ifdef __cplusplus
extern "C" {
#endif
/* add your code here */
#ifdef __cplusplus
}
#endif

通用链表面向特定数据的操作
面向特定数据的操作会很多,但是若将其一一实现的话会产生大量重复的代码。将遍历和对数据的操作分离开,使用函数指针将对数据的操作传入就是一个良好的实现框架,称为回调函数法。使用回调函数来进行数据操作的时候会产生一些中间数据,对此引入一个上下文context(缩写为ctx)的概念,用它来做参数负责传入传出数据和保存中间变量。框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 //定义函数指针别名
typedef STAT (*DListVisitFunc)(void *ctx, void *data);
//实现遍历
STAT dlist_foreach(DList *thiz, DListVisitFunc visit, void *ctx)
{
/* some code */
stat = visit(ctx, some_data);
/* some code */
}
//实际回调函数
static STAT sum_cb(void *ctx, void *data)
{
/* some code */
}
//调用方法
long long sum = 0;
stat = dlist_foreach(pdlist, sum_cb, &sum);

面向特定数据的操作不应该杂糅到通用链表中,否则会造成不必要的耦合度和复杂度。

Creative Commons License
This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

Write a comment