开始的工作
我是在2018.11.5开始维护和修改这个winform的ERP系统的。
我感觉代码很乱,因为代码里用了太多继承,窗体类之间相互的访问彼此的成员又很随意。
一句话来说就是耦合性高,封装性太低。
封装性的本质是,一个类自己管理自己的【数据】,和与之相关的【方法】。
其他类和对象,不能随意修改我的数据和方法。
你想要我的数据我给你,你想要我的方法,我来执行。
如果我不知道怎么回事,你就改变了我,那我就会很懵的。
下面列举一些封装性低的地方。
溺爱孩子的父辈
过度的使用继承机制会破坏子类的封装性,应该使用接口来代替继承,这是这个时代的我被教导的金科玉律。
使用继承机制,可能一开始写的时候是省事了,但是后期改起来却更加麻烦了。
因为对于后期一个改代码的人来说,他只关心他新加入的类,或者一个类中他改的那部分是最好的;如果他以前没有接触过这个项目还要瞻前顾后的翻父类子类,去翻和它有数据交互的类;那么他的工作就很麻烦。
这段代码的主要作用就是赋值,将drStore中的一些字段的值赋给drItem中的一些字段。
明明是个简单的赋值,为什么要有这么多strTag == "xxx"的判断呢?
原来strTag是一个和窗体名相同的字段。
而这些窗体呢,都继承自同一个【父窗体】。
这段赋值代码就是在父窗体内的,因为每个表单上都有一个选择物料/产品的按钮,这是一个共性。
于是这个作者就这样写了,在父类中逐个判断子类的类型,然后根据类型不同,逐个赋值。
- 有点搞笑
这样写我觉得是有些搞笑的,举个现实的例子就是:
我问小明,你吃饭了吗?
小明说,我不知道,你去问问我爸爸吧。
然后我问明爸,小明吃饭了吗?
然后,明爸不慌不忙的说:大明吃了,二明吃了,小明没吃。
what?明爸你糊涂了吧?我没问大明二明呀?
然后是小明,你好不成器啊,自己吃了没吃都不知道,还要你爸来管。
- 应该怎样?
就算是非要用继承去写,遇到不一样的地方应该override呀。
热情的朋友
如果A类需要调用B的方法,那么我就可以说A类依赖于B类。
一般来说,我应该,小心的抽离出关键的参数,让A类把这几个参数给到B类,拿到我的结果,继续向下走我的方法。
然而我看到,原来的作者是这样写的:
A类调用了B类的一个方法,这个方法的一个参数就是A类的实例本身。
假设A类的实例是a,B类的实例是b。
然后b怎么处理的呢?首先它做了自己的事情,帮助a拿到了一些它想要的数据。
接着,b很主动的帮助a给它的公开字段赋值了!
-------------------------------------------------------------------------------------------------------------------------
这样做的坏处在调试的时候最容易看出来,如果b只提供方法的话,那我a用了b的方法拿到返回值后,就继续往下走了。
这样我调试的时候就可以F10跳过方法的具体实现,直接拿到返回值看下面的代码怎么走的了。
但是b你帮我a赋值的话,我还要再去b里看看你到底是怎么做的。
可读性太差。
当时修改的方向
1是界面干净。---------------------------因为界面干净,用户看着用着就舒服。
2是每个类的对外接口干净。--------因为这样别人或我自己再去使用这个类的时候很会很容易。
因为到处都感觉有问题,但是到处都改的话工作量很大,所以,最后想到优先保证两个干净。
后来想了一些办法
为了对抗耦合性,后来想了一些办法。
1.是写了一个大大的静态帮助类XHelper。
因为一个可以随便剪切粘贴的纯函数,是封装性良好的最小模块。
这个帮助类的功能包括很多方面:
- 文件操作。数据库,ini,Excel,导入导出等等。
- 控件操作。GridView有关的,TreeList有关的。
- 增删改查有关。增删改查的帮助方法,数据绑定方法,输入校验方法,自动编号方法,下拉框填数据方法,弹出式选框填数据的方法,对后端对象操作的方法。
- 辅助功能。日志,权限,调试用方法,加密,解密,压缩,解压等等。
2.是写了一些代码生成工具。
有些代码虽然是重复的,但是并不适合封装起来,用继承的话就更乱了,因为本身它们有各自的特点,需要在细节处小修小改。
所以用代码生成工具生成这些代码是最正确的选择。
耦合性的意义
耦合的代码看多了,我想了太多的消灭耦合的办法后,我也想了下耦合性存在的意义。
没有耦合性,系统是无法工作的。就像显示器如果没有插电的接口,就没电不会亮;没有连接主机的接口,就没有显示的内容。
主机,显示器,电源需要相互联系,才能组成系统,才能正常工作。
所谓的耦合,换个褒义词,就是联系嘛。
继承机制对于封装控件来说很完美,确实控件的属性和方法在一起用起来很舒服。
再后来有些看开了
1.是因为一开始大量的改动引起了程序的不稳定。
改同样一个问题,我最简单的办法是按照原来的编码风格去写。
原作者用了耦合性高的写法,我强制改成单一职责的,中间改动量是非常大的,就需要很多测试才能知道对原来的功能有没有影响。
因为原来的代码可能用在很多地方,你的改法满足了这个地方,可能另一个地方会出问题。
所以改动的【成本最低选择】是,照着原来的风格改。
2.是因为我觉得我头脑里的这些观念是时代的产物。
我感觉编码这一工作的发展趋势是越来越工业化,越来越分工明确的。
如果说之前写程序的人员都很稳定,可能用很多继承去写是合适的。
我自己写的,明天也是自己改,我知道父类什么样,我也知道功能有冗余的类是什么样的,我继续用这些东西我还觉得很简单。
而现在可能变成了流水的兵去作业,我们肯定希望新来的人不需要了解太多历史就能工作,希望整个框架是简明的,没有那么多隐含条件,我就会把工作分的很细。
3.我自己发现我自己的代码也是越写越乱的。
我写了一个类A,一开始他只需要给类B提供数据,于是我在类A里写了一个方法FuncForB。
后来呢?我发现类A还要给类C提供数据,我就在类A里写了另外一个方法FuncForC。
然后呢?我发现FuncForC对B也能适用,FuncForC的写法貌似更原子性,能够满足各种数据获取的需求。
所以呢?去掉FuncForB,然后改一下类B。
我写前面的时候真的很难知道,我写的东西对后面通用不通用。
所以要写出整洁的代码,是需要一些重构,需要一些经验的。
就像是你可能把常用的东西放桌上,放着放着就乱了,需要整理才能干净。
现在的观点
因为本身我对整洁有些强迫症,才会有去整理的想法。
作为工作来说,要不要做这件事,要多考虑的是有没有用。
代码整洁最大的用处是:日后好改。
从“功用”角度看,最重要的就是:把用户经常想要修改的地方抽象出来,他要改,我马上可以改掉。
然后是,大部分时候能很快的实现一些基本功能。
代码有一万种写法,还是从业务需求去考虑它,才能捋清楚。