华为编程规范化

1 排版
¹1-1:程序块要采用缩进风格编写,缩进的空格数为4个。
说明:对于由开发工具自动生成的代码可以有不一致。
¹1-2:相对独立的程序块之间、变量说明之后必须加空行。
示例:如下例子不符合规范。
if (!valid_ni(ni))
{
     ... // program code
}
repssn_ind = ssn_data[index].repssn_index;
repssn_ni   = ssn_data[index].ni;
应如下书写
if (!valid_ni(ni))
{
     ... // program code
}

repssn_ind = ssn_data[index].repssn_index;
repssn_ni   = ssn_data[index].ni;
¹1-3:较长的语句(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
                           + STAT_SIZE_PER_FRAM * sizeof( _UL );

act_task_table[frame_id * STAT_TASK_CHECK_NUMBER + index].occupied
               = stat_poi[index].occupied;

act_task_table[taskno].duration_true_or_false
               = SYS_get_sccp_statistic_state( stat_item );

report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
                       && (n7stat_stat_item_valid (stat_item))
                       && (act_task_table[taskno].result_data != 0));
¹1-4:循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操作符处划分新行,操作符放在新行之首。
示例:
if ((taskno < max_act_task_number)
     && (n7stat_stat_item_valid (stat_item)))
{
     ... // program code
}

for (i = 0, j = 0; (i < BufferKeyword[word_index].word_length)
                     && (j < NewKeyword.word_length); i++, j++)
{
     ... // program code
}

for (i = 0, j = 0;  
      (i < first_word_length) && (j < second_word_length);  
      i++, j++)
{
     ... // program code
}
¹1-5:若函数或过程中的参数较长,则要进行适当的划分。
示例:
n7stat_str_compare((BYTE *) & stat_object,
                    (BYTE *) & (act_task_table[taskno].stat_object),
                    sizeof (_STAT_OBJECT));

n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER
                                       + index, stat_object );
¹1-6:不允许把多个短语句写在一行中,即一行只写一条语句。
示例:如下例子不符合规范。
rect.length = 0;   rect.width = 0;

应如下书写
rect.length = 0;
rect.width   = 0;
¹1-7:if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加括号{}。
示例:如下例子不符合规范。
if (pUserCR == NULL) return;
应如下书写:
if (pUserCR == NULL)
{
     return;
}
¹1-8:对齐只使用空格键,不使用TAB键。
说明:以免用不同的编辑器阅读程序时,因TAB键所设置的空格数目不同而造成程序布局不整齐,不要使用BC作为编辑器合版本,因为BC会自动将8个空格变为一个TAB键,因此使用BC合入的版本大多会将缩进变乱。
¹1-9:函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case语句下的情况处理语句也要遵从语句缩进要求。
¹1-10:程序块的分界符(如C/C++语言的大括号‘{’和‘}’)应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。
示例:如下例子不符合规范。
for (...) {
     ... // program code
}

if (...)
     {
     ... // program code
     }

void example_fun( void )
     {
     ... // program code
     }

应如下书写。
for (...)
{
     ... // program code
}

if (...)
{
     ... // program code
}

