简约代码

简约代码

                                                                   Lean Code

Abstract:  The concept for lean code is propsed and defined in terms of conciseness and simplicity.  Several key practices for producing lean code are described.  Two engineering examples are analyzed and the results show that leanness of code contributes to the quality and cost of software.

Key words: Lean code, Leanness

 

1           引言

早期的嵌入式软件设计,由于受硬件能力的限制,必须尽可能地缩小代码的规模和降低运行开销。随着硬件存储容量和运算速度的提高,在嵌入式系统的软硬件功能划分中,软件承担了更多的功能,这导致软件规模成倍增长。但是,这并不意味现在的嵌入式程序设计可以不考虑代码的规模问题。实践表明,臃肿的代码是软件故障的来源之一,也是导致开发和维护成本增加的原因之一。

国外提出“Lean Electronics”的概念。受此启发,本文提出“简约代码”的概念,旨在通过设计简约的代码来提高软件质量和降低开发和维护成本。

2           什么是简约代码

简约代码对应的英文是lean code。笔者查找了国外文献,发现有使用lean code,但还不是一个专用术语,因此也没有相应的定义。

笔者认为,所谓简约代码,是指以易于理解的方式并用较少的源程序行数实现软件功能的代码。

这个定义结合了软件质量的McCall模型中衡量质量要素的两个尺度[1]

—— 简明性(Simplicity):代码容易被理解的程度。

—— 简洁性(Conciseness):以代码行数衡量的代码紧凑程度。

换言之,简约代码是具有简明性简洁性的代码,代码的简约性即是简明性简洁性的结合。代码的简约性对以下软件质量要素有正面影响[2]

—— 正确性

—— 可靠性

—— 效率

—— 易维护性

—— 易测试性

—— 灵活性

—— 易移植性

—— 易复用性

代码的简约性对开发和维护成本也有较大影响。现在一般根据源代码行数计算成本。假设每行的成本为100元,一个有50万行源代码的系统缩小10%源代码就可降低成本500万元。

3           代码简约化的方法

如何写代码通常被认为只是一个风格和技巧问题,因此没有关于编码方法的研究和教育,程序员各行其是地写代码。笔者认为,编码方法对于软件工程的实践者是重要的,有必要进行研究和总结。以下是笔者归纳的使代码简约化的一些方法。

3.1       良好的软件设计

良好的软件设计是产生简约代码的基础。软件设计中的以下方法能有效地提高代码的简明性和简洁性[3]

a)    信息隐藏原则。Parnas提出的信息隐藏原则已被软件界广为接受。按照这个原则划分软件模块可产生良好的软件结构,从而形成良好的代码结构。

b)    弱耦合。耦合是指两个模块之间的相互依赖关系。这种关系越少或越简单,相应的代码就越少。但要注意,外部耦合(即允许多个模块访问同一个全局变量)和公共耦合(即允许多个模块访问同一个全局性数据结构)是较强程度的耦合,应尽量避免。

c)    高内聚。内聚是指一个模块内各组成部分的处理动作的组合强度。功能内聚使实现某个功能的代码将集中在一个模块中,而不会重复地散布到各个模块。

d)    低扇出数。模块的扇出数是指其直属的下级模块的个数。模块的扇出数越低,它的内聚度就越高,不易造成混乱。

e)    高扇入数。模块的扇入数是直其直接上级模块的个数。模块的扇入数高,说明模块的通用性强、冗余度低。

3.2       数据接口设计要考虑代码实现

航空电子综合系统的各个分系统之间要通过数据总线交换几千个数据。数据接口控制文件的设计对代码实现的规模有较大影响。

例:根据数据接口控制文件对/后半球循环选择定义结构分量为

        UNSIGNED16  Hemisphere:2;

        /*/后半球选择(0=不用,1=自动,2=前半球,3=后半球)*/

    实现循环选择需要如下代码:

    if (data_block.Hemisphere = 3)

        data_block.Hemisphere = 1;

    else

        data_block.Hemisphere ++;

如果/后半球循环选择的定义是:

        UNSIGNED16  Hemisphere:2;

        /*/后半球选择(0=自动,1=前半球,2=后半球,3=不用)*/

那么只需要一行语句:

    data_block.Hemisphere = data_block.Hemisphere + 1% 3

3.3       选用使程序简单的数据结构

用不同的方式定义数据结构,将产生不同的代码。

例:一个机场数据块中有5个数据,每个数据用一位表示其是否有效,每次只能有一个数据有效。因此,在把某一位置1后,还要用四行语句把其它位置0

