最近在项目中发现一个事件,功能很简单,也达到了预期的功能效果,但是编程不仅仅是功能实现了就可以了,更应该是如何完美的实现功能。下面我将这个事件的代码贴出来,进行一下分析。代码不是很长,总共才19行,该事件是确定按钮对象上面的一个事件定义。每点击一次确定就会触发一次该事件。
1 string ls_error,ls_zyhm 2 sql_lis = create Transaction 3 uo_support.set_trans_object(sql_lis) 4 IF NOT uo_support.dbconnect(2) THEN return false 5 ls_zyhm = string(istr_jsxx.jsbr.zyh) 6 gf_begin_transaction(sql_lis) 7 update lis_requisition_info set cypb = 8 where inpatient_id=:ls_zyhm and patient_type=1 using sql_lis; 8 if sql_lis.sqlcode = 0 then 9 gf_commit_transaction(sql_lis) 10 if isvalid(sql_lis) then destroy(sql_lis) 11 else 12 ls_error = "更新:lis_requisition_info表失败!!~r~n"+sql_lis.sqlerrtext 13 gf_rollback_transaction(sql_lis) 14 if isvalid(sql_lis) then destroy(sql_lis) 15 gf_write_log(ls_error) 16 is_errtext = is_errtext + ls_error 17 return false 18 end if 19 return true |
很多人第一眼看到该代码可能觉得sql_lis没定义,首先该代码实现了预期的功能,并且能够运行,说明sql_lis定义了,大家普遍觉得sql_lis没定义是有原因的,因为sql_lis的创建与销毁完全是按照局部变量来实现的,所以大家一眼就觉得sql_lis是局部变量,但是在该项目中sql_lis是被定义成了全局变量。
何为全局变量?我一直持有一个观念,就是全局变量在系统开始之初就被赋值,系统的运行过程中不能再对全局变量进行重新赋值。这个是有原因的,全局变量使用起来非常的灵活,全局变量是供各个模块共同使用的变量,如果每个模块都随意按照自己的需要对全局变量进行重新赋值,会对使用该全局变量的模块造成很严重的影响。
问题二:uo_support对象的事务属性赋值
如果不熟悉系统框架,可能看不出这个问题,事件第三行对uo_support对象的事务属性进行了赋值,在此sql_lis作为一个引用参数被传入到uo_support对象中,当后面uo_support调用dbconnect方法时,sql_lis就连上了数据库。当然,uo_support对象的事务属性也变成了sql_lis,紧接着后面又把sql_lis给销毁了,后面无论是修改密码还是退出登录对数据库的操作都使用了sql_lis,造成事务对象不可用,当然了即便不销毁,uo_support里面的操作使用sql_lis也是错误的(表跟数据库不匹配)。
问题三:sql_lis断开连接
sql_lis对象销毁之前,没有将数据库连接断开,本身该事件就是点击确定按钮的时候调用的,操作非常频繁,数据库连接不断开,势必会造成数据库会话的不断上升,直到系统退出数据库才会释放该进程占用的所有会话。也许有人会反驳了,如果数据库会话不断上升的话,数据库连接应该会被占满才对啊。大家都知道pb开发出来的系统是单线程运行的,重复相同的操作,很大可能会造成sql_lis使用了相同的内存块,在这种情况下,数据库端是不会重复开辟会话而是直接使用了上次没有释放的会话。我特意进行了测试,调用完该事件之后,点击另一个按钮故意创建一个对象不释放内存,这样再次回过头来调用上面事件的时候,由于新创建的sql_lis被分配到了新的内存块,造成数据库端重新建立了一个新的连接,造成数据库端session的上升。
问题四:sql_lis连接到底应该放在什么地方
在这个事件中,sql_lis的连接放在了事件当中,但是该事件是频繁被调用的,这样的后果就是sql_lis不断的被创建,不断的连接数据库,不断的销毁sql_lis.大家都知道tcp建立连接是三次握手,断开连接是四次握手。也就是说在不断的操作过程中,注意我们的系统是成百上千的客户端在同时使用的,并不是单机系统。tcp的建立连接与断开连接是非常占用网络带宽的。生产环境中,数据库的配置是不会变的,也就是说数据库一旦建立连接,在网络没有断开的情况下是一直可以使用的,完全没必要在局部操作中去建立数据库连接。这也是为什么基本所有系统都是刚开始启动时连接上数据库,后面系统的运行过程中一直使用的原因。当然了有的系统在使用的过程中也会判断一下连接是不是由于断网等原因造成不可再用,在这样的情况下,是可以重新进行数据库连接的。
问题五:数据库连接失败的时候直接返回
第四行代码可以看到,sql_lis连接失败的时候直接返回了false,此时虽然sql_lis连接数据库失败,但是sql_lis对象本身已经创建了,也已经分配内存了,返回之前需要先销毁sql_lis对象。也许有人会说了,代码都执行完了,sql_lis对象不是会自动销毁吗?前提是sql_lis在此处是全局变量,事件结束了,sql_lis的生命周期并没有结束,作为全局变量,他是不会自动销毁的。当sql_lis网络断掉后,其他数据块网络如果正常,频繁的调用该事件,就会不停的创建sql_lis,而且创建完了又不销毁,这样的话,很快内存就会泄露干净了。
短短的19行代码,暴露出了五个非常严重的问题,编程不是儿戏,实现功能是前提,如何更好的实现是重点。当客户不停抱怨的时候,当程序bug给客户带来利益损失的时候,我们是否能够静下心来自我反思一下。任务重,时间紧不是借口,产品的健壮与否关系到公司乃至所有同事的利益。日常的工作中,养成良好的编程习惯,为产品的健壮做出程序员该做的贡献吧。