如何写程序(1)

来源:互联网 发布:360防蹭网软件手机 编辑:程序博客网 时间:2024/05/16 20:31
本文是鄙人在学习《代码大全2》的一些心得和笔记,以及自己的一些理解,在此与各位码农,共同探讨和学习。
我想先从如何写程序来深入浅出的讨论编程的一些事儿,现在我们一起开始吧。
 这篇文章对应书的第六章的前面的部分,有兴趣的可以看下原著。

1、如何应用ADT。
1.1 ADT是什么?
ADT其实就是将数据和操作联合起的集合,对于面向对象编程来讲,说白了就是类,对于非面向对象程序来讲就是结构。

1.2 ADT有何好处?
(1) 可以隐藏实现的细节
就是将处理过程隐藏起来,举个例子:对于空调来讲,开机可能要进行很复杂的操作,开启压缩机,读取系统设置,改变电压等等。假设空调是一个ADT,遥控器上的一个开启只是一个简单到爆的按钮。
把一系列操作隐藏起来,可以减少外部逻辑的复杂性,更重要的是,如果一个不懂空调的人,来操纵空调,那将是非常危险的,不管对于用户还是空调。而且对于用户来讲,他也不关心空调是什么技术实现的,他只关心管用不管用,而且你制作遥控器(空调的外部接口)的时候,就应该把用户当成小白。而且从使用上来讲,封装可以尽量让用户避免记住一些过程,记住更多方法本身就很难,在记住他们的调用顺序就更难了,就像OpengGL很难学一样。除非你是黑客,否则你也不像去底层直接改变某些东西。

(2) 改动不影响整个程序
 比如我封装了一个字体,所有的地方都用到了这个字体,但是如果我没有字体的ADT,换成了用文件名,设置字号的一系列底层的操作来控制,万一我想改变字体的字号,那么将是非常恐怖的,你需要把所有用到字体的地方都改一遍,那将是伤筋动骨的大事了,而且很容易发生,漏了,忘了的情况。然而对于开发来讲,改动是常有的,也是避免不了的,尤其是某些设计。

(3)让接口能提供更多的信息
比如现在的手机或者电脑,甚至电视都会提供高清屏(Retina),如果你封装了屏幕的ADT,你大可以提供setRetina(bool)这种接口,这种接口的要比1024*768,1920*1080看起来有意义的多。1024这种被称为Magic Number(有机会会讲到),在写程序的时候应该尽量避免出现Magic Number。

(4) 更容易改善性能
由于有了(2)的优点,你单独的优化你的子程序,你的ADT,你的整个程序的性能都会有所提升,这是显而易见的。

(5)Debug的时候更容易找到错误
比如让字体加粗,可能会使用这样的代码 font.attribute |= 0x02。这样的缺点显而易见,因为有可能你会把0x02写成0x20,而这种错误,往往是最脑残,最不容易发现的,而且是最容易犯的,而你可能会认为font本身有问题,那就更糟糕了,但是如果你提供setBold(bool)的接口,一旦出错就显而易见了,肯定是font内部出错,而内部也仅需要改一个地方就可以消除Bug。

(6)程序更具有可读性
由上例可知,setBold比0x02看起来好理解的多,也就想(1)里面说的,你要把你程序的使用者当小白,你可以在成员名称上,做的让你的程序更具有可读性,而面向对象本身,也是提供了谁的概念,“谁干什么”本身也比一切都是"我干什么"好理解,而且逻辑判断也简单。

(7)避免大量的传递数据
写过C语言的函数,并且不用结构,你就知道用ADT的好处了,导出传数据,会让程序的逻辑一团糟,可读性也很差,这也就是说明了为何OpenGL(C语言的接口)显得很难学,因为都是大量的数据传递,而且你还要考虑变不变的问题,显得很糟糕。

(8)可以像现实生活中那样操作实体(对象)
足够好的面向对象和抽象,就可以实现像现实生活中,那样使用,更容易让使用者对程序,架构进行组织和构建。

1.3 一些ADT的应用范围 
(1) 把常用的数据类型封装成ADT,并且不在用底层的类型
写程序经常用的Model类,就是一个很好地体现,而且比如表(list) ,散列表(map),队列(queue),栈(stack),而不适用基本的数组等结构也是一种体现。

(2)把文件这种常用的比较"真实"的对象也看做ADT
例如C语言的FILE指针就是一个很好的例子

(3)简单的事物也可作ADT
比如灯,提供开,和关方法,把额定电压等也封装进去,这会让整个系统看起来是一个完整的系统,程序更有可读性。