typedef struct{

    unsigned short Airport_Number_Is_Valid:1;

    unsigned short Airport_Height_Is_Valid:1;

    unsigned short Runway_Magnetism_Course_Is_Valid:1;

    unsigned short Airport_TACAN_Wave_Band_Is_Valid:1;

    unsigned short Airport_TACAN_Channel_Is_Valid:1;

    unsigned short spare:11;

    short Airport_Number;

    short Airport_Height;

    short Runway_Magnetism_Course;

    short Airport_TACAN_Wave_Band;

    short Airport_TACAN_Channel;

}Airport_Data_Type;

如果改变结构定义如下,只要用一行语句把某个常数赋予给Valid_Flag分量即可:

#define Airport_Number_Valid 1

#define Airport_Height_Valid 2

#define Runway_Magnetism_Course_Valid 4

#define Airport_TACAN_Wave_Band_Valid 8

#define Airport_TACAN_Channel_Valid 16

typedef struct{

    unsigned short Valid_Flag;

    short Airport_Number;

    short Airport_Height;

    short Runway_Magnetism_Course;

    short Airport_TACAN_Wave_Band;

    short Airport_TACAN_Channel;

}Airport_Data_Type;

3.4       强化子程序

虽然子程序是一种古老的方法,但并不是所有的程序员灵活地、充分地运用它来概括和提炼代码

例:

                if (X >= 1024)

                    X = 1024;

                else if (X <= -1024)

                    X = -1024;

                if (Y >= 1024)

                    Y = 1024;

                else if (Y <= -1024)

                    Y = -1024;

上述代码可简化为一个实现数值限幅的子程序。

3.5       简化算法

不同的算法将产生差别很大的代码。

例:

unsigned char inverse(unsigned char  para)

{ 

   unsigned char res_temp,inv_temp;

   inv_temp=para;inv_temp&=0x01;inv_temp<<=7;inv_temp&=0x80;res_temp=inv_temp;

   inv_temp=para;inv_temp&=0x02;inv_temp<<=5;inv_temp&=0x40;res_temp|=inv_temp;

   inv_temp=para;inv_temp&=0x04;inv_temp<<=3;inv_temp&=0x20;res_temp|=inv_temp;

   inv_temp=para;inv_temp&=0x08;inv_temp<<=1;inv_temp&=0x10;res_temp|=inv_temp;

   inv_temp=para;inv_temp&=0x10;inv_temp>>=1;inv_temp&=0x08;res_temp|=inv_temp;

   inv_temp=para;inv_temp&=0x20;inv_temp>>=3;inv_temp&=0x04;res_temp|=inv_temp;

   inv_temp=para;inv_temp&=0x40;inv_temp>>=5;inv_temp&=0x02;res_temp|=inv_temp;

   inv_temp=para;inv_temp&=0x80;inv_temp>>=7;inv_temp&=0x01;res_temp|=inv_temp;

   return res_temp;

}

上述代码用40条语句来把一个8位无符号数的从高到低位置换为从低到高位。实际上用以下语句即可实现:

for(res_temp=0, i=0;i<8; para>>=1) res_temp=(res_temp<<1)|(para & 1);

3.6       简化条件表达式

复杂的条件表达式既增加了多余的代码,又不容易理解。冗余的条件表达式使测试不能满足覆盖率要求。

1

        if (New_Data->Main_Mode != Old_Data->Main_Mode

            && New_Data->Main_Mode == NORMAL

            && Old_Data->Main_Mode == IBIT)

               Control_Process(CTRL_NORMAL);

后两个条件隐含了第一个条件New_Data->Main_Mode != Old_Data->Main_Mode,因此可以删除第一个条件

2

        if (Block.Operation_Mode == 1)

            process1;

        else if (Block.Operation_Mode == 0)

            process2;

Operation_Mode是结构变量Block的分量,在结构定义中只占1bit,这样它的值只能是10,因此else后面的条件是多余的,并且无法设计使之为假的测试用例。

3.7       用数组代替重复的控制序列

例:

        if (Submode == 1)

            Draw_Work_Mode(Dev_ID, "INTC CL",0);   

        else if (Submode == 2)

            Draw_Work_Mode(Dev_ID, "INTC BL",0);

        else if (Submode == 3)

            Draw_Work_Mode(Dev_ID, "INTC BS",0);

        else

            Draw_Work_Mode(Dev_ID, "INTC",0);

