【推荐】优秀代码

 

作为一个程序员,我们必须时刻想着两个问题:1,什么样的代码可以谓之为优秀?2,怎样才能写出优秀的代码?

 

一段优秀的代码,它一般需要满足以下几个条件:

#统一规范#

所有的代码,第一前提必须是统一规范,而常见的统一规范主要包括有以下内容:

1)统一编辑器规范

在团队开发中,我们并不对各个开发人员使用的编辑器做硬性要求,你可以使用常见的如Eclipse、WebStrom、Sublime等集成开发环境IDE,也可以使用UltraEdit这种,甚至Vim都无所谓。但是我们还是会做一些统一规范要求,比如Tab键统一使用空格Space替换(一个Tab为四个Space)、文件编码统一为UTF-8、换行的Line Delimiter统一为Unix的LF而不是Windows的CRLF(PHP代码尤其重视这点)等等。

2)统一代码风格

我们知道代码的风格有很多种,尤其是在涉及到括号的使用上,比如:

  • K&R括号风格

  K&R风格是最早为人们所喜爱的放个,它是由C语言之父Kernighan和Ritchie在他们的《C程序设计语言》一书中确立的,它也常被认为是最初和最好的风格,它可以在一个小屏幕中尽可能显示更多的信息。这个也是我个人最常用的风格。

int k_and_r() {
    int a = 0, b = 0;
    while (a != 10) {
        a++;
        b++;
    }
    return b;
}
  • 悬挂式括号风格

  悬挂式的风格在空间上显示上更加开阔,由于有着更明显的前括号,也使得代码更加易于浏览;但在竖向空间上占用更多。

int exdented()
{
    int a = 0, b = 0;
    while (a != 10)
    {
        a++;
        b++;
    }
    return b;
}
  • 缩进的括号风格

  缩进风格并不太常见,在这种风格下括号随代码一起缩进,这种风格也被称为“Whitesmith”风格,因为早期的Whitesmith的C编译器的示例代码使用的就是这种风格,个人并不推荐。

int indented()
    {
    int a = 0, b = 0;
    while (a != 10)
        {
        a++;
        b++;
        }
    return b;
    }
  • 其他风格

  还有一些其他的括号风格,比如GNU风格是介于悬挂式和缩进式风格之间的一种风格,括号被放置在各个缩进级别的一半的位置

3)统一命名规范

命名包括文件的命名、类的命名、方法的命名、变量的命名等,良好的命名使得代码易于阅读,也更加易于维护。命名的方法有很多,常见的的有:

  • 匈牙利命名法

  匈牙利命名法是一种有争议的命名约束,它将关于变量或函数的类型的信息编入它们的名称当中,要求开头字母使用变量类型的缩写,其余部分用变量的英文或中文的缩写,同时要求单词的第一个字母大写。这种命名法最初是在20世纪80年代的Microsoft公司中出现的,并在该公司的Win32 API和MFC库中得到了广泛的使用,也因此导致了一定的流行性。之所以被称为“匈牙利命名法”,是因为它的创始人Charles Simonyi是匈牙利人。此外,变量名看起来像是使用匈牙利语书写的,但是要理解它并不容易,很多非Windows的程序员都会被比如lpszFile、rdParam和hwndItem等的奇怪名字给搞糊涂。

int iMyAge;         // "i"是int类型的缩写
char cMyName[0];    // "c"是char类型的缩写
  • 驼峰式命名法

  有的时候又称为“小驼峰命名法”,它在Java语言库以及很多C++代码库中得到了广泛使用,这种命名主要源于其大写字母的布局很像骆驼的驼峰,它规定第一个单词字母小写,后面其他的单词首字母大写。

int myAge;
char myName[0];
  • 帕斯卡命名法

  这种命名法跟上面的驼峰命名法很相像,唯一区别就是其第一个字母也大写,所以有时又称为“大驼峰式命名法”。

int MyAge;
char MyName[0];
  • 下划线命名法

  这种风格在C++标准库和GNU Foundation中比较常见,也即用下划线来隔开不同的单词。

int my_age;
int my_name[0];

事实上,采用什么样的命名方式都是可以的(虽然我个人更偏向于驼峰命名法),更重要的是命名必须清晰,比如函数名可以采用动名词+静名词的组合来命名,而不是用foo和bar这种古怪的名称。另外,建议在清晰的基础上保持简洁,否则也很可能出现类似someTypeWithMeaningfulNaming这种非常冗余的命名,比如在for循环中使用i而不是使用index就是一种推荐做法。

#简洁清晰#

什么叫做简洁?能够一句代码解决的事情,就不要写成两句代码。

