高质量程序设计艺术(Code Quality The Open Source Perspective)

https://book.douban.com/subject/2354769/

现实中的软件质量能够从4个方面观察

  1. 使用质量,比如使用的体验
  2. 外部质量,外部进行测试所体现的质量
  3. 内部质量,就是单纯对于单个部件进行评估
  4. 过程质量。关注过程而不是产品本身有可能是值得的,而且有时候还是不可或缺的。

想象一下如果需要制造一个航空母舰,海军绝对不可能知识通过检查完工的航空母舰就确定它是否符合需求,海军需要做的是检查与航母的建造过程相关的过程元素,这可能包括设计师的资质证明,焊点的X光板以及所有的组装好的部件的验收测试结果。但是千万不要把这个过程走向了极端,不管我们就会过分关注过程质量


对于软件质量能够从几个方面进行反应,下面就是软件质量具体的体现方面,这些内容都是相互影响的,在软件设计中我们需要做适当的权衡:

  1. 功能性
    1. 相配度,软件功能和用户是否相配
    2. 准确性,计算结果的准确性
    3. 互操作性,和其他软件的互操作性
    4. 安全性,软件数据的安全保障
  2. 可靠性
    1. 成熟,软件不容易出故障
    2. 容错,软件出现错误情况下依然能够继续工作
    3. 可恢复性,恢复数据并且继续执行
  3. 可用性
    1. 易懂,用户能够非常容易快速使用
    2. 可掌握性,掌握它需要付出的努力少
    3. 可操作性,操作这个软件需要的精力少
    4. 吸引力,用户愿意使用这个软件
  4. 效率
    1. 时间和空间的权衡
    2. 资源的使用,对于CPU,内存还有磁盘,网络这些使用需要越少越好
  5. 可维护性
    1. 可分析性,找到修正和改进地方的难易程度
    2. 可变性,完成一个修改的难易程度
    3. 稳定性,修改之后出现问题的程度
    4. 易测性,是否能够验证修改的结果
  6. 可移植性
    1. 适应性,软件代码在不同环境中运行的能力
    2. 可安装型,软件在各种环境中安装可行性
    3. 共存,软件在拥挤的环境中表现如何
    4. 可替代性,软件某个部分是否能够通过其他部分来代替

可以看到使用底层并行原语一定需要提高警惕。这种代码中的错误可能会导致通过测试很难发现,诊断和调试。使用形式化方法来对代码进行推理可能是一个很吸引人的建议,但是事实上,这种推理作为确保代码正确性的唯一选择,对于所有缺少数学方面训练的人来说极其困难。因此,请试着将这些代码替换为更好用的高级代码单元或者是基于耦合度较低的单元来设计系统-通过管道通信的进程或者是黑板是松耦合并行系统两个例子:-)


在(按照“不造成其他伤害”的方式)清理自己的状态之后将错误向上传递是很明智的献策略,因为这允许系统的另一个对状况了解更全面的部分更高效的处理错误:-)


能够以资源泄露方式泄露的元素包括:文件句柄,TCP/IP连接,Windows GDI对象,JDBC或ODBC连接和程序许可


可变大小容器并不是万金油。在嵌入式与安全性关键的系统中,通常更倾向于使用固定大小的数组,以保持软件的确定性。在此类应用领域中,使用固定大小的数组可以避免动态内存管理是伴随的内存区域与分配时间可变性。


在规范化的非冗余数据格式与其更高效并且有时间复杂度耕地的等效数据格式之间作取舍是非常棘手的,数据库设计人员在工作时整天都要应付这种问题。(ok,如果以后做数据库的话,那么需要花一些时间好好考虑数据的规范化,如何组织数据)


评估现有安全措施并且规划新的安全措施时,正确的方法就是进行安全专家们所说的风险分析(risk analysis):确定你想要保护什么,以及这些被保护的对象对你的重要性(你的资产以及它们的价值),找出你的资产所面临的风险,并且对降低这些风险的各种方法进行评估。


在查找代码的漏洞时候,忽略可以被攻击者任意修改和部署的代码,而将精力集中在可能会导致安全问题的代码上面


时间是大自然用来防止所有事情同时发生的手段


如果延迟响应会造成降级运行那么就是软实时系统,如果延迟响应会造成运行的错误就是硬实时响应