上述代码可改为:

    char mode_str[4][8] = {"INTC CL","INTC BL","INTC BS","INTC"}

    Draw_Work_Mode(Dev_ID, &mode_str[Submode][0], 0);

3.8       用数据代替操作

例:

                unsigned short cstr[10];

            cstr[0]  = Ch_Zi30; /**/

            cstr[1]  = Ch_Xi40; /**/

            cstr[2]  = Ch_Tong30; /**/

            cstr[3]  = Ch_Ming20; /**/

            cstr[4]  = Ch_Cheng10; /**/

            put_cstring (dev_id, cstr, 5);

上述代码可改为:

                unsigned short cstr_subsystem_name[5] =

                   { Ch_Zi30, Ch_Xi40, Ch_Tong30, Ch_Ming20, Ch_Cheng10};

            put_cstring (dev_id, cstr_subsystem_name, 5);

4           实例分析

受篇幅限制,我们只分析工程实践中的两个实例。

4.1       实例1

实例1是一个地面检测设备软件,由三个人开发,用C语言编码。源代码行数是1944逻辑行[1]。该软件问题较多,经常不能正常工作。经分析原代码后,发现总体结构设计不够合理,原开发者相互协调不够、各编各的代码,造成代码臃肿、不能有效处理硬件各种状态。经修改后,源代码行数削减到1034逻辑行,是原来的53%。新代码能稳定、正常地实现预期功能,检测时间从40秒下降到15秒。

4.2       实例2

实例2是显示画面数据处理中的一个软件部件,由一人开发,用C语言编码。源代码行数是2382逻辑行。该软件部件的模块划分不够清晰,内聚度低,重复代码多。用Ada语言改写后的源代码行数是1765逻辑行,是C语言版本的74%。改写的Ada语言版本只使用了常规的高级语言设施,没有使用Ada语言的类属等特色,因此两者之间有可比性。

以下是由于重复代码而形成缺陷的一个例子:

    /*1段代码*/

    if ((B_02_06.Gs_El_Valid == 1) && (B_02_06.Gs_Az_Valid == 1))

    {

        X = (short)(B_02_06.Gs_Az * (PI / 2.0 / 16384.0) * 1000 * SCALE);

        Y = (short)(B_02_06.Gs_El * (PI / 2.0 / 16384.0) * 1000 * SCALE);

    }

    if ((B_02_06.Pullup_Warning_Sign_Valid == 1) &&

       (B_02_06.Pullup_Warning_Sign == 1))

        Draw_Air_UP_Symbol(Dev_ID, X, Y);

 

    /*2段代码*/

    X = (WORD16)(B_02_04.Hit_Az * (PI / 2.0 / 16384.0) * 1000 * SCALE);

    Y = (WORD16)(B_02_04.Hit_El * (PI / 2.0 / 16384.0) * 1000 * SCALE);

    if ((B_02_04.Pullup_Warning_Sign_Valid == 1)&&

       (B_02_04.Pullup_Warning_Sign == 1))

        Draw_Air_UP_Symbol(Dev_ID, X, Y);

 

    /*3段代码*/

    X = (WORD16)(B_02_05.Hit_Az * (PI / 2.0 / 16384.0) * 1000 * SCALE);

    Y = (WORD16)(B_02_05.Hit_El * (PI / 2.0 / 16384.0) * 1000 * SCALE);

    if ((B_02_05.Pullup_Warning_Sign_Valid == 1)&&

       (B_02_05.Pullup_Warning_Sign == 1))

        Draw_Air_UP_Symbol(Dev_ID, X, Y);

      

上述三段代码都是根据AzEl计算函数Draw_Air_UP_Symbol所需的坐标参数XY。但第1段代码与后两段代码的处理不同。经分析后发现后两段代码不符合需求。如果按简约代码的原则写代码,就不会出现这种问题。

5           结论

嵌入式软件的代码是否具有简约性对软件的质量和成本有不可忽视的影响。本文根据工程经验作了粗浅的分析,为提倡简约代码而抛砖引玉。

 

参考文献

1           Pressman, R.S., Software Engineering A Practitioners Approach, McGraw-Hill, Inc., 1992

2           朱鸿、金凌紫,软件质量保障与测试,科学出版社,1997

汤铭瑞,航天型号软件研制过程,宇航出版社,1999

 

后记 本文写于2003年,未能发表,现作为博客首篇。



[1] 本文的C语言逻辑行是指分号的总数、语句结尾符号的总数(如:‘}’等)、不带有块语句结构的iffor关键字的总数、逻辑编译指令的总数,不包括空行和注释行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值