您的位置:首页 > 编程语言

如何编写可信赖的代码

2013-11-20 19:33 295 查看
软件, 逻辑之塔, 精微的艺术。

遵循严格的代码规范, 学习好的编程模式, 避免常见的编程错误, 持续小步重构改进, 严格测试与必要文档, 理解所写的每一行代码, 正确使用
API, 代码 Review , 追求更好的解决方案, 注重整体设计

作为一名合格的程序员, 需要满足的两个最基本要求:

1. 能够编写值得信赖的程序和文档;

2. 能够无障碍地与团队成员进行探讨交流。

假设我们是一段代码的使用者, 对代码的要求会是怎样的?

代码有必要的文档, 详细说明了它的主要意图及设计思想;

代码出色地完成了它所声称的工作, 对于不合法情况能够给出合理的返回结果或友好提示; 能够应对大数据量;

代码稳固, 在异常情况下依然坚挺, 至少可以给出友好提示, 或降级运行;

容易读懂、修改和扩展。

通常情况下, 主要考虑以下质量属性: 正确性、性能、 健壮性、 可维护性。

什么样的代码是不可信赖的?

1. 没有文档;

2. 不能(正确有效地)完成代码文档所声称的事情;

3. 不能处理不合法情况;

4. 无法应对大数据量的情况;

5. 没有错误提示或不友好;

6. 可能被恶意代码攻击;

7. 难以读懂、理解和修改;

这就有几个问题要思考一下, 可以根据这几个问题按图索骥, 一步步地逼近高质量编程。

1. 这段​代码想要完成什么样的工作? 要求怎样的输入, 可以预期获得怎样的输出(输入-输出模型)?

2. 什么是合法情况, 什么是不合法情况? 如何统一处理, 减少这种合法性检测的时间成本?

3. 代码所要应对的数据量有多大? 是否可能存在时间或空间上的性能瓶颈? 如果有, 需要仔细设计好的结构和算法去解决;

4. 代码所运行其中的环境如何(数据库操作/网络调用/文件读写)? 可能发生怎样的异常,导致什么错误? 如何进行异常处理?

5. 如何保证代码可读? 是否有可扩展性需求? 如果有, 需要在哪些方面提供灵活性?

基本方法:

1. 编写文档, 描述所要完成的工作, 输入是什么, 输出是什么; 这是一个自我表达的过程, 有助于清晰思路;

2. 编写有效的测试用例, 设立检验关卡。

3. 编写代码, 持续小步重构、改进, 必要的时候同步更新文档, 通过测试用例;

4. 代码Review. 添加新的测试用例或需求, 持续改善。

以上采用的是“文档与测试导引” 的方法。既要写文档, 又要写测试, 这种方法看上去工作量大, 实际上反而可能是一种捷径。 实际上, 文档、测试、开发三者的本质都是表达, 彼此会是相辅相成的。 当然, 与“测试驱动论”或“文档驱动论”不同, 编写文档以及通过测试用例并不是最终目的, 只是一种检验途径。 还需要尽力去编写可读性良好的代码。

编写可信赖的代码, 需要采用多种方法和手段去逐步逼近。 以下是一些好的做法:

1. 确定一种严格的代码风格和规范, 严格遵行它。 良好的代码风格不会保证程序的正确性, 但是能保证代码清爽易读,更容易找出错误;

a. K&R C 代码风格, 详见 《The C Programming Language》 ;
b. 《Java 编程规范》;
c. 统一命名, 必要的注释和文档 ;

2. 阅读 《 Effective Java 》, 《代码整洁之道》, 《Writing solid code》、 《编写可读代码的艺术》《代码大全》, 《Unix / Linux 设计思想》,《敏捷技能修炼》, 《程序设计实践》(K&R) 等编程书籍, 掌握好的编程理念和模式, 尽可能使用习惯用法和推荐用法,这些有助于避开一些常见陷阱,
自然地写出好的代码;

a. 集合/容器遍历: 使用 foreach 或 迭代器模式;
​ b. 数组遍历采用半开半闭模式: begin <= index < end ;

b. 自顶向下, 意图导航;
c. 搭建原型, 逐步细化;
d. 接口与实现分离解耦;
e. 构造与使用分离解耦;
f. 事件与处理器链分离解耦;
g. “一个事实”原则; “对修改关闭, 对扩展开放”原则; KISS 原则;