void example_fun( void )
{
     ... // program code
}
¹1-11:在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
说明:采用这种松散方式编写代码的目的是使代码更加清晰。
由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如果语句已足够清晰则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在C/C++语言中括号已经是最清晰的标志了。
在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。
示例:
(1) 逗号、分号只在后面加空格。
int a, b, c;  
(2)比较操作符, 赋值操作符"="、 "+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位域操作符"<<"、"^"等双目操作符的前后加空格。
if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
(3)"!"、"~"、"++"、"--"、"&"(地址运算符)等单目操作符前后不加空格。
*p = 'a';         // 内容操作"*"与内容之间
flag = !isEmpty; // 非操作"!"与内容之间
p = &mem;         // 地址操作"&" 与内容之间
i++;              // "++","--"与内容之间
(4)"->"、"."前后不加空格。
p->id = pid;      // "->"指针前后不加空格
(5) if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。
if (a >= b && c > d)
½1-1:一行程序以小于80字符为宜,不要写得过长。  
2 注释
¹2-1:一般情况下,源程序有效注释量必须在20%以上。
说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。
¹2-2:说明性文件(如头文件.h文件、.inc文件、.def文件、编译说明文件.cfg等)头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
示例:下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。
/*************************************************
   Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
   File name:       // 文件名
   Author:        Version:         Date: // 作者、版本及完成日期
   Description:     // 用于详细说明此程序文件完成的主要功能,与其他模块
                   // 或函数的接口,输出值、取值范围、含义及参数间的控
                   // 制、顺序、独立或依赖等关系
   Others:          // 其它内容的说明
   Function List:   // 主要函数列表,每条记录应包括函数名及功能简要说明
     1. ....
   History:         // 修改历史记录列表,每条修改记录应包括修改日期、修改
                   // 者及修改内容简述  
     1. Date:
        Author:
        Modification:
     2. ...
*************************************************/
¹2-3:源文件头部应进行注释,列出:版权说明、版本号、生成日期、作者、模块目的/功能、主要函数及其功能、修改日志等。
示例:下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。
/************************************************************
   Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
   FileName: test.cpp
   Author:         Version :           Date:
   Description:      // 模块描述      
   Version:          // 版本信息
   Function List:    // 主要函数及其功能
     1. -------
   History:          // 历史修改记录
         


7 可测性
¹7-1:在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。
说明:本规则是针对项目组或产品组的。
¹7-2:在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少要有所在模块名(或源文件名)及行号。
说明:统一的调测信息格式便于集成测试。
¹7-3:编程的同时要为单元测试选择恰当的测试点,并仔细构造测试代码、测试用例,同时给出明确的注释说明。测试代码部分应作为(模块中的)一个子模块,以方便测试代码在模块中的安装与拆卸(通过调测开关)。
说明:为单元测试而准备。
¹7-4:在进行集成测试/系统联调之前,要构造好测试环境、测试项目及测试用例,同时仔细分析并优化测试用例,以提高测试效率。
说明:好的测试用例应尽可能模拟出程序所遇到的边界值、各种复杂环境及一些极端情况等。
¹7-5:使用断言来发现软件问题,提高代码可测性。
说明:断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。
示例:下面是C语言中的一个断言,用宏来设计的。(其中NULL为0L)
#ifdef _EXAM_ASSERT_TEST_   // 若使用断言测试

void exam_assert( char * file_name, unsigned int line_no )
{
     printf( "/n[EXAM]Assert failed: %s, line %u/n",
             file_name, line_no );
     abort( );
}

#define   EXAM_ASSERT( condition )
     if (condition) // 若条件成立,则无动作
         NULL;
     else   // 否则报告
         exam_assert( __FILE__, __LINE__ )

#else   // 若不使用断言测试

#define EXAM_ASSERT(condition)   NULL

#endif   /* end of ASSERT */
¹7-6:用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况。
¹7-7:不能用断言来检查最终产品肯定会出现且必须处理的错误情况。
说明:断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。
¹7-8:对较复杂的断言加上明确的注释。
说明:为复杂的断言加注释,可澄清断言含义并减少不必要的误用。
¹7-9:用断言确认函数的参数。
示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,如下。
int exam_fun( unsigned char *str )
{
     EXAM_ASSERT( str != NULL );   // 用断言检查“假设指针不为空”这个条件
    
     ... //other program code
}
¹7-10:用断言保证没有定义的特性或功能不被使用。
示例:假设某通信模块在设计时,准备提供“无连接”和“连接” 这两种业务。但当前的版本中仅实现了“无连接”业务,且在此版本的正式发行版中,用户(上层模块)不应产生“连接”业务的请求,那么在测试时可用断言检查用户是否使用“连接”业务。如下。
#define EXAM_CONNECTIONLESS 0 // 无连接业务
#define EXAM_CONNECTION      1 // 连接业务

