当你决定使用库、框架、同事的代码、整个应用或者你自己的代码的时候,要记住选择正确的代码重用的几个指导原则。
1、理解功能与限制
要花时间去熟悉代码。理解其功能与限制还是很重要的。开始阅读文档与公开接口或API。理想情况下,有这些去理解怎么使用该代码就够了。然而,如果库没有提供清晰接口与实现的分别的话,如果有源码,你可能需要去看源码了。还有,你可以与用过这些代码的程序员沟通,可能能解释一些奇奇怪怪的东东。一开始要学习其基本功能。如果是一个库,那么它提供了什么功能?如果是一个框架,怎么让你的代码匹配?要继承什么样的类?要自己写什么样的代码?还要考虑依赖代码类型的特定问题。
当选择库时要记住以下几点:
- 在多线程编程时使用库是否安全?
- 库是否暗含了使用代码的特定编译器设定?如果答案为是的话,你的项目是否可以接受。
- 该库是否还依赖其他库?
还有,对于特定库还要做一些细节的考察:
- 需要什么样的初始化与清除调用?
- 如果你需要继承类,需要调用哪个构造器?需要重写哪个虚成员函数?
- 如果调用返回内存指针,谁负责释放内存:调用者还是库函数?如果库函数负责的话,它什么时候释放?强烈推荐看看是否可以使用智能指针来管理库分配的内存。
- 调用返回企的类型是值还是引用?
- 可能抛出的异常
- 库调用错误情况的检测,有没有假设条件?错误是怎么处理的?客户程序怎么展示错误?避免使用库跳出消息框,在标准输出上打印信息,或者中断程序运行。应该由客户程序决定怎么通知用户错误,而不是库。
2、理解学习成本
学习成本是指开发者学习怎么使用库花费的总的时间。并不只是开始使用库时初始成本,而是随着时间推移不断增加的成本。只要当成员加入项目,就需要学习使用库。
对于特定库该花费是非常大的。这种情况下,如果发现你需要的功能在众所周知的库中,建议你使用该库,而不是使用那些外部的并不为人所知的库。例如,如果标准库提供了你需要的数据结构和算法,就用它了,别管其他的。
3、理解性能
了解库或者其他代码提供的性能保证是非常重要的。即使你的程序对性能不敏感,也要确信使用的代码不能在你的使用中拥有糟糕的性能。
3.1大O符号
程序员通常讨论或者记述算法和类性能时使用大O符号。我们就来简单讨论并解释一下算法复杂性的一般概念和大O符号,并不涉及不必要的深奥的数学知识。如果你很熟了,可以略过。
大O符号表明的是相对而不是绝对的性能。例如,它不会说一个算法运行了多长的时间,比如说30毫秒,大O符号给出了一个算法当它的输入增长时怎样执行。举例说明,输入要排序的元素数目给排序算法,在键值查询时哈希表中的元素数量,磁盘之间复制文件的大小等。
大O符号只应用于其速度依赖于输入的算法。不适用于没有输入或者运行时间随机的算法。实践上,你会发现,大多数感兴趣的算法的运行时间都取决于它们的输入。所以这个限制影响不大。
正式一点的说法,大O符号给出了以输入大小多少作为参数的函数算法的运行时间,也就是大家所说的算法的复杂性。其实并不像听起来那么复杂。例如,一个算法可以两倍的时间处理两倍的输入元素。这样的话,如果1秒钟处理200个元素,那么2秒钟就会处理400个元素,4秒钟就会处理800个元素。如下图所示。也就是说,这个算法的复杂性是线性的,因为从图示可以看出,画出来就是一条直线。
大O符号总结该算法的线性性能表示为:O(n)。O只是表示你用大O符号,而n代表了输入的数量。O(n)给出的就是算法速度为输入数量的线性函数。
当然了,并不是所有的算法都是输入数量的线性性能。下表总结了一些通常的复杂性,以从最好到最差的性能顺序排序。
算法复杂性 | 大O符号 | 解析 | 算法举例 |
常数 | O(1) | 运行时间与输入数量无关 | 访问数组中的单个元素 |
对数 | O(log n) | 运行时间是输入数量的以2为底的对数的函数 | 以二分法在排好序的列表中查找一个元素 |
线性 | O(n) | 运行时间与输入数量成正比 | 在没有排序的列表中查找一个元素 |
线性对数 | O(n log n) | 运行时间是输入数量的对数函数的线性倍数的函数 | 合并分类查找 |
平方 | O() | 运行时间是输入数量的平方的函数 | 像选择排序的慢性排序查找 |
指数 | O() | 运行时间是输入数量的指数函数 | 优化旅行销售员问题 |
给出函数的输入数量而不是绝对数的性能有如下两个好处:
- 与平台相关。在一台计算机上以200毫秒运行一段代码不能说明在第二台计算机上就是这个数字。如果不是同样的负载在同一台计算机上运行也很难比较两个不同的算法。另一方面,根据输入数量给给出函数的性能适用于任何平台。
- 输入数量的函数性能包含了一定规格的算法的所有可能的输入。一个算法运行所用的以秒计的特定时间只包含了一种特定的输入,而与其他输入无关。