从项目管理的角度,你需要试着估计项目的性能风险,并且给项目的关键用例设定精确的量化目标(比如静态页面必须在50us内传输完毕).在建模阶段为系统的体系结构评估各种设计方案是很有益处的:-)


程序被装载到系统之后,任意时刻都是在下面3种状态下面运行

  1. 直接执行代码.这执行代码所消耗的时间是user time,进程在用户的上下文中执行
  2. 内核为该程序直接执行代码。这个部分所消耗时间是sys time
  3. 等待某个外部操作结束。一般是读写某个慢速外设,可能是磁盘,打印机或者是网络,这部分是idle time。

实际时间运行时间real time=user time+sys time+idle time. 可以分析出下面3种情况:

  1. real time>>user time+sys time.这就表明受限于I/O,诊断工具就是使用一些磁盘,网络虚拟内存统计工具或者是系统调用跟踪。可以考虑进行高速缓存或者是做更好的IO工作
  2. sys time>user time,那么表明受限于内核。那么改进内核或者是采用高速缓存或者是好的CPU
  3. user time=real time.那么表明受限于计算,如果需要改进的话那么只有采用更好的算法了或者是好的CPU

书里面介绍了如果进行剖析的工具,现在还不是用的上,但是知道问题在哪里了这样分析就会更有针对性:-).书中对于这部分还是介绍的很详细的


系统颠簸主要和程序访问局部性相关,解决系统颠簸的问题

  1. 减少系统的内存使用
  2. 使用配有大量物理内存的系统
  3. 改善系统的访问局部性,采用更大的高速缓存

有些系统一旦程序的内存影响开始增长就永远不会收缩,所有被释放的内存块只能在特定的程序实例中重用


使用数据的映射比如mmap这样的函数,那么进程可以将其虚拟内存的一部分安排给这个特定文件的磁盘缓冲区。因此访问映射后内存块的结果是内核将文件的相应部分直接读入这个内存块中无需数据复制到内核的常规磁盘缓冲区或者反之,也就避免了相应的开销


可移植性分为:

  1. 操作系统.
    1. 可选的程序,gcc,awk,sed,install,ln,ranlib
    2. 库的存在curses,dbm,mp
    3. 特性库函数的支持alloca,getpgr,mmap,strcol
    4. 某些头文件的存在signal.h,dirent.h
    5. 某些结构如何定义的st_dev,st_blocks
    6. 各种类型的支持size_t,pid_t
    7. C编译器的支持,比如const,inline,##,long long
    8. 操作系统服务,如对X窗口的支持等
  2. 处理器体系结构
    1. 数据的大小和长度
    2. 数据存储的方式big endian或者是little endian
    3. 特定的汇编指令
  3. 编译器与语言特性
    1. 编译器错误
    2. 非标准的扩展
    3. 新语言的特性
    4. 二进制兼容性
  4. 图形界面用户环境(这个部分从来就没有统一过,跨平台的开发工具有)
    1. Java
    2. Tcl/Tk
    3. Qt
    4. GTK
    5. wxWindows
  5. 区域(这个没有看)
  6. 硬件设备与平台

尽管代码与物质的制品不同,不去管它也不会老化,但是程序还是因为一些原因被修改。可能是为了改正现有的错误而作的修正,可能是因为适应新的环境,也可能是为了满足新的需求而作的改进。包括增强系统功能的渐进行为(progressive activity)为了对系统演变所产生的负面影响进行校正的反回归行为(antiregressive activity).包括可分析性(analyzablity),可变性(changeability),稳定性(stability),可测性 (testability). 所以嘛,代码总是需要改的,以前我曾想过这个问题,软件不会老化,那么为什么需要去维护呢?人们买了一台机器之后如果发现这个机器不合用了,他们会去重新买另外一个机器,而不会要求机器开发者去重新修正。可能人们对于软件开发的灵活性和软件本身给予了太高的希望把


在调试器不合用的情况下面使用日志语句是有帮助的。有一些应用比如嵌入式一旦部署就无法调试,但是增加日志记录功能是即使在资源受限的嵌入式系统中也是可行的。而有一些其它应用比如GUI界面的应用,后台程序,控制台游戏还有网络交互都是难以作交互式调试的,更多的对于语言如C++的宏或者是template调试都存在困难,这时候使用日志….