个人的一点编程感悟

到新公司也差不多有2年时间了,今天闲来无事,总结总结这段时间的收获吧。(由于本人目前也只是菜鸟级别,部分观点可能略显肤浅,因此,本文更多的是从初级程序员的角度来分析,如果你是高手的话,就没必要浪费你的时间了,呵呵)

注:欢迎大家转载,但请注明出处,谢谢 http://blog.csdn.net/xinghun61/article/details/7537190

1. 何谓优秀的代码

不知大家有没有思考过这个问题,什么样的代码算是优秀的代码?代码执行效率非常高,被极度优化过?运用了大量先进的技术?采用了大量的设计模式?

个人感觉这些都不是最重要的,所谓优秀的代码,就是能让别人看得懂的代码,而上面这些技术如果被滥用,无疑会使代码复杂化,不便于后续维护。那么,怎样的代码才能让别人看得懂呢?

<1 必要的注释

其实,在我所服务过的公司中,对代码注释都没有明确的要求,所以,注释到底要详细到什么地步我也不好把握,但有一点应该是可以肯定的,那就是在3个月后,回头再看自己写的代码,自己应该能看懂才行吧(可惜在现实工作中,还真有同事看不懂自己的代码,呵呵)

<2 规范的代码排版

这个大家可别觉得和功能实现无关而忽略了,这个可是后期维护人员能轻松看懂你写的代码的最基本的条件,试问下面的代码,哪种更容易看懂?

if AParam = CInvalidValue then AParam := CDefaultValue; //至少我在看到这行代码时往往会忽略掉后面的赋值逻辑
  ShowMessage(AParam);


 

if AParam = CInvalidValue then
  AParam := CDefaultValue;
ShowMessage(AParam); //这里ShowMessage和if的缩进距离一样,因此,很容易判断其逻辑上是属于同一层的,if之后,肯定会执行ShowMessage,而上例我会认为ShowMessage不是每次都会执行

<3 避免重复

都说程序员是懒惰的,从不写重复的东西,但为何现在公司的程序员都这么勤快呢(至少我现在所在的公司就是这样,呵呵)
其实要做到不重复,应该并不简单。

首先,对类的基本概念,要非常清晰,对封装(不同访问级别的合理定义),继承,多样性(虚方法和重写)这些老生常谈的概念要了然于胸,做到灵活运用。只要做到这点,应该可以省去50%的重复代码。对于类的使用场景,大致分为2种:

(a) <可参考继承的概念来理解>对现有标准控件进行改造,以适应现在业务需求的要求。例如,现在程序越做越漂亮,往往都有换肤的功能,而Delphi标准控件是没这功能的(好像XE2有吧,没怎么关注过,呵呵),因此,我往往会从标准控件中继承一个子类,然后,重写其Paint函数来添加皮肤功能。

(b) <可参考多样性来理解>对于某一类界面,例如安装界面,其控件布局都是一样的,包含主标题,中间功能区,上一步、下一步按钮等等,因此,我们完全可以定义一个基类,在基类中实现控件布局的功能及对窗口链(当点上一步、下一步时,其实会将各个窗口组成一个链式的结构)的管理,在子类中实现中间功能区的功能,以达到对公共部分的封装。否则,如果对每个界面写个实现类,那么,光要实现上一步、下一步的功能,估计都会郁闷的要死,毕竟,所有可能的组合关系可是个笛卡尔集啊。

其次,灵活运用接口。像Delphi这样单根继承的语言,类的使用也是有很多局限性的,而接口在这种情况下就能弥补这种不足。例如,我们在使用表格控件时,会不会觉得其只有一种数据编辑方式(TInplaceEdit控件)太单调,我们如果想在不同的列上显示不同的编辑控件,该如何处理,例如,日期列显示TDateTimePicker,性别列显示TCheckBox,国籍列显示TComboBox。由于这些类都不是从同一个基类继承的,因此,我们没法通过封装基类的方式来处理,因此,就可以引入接口,将表格和编辑控件的交互方法封装到接口中,表格只针对接口进行操作,各个编辑控件通过实现这个统一的接口,以期达到代码封装的目的。