什么叫做清晰?虽然一句代码能够解决事情,但我们有的时候却将其拆成了多句代码,使得其变得可能有点冗余。

优先程度上,清晰>简洁。

每个人写的代码,它的后续维护者可能是一个初级程序员,如果他不能理解你的代码逻辑,或者说你的代码逻辑很难理解,那么他就有可能会犯一些错误。复杂的结构和不常用的语言技巧虽然可以证明你在运算符优先级方面有着熟练经验,但是这些实际上会扼杀代码的可维护性。比如下面两段代码:

int a = b = c = 10;
int result = a * b + b * c - a + b / c;

// 下面的代码虽然不如上面代码简洁,但是更加的简单
int a = 10, b = 10, c = 10;
int result = (a * b) + (b * c) - a + (b / c);

清晰的代码,也往往意味着简单的代码。如果对于某段代码不是基于性能上的需要而写得复杂,那么,请保持代码的简单清晰。

#必要注释#

作为一个负责任的程序员,我们有义务给我们的代码写注释,即使在编码任务再繁重的情况下。

但我们也需要注意的是,注释不是越多越好,我们更加重视注释的质量,而不是数量。很多时候,一个好的命名已经可以能够帮我们省下很多注释。比如下面的代码:

for (int i = 0; i < wlst.size(); i++)
k(wlst[i]);

// 上面代码改成下面代码
for (int i = 0; i < widgets.size(); i++) {
    printWidget(widgets[i]);
}

注释中应该包含的内容包括有:

  • 解释为什么,而不是怎么样

  注释不应该描述代码是怎样运行的,这完全可以通过阅读代码来了解,你更加应该注意的是描述为什么有些东西要这么写,比如下面的代码的注释就不是必须的,完全可以去掉:

// 循环遍历所有的widget
for (int i = 0; i < widgets.size(); i++) {
    // 打印这些widget
    printWidget(widgets[i]);
}
  • 不要描述代码

  不要试着去描述代码写你的代码都干了什么,更应该要写的是你的代码为什么要这样写。比如下面的代码的注释完全是无效的:

// index自增(我们这里需要写的注释更应该是为什么要让index自增)
++index;
  • 不要取代代码

  不要试图在注释中去说明某个代码的限制条件,更应该地使用代码本身的机制去实现。还有,当有的时候你发现可能要花大量的注释来描述某段代码的功能的时候,更好的做法是用代码去描述,比如把一大段的代码拆分成多个子函数,给每个子函数赋予更合适的命名等。比如:

// 下面这个方法不允许被类以外访问
public void getMyAge() {
}

// 完全可以不要上面的注释,而使用如下的代码实现
private void getMyAge() {
}
  • 避免给代码造成分心

  注释不应该给本身的代码造成分心,比如有些程序员喜欢在if的结束加上// end if (a < 1) 这样的注释,而这种注释则是完全没必要的,只会给原本阅读代码造成分心,更合适的做法是通过正确合适的缩进方式等来保证代码可读性。

#健壮安全#

我们在编写代码的时候,需要考虑各种方方面面的因素,提高代码的健壮性。比如,在编写最常见的登录代码时,就需要考虑到用户输入的用户名和密码的多种情况,比如:

public void checkLogin(String username, String password) {
    if (username == null || password == null) {
        // Error
    }
    if (username.trim().equals('') || password.trim().equals('')) {
        // Error
    }
    // do something
}

不能只是简单地检查用户名和密码是否匹配,还必须要考虑到当用户名或密码为空时候的处理逻辑,如果用户名是手机号,还必须使用正则表达式来检查所输入的用户名是否符合手机号码样式等等。

不仅于此,对于安全性上的要求,还必须要对用户输入的字符进行处理,防止用户输入类似<script>alert()</script>的XSS攻击代码和类似'or'1'='1的SQL注入攻击代码等。

总之,要编写健壮安全的代码,必须时刻考虑到:

  • 使用“防御性编程”

  “防御性编程”中心原则是“不做设想”,也即永远不要设想用户会按照我们写代码的预期或要求来使用代码。一些简单的防御性规则,比如“检查所有的输入”和“验证所有的运算”,可以帮我们把代码中许多的安全隐患给消除掉。

  • 代码审核

  初级开发人员的代码在早期必须经由高级开发人员进行代码审核,有经验的高级开发人员能够通过阅读代码就能发现很多问题,比如边界值的判断上,数组是否会造成溢出和字段长度是否超出数据库限制等这些都是可以通过代码审核来发现。

  • 严格地执行测试和调试,尽可能地消除Bug。

  一些敏捷开发的TDD测试驱动开发就是这么做的,它要求代码必须是可以被测试的,并且这些可以被测试的代码时刻能够通过测试样例。