int msg_process( EXAM_MESSAGE *msg )
{
     unsigned char service; /* message service class */

     EXAM_ASSERT( msg != NULL );

service = get_msg_service_class( msg );

     EXAM_ASSERT( service != EXAM_CONNECTION ); // 假设不使用连接业务

     ...   //other program code
}
¹7-11:用断言对程序开发环境(OS/Compiler/Hardware)的假设进行检查。
说明:程序运行时所需的软硬件环境及配置要求,不能用断言来检查,而必须由一段专门代码处理。用断言仅可对程序开发环境中的假设及所配置的某版本软硬件是否具有某种功能的假设进行检查。如某网卡是否在系统运行环境中配置了,应由程序中正式代码来检查;而此网卡是否具有某设想的功能,则可由断言来检查。
对编译器提供的功能及特性假设可用断言检查,原因是软件最终产品(即运行代码或机器码)与编译器已没有任何直接关系,即软件运行过程中(注意不是编译过程中)不会也不应该对编译器的功能提出任何需求。
示例:用断言检查编译器的int型数据占用的内存空间是否为2,如下。
EXAM_ASSERT( sizeof( int ) == 2 );
¹7-12:正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉)。
说明:加快软件运行速度。
¹7-13:在软件系统中设置与取消有关测试手段,不能对软件实现的功能等产生影响。
说明:即有测试代码的软件和关掉测试代码的软件,在功能行为上应一致。
¹7-14:用调测开关来切换软件的DEBUG版和正式版,而不要同时存在正式版本和DEBUG版本的不同源文件,以减少维护的难度。
¹7-15:软件的DEBUG版本和发行版本应该统一维护,不允许分家,并且要时刻注意保证两个版本在实现功能上的一致性。
½7-1:在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码如打印函数等。
说明:程序的调试与测试是软件生存周期中很重要的一个阶段,如何对软件进行较全面、高率的测试并尽可能地找出软件中的错误就成为很关键的问题。因此在编写源代码之前,除了要有一套比较完善的测试计划外,还应设计出一系列代码测试手段,为单元测试、集成测试及系统联调提供方便。
½7-2:调测开关应分为不同级别和类型。
说明:调测开关的设置及分类应从以下几方面考虑:针对模块或系统某部分代码的调测;针对模块或系统某功能的调测;出于某种其它目的,如对性能、容量等的测试。这样做便于软件功能的调测,并且便于模块的单元测试、系统联调等。
½7-3:编写防错程序,然后在处理错误之后可用断言宣布发生错误。
示例:假如某模块收到通信链路上的消息,则应对消息的合法性进行检查,若消息类别不是通信协议中规定的,则应进行出错处理,之后可用断言报告,如下例。
#ifdef _EXAM_ASSERT_TEST_ // 若使用断言测试

/* Notice: this function does not call 'abort' to exit program */
void assert_report( char * file_name, unsigned int line_no )
{
     printf( "/n[EXAM]Error Report: %s, line %u/n",
             file_name, line_no );
}

#define   ASSERT_REPORT( condition )
     if ( condition ) // 若条件成立,则无动作
         NULL;
     else // 否则报告
         assert_report ( __FILE__, __LINE__ )

#else // 若不使用断言测试

#define ASSERT_REPORT( condition )   NULL

#endif /* end of ASSERT */

int msg_handle( unsigned char msg_name, unsigned char * msg )
{
     switch( msg_name )
     {
         case MSG_ONE:
             ... // 消息MSG_ONE处理
             return MSG_HANDLE_SUCCESS;
    
             ... // 其它合法消息处理
    
         default:
             ... // 消息出错处理
             ASSERT_REPORT( FALSE );   // “合法”消息不成立,报告
             return MSG_HANDLE_ERROR;
     }
}
8 程序效率
¹8-1:编程时要经常注意代码的效率。
说明:代码效率分为全局效率、局部效率、时间效率及空间效率。全局效率是站在整个系统的角度上的系统效率;局部效率是站在模块或函数角度上的效率;时间效率是程序处理输入任务所需的时间长短;空间效率是程序所需内存空间,如机器代码空间大小、数据空间大小、栈空间大小等。
¹8-2:在保证软件系统的正确性、稳定性、可读性及可测性的前提下,提高代码效率。
说明:不能一味地追求代码效率,而对软件的正确性、稳定性、可读性及可测性造成影响。
¹8-3:局部效率应为全局效率服务,不能因为提高局部效率而对全局效率造成影响。
¹8-4:通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。
说明:这种方式是解决软件空间效率的根本办法。
示例:如下记录学生学习成绩的结构不合理。
typedef unsigned char   BYTE;
typedef unsigned short WORD;

typedef struct STUDENT_SCORE_STRU


     BYTE name[8];
     BYTE age;
     BYTE sex;
     BYTE class;
     BYTE subject;
     float score;
} STUDENT_SCORE;

因为每位学生都有多科学习成绩,故如上结构将占用较大空间。应如下改进(分为两个结构),总的存贮空间将变小,操作也变得更方便。
typedef struct STUDENT_STRU
{
     BYTE name[8];
     BYTE age;
     BYTE sex;
     BYTE class;
} STUDENT;