最后,也是最最简单的避免重复的方法就是函数,将相同的代码重新封装成一个函数,这个就不再展开了,慢慢养成习惯就好了。

<4 统一的变量、函数命名方式

由于没有编码规范,现在公司的代码看起来乱七八糟,变量命名有用"v"开头的,有用"_"开头的,更有甚者,有直接用a、b、c的,而我自己则采用了C的骆驼命名规则外加自己的一点创造,因此,导致在看其他人的代码时,效率极低,再加上代码格式不规范,警告提示一堆一堆的,那看起来真是叫一个郁闷啦。所以,这里也有必要吐槽一番,发泄一下多年来的郁闷,以示提醒吧,呵呵。

2. 现状分析

吐槽了这么久,也该聊点技术了。

先谈谈我所发现的目前公司代码的问题点,后面会针对这些问题给出解决方案。

<1 大量的结构类型定义

由于目前的主要工作是做UI,因此,牵涉到大量不同类型数据的界面展示问题,而这种复杂的结构类型及缺少对结构的统一处理方式,导致了繁杂的数据解析及界面展示逻辑。例如,某个控件,里面包含4、5个表格展示的功能,但由于数据结构不一致,导致绘制表格的功能被完完整整的重复实现了5遍,不仅代码及不稳定,经常出现Bug,而且,出现Bug之后,光定位问题的原因,都要费好大的劲。

<2 及不规范的数据变更通知方式

数据是在不停的变动的,当数据变动之后,就需要同步的通知界面对应部分重新进行绘制,而由于上面实现的复杂性,导致这种有变动才更新、哪里变动就重绘哪里的思路变得不可能,最终结果就是界面经常做了大量的重复绘制,整个程序的性能极低。

<3 缺少必要的线程保护

在收发数据时,为了提高效率,防止界面假死,我们往往都会在线程中来处理,因此,免不了要用到锁,而由于前面提到的复杂性,导致锁的粒度设置过大或过小,要么起不到线程同步的效果,要么加深了对性能的影响。

<4 数据结构和功能模块的紧耦合

由于功能包需要直接引用数据结构定义文件来解析每个字段的值,导致功能模块和数据紧耦合在一起,结果是结构定义中一个小小的改动,可能导致所有功能包都需要重新编译,这是多么郁闷的事情啊。

3. 解决方案

针对上面的种种问题,最近设计了几个控件,用于统一数据操作方式,达到简化代码的目的,而这些控件也可以说是最近这2年所有开发经验的一个结晶吧,呵呵。

<1 设计思路

参考了Delphi的DBExpress的设计,因此,这套控件大致上也分为TConnection、TDataSet、TDataSource及各种数据感知控件。

TConnection——用于对数据收发控件进行封装。其本质上是封装了对一个叫IConnection的接口的操作。

TDataSet——对数据结构的2次封装,通过统一的方式来读写里面的数据。其本质是对一个叫TDataItem的封装。

        TDataItem——用于代替上面提到的各种结构类型,是今后定义的所有数据结构的基类,在定义数据结构时,每个字段都要定义成published类型的属性。

        TData——TDataItem类型的数组,内部通过TList实现。

TDataSource——消息传感器,其用途是当TDataSet中的数据有变动时,通知关联的数据感知控件进行重绘。

数据感知控件——数据的最终显示画面。

<2 技术盘点

(a) RTTI。这个其实也是这套控件中最核心的部分,其在TDataItem中会用到,TDataSet通过字段的字符串名称就可以获取到对应字段的值来。而RTTI最核心的代码无非就是SetXXXProp(例如SetOrdProp、SetInt64Prop、SetFloatProp等)和GetXXXProp(例如GetOrdProp、GetInt64Prop、GetFloatProp),然后,就是对TPropInfo结构的各种解析了,具体方法问“度娘”吧,一搜一大把。

