您的位置:首页 >谈谈编程(3) 编程实践
发布于2026-04-30 阅读(0)
扫一扫,手机访问
鸠集遗失,鉴玩整理,昼夜精勤,每获一卷,遇一画,毕孜孜葺缀,竟日宝玩,可致者必货敝衣, 减粮食。妻子童仆切切嗤笑,或曰:终日为无益之事何补哉。既而叹曰:若复不为无益之事,则安能悦有涯之生。
--- 唐 张彦远 《历代名画记》

说到底,编程这件事可以拆解为三个核心要素:语言、环境和思想。这三者环环相扣,构成了程序员工作的基本盘。
关于编程语言的优劣之争,似乎从未停歇。但跳出争论本身来看,除了最底层的汇编语言,每一种高级语言、脚本、标准库乃至框架,其实都封装了大量成熟的编程经验和设计智慧。因此,与其执着于“最好”的语言,不如拓宽视野,多熟悉几种有代表性的语言。
一个比较理想的路径是:掌握一两种汇编语言(比如CISC架构的X86和RISC架构的ARM),一种面向过程的语言(C语言是经典选择),两到三种面向对象语言(C++、Ja va、Delphi各有所长),再加上一两种脚本语言(Perl、Python、Ruby都是不错的选择)。如果学有余力,不妨接触一些学术气息更浓的语言,比如Scheme,它们往往能带来截然不同的思维启发。
学习新语言的价值远不止多一门工具。它更像打开一扇通往新世界的大门,让你能吸收其背后的设计哲学,并连接上使用该语言的庞大生态——海量的源代码、技术文章和书籍都成了你的学习资源。
这里所说的“环境”,指的是程序所有外部条件的总和,主要包括开发时环境和运行时环境。编写任何一段代码,都应对其相关的环境了如指掌。
开发时环境涵盖了我们使用的编译链接工具、复用的代码库(框架、类库、控件等)、系统的逻辑架构、代码的文件组织方式,以及调试工具等一切辅助开发的要素。
运行时环境则指程序执行时所处和所依赖的一切。从小处着眼,需要清楚每段代码运行的上下文(线程与堆栈),每个变量内存空间的来龙去脉。从大处看,则要理解整个系统运行的脉络:各个逻辑模块如何协作,不同线程如何通信,调度可能在何时发生,系统中存在哪些不确定因素等等。对运行时环境理解得越透彻,写出健壮、高效代码的把握就越大。
软件世界纷繁复杂,但有一些比较“典型”的类型,构成了我们常见的开发场景:
Windows操作系统;
Linux操作系统;
编译器;
虚拟机;
调试器;
Windows的单机应用程序;
Windows的驱动程序;
Linux的应用程序和驱动程序;
基于socket的客户端-服务器程序;
数据库应用程序;
使用数据库的Web应用程序;
使用RTOS的嵌入式软件;
不使用RTOS的单片机程序;
DSP程序;
嵌入式环境的第三方程序(如J2ME应用、BREW应用、Symbian应用等);
各种中大型程序的脚本环境、插件的开发与运行环境;
Office应用程序开发;
Flash编程;
当然,软件种类实在太多,这里的“典型”也只是基于常见经验的归纳,难免挂一漏万。关键在于,程序员应当对自己所开发软件类型的运行机制有深入的了解。
以Windows和Linux这类操作系统为例,最好能概览其启动与运行的全过程:从BIOS读取主引导记录,到系统加载;从应用程序或动态链接库的装载,到操作系统核心模块的功能分工;以及Windows如何通过COM机制将功能模块组合起来。这些知识构成了PC程序运行的基本背景。
相比之下,许多嵌入式环境要简单一些,尤其是代码直接从NOR Flash运行的场景。一方面,程序员往往能接触到系统运行的所有代码;另一方面,嵌入式系统对第三方程序的支持机制相对简单,有时甚至没有,有时通过Ja va虚拟机来实现。不过,如今的智能手机应用处理器多采用NAND Flash,从BIOS启动一小段代码,再将系统载入RAM运行,同时支持应用程序动态加载,其复杂程度已非常接近PC环境。
开发单机应用程序,除了掌握语言本身,重点在于熟悉各种库、框架和组件。通用库提供了常用函数、容器算法、图形界面和程序框架,是对系统API的封装和拓展,例如:
移植性较好的C标准库、STL、Boost、Tk等;
Windows平台上的MFC、VCL(Delphi/C++ Builder);
Linux平台上的ncurses、X Window、GTK、Qt等;
数据库访问方面:VC++的ODBC、DAO、ADO,Borland的BDE等;
Borland还推出过源码级跨平台的库,如Delphi/Kylix的CLX、dbExpress;
以及CPAN上大量的Perl模块、Ja va庞大的类库等等。
上面列举的是一些通用库。实际上,还有海量针对特定领域的专用库,以及提供二进制接口的组件和控件。
编译器和虚拟机本质上也是单机应用程序(在嵌入式环境中,虚拟机可能作为系统软件的一个模块)。但它们地位特殊。作为程序员,有必要厘清编译器、集成开发环境、软件框架、虚拟机和操作系统各自为我们承担了哪些工作。
同样,调试器也是一个应用程序。理解调试器的基本原理、能力边界和限制至关重要。当调试器与目标程序运行在不同CPU上时,调试是如何实现的?方式多种多样:例如JTAG调试直接利用目标CPU的调试接口;串口调试则需要将一段调试代码与目标程序链接,通过嵌入的调试代码与PC机上的调试器通信。不同的实现方式直接决定了调试器的功能和局限。
眼下最热门的软件类型,莫过于基于数据库的Web应用了,各类网站、网络游戏、企业及政务管理系统皆属此列。这个领域汇聚了产业链各环节的大量厂商,以及五花八门的Web服务器、基础平台和应用开发框架。随便列举一下,就能看到一串熟悉的名词:
HTML/CSS和CGI;
Ja va Applet、Ja vaScript、ActiveX控件;
PHP、ASP、JSP、Servlet;
.NET家族:ASP.NET、ADO.NET等;
J2EE(带或不带EJB)、Spring、Struts、Hibernate;
Ruby on Rails、Plone等。
在单一软件类型上,能汇聚如此多的开发技术、框架和模式,堪称蔚为壮观。这个领域也确实成了各种最新编程思想、方法和设计模式的演武场,其地位堪比当年的编译器领域,值得所有程序员深入研究和学习。
COM技术最初可视为OLE发展的副产品,但其重要性早已超越了OLE本身。它首先被独立出来,成为OLE和ActiveX的基础,进而逐步演变为Windows平台上二进制组件集成的基石。COM与RPC结合产生了DCOM,DCOM再与MTS结合又催生了COM+。尽管这些技术都扎根于Windows平台,但组件技术背后的基本思想却是超越具体环境的。这揭示了一个关键:对程序员而言,存在着一个独立于特定语言和环境的领域,那就是编程的思想。
举个例子,看看高通公司的BREW平台,就能发现它从COM中汲取了多少养分。嵌入式平台的程序员借鉴PC平台的技术,这正是编程思想价值的体现。对于程序员来说,各种编程思想和设计模式才是最宝贵的财富。这里所说的设计模式,并不局限于GOF的《设计模式》一书,任何惯用的、有效的解决问题的手法,都可以被视为一种模式。
一些根本的编程思想,其实源于更普遍的智慧。例如,约翰·洛克在1690年的《人类理解论》中写道:
“心智的活动,除了尽力产生各种简单的认识之外,主要表现在以下三个方面:1)将若干简单认识组合成一个复合认识,由此产生出各种复杂的认识。2)将两个认识放在一起对照(在这样做时并不将它们合而为一),不管它们如何简单或者复杂,由此得到有关它们的相互关系的认识。3)将有关认识与那些在实际中和它们同在的所有其他认识隔离开,这就是抽象。所有具有普遍性的认识都是这样得到的。”
程序员能从这段三百多年前的文字中看到什么?组合、对照、抽象——这些基本的思维工具,恰恰也是程序员最核心的工具。
编程是一门实践性极强的技艺。就像必须跳进水里才能学会游泳一样,我们必须大量阅读和编写代码,才能真正掌握编程。
任何技艺的学习都包含“知识”和“能力”两部分。知识是明确的,可以通过时间积累。能力则更微妙,难以言传,也不易掌握。通常,能力强的人能更快地积累知识,善于从既往实践中总结规律,复用已有模式。他们能迅速对复杂环境形成清晰认知,并用最简洁、优雅的方式解决问题。如何提升编程能力,路径因人而异,每个人都应找到适合自己的学习节奏和规划。
编程的表象千变万化,但其背后仍存在一些规律。在特定条件下,这样做往往比那样做更好。我们可以将这些经验总结为原则、规则或任何其他名称。
下面松散地列举了一些在实践中被反复提及的规则。重要的是,我们应在自己的实践中检验这些规则,并最终总结出属于自己的心得。
规则一 这世界上唯一的真理就是不要盲目相信真理
从某种意义上说,原则、规矩这类东西,就是用来被打破的。所谓原则,不过是对历史上某种成功经验的总结。如果我们踏入的河流与前人走过的非常相似,自然可以参考其经验。但关键在于,我们是主动地“拿来”,而非被动地“遵守”。
规则二 未蕴而变,自欺也;知律而变,智者之道也
这句话原指学习诗词,必须先了解格律,然后才能谈变化,否则就是自欺欺人。编程亦然。在有资格打破一条原则之前,首先要彻底了解这条原则。先掌握事物的规律,而后才是变化与灵活应用。不要轻易否定自己尚未理解的东西。
规则三 寻找结构和成本的平衡点。当结构不能容纳变化时,重构代码到新的平衡点
著名的开闭原则要求:软件实体应对扩展开放,对修改关闭。也就是说,理想的软件结构应该允许在不修改原有代码的前提下扩展新功能。
但好的结构是有成本的。程序员总是在做权衡,寻找结构与成本之间的最佳平衡点。我们会预留一些弹性,让结构能够承受一定程度的需求变化。
规则四 要针对接口编程,不要针对实现编程
这一点前文已有涉及。我们都希望通过工作有所收获,积累可复用的经验。只有将代码分解为尽可能独立的模块,并针对接口而非具体实现进行编程,才能有效保护我们的工作成果,实现经验的沉淀和积累。
规则五 最少知识原则:模块对外界的了解应当尽可能少
这也是一条被反复验证的原则。所谓“圣人之治”,往往是“虚其心,实其腹;弱其志,强其骨,常使民无知无欲”。让被管理的对象尽可能简单,对象间的关系尽可能清晰,可以显著降低管理的复杂度与成本。
在面向对象编程中,“组合优于继承”的倡导,正是因为继承会将基类的实现细节暴露给子类,导致两者耦合过紧。此外,继承关系是静态的,而组合方式能提供更大的灵活性。
规则六 尊重习惯
在任何领域进行编程,都应尽量了解并遵循该领域的既有习惯和约定。这样做的好处显而易见:他人能更容易地理解和使用你的程序,出错的概率也会降低。
规则七 程序中不要出现Magic Number或其他神秘的东西
Magic Number就是指代码中直接出现的、意义不明的数字,比如17、42等。请用具有描述性的常量名来替代它们。恰当的常量名、变量名和函数名本身就是最好的注释。结构清晰、命名良好的代码,甚至可能不需要额外的注释。
“需要大量注释的代码”本身就是一种“代码坏味道”,这常常意味着代码需要重构,以使其更易于理解。
规则八 “两顶帽子”和“小步前进”
“两顶帽子”的方法前文讨论过。面对复杂问题,我们不仅要从结构上分解它,还要将实现步骤尽可能细化,集中优势兵力,一次只解决一小部分问题。n-1个问题总比n个问题好对付。随着我们逐步推进,自身越来越强大,问题则越来越弱小,关键就在于将这种优势保持到最后。而“保持优势”的诀窍,在于正确的理解和合理的任务划分。
规则九 适当地引入中间层,可以解决所有问题
这条规则的实质是“抽象”。遇到难题时,多引入一层接口,将问题封装起来,往往能柳暗花明。
抽象本身并没有消除系统的复杂性,但它减少了我们在某一时刻需要处理的细节数量。或者说,抽象将复杂性隐藏在接口和模型背后,推迟了我们不得不处理这些复杂性的时刻。
这条规则还有一个更形象的说法:“多做白日梦”。假设我们已经得到了梦寐以求的解决方案或工具,接下来会怎样?通过这种“白日梦”式的思考,我们可以在更高层次上审视问题,或许能找到解题线索,甚至可能发现这个问题本身价值不大。即使一无所获,我们的损失也微乎其微。
规则十 不要依赖调试,尽可能地依赖分析和推理
一个有用的习惯是:在动手调试前,先在头脑中预演整个调试过程,计算需要做什么,以及可能得到什么结果。“预演”的结果往往是,发现根本不需要这样调试,或者应该尝试另一种方法。
根据测不准原理,任何实际的测量和调试行为,都会影响甚至改变研究对象的状态。因此,调试应该用来辅助和验证我们的推测,而不是作为解决问题的首要依赖。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9