#高效性能#

随着现在处理器的越来越强大,很多时候应用的瓶颈并不在代码而在其他地方(主要是I/O操作,比如读写文件和数据库等),个人更不推荐为了所谓的性能而牺牲了代码的可读性和可维护性,除非这块代码确实是必须优化的。

但我们也不能仗着处理器的强大就可以完全不顾代码的性能,有些简单的优化原则在我们写代码的时候就需要时刻遵循。

  • 将工作推迟到必须时再做

   如果不是马上就要使用某个文件,那么就不要打开它;如果暂时不需要某个值,那就不要去计算它;如果没有某个函数程序也可以运行,那就不要去调用它。

  • 在函数中做进一步检查以避免多余的工作

  如果一个可能会导致函数计算无效的条件,那么对这个条件的判断最好放在顶部,防止做了过多无用工作,比如:

public int calculate(int number) {
    if (number == 0) {
        return 0;
    }
    // do something
}
  • 将不变条件的计算移到循环外

  由于循环中是每次都要做的,那么每次循环中如果某个值都保持不变,则将该值移到循环外面,比如:

for (int i = 0; i < tree.appleCount(); i++) {
}

// 上面代码可以改成如下
int appleCount = tree.appleCount();
for (int i = 0; i < appleCount; i++) {
    // do something
}
  • 利用“短路求值法”

  确保将可能失败的测试放在最前面以节省时间,比如if (condition_one && condition_two),确保condition_one不为真的可能性比condition_two更大。

  • 不要重复进行相同的工作

  比如将公共的代码提取到共享函数中以避免重复计算,或者将某个经常被用到的需要被计算出来的值放到缓存中以备使用。

最后,各种不同的编程语言都有其不同的优化之处,比如Java中使用增强式foreach循环就比传统的for循环更加高效,具体到各种语言再具体分析。

#易于扩展#

首先,我们对于程序的扩展性判断,并不是需要其能支持未来需求的变更的可能性。相反,我们不提倡对代码做“过度设计”,代码的可扩展性更主要的是体现在未来能够对需求变化快速响应上,能够跟随需求的变化而快速变化,而不是一味不变地支持需求的变化。

但是这也肯定也不能成为我们偷懒的借口,我们还是可以使用一些常规的原则来增强代码的可扩展性,尤其是时刻保证代码的“低耦合”非常重要。

  • 动态按需加载

  不同的功能拆分出来,而不是在一个函数里完成,比如:

public void init() {
    // load database
    // init environment
    // init ui
    // do something
}

// 上面的方法把所有的事情都放在一起,扩展性很差,我们需要把不同的功能拆分成不同的函数
public void loadDatabase() {}
public void initEnvironment() {}
public void initUi() {}
public void init() {
    loadDatabase();
    initEnvironment();
    initUi();
}

对于配置文件也是一样,我们需要在程序的入口处能够根据当前环境不同而加载不同的配置文件,比如Web应用中常见的做法是将本地环境、测试环境、正式环境分成三个配置文件,然后根据当前请求域名的不同而加载不同的文件,而不是把所有的配置写到一个文件里。

  • 抽象接口

  对于一些功能类似的,完全可以抽象出一个抽象类,在这个抽象类中定义功能接口,然后再在不同的子类中实现该接口的不同功能,比如Web应用中一种常见的做法是在抽象控制类中定义GET/POST/PUT/DELETE请求协议的接口方法,然后再在不同子类中分别实现不同的功能细节。

public abstract class BaseController {
    public void get();
    public void post();
    public void put();
    public void delete();
}

 #写在最后#

上面写了这么多,其实说白了提高写代码能力唯一的途径就是多写代码,多思考。对于初级程序员来说,把代码写得具有可维护性(规范/简洁/注释)是最基本的要求,而对于有经验的程序员来说,还必须考虑代码的健壮性、安全性、高效性和可扩展性等。写代码的时候多考虑下这些东西,经常重构自己的代码,就离写出优秀的代码不远了。

最后,给大家推荐几本个人觉得不错的书籍,这几本书对我的代码生涯有着极大的影响。

1、《编程匠艺 编写卓越的代码》,电子工业出版社,Pete Goodliffe(著),韩江, 陈玉(译);

2、《重构 改善既有代码的设计》,人民邮电出版社,Martin Fowler(著),熊节(译);

3、《大话设计模式》,清华大学出版社,程杰(著);

4、《Java优化编程(第2版)》,电子工业出版社,林胜利, 王坤茹(编著);

 

posted @ 2017-01-16 15:21  brishenzhou  阅读(2358)  评论(1编辑  收藏  举报