h. 充分利用已有成熟库和组件;
i. 通过封装, 屏蔽低层复杂性, 使用容易理解的高层语义构建程序;
j. First Make it Right, Then Make it Good.

3. 熟悉常见错误代码模式, 避免犯相同的错误。 聪明的人从别人的错误中汲取教训。 《Code Quality: The Open Source Perspective》 这本书从代码层面深入讨论了各种编程错误。

a. 变量未声明或初始化即使用; 作用域过大; 无意识的名字冲突;

b. 运算符误写; 优先级错误; 计算溢出; 不正确或不成熟的算法;
c . 越界错误; +1 或 -1 循环错误; 空处理错误; 临界处理错误;

d. 不正确或不完整的 API 调用; 对返回结果不作检测;

e. 缺失错误处理或草率了事, 比如捕获异常却不处理;
f. 同步更新错误, 比如添加的新字段没有被输出, 关联字段未能同步更新;

g. 并发与时序错误, 比如 并发修改共享可变对象容易产生并发错误, ajax 异步调用容易导致时序错误;
h. 资源泄露, 比如打开文件却没有关闭;
i. 缓冲区溢出; 恶意代码注入;
j. 权限赋予不当; 应用目录泄露;
k. 使用复杂算法过早优化;
L. 手工解析输入而不是使用成熟的库; 未作参数合法性验证;
m. 残留的无用代码;
n. 代码与文档不同步;
o. 错误的类型转换, 或其他转换;
p. 未能正确理解需求;
q. 使用全局变量而无意识地修改;
r. 使用冷僻用法或奇淫巧技;
(探索精神是可嘉奖的,但不宜用在项目中)

根据二八定律, 80% 的 BUG 是由 20% 的编程错误引起的, 因此, 熟悉这 20% 的编程错误并避免之, 就能避免 80% 的BUG 。此外, 有些 BUG 可能是由系统集成导致的, 属于更精微的层面, 另当别论。

充分利用编译器及静态代码分析工具检测代码。

4. 持续小步重构改进,消除重复和代码坏味;

a. 重复代码抽取为公共方法;

b. 相似逻辑提炼框架处理;

c. 使用设计模式优化结构;
d. 充分使用短小函数;

e. 删除未用到的代码;

5. 设立严格的检验关卡, 仔细测试;

a. 优先级: 关键部件 》 常用部件 》 不常用部件;

b. 正常情况测试;

c. 临界情况测试: 空/越界/一/二/;

d. 等价类测试;

e. 分支测试;

f. 性能压力测试;

6. 深入理解实现原理和细节,准确理解和使用 API, 理解代码所做的事情;

a. 完整理解 API 含义, 而不是一知半解的情况下调用;
b. 检测 API 返回值并作出合理的措施;
​ c. 理解自己所编写的每一行代码, 它是如何与框架进行交互的;



​ eg. jquery $.ajax() 方法, 充分理解该方法所涉及的概念和知识点

​ String vmName = request.getParameter(HouyiApiParams.KEY_VM_NAME);

​ 容器框架是如何获取到该参数的值的。

7. 代码讲解,有助于发现所犯的错误, 也能锻炼口头表达能力;

a. 团队成员之间进行代码 Review;
b. 给自己讲解所编写的代码;

8. 思考更好的表达方式和方案, 追求更好的可维护性;

a. 使用多线程并发处理 IO 调用, 而不是迭代;
b. 使用异步调用改善响应流畅性;
c. 使用 API 接口代替数据库查询;
d. 对于简单方案的性能较低的实现方式, 思考更有效率的实现方式;
e. 对于代码中不太清晰的地方, 逐步替换为更清晰的实现;

9. 阅读优秀开源项目代码, 潜移默化地汲取优秀的做法、思路和方案;

10. 关注整体设计、细致编程、考虑周全; 有些问题是由整体设计引入的, 比如整体的错误处理, 无法通过局部层面来解决。

不当的架构设计会使代码修改和扩展变得困难, 迫使开发者编写大量临时方案去解决某个具体问题; 日积月累, 整个系统就会越来越腐烂, 越来越难以维护。 始终保持简洁有效的架构设计。

形成开发与测试的良性循环,建立高质量编程的一套方法和习惯(包括开发时间估算), 对每一个功能都能遵照这种方法和习惯。

时间与质量的平衡。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: