Linux初学者直接寻求经验丰富的老手来帮忙,是最为快捷的学习方法。但是困惑也随之而来。初学者们会发现,找不同的人在解决相同问题的时候,所采用的方法都是大相径庭的,根本找不到规律。老手们在解决同一个问题上所持的观点也非常迥异,有些甚至是截然相反。更让初学者恼火的是,不同的Linux发行版,在对待同一个问题的时候,也会有完全不同的实现方法。这些越发地让初学者感到恐惧,不知道Linux该如何学起。 需要面对这种困惑的不单单是初学者们,那些所谓“经验丰富”的老手们也大多是在为了这样那样的差别而疲于奔命。难道Linux是一个十分“不靠谱”的系统,解决一个问题就没有一个统一的方法吗? 答案是:这个真没有! 因为Linux本身的设计是一种基于机制与策略相分离的设计,又因为Linux极其自由的特性,任何人都可以修改、包装、发行,还不用对任何人负责,这就导致了不同的发行版在提供同一个功能的时候会有很大的实现上的差别。而且即便在相同的发行版中,不同的用户由于对知识覆盖面的不同,依然可以选择不同的方式去完成同一个功能。结果就是,从表面看,Linux在使用上几乎没有规律可寻。那么要学好Linux,能够轻松驾驭Linux,还能编写出符合Linux传统的程序,有技巧吗? 答案是:这个可以有! 4.4.1 机制与策略 透过现象看本质,到底什么是机制,什么是策略,为什么要将机制与策略分离呢? 要搞清楚机制和策略,可以从我们生活中最实在的例子出发。比如:吃饭和睡觉。吃饭可以在家里吃、去饭店吃、去亲戚朋友家吃,可以站着吃、坐着吃、躺在沙发上边看电视边吃,怎么吃都是吃;睡觉可以在床上睡、地上睡、沙发上睡,可以躺着睡、趴着睡、蜷着睡、搂着老婆一起睡,怎么睡都是睡。那么吃饭和睡觉就是机制。至于怎么吃,怎么睡则是策略。至于怎么吃着香,怎么睡着沉,那是你自个的事儿,别人说什么都是白说。 机制和策略反映在系统设计上,就是目标功能和实现方法。目标功能是必须要做的事情,为了实现一个目标功能可以有很多种方法。具体采用那种方法往往有很多因素的考量,如易用性、扩展性、简洁性、效率等方面,至于是好是坏,用的人舒服就行。目标功能却不是这样,它很多时候必不可少。就像饭得吃,觉得睡一样,少一样你都活不了。 从机制和策略的这些差别上,我们就很容易得出一个结论——它们最好能分开,别掺合在一起。因为如果生把它们揉在一起的话,那至少会有两个负面效应:一是策略变得很死板,很难适应未来的需求变化;二是任何策略上的变化很可能会改变机制本身。这就像有人规定你必须怎么吃饭和睡觉一样,基本上都会让你吃不香睡不好。如果能将两者剥离,就有可能在探索新策略的时候不会对机制产生什么实质性的影响。毕竟总是能吃得香睡得好,身体才能倍儿棒不是? 实现这种剥离的方法也很简单。最方便的就是将一个应用当作一个库来写,这个库包含许多能够由内嵌脚本语言驱动的服务程序,而整个应用的控制流程则用脚本来撰写。前面所讲的“两大笨”不就是朝着这个目标而努力的吗? 4.4.2 接口与引擎 要说设计一个简单应用也需要抽象出机制和策略来,那显然太过高瞻远瞩了,有点够不到边儿的感觉。但是这种设计原则依然值得学习和参考。为了能够得着边儿,就放低一些姿态,让接口与引擎分离。 这里所说的接口与面向对象中的interface可不是一回事。这里的接口是指程序与人的交互界面,可以是文本的,也可以图形的。至于引擎也跟汽车、摩托车之类的没什么关系,它是指一个程序的核心算法。 在Linux中这样的例子很常见,最为有名的就是vi和Emacs了。在文本的控制台下,vi和Emacs提供了基于文本的操作界面;在图形环境下,vi和Emacs又能提供基于图形的操作界面。在一些脚本语言的解释器中,这种设计也很常见,比如Python。当没有指定具体要执行的py文件时,“python”命令会进入一个交互式界面;而指定具体要执行的py文件时,“python”就会执行对应的脚本,并且在脚本结束之后也随之退出。 这样做的最大好处就是可以减少工作量,同时又具备良好的适应能力。因为有关文本编辑或脚本解释的代码只需要写一份就行,程序如何跟人交互可以视当前环境而定。带给用户的最大感受就是这个程序很贴心,永远能满足用户的喜好。 其实这就是现如今特别流行的MVC(模型—视图—控制器)模式。M代表“模型”,在Linux世界里通常被称为“引擎”,模型包含了应用程序专属的数据结构和逻辑;V代表“视图”,是应用程序专属数据结构和逻辑的可视化形式,视图组件一般由模型负责通知更新,并且作出相应变化;C代表“控制器”,处理用户的请求并将它们反馈给模型。在具体实践中,视图和控制器部分的结合,往往比它们与模型的结合更为紧密,有些时候就是一体的。 在Linux中,MVC模式的应用比其他任何领域都要普遍,这主要得益于Unix中“只做一件事并做好”的优秀传统,和自身强大易用的IPC(进程间通信)机制。估计好多人在看到这个事实的时候,会非常憎恨自己没有早一点学习Linux或Unix。否则那些什么倒霉的Struts 或许就会出自于自己的手下,让Craig McClanahan 见鬼去! 4.4.3 不用重新造轮子 这年头程序员们最喜闻乐见的话题,应该就是“重用”了,也就是“不用重新造轮子”。重用,大多是指源代码的重用,于是搞出了各种面向对象的编程方法,甚至总结出了一套“设计模式”。好多教科书也都是这么教导我们的。但是好多人一想到“重用”就头晕,尤其是设计模式。这并不是说设计模式不好,只是学习并能掌握设计模式的确不是一件容易的事儿,必须花费大量的时间和精力去想象和理解,还得有足够的实践机会。要是能够将设计模式运用得炉火纯青、收放自如的话,这年头基本上就会成为被众多粉丝顶礼膜拜的技术大神。 如果真是这样,那么Linux下的程序员就人人都是大神。得益于Linux的策略与机制分离设计,“重用”这事儿就太小儿科了。而且在Linux下的重用,你连代码长什么样都不用管,编好的程序直接重用。如果你还没有忘记前面的两个小节(“万般皆文本”和“四处用脚本”)的话,那么在那个地方所介绍的一些技巧就是干这种事儿的。 尤其是在编写具有GUI界面的工具程序时更是如此。因为很多程序在CLI的文本界面中就已经提供了,比如查看系统进程信息的ps命令。如果你想编写一个GUI界面的类似于Windows的“进程管理器”这样的程序,完全不用了解Linux内部的运行机制和有关的系统API,直接利用ps命令做一个GUI的外壳就成了。如果不只是查看进程,还想关闭一些占用你内存的进程,还有kill命令可以包装。查看磁盘使用率还有du命令,等等…… 4.4.4 内在的支持 如果你说用shell做不了图形界面,那没关系。不是还有各种强大的脚本语言吗?比如Python,做个GUI完全不成问题。如果你觉得用Python做GUI有点怪,那不是还有C,甚至C++呢吗? 永远不要忘记Linux是一个支持多进程的操作系统。在Linux中创建进程比创建线程还容易,Linux进程的资源开销也一点不比线程多。进程与线程更为不同的特性是,进程并不总是一个程序的并行执行分支,还可以是另外一个完全独立的程序,甚至是脚本。那么制作一个GUI外壳就非常简单了,直接利用多进程机制启动另外一个具备相关功能的程序或脚本就行了。 我想,现在在你的心中一定是有一个大大的问号的。众所周知的是:进程具有完全独立的内存和资源空间,互相不会干涉。那么两个进程如何才能像线程那样自如的交换数据,能够实现我们想要的GUI包装呢? 别忘了,前面就提到过,Linux自身就提供了一套强大易用的IPC机制。这使得在Linux中,两个进程进行数据交换是极其容易的事情,甚至比线程那种“资源共享同步”还要方便。 Linux提供的IPC机制主要有:信号、管道、IO重定向、共享内存、套接字等多种类型。 这其中信号相对较弱,只能提供一个字长的信息,这就相当于两个人平时各干各的,有事儿时吼一声。这种方式虽然即简陋又简单,但是效果有时非常好。 比较常用的是管道和IO重定向,前面几个小节就已经多次提到过了。管道只提供单向通信能力,即一侧输入一侧输出。在具体编程中,管道经常与IO重定向结合使用,最常用的技巧就是将一个进程的标准输出重定向给管道的输入端,另外一个进程就能从管道的输出端获取前者的输出结果。 共享内存很适合大块数据的共享,但是否必须要用,应做多方面的考量,因为经常会引起竞争。解决这种竞争不但麻烦,还到处都是陷阱,一不小心就容易犯错误。不到万不得已不建议使用。 套接字可以说是最为强大的灵活的IPC机制了。原本套接字是用于网络通信的,但是那本身就是一种不同进程间的通信,只是这两个进程不在一个机器上罢了。既然不在一个机器上都能通信,那么在一个机器上的自然也不在话下了。 有这么多IPC机制可以选择,总是会有一种适合于你的。而且这也提醒你,如果你的程序规模连做“接口和引擎分离”都觉得有些小题大做,那么就可以考虑支持一种IPC,或许在不久的将来就有人能够用得上。其实,只要将结果输出到屏幕上(代表标准输出),那就已经支持了。 4.4.5 沉默是金,吝啬是银 如果你从来没想过要去了解什么IPC机制,或者对这些完全不敢兴趣,只是想写程序。那么也不要着急上来就是GUI,而且也不要搞什么交互式操作,更不要有多么复杂NB的屏幕输出提示。换句话说,就是先让程序的核心功能运行起来,多简陋都行。在Linux下一直流行一句名言:“沉默是金,吝啬是银。” 不让程序有多么复杂NB的输出提示,说起来还是有一些历史原因的。遥想Unix诞生之初,显示器可是金贵的奢侈品。那个时候程序要输出点什么可见的东西都是通过打印机打印出来的。首先就是不环保,其次就是慢得要死,输出多了肯定遭人骂。虽然现在CRT显示器的价格可能比同等重量的切糕便宜不少,但是这一传统还是有很强的实用意义的。最典型的就是当有人将你的程序的屏幕输出重定向给另外一个程序的输入时,那些看似复杂NB的输出,好多时候基本上是没用的。好一点的情况是付出一点时间来找出有用的,最坏的情况会导致根本没法用,不幸的是最坏的情况总是会发生。一旦这样,你的程序恐怕只能是自娱自乐的了。少说话,多办事,永远是有好处的。沉默是金。 吝啬是银就是指不要轻易写大程序。估计大多数程序员在写GUI程序时,都会有类似经验,那就是GUI代码占据了程序95%的代码,而且跟程序本身的功能还没多大关系,bug还经常出现在这个地方。在任何时候,能够尽快将没有问题的程序交给用户,都是最明智的决定。即便他们会抱怨使用起来不方便,但毕竟他们已经开始用了。至于好用不好用、方便不方便的问题,可以接下来协商解决。前面说不要搞什么交互式操作,也是基于这个因素。因为你最初设计的交互式操作,用户也未必买账,而且还延误了你的开发时间,怎么算都是不划算的。 一旦你的程序做到了即沉默又吝啬,那么接下来的问题就好办了,除非核心功能上有毛病。因为这个时候你已经做到了“机制与策略”相分离了。用户喜欢什么样的操作方式,给他们提供就是了。或者用户自己来增加新的操作方式,也是没什么问题的。