typedef struct STUDENT_SCORE_STRU
{
     WORD student_index;
     BYTE subject;
     float score;
} STUDENT_SCORE;
¹8-5:循环体内工作量最小化。
说明:应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。
示例:如下代码效率不高。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
     sum += ind;
     back_sum = sum; /* backup sum */
}

语句“back_sum = sum;”完全可以放在for语句之后,如下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
     sum += ind;
}
back_sum   = sum; /* backup sum */
½8-1:仔细分析有关算法,并进行优化。
½8-2:仔细考查、分析系统及模块处理输入(如事务、消息等)的方式,并加以改进。
½8-3:对模块中函数的划分及组织方式进行分析、优化,改进模块中函数的组织结构,提高程序效率。
说明:软件系统的效率主要与算法、处理任务方式、系统功能及函数结构有很大关系,仅在代码上下功夫一般不能解决根本问题。
½8-4:编程时,要随时留心代码效率;优化代码时,要考虑周全。
½8-5:不应花过多的时间拼命地提高调用不很频繁的函数代码效率。
说明:对代码优化可提高效率,但若考虑不周很有可能引起严重后果。
½8-6:要仔细地构造或直接用汇编编写调用频繁或性能要求极高的函数。
说明:只有对编译系统产生机器码的方式以及硬件系统较为熟悉时,才可使用汇编嵌入方式。嵌入汇编可提高时间及空间效率,但也存在一定风险。
½8-7:在保证程序质量的前提下,通过压缩代码量、去掉不必要代码以及减少不必要的局部和全局变量,来提高空间效率。
说明:这种方式对提高空间效率可起到一定作用,但往往不能解决根本问题。
½8-8:在多重循环中,应将最忙的循环放在最内层。
说明:减少CPU切入循环层的次数。
示例:如下代码效率不高。
for (row = 0; row < 100; row++)
{
     for (col = 0; col < 5; col++)
     {
         sum += a[row][col];
     }
}

可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)
{
     for (row = 0; row < 100; row++)
     {
         sum += a[row][col];
     }
}
½8-9:尽量减少循环嵌套层次。
½8-10:避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。
说明:目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。
示例:如下代码效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
     if (data_type == RECT_AREA)
     {
         area_sum += rect_area[ind];
     }
     else
     {
         rect_length_sum += rect[ind].length;
         rect_width_sum += rect[ind].width;
     }
}

因为判断语句与循环变量无关,故可如下改进,以减少判断次数。
if (data_type == RECT_AREA)
{
     for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
     {
         area_sum += rect_area[ind];
     }
}
else
{
     for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
     {
         rect_length_sum += rect[ind].length;
         rect_width_sum   += rect[ind].width;
     }
}
½8-11:尽量用乘法或其它方法代替除法,特别是浮点运算中的除法。
说明:浮点运算除法要占用较多CPU资源。
示例:如下表达式运算可能要占较多CPU资源。
#define PAI 3.1416
radius = circle_length / (2 * PAI);

应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数
radius = circle_length * PAI_RECIPROCAL / 2;
½8-12:不要一味追求紧凑的代码。
说明:因为紧凑的代码并不代表高效的机器码。
9 质量保证
¹9-1:在软件设计过程中构筑软件质量。
¹9-2:代码质量保证优先原则
      (1)正确性,指程序要实现设计要求的功能。
      (2)稳定性、安全性,指程序稳定、可靠、安全。
      (3)可测试性,指程序要具有良好的可测试性。
      (4)规范/可读性,指程序书写风格、命名规则等要符合规范。
      (5)全局效率,指软件系统的整体效率。
      (6)局部效率,指某个模块/子模块/函数的本身效率。
      (7)个人表达方式/个人方便性,指个人编程习惯。
¹9-3:只引用属于自己的存贮空间。
说明:若模块封装的较好,那么一般不会发生非法引用他人的空间。
¹9-4:防止引用已经释放的内存空间。
说明:在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块(如C语言指针),而另一模块在随后的某个时刻又使用了它。要防止这种情况发生。
¹9-5:过程/函数中分配的内存,在过程/函数退出之前要释放。
¹9-6:过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出之前要关闭。
说明:分配的内存不释放以及文件句柄不关闭,是较常见的错误,而且稍不注意就有可能发生。这类错误往往会引起很严重后果,且难以定位。
示例:下函数在退出之前,没有把分配的内存释放。
typedef unsigned char BYTE;