(b) 运算符重载。在读写数据时,由于数据类型不一样,有Integer、Extended、String等等,因此,需要一种统一的类型来表示这些不同的数据。在Delphi中,有一个叫Variant的类型可以实现这种需求,可惜,其无法实现对类的封装,所以,我们只有自力更生啦,通过运算符隐式转换,来自己实现这种功能(注:由于运算符重载是在2006版本中实现的,因此,这套控件在之前的版本中是无法正常工作的)。运算符重载定义的大致代码如下:

  TCommonData = record
    FTemp: String; //不要使用这个变量,这个只是为了给FString用的,防止引用计数变为0
    class operator Implicit(AValue: Integer): TCommonData; overload;
    class operator Implicit(AValue: Int64): TCommonData; overload;
    //......
    function AsInteger: Integer;
    function AsInt64: Int64;
    //......
    class operator Equal(ALeft: TCommonData; ARight: TCommonData): Boolean;
    class operator GreaterThan(ALeft: TCommonData; ARight: TCommonData): Boolean;
    class operator LessThan(ALeft: TCommonData; ARight: TCommonData): Boolean;
    case FVariantType: TVariantType of
      vtNull: ();
      vtInteger: (FInteger: Integer);
      vtInt64: (FInt64: Int64);
      vtChar: (FChar: Char);
      vtFloat: (FFloat: Extended);
      vtString: (FString: PChar);
      vtClass: (FClass: TObject);
  end;


<3 运作流程

控件分成2个bpl来实现:

DataStructure.bpl——所有数据结构的定义包。类型定义全部放到implementation段中(这样做是防止功能代码直接引用这个包),在包的初始化过程中,通过调用TConnection上定义的全局函数(通过类方法实现),将结构定义“注册”到系统中(所有结构由TConnection统一管理)。

CoreControl.bpl——包含TConnection、TDataSet、TDataSource、数据感知控件的实现代码。

(a) 由于各种功能包不会直接引用DataStructure.bpl,而是通过CoreControl.bpl的TConnection,在运行时才与数据结构进行关联的,从而达到了业务逻辑和数据的分离,不管DataStructure.bpl如何改变,其他包都不需要改动,达到了解耦的目的。

(b) 在需要取数时,只需简单的传入要取的字段的名称,TDataSet通过调用封装的RTTI方法,从TDataItem中将值取出来返回给调用函数,达到统一数据处理的目的。

(c) 由于有了统一的数据处理方式,数据感知控件可以写得更加灵活,以表格为例,可以简单的在每列上定义一个关联字段名称,在显示时,直接通过对应的字段名称,从关联的TDataSet中通过上面(b)的方法将数据取出来,以达到对功能的封装和简化实现逻辑。

(d) 对于线程同步的问题,也可以很容易的封装到TDataSet中,通过读写锁,最大限度的提高数据读写效率,提高程序性能。

<4 已知的缺陷

(a) 通过RTTI读写数据,肯定比通过直接读写结构字段要慢得多,因此,性能问题有待真实环境的考验(目前从测试数据来看,似乎还比较乐观,没有想象中的那么差,呵呵)。

(b) 由于现在的实现逻辑仍然是先将所有数据结构在DataStructure.bpl中全部定义好,因此,在运行过程中,数据结构是无法变动的,这对于后续要添加的“计算字段”的功能无疑提出了挑战(实际上,由于数据读写逻辑全部都封装在TDataItem中,今后只要重新实现这个类,应该很容易解决的)。

 

好了,就写到这里吧,呵呵。由于本人不太喜欢伸手党,更不喜欢不劳而获,因此,请不要找我要源码。不过,如果你对这套逻辑感兴趣,并希望自己实现一个的话,我很乐意在你实现过程中遇到问题时,共同切磋交流一下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值