编程漫谈(四):基本功
——读《编程珠玑I》有感:
Program.Program better. Program faster.
要从事软件开发,首先要学会编程。如何编程呢?如何编写更高效更优雅的程序呢?《编程珠玑》通过简单而熟悉的示例,揭示了许多非常有益的编程原理和技巧,极具启发性。
基本流程:
问题定义—— 应用框架与界面设计 —— 选择合适的数据结构和高效的算法(对象和消息)—— 性能估计 —— 接口声明 —— 伪代码 —— 程序验证—— 编码 —— 测试与调试 —— [代码优化]—— 代码重审和回顾。
问题定义:赢在起点。清晰明确的问题描述和定义几乎决定成功的一半。作为问题求解,如何正确地理解问题,从何种视角去思考问题,往往会产生天壤之别的结果;作为软件开发,“有所为而有所不为”,应当作为基本准则。谁都期望能够开发出性能强劲的、多能多才的软件,但并不是谁都能够承受这样的研发成本(时间、资金和人力)。在成本与所能获得的最终产品之间,必须作出合适的权衡和折衷。
经过良好抽象的问题,更容易得到可复用的问题解决方案。比如,《编程珠玑I》第12章,取样问题,从随机选区列表抽样,抽象成“从n个整数中随机抽取m个有序整数”的问题;这样,问题的解决方案还可以用于其它领域的抽样问题。
应用框架和界面设计: 在明确问题的求解目标之后, 就要思考如何求解问题了。 通常会有一个初步的思想,然后逐渐形成整个比较成熟的方案和应用框架。 与此同时, 定义良好的界面设计也是非常关键的。 在界面之上, 是依靠界面服务的应用程序的实现; 在界面之下, 是用来提供界面服务的数据结构与算法的实现。
数据结构与算法:掌握一门语言,就可以开始编写程序;而数据结构和算法,则可以帮助写出更为强大的程序。 粗浅地将《编程珠玑》阅读了一遍,发现:计算机程序的根本仍然是: 数据结构 +算法。数据结构和算法通常还是估算和提高系统期望性能的关键环节。
对象和消息, 实际上是数据结构与算法的封装。 在面向对象程序中, 每一个类都是一些绑定在一起的“小数据结构”和“小算法”的携带者, 通过接口提供自己的服务;每一个对象都是状态携带者, 通过状态的变化来表征系统的变化,从而实现系统的特征和功能。
数据结构有着非常重要的作用。有时候,复杂的逻辑,只要借助一定的数据视图,就能得到很大的简化,比如日期计算。《编程珠玑I》中谈到的重要数据工具有:超文本、值-对、字典、数据库、表格、模板等。
性能估计:通常,通过算法的大O分析来粗略了解系统可能达到的性能;另外,一定的粗略估算能力也是非常必要的(见编程珠玑第七章):常识、关键参数、经验法则、量纲检验、安全系数。
接口声明:涉及到系统的设计。系统由哪些数据结构(或对象)组成,它们如何进行交互(函数,消息)以完成系统的功能; 仔细定义每一个接口的声明(功能描述、参数列表、返回值、前置条件、后置条件),并使之良好地协作。
伪代码:不要急于编程。使用伪代码来表达思路,勾勒程序的大致框架。这常常也会让思路更明晰:因为在这个过程中,通常可以发现系统有哪些子功能要实现,有利于模块化。
程序验证:你能保证自己编写的程序在任何时候都正确吗?还是仅在你视线所及的范围内正确?你期望写出的程序仅仅是看上去正确吗?当然,我们都希望能够早早完工,可是,急不得,懒不得。学习一定的程序验证技术,也是有益无害的事情。虽然起初可能麻烦一点,但一段时间习惯之后,就不会觉得怎么样了。万事开头难,过了槛就好了。
程序验证的常用方法:循环不变式和断言。这实际上是根据三种控制流来确立的技术。
编码:终于可以编写程序了。恐怕经过上述折腾,编写程序的兴致早就减得不剩多少了。可是,一切,都是为了编码更加顺利,更加有创造性。总不期望发现,编码就是在Ctrl+C/V;当编码到一定程度时,突然卡了壳;突然发现思路有问题;突然发现性能无法满足所期望的;突然发现,之前的努力都白费了?
很多人抱怨工作中的编码没有创造性可言,可是,有没有想过,你不去思考如何更有创造力的写程序,难道期望创造性自动跑到你面前吗?你不去努力使用算法、数据结构,难道它会自动跑到你跟前要求你去使用吗?你不去主动思考、勤于发现,难道你就指望奇迹自动降临于身?
编码时,尽力遵循良好的代码规范,使之可读性良好,为后面的代码重审和回顾打下良好的铺垫。
测试和调试:如何得到一段值得信赖的、可以放心复用的程序呢?苛刻的、彻底的测试。不要怕麻烦。凡事习惯就好了。编写自动化测试;掌握使用断言来进行调试。
程序测试通常可以分为两种: 自动化测试和手工测试。对于小数据集,0-10,可以构建自动化测试;另外,还要构建手工测试,使得能够对特殊情形进行测试。
代码优化: 代码优化通常意味着性能的小幅提升。通常是应用缓存原理,这需要对计算机组成和操作系统有深入的理解。代码优化通常意味着程序可靠性、可读性和可维护性的降低,程序复杂度的增加;因此要慎重进行。
代码重审和回顾:没有总结就没有进步;“写了代码就丢弃”也不能期望有进步。通过代码重审和回顾,进一步改进代码的质量:可用性、可读性、可扩展性、可移植性、安全性、可靠性。
其它:
大数据集处理问题:初学者可能通常会考虑 1– 100 之内的小数据集处理;但进入到软件开发领域,大数据集处理,海量数据处理,就成了一个必须考虑的问题。对于排序问题来说,当n < 50 ,或许采用插入排序就可以了;但当n等于几百万,几千万时,就不能采用插入排序了。这说明:小数据集的处理方案和大数据集的处理方案往往会是迥然不同的。
实用程序和工具箱:卓越的工匠都会备用自己喜爱的工具箱。程序员也不例外。初学者可能主要在于编程练习;但进入一定阶段之后,就必须考虑编写实用性强的工具和程序,而不是总停留在玩具程序的级别上。
标准库;实用程序;熟悉的IDE;开源小工具;常用数据结构;算法技术;正则表达式;数据库使用;编程技术;编程技巧;设计思路;问题求解方案;错误录;等等。
自动化重复工作:时常注意日常生活中是否有能够自动化的重复性工作,并通过编写实用程序和工具去简化它。如果当时因为时间来不及,那么,过后也应当尽力补上。
习惯的变革:人天性通常都是“能躺着就不站着”的,因此,培养了很多不好的习惯。程序员呢,通常期望能够快速编写出满足功能的程序,然后运行察看是否如预期,接着便丢到一边不再理会了。什么问题分析、伪代码?什么程序验证、测试?什么代码重审和回顾?都扔到一边去。结果是,你逃掉了一分钟的测试时间,却用了将近一小时调试时间来补偿。划得来吗?
其实,培养好的习惯,是一种一本万利的投资。只要最初一两个月坚持住,习惯成自然;之后,你便只是尽享好习惯带来的益处了。
从编程到软件开发
从某种意义上来说,在懂得《数据结构、算法技术、操作系统、组成原理》这些专业知识之后,再加上“勤于思考,善于追根究底”的探索精神,以及大量的编程实践,就可以逐渐掌握编程的基本原理和能力。
在学会编程之后,就可以阅读关于软件架构的书籍了,理解软件是如何构建起来的。数据库,计算机网络等等,实际上也是通过“编程原理+软件架构”的产物,只是它们可以被用来作为更大系统的组件。