int example_fun( BYTE gt_len, BYTE *gt_code )
{
     BYTE *gt_buf;

     gt_buf = (BYTE *) malloc (MAX_GT_LENGTH);
     ...   //program code, include check gt_buf if or not NULL.
    
     /* global title length error */
     if (gt_len > MAX_GT_LENGTH)
     {
         return GT_LENGTH_ERROR; // 忘了释放gt_buf
     }
    
     ...   // other program code
}

应改为如下。
int example_fun( BYTE gt_len, BYTE *gt_code )
{
     BYTE *gt_buf;

     gt_buf = (BYTE * ) malloc ( MAX_GT_LENGTH );
     ...   // program code, include check gt_buf if or not NULL.
    
     /* global title length error */
     if (gt_len > MAX_GT_LENGTH)
     {
         free( gt_buf   ); // 退出之前释放gt_buf
         return GT_LENGTH_ERROR;  
     }
    
     ...   // other program code
}
¹9-7:防止内存操作越界。
说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。
示例:假设某软件系统最多可由10个用户同时使用,用户号为1-10,那么如下程序存在问题。
#define MAX_USR_NUM 10
unsigned char usr_login_flg[MAX_USR_NUM]= "";

void set_usr_login_flg( unsigned char usr_no )
{
     if (!usr_login_flg[usr_no])
     {
         usr_login_flg[usr_no]= TRUE;
     }
}

当usr_no为10时,将使用usr_login_flg越界。可采用如下方式解决。
void set_usr_login_flg( unsigned char usr_no )
{
     if (!usr_login_flg[usr_no - 1])
     {
         usr_login_flg[usr_no - 1]= TRUE;
     }
}
¹9-8:认真处理程序所能遇到的各种出错情况。
¹9-9:系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。
¹9-10:系统运行之初,要对加载到系统中的数据进行一致性检查。
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。
¹9-11:严禁随意更改其它模块或系统的有关设置和配置。
说明:编程时,不能随心所欲地更改不属于自己模块的有关设置如常量、数组的大小等。
¹9-12:不能随意改变与其它模块的接口。
¹9-13:充分了解系统的接口之后,再使用系统提供的功能。
示例:在B型机的各模块与操作系统的接口函数中,有一个要由各模块负责编写的初始化过程,此过程在软件系统加载完成后,由操作系统发送的初始化消息来调度。因此就涉及到初始化消息的类型与消息发送的顺序问题,特别是消息顺序,若没搞清楚就开始编程,很容易引起严重后果。以下示例引自B型曾出现过的实际代码,其中使用了FID_FETCH_DATA与FID_INITIAL初始化消息类型,注意B型机的系统是在FID_FETCH_DATA之前发送FID_INITIAL的。

MID alarm_module_list[MAX_ALARM_MID];

int FAR SYS_ALARM_proc( FID function_id, int handle )
{
     _UI i, j;

     switch ( function_id )
     {
         ... // program code
    
         case FID_INITAIL:
             for (i = 0; i < MAX_ALARM_MID; i++)
             {
                 if (alarm_module_list[i]== BAM_MODULE // **)
                    || (alarm_module_list[i]== LOCAL_MODULE)
                 {

                     for (j = 0; j < ALARM_CLASS_SUM; j++)
                     {
                         FAR_MALLOC( ... );
                     }
                 }
             }

             ... // program code

             break;
    
         case FID_FETCH_DATA:

             ... // program code

             Get_Alarm_Module( );   // 初始化alarm_module_list

             break;
    
         ... // program code
     }
}

由于FID_INITIAL是在FID_FETCH_DATA之前执行的,而初始化alarm_module_list是在FID_FETCH_DATA中进行的,故在FID_INITIAL中(**)处引用alarm_module_list变量时,它还没有被初始化。这是个严重错误。
应如下改正:要么把Get_Alarm_Module函数放在FID_INITIAL中(**)之前;要么就必须考虑(**)处的判断语句是否可以用(不使用alarm_module_list变量的)其它方式替代,或者是否可以取消此判断语句。
¹9-14:编程时,要防止差1错误。
说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后,应对这些操作符进行彻底检查。
¹9-15:要时刻注意易混淆的操作符。当编完程序后,应从头至尾检查一遍这些操作符,以防止拼写错误。
说明:形式相近的操作符最容易引起误用,如C/C++中的“=”与“==”、“|”与“||”、“&”与“&&”等,若拼写错了,编译器不一定能够检查出来。
示例:如把“&”写成“&&”,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);  
被写为:
ret_flg = (pmsg->ret_flg && RETURN_MASK);

rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被写为:
rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
¹9-16:有可能的话,if语句尽量加上else分支,对没有else分支的语句要小心对待;switch语句必须有default分支。
¹9-17:Unix下,多线程的中的子线程退出必需采用主动退出方式,即子线程应return出口。
¹9-18:不要滥用goto语句。
说明:goto语句会破坏程序的结构性,所以除非确实需要,最好不使用goto语句。
½9-1:不使用与硬件或操作系统关系很大的语句,而使用建议的标准语句,以提高软件的可移植性和可重用性。
½9-2:除非为了满足特殊需求,避免使用嵌入式汇编。
说明:程序中嵌入式汇编,一般都对可移植性有较大的影响。
½9-3:精心地构造、划分子模块,并按“接口”部分及“内核”部分合理地组织子模块,以提高“内核”部分的可移植性和可重用性。
说明:对不同产品中的某个功能相同的模块,若能做到其内核部分完全或基本一致,那么无论对产品的测试、维护,还是对以后产品的升级都会有很大帮助。
½9-4:精心构造算法,并对其性能、效率进行测试。
½9-5:对较关键的算法最好使用其它算法来确认。
½9-6:时刻注意表达式是否会上溢、下溢。
示例:如下程序将造成变量下溢。
unsigned char size ;
while (size-- >= 0) // 将出现下溢
{
     ... // program code
}

当size等于0时,再减1不会小于0,而是0xFF,故程序是一个死循环。应如下修改。
char size; // 从unsigned char 改为char
while (size-- >= 0)
{
     ... // program code
}
½9-7:使用变量时要注意其边界值的情况。
示例:如C语言中字符型变量,有效值范围为-128到127。故以下表达式的计算存在一定风险。
char chr = 127;
int sum = 200;

chr += 1; // 127为chr的边界值,再加1将使chr上溢到-128,而不是128。
sum += chr; // 故sum的结果不是328,而是72。

若chr与sum为同一种类型,或表达式按如下方式书写,可能会好些。
sum = sum + chr + 1;
½9-8:留心程序机器码大小(如指令空间大小、数据空间大小、堆栈空间大小等)是否超出系统有关限制。
½9-9:为用户提供良好的接口界面,使用户能较充分地了解系统内部运行状态及有关系统出错情况。
½9-10:系统应具有一定的容错能力,对一些错误事件(如用户误操作等)能进行自动补救。
½9-11:对一些具有危险性的操作代码(如写硬盘、删数据等)要仔细考虑,防止对数据、硬件等的安全构成危害,以提高系统的安全性。
½9-12:使用第三方提供的软件开发工具包或控件时,要注意以下几点:
(1)充分了解应用接口、使用环境及使用时注意事项。
(2)不能过分相信其正确性。
(3)除非必要,不要使用不熟悉的第三方工具包与控件。
说明:使用工具包与控件,可加快程序开发速度,节省时间,但使用之前一定对它有较充分的了解,同时第三方工具包与控件也有可能存在问题。
½9-13:资源文件(多语言版本支持),如果资源是对语言敏感的,应让该资源与源代码文件脱离,具体方法有下面几种:使用单独的资源文件、DLL文件或其它单独的描述文件(如数据库格式)
10 代码编辑、编译、审查
¹10-1:打开编译器的所有告警开关对程序进行编译。
¹10-2:在产品软件(项目组)中,要统一编译开关选项。
¹10-3:通过代码走读及审查方式对代码进行检查。
说明:代码走读主要是对程序的编程风格如注释、命名等以及编程时易出错的内容进行检查,可由开发人员自己或开发人员交叉的方式进行;代码审查主要是对程序实现的功能及程序的稳定性、安全性、可靠性等进行检查及评审,可通过自审、交叉审核或指定部门抽查等方式进行。
¹10-4:测试部测试产品之前,应对代码进行抽查及评审。
½10-1:编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。
½10-2:同产品软件(项目组)内,最好使用相同的编辑器,并使用相同的设置选项。
说明:同一项目组最好采用相同的智能语言编辑器,如Muiti Editor,Visual Editor等,并设计、使用一套缩进宏及注释宏等,将缩进等问题交由编辑器处理。
½10-3:要小心地使用编辑器提供的块拷贝功能编程。
说明:当某段代码与另一段代码的处理功能相似时,许多开发人员都用编辑器提供的块拷贝功能来完成这段代码的编写。由于程序功能相近,故所使用的变量、采用的表达式等在功能及命名上可能都很相近,所以使用块拷贝时要注意,除了修改相应的程序外,一定要把使用的每个变量仔细查看一遍,以改成正确的。不应指望编译器能查出所有这种错误,比如当使用的是全局变量时,就有可能使某种错误隐藏下来。
½10-4:合理地设计软件系统目录,方便开发人员使用。
说明:方便、合理的软件系统目录,可提高工作效率。目录构造的原则是方便有关源程序的存储、查询、编译、链接等工作,同时目录中还应具有工作目录----所有的编译、链接等工作应在此目录中进行,工具目录----有关文件编辑器、文件查找等工具可存放在此目录中。
½10-5:某些语句经编译后产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息。
说明:在Borland C/C++中,可用“#pragma   warn”来关掉或打开某些告警。
示例:
#pragma warn -rvl // 关闭告警
int examples_fun( void )
{
       // 程序,但无return语句。
}
#pragma warn +rvl // 打开告警
编译函数examples_fun时本应产生“函数应有返回值”告警,但由于关掉了此告警信息显示,所以编译时将不会产生此告警提示。
½10-6:使用代码检查工具(如C语言用PC-Lint)对源程序检查。
½10-7:使用软件工具(如 LogiSCOPE)进行代码审查。
11 代码测试、维护
¹11-1:单元测试要求至少达到语句覆盖。
¹11-2:单元测试开始要跟踪每一条语句,并观察数据流及变量的变化。
¹11-3:清理、整理或优化后的代码要经过审查及测试。
¹11-4:代码版本升级要经过严格测试。
¹11-5:使用工具软件对代码版本进行维护。
¹11-6:正式版本上软件的任何修改都应有详细的文档记录。
½11-1:发现错误立即修改,并且要记录下来。
½11-2:关键的代码在汇编级跟踪。
½11-3:仔细设计并分析测试用例,使测试用例覆盖尽可能多的情况,以提高测试用例的效率。
½11-4:尽可能模拟出程序的各种出错情况,对出错处理代码进行充分的测试。
½11-5:仔细测试代码处理数据、变量的边界情况。
½11-6:保留测试信息,以便分析、总结经验及进行更充分的测试。
½11-7:不应通过“试”来解决问题,应寻找问题的根本原因。
½11-8:对自动消失的错误进行分析,搞清楚错误是如何消失的。
½11-9:修改错误不仅要治表,更要治本。
½11-10:测试时应设法使很少发生的事件经常发生。
½11-11:明确模块或函数处理哪些事件,并使它们经常发生。
½11-12: 坚持在编码阶段就对代码进行彻底的单元测试,不要等以后的测试工作来发现问题。
½11-13:去除代码运行的随机性(如去掉无用的数据、代码及尽可能防止并注意函数中的“内部寄存器”等),让函数运行的结果可预测,并使出现的错误可再现。
12 宏
¹12-1:用宏定义表达式时,要使用完备的括号。
示例:如下定义的宏都存在一定的风险。
#define RECTANGLE_AREA( a, b ) a * b
#define RECTANGLE_AREA( a, b ) (a * b)
#define RECTANGLE_AREA( a, b ) (a) * (b)
正确的定义应为:
#define RECTANGLE_AREA( a, b ) ((a) * (b))
¹12-2:将宏所定义的多条表达式放在大括号中。
示例:下面的语句只有宏的第一条表达式被执行。为了说明问题,for语句的书写稍不符规范。
#define INTI_RECT_VALUE( a, b )/
     a = 0;/
     b = 0;

for (index = 0; index < RECT_TOTAL_NUM; index++)
     INTI_RECT_VALUE( rect.a, rect.b );

正确的用法应为:
#define INTI_RECT_VALUE( a, b )/
{/
     a = 0;/
     b = 0;/
}

for (index = 0; index < RECT_TOTAL_NUM; index++)
{
    INTI_RECT_VALUE( rect[index].a, rect[index].b );
}
¹12-3:使用宏时,不允许参数发生变化。
示例:如下用法可能导致错误。
#define SQUARE( a ) ((a) * (a))

int a = 5;
int b;
b = SQUARE( a++ ); // 结果:a = 7,即执行了两次增1。

正确的用法是:
b = SQUARE( a );
a++; // 结果:a = 6,即只执行了一次增1。

你可能感兴趣的:(程序员之路)