怎样才算好代码?

好代码具备的特性

  • 正确:代码应当正确处理所有预期输入(expected input)和非法输入(unexpected input)。
  • 高效:不管是从空间上还是从时间上来衡量,代码都要尽可能地高效运行。所谓的“高效”不仅是指在极限情况下的渐近效率( asymptotic efficiency,大O记法),同时也包括实际运行的效率。也就是说,在计算O时间时,你可以忽略某个常量因子,但在实际环境中,该常量因子可能有很大影响。
  • 简洁:代码能写成10行就不要写成100行。这样开发人员才能尽快写好代码。
  • 易读:要确保其他开发人员能读懂你的代码,并弄清楚来ᴜ去ᑡ。易读的代码会有适当注释,实现思路也简单易懂。这就意味着,那些包含诸多位操作的花ά的代码不见得就是“好”代码。
  • 易维护:在产品生命周期内,代码经过适当修改就能应对需求的变化。此外,无论对于原开发人员还是其他开发人员,代码都应该易于维护。

 编写好代码的一些具体方法

1. 多用数据结构

示例:编写一个函数,对两个简单的多项式求和,其形式为Ax^a+ Bx^b +…(其中系数和指数为任意正实数或负实数),即多项式的每一项都是一个常量乘以某个数的n次幂。

这个函数有多种实现方式。

  • 最差的实现方式

最差的实现方式就是将多个多项式存储为一个double型数组,其中第k个元素对应的是多项式中x^k的系数。采用这种结构有一定的问题,如此一来,多项式就不能含有负的或是非整数指数。要想用这种方法来表示x^1000多项式的话,这个数组就得包含1000个元素。

int* sum(double *poly1, double *poly2)
{
    ...
}
  • 较差的实现方式

一种不算最差的实现方式是将多项式存为一对数组coefficients和exponents。采用这种方法,多项式的所有项可以按顺序存放,只要系数和指数配对,多项式的第 i 项表示为coefficients[i] * x^exponents[i]。

采用这种实现方式,如果coefficients[p] = k 和 exponents[p] = m,则第p项为kx^m。尽管这么做没有上面那种解法的限制,但还是很凌乱。一个多项式就要用两个数组记录。如果两个数组长度不同,多项式就会出现“未定义”值。而要返回多项式更是麻烦,因为一下子得返回两个数组。

??? sum(double *coeffs1, double *expon1, double *coeffs2, double *expon2)
{
    ...
}
  • 较好的实现方式

对于这个问题,较好的实现方式就是为多项式设计一种数据结构。

struct polyTerm {
    double coefficient;
    double exponent;
}

PolyTerm* sum(PolyTerm *poly1, PolyTerm *poly2) {
    ...
}

可以看到,通过设计一种合理的数据结构,可以较好地解决示例中的问题。

2. 适当重用代码

示例:检查某个二进制数(以字符串形式传入)是否等于以字符串表示的十六进制数。

我们可以通过善用代码重用巧妙解决该问题。

int compareBinToHex(char *binary, char *hex)
{
    int n1 = convertToBase(binary, 2);
    int n2 = convertToBase(hex, 16);
    if(n1<0 || n2<0)
        return FALSE;
    if(n1 == n2)
        return TRUE;
    else
        return FALSE;
}

//将指定进制字符串转换成整型值
int convertToBase(char *number, int base)
{
    if(base<2 || (base>10 && base!=16))
        return -1;
    int value = 0;
    int length = strlen(number);
    for(int i=length-1; i>=0; i--){
        int digit = digitToValue(number[i]);
        if(digit < 0 || digit >= base)
            return -1;
        int exp = length-i-1;  //指数值
        value += digit * pow(base, exp);
    }
    return value;
}

//将数字字符准换成对应的整数数值
int digitToValue(char c)
{
    if(c>='0' && c<='9')
        return c-'0';
    else if(c>='a' && c<='f')
        return c-'a'+10;
    else if(c>='A' && c<='F')
        return c-'A'+10;
    else
        return -1;
}

我们本可以实现两套代码,分别实现二进制数和十六进制数的转换,但这么做只会加大代码的编写难度,而且维护起来也更难。相反,我们还是通过编写convertToBase() 和 digitToValue() 的方法来重用代码。

3. 模块化

编写模块化代码是指将孤立的代码块划分为相应的方法(函数)。这有助于让代码更易读,可读性和可测试性更强。

示例:编写交换整型数组中的最大和最小元素的代码。

实现方法1:将全部代码写在一个函数里。

void swapMinMax(int *array, int len)
{
    int minIndex = 0;
    for(int i=1; i<len; i++){
        if(array[i] < array[minIndex])
            minIndex = i;
    }
    
    int maxIndex = 0;
    for(int i=1; i<len; i++){
        if(array[i] > array[maxIndex])
            maxIndex = i;
    }
    
    int temp = array[minIndex];
    array[minIndex] = array[maxIndex];
    array[maxIndex] = temp;
}

实现方法2:采用更模块化的方式,将相对孤立的代码块隔离到对应的函数中。

int getMinIndex(int *array, int len)
{
    int minIndex = 0;
    for(int i=1; i<len; i++){
        if(array[i] < array[minIndex])
            minIndex = i;
    }
    return minIndex;
}

int getMaxIndex(int *array, int len)
{
    int maxIndex = 0;
    for(int i=1; i<len; i++){
        if(array[i] > array[maxIndex])
            maxIndex = i;
    }
    return maxIndex;
}

void swap(int *array, int m, int n)
{
    int temp = array[m];
    array[m] = array[n];
    array[n] = temp;
}

void swapMinMaxBetter(int *array, int len)
{
    int minIndex = getMinIndex(array, len);
    int maxIndex = getMaxIndex(array, len);
    swap(array, minIndex, maxIndex);
}

虽然前面的非模块化代码看起来也不怎么糟,但模块化代码的一大好处在于它易于测试,因为每一部分都可以单独验证。随着代码越来越复杂,编写模块化代码就变得越发重要。模块化的代码也更易阅读和维护。

4. 灵活、健壮

编写灵活、通用的代码,也就意味着使用变量,而不是在代码里直接把值写死,或者使用模板/泛型来解决问题。要是有办法编写代码解决更普遍的问题,那我们就应该这么做。

5. 错误检查

写代码很细心的人有一个明显的特征,那就是他不会想当然地处理输入信息。相反,他会用ASSERT 语句或if语句仔细验证输入数据是否合理。

比如,回到前面那段将基数为i的进制数(比如基数为2或16)转换成整数的代码。

//将指定进制字符串转换成整型值
int convertToBase(char *number, int base)
{
    if(base<2 || (base>10 && base!=16))
        return -1;
    int value = 0;
    int length = strlen(number);
    for(int i=length-1; i>=0; i--){
        int digit = digitToValue(number[i]);
        if(digit < 0 || digit >= base)
            return -1;
        int exp = length-i-1;  //指数值
        value += digit * pow(base, exp);
    }
    return value;
}

在第4行,我们检查基数是否有效(假定除16外,大于10的基数都是无效的,没有标准的字符串表示形式)。在第10行,我们另加了一处错误检查:确保每个数字都落在允许范围内。

诸如此类的错误检查在实际的产品代码中至关重要,在编写代码时绝不能掉以轻心。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值