(4)不要让ADT依赖存储介质
 比如一个报表,你可以将其内容封装到一个文件类型上,但是如果把报表读取到内存中,以及修改等操作就不起作用了,正确的做法是要将存储介质和内容(Model对象)分离成两部分,至少是两个以上的ADT,而不是混合在一起的一个。

1.4 好的抽象
(1)类的接口应该展现一致的抽象层次
如果你的一个类不够抽象和完整,里面掺杂了多个抽象层次,应该封装成多个类,而不是一个
举个例子

class Members : public ListContainer
{
public :
          void addMember(Member m);          a
          void removeMember(Member m);         b  
          
          
Member NextItemInList();        c      //list需要实现的接口
          
Member FirstItem();       d    //list需要实现的接口
          
Member LastItem();        e     //list需要实现的接口

a与b在同一个抽象层次上,而c,d,e在同一个抽象层次上,而ab与cde很明显不在同一个抽象层次上,这种代码是混乱的,也是容易出错的。
正确的做法应该是把使用类库这一事实隐藏起来,比如
 
class Members
{
          void addMember(Member m);          a
          void removeMember(Member m);         b  
          Member NextMember();        c      //封装过的接口
          Member FirstMember();       d    //封装过的接口
          Member LastMember();        e   //封装过的接口

          private:
          ListContainer  m_memberList;    //以藏使用类库这一事实 



这样abcde五个接口的抽象层次都在Member上 ,而且Item更抽象,不能一眼看出Item(组件)是什么,而Memer一目了然。Item的应该到容器的抽象层次,组件(Item)不应该出现在成员们(Members)的世界里,这才是同一层次的抽象。

(2)一定要理解类所实现的抽象到底是什么
 有些类很像,但是你必须要理解你真正要的是什么,(代码大全的例子)我想用栅格组件(grid显示表格的组件)而不是电子表格(spreadSheet)组件,电子表格比栅格复杂的多,提供了150个接口。而我们的栅格可能只需要十几个,我们想让一个程序员一个栅格类,隐藏掉栅格底层采用电子表格这一事实,他很抱怨的道:"根本没有必要"。过几天,他交来了栅格类,而这个类竟然把电子表格的150个接口,全部暴露出来,然而这并不是我们想要,我们只需要,我们用到的那几个接口,而且暴露150个接口,就要支持150个接口的功能,易用性和稳定性都偏离了我们的初衷。

 (3) 提供成对的服务
一般有开,就有关,才比较合理,比如数据库。总有一个相反的子程序与之对应。但是具体也要看需求,并不能盲目的进行相反的操作。

(4) 把不相关的信息扔出去
有时候你发现,有很多子程序(函数),只访问到一半属性,那么这时候,你应该考虑是否把这个类拆成了两个,一般出现这种情况,说明你把两个客观上不相关的ADT主观的强加到一起了。与(1)不同的时,(1)指的是抽象层次,这里指的是相关性。 

(5)尽可能让参数类型符合语法,而不是表达语义
比如你在一个子程序填写了一些注释如下:
”不允许传未经初始化的参数,否则会导致程序崩溃“
这时候你该注意了,你现在就犯了(5)中的错误,你应该想办法把语义错误,转变成语法错误,想办法让编译器提出错误或者警告。当然你也可以用断言+打印错误日志来提示给用户,你应当尽量不给错误可能出现的机会,而语法错误不解决就无法运行,这样就可保证在Debug的时候,不必考虑你的程序出现错误的可能,而浪费时间,陷入困境。还有一点,可以让你的接口使用者更好的理解你的程序。

(6) 谨防在修改时破坏接口的抽象
有时候你可能会盲目的扩展你的接口,你可能认为你的接口越多,功能就越强大,但是这样往往会犯如下错误:
class Member
{
public:
       String  getName() const;    a

       SqlQuery findMemberByName(string Name);    b

这种代码,已经是很难做到清晰和抽象了,已经变成了一堆零散功能的大杂烩了。
查询数据库跟Member本身并不存在什么关系,非要强加在一起,就破坏了Member的抽象性,让它跟现实生活中的Member,相差甚远。


(7)不要添加与接口抽象不一致的公用成员
每次添加成员的时候,你应当像函数一样去考虑,它是不是属于这个类

(8) 同时考虑抽象性和内聚性
抽象性和内聚性往往是密不可分的,好的抽象往往有好的内聚,而好的内聚也会有好的抽象。
理解抽象可能比理解内聚更容易一些,如果你发现你的内聚性不够的话,你又不知道从何改起,那么不妨你看看它抽象不抽象。

今天就写到这里了 


0 0
原创粉丝点击