批判《不懂接口、反射、委托、设计模式足足写了5年的代码》(技术篇)

  原文地址:不懂接口、反射、委托、设计模式足足写了5年的代码 -- 写给初学者(谈美女生成器不谈代码生成器)

  吉日有三篇文章,是我最深恶痛绝的,堪称误导新人之三部曲

1.白话讲反射技术 --- 适合初学者入门引导

2.白话讲山寨SOA,少一些迷惑、多一些理解,你的程序架构SOA了吗?

3.不懂接口、反射、委托、设计模式足足写了5年的代码 -- 写给初学者(谈美女生成器不谈代码生成器)

  相信大家都拜读过了,不管是抱着娱乐的心态,抑或是想从中学习的热情。

第1篇是有严重错误的,属于不懂装懂型的水文。相关错误参见小赵的这篇文章:谈吉日嘎拉的《白话反射技术》及其他(技术篇)

第2篇则是根本就没用过SOA,脑子一热却跳出来教育新人。相关批判参见:我眼中的SOA,以及在实际项目中的应用经验

第3篇则是最空洞无物的一篇。打着“不懂……却写了5年代码”作幌子,“教育”初学者,却没有任何实际内容。这也是我本文要批判的主题。

 

  批判《不懂接口》一文,真的很难,因为文中也就那个图和下面的4点原则和他要说的主题有关系。至于其他文字,都是废话,不看也罢。

  对于那个图,也就是所谓的“美女生成器”,姑且不论它的粗俗,但我想,也正是这一点,博得了下面一群看客的叫好声,然而当我们仔细看图,以及图中的6段文字,会发现它毫无教育意义。

  首先这是一个流程图,而不是UML图。在没有UML图的配合下,是很难看懂例子中接口、继承、组合关系的。所以说,做技术的严谨,在此人身上毫无体现。

  那好吧,让我们将就着看下去。

 

      image 

  为什么输入参数也可以是接口,而不光是变量和类呢?

  这牵扯到了方法设计的原则,而与接口设计原则无关。

  在大量的设计实践中,我们发现,在声明方法的参数类型时,要尽可能地指定最弱的类型,并且在基类上定义接口,这样才可以输入更广泛的参数类型,灵活性就更大。

  吉日的文字是含糊的,他只是把一个小经验拿出来,但是知其然不知其所以然,更不要说能解释给初学者听了。

 

      image

  这个地方说的是inline函数,就是根据石头和水,先做出黄金,再做出来钻石,最后生成美女,也就是最终的返回值。说到底还是把一个复杂的方法拆分成3个简单的方法,按照吉日的思路,代码应该是下述这样的: 

    public class MMConverter
    
{
        
public MM ConvertFromStoneToMM(IStone stone, Water water)
        
{
            var gold 
= ConvertFromStoneToGold(stone, water);

            var diamond 
= ConvertFromGoldToDiamond(gold);

            
return ConvertFromDiamondToMM(diamond);
        }


        Gold ConvertFromStoneToGold(IStone stone, Water water)
        
{
            
return new Gold();
        }


        IDiamond ConvertFromGoldToDiamond(Gold gold)
        
{
            IDiamond diamond 
= DiamondFactory.CreateDiamond("Shanghai");

            Console.WriteLine(diamond.Quality);

            
return diamond;
        }


        MM ConvertFromDiamondToMM(IDiamond diamond)
        
{
            
return new MM();
        }

    }

  明眼人应该看出来了,这种拆分是面向过程的做法,和接口设计扯不上关系。

 

      image

  数这段注释问题最多,分别讨论如下:

  1)“系统有明确的输出”。由于系统的输入参数的类型范围要尽可能的宽,所以吉日在反向思考后,认为系统的输出(也就是返回类型)要尽可能的窄,尽量不要限于一个具体的类型。而且,在吉日的10年编码生涯中,经过重复重复再重复的代码机式编程,也感觉到返回类型似乎应该是明确的。

  但是,他只说对了一半。我们要谅解他10年来的大半时间在进行过程式编程,在没有polymorphism(包括override和overload)的情况下,吉日自然是觉得需要什么就返回什么了。

  我们举一个例子,写一个IO操作的方法,到底是返回FileStream还是返回Stream呢?我必须诚实地讲,没有定论。除非我事先知道肯定是文件的IO操作,这时候肯定直接返回FileStream;否则,请大家返回到Stream级别的对象。

  我在想,如果方法仅返回类型不同,也算是一种overload,那么就不会没有定论了。有几个类型我就写几个overload方法,这是最霸道的做法。但可惜的是,只有IL允许这么搞,C#做不到。

  2)“不可能这里输出的是油,应该输出钻石就输出钻石?这句话就比较扯淡了,我是没读明白。那些挺“吉”的fans,谁能帮我解释解释?

  3)此外我对“当然还可以有多个输出,例如ref、out等”这句话感到莫名其妙。我们知道,ref和out是基于语言的,解决的是向方法传递的是参数的一份copy还是参数引用本身这样的问题,而和接口以及OO设计没有关系。

  这里就属于吉日穷显摆了,我们真的需要使用ref和out在工厂方法中返回多个输出么?没必要,或者说这是一种糟糕的设计,无疑给初学者极大的误导。

  在OO的设计中,如果真的需要有多个输出,我们可以把这个变量提升到类的级别,这样就可以在方法返回前,设置这个变量的值,以达到同样的功效。或者,对于多个输出,可以把它们抽象为一个实体的两个属性,让方法返回这个实体类型就可以了。

  而对于ref和out,我曾经做过两年的VB6,对此深有体会,在面向过程的编程模型中,ref的使用是非常频繁的。

  这么看,吉日写了10年程序,虽然也在用C#,不客气地说,还是基于面向过程的编程方式,他所谓的重构,只是把一段代码抽象为一个方法。在抽象的过程中,由于原先面向过程的编程模型,而他又把握不好如何按照OO的思想重构,就只好大量的使用ref来拆东墙补西墙了。

 

      image

  这段注释是最费解的,因为按照他的流程图,我找不出“接口对不上”这句话中“接口”所指,更提不上“中间处理转换一下”。

  根据我的经验,做出如下2种猜测:

  1)有一些程序员没有养成良好的术语习惯,不区分“接口”和“接口方法”,所以他们常说“我到时候暴露一个接口给你调用”,而对方一般也能听懂。

  2)如果吉日真的是在处理接口兼容性的问题,那么他这里提到的“中间处理转换一下”,无外乎以下几种模式:AdapterProxyFacadeBridge。Adapter和Proxy是最容易想到的,因为它们能处理大部分的接口兼容性问题。但是,吉日还停留在过程式编程的阶段(虽然他不一定承认),我想,他这里应该指的是Facade吧。我又去翻了他另外的水文,就是那些只有图片和广告而没有思想和实现的《通用权限》和《大恶人闭门造车》系列文章,从数据库操作的封装上,大致证实了我的猜测。

  我在上述模式下面都添加了超链接,指向我早期写的设计模式系列文章,对设计模式不是很熟悉的朋友可以点击了解相关技术。

 

      image 

  让我们来看第5点,

  画了一堆图,就这个比较靠谱,说得是简单工厂,应该大书特书一笔的,结果是一句话“这就是所谓的接口,就这么简单”,轻描淡写敷衍了事。

  我一时手痒,给出了它的实现:   

Code

 

      image

  我说这段全都是废话,那些挺“吉”的人心里肯定不爽。说了半天,还是把几千行的方法拆成若干小方法的事情,用不着你说,那些新人照样知道要这么写程序。

 

  我们再来看他总结的所谓4条原则,水分就更大了,4条原则都是套大帽子,都没有错,但也啥都没说清楚,所以说是水文,让我们一条条分析:

1。分工、职责明确原则:每个装置都有明确的分工,都应明确的功能定位,该干啥的,就应该干啥,职责定位不能乱套,你不能指望汽车飞起来,飞机潜入海底吧,该干啥的就应该干啥?一个函数不要写太长,也有这个味道,应该干啥的都区分好,别写个上千上万行的代码出来。

评:老生常谈,把几千行的方法拆成若干小方法的事情又说一遍。

2。输入、输出明确原则:每个装置都有明确的输入输出,甚至都有阀值限制、严格输入输出的参数,应该流进来什么?进行什么样的处理,最后流出去什么?往小了讲,类似我们天天写的一个函数、往大了讲,就是类似我们公司开发的一个个子系统,每个函数,每个子系统都应该有明确的功能定位,不应该重复、不应杂乱无章。

评:方法的参数和返回值的范围:参数类型要尽可能宽,这一点前面我已经给出说明了。但是返回类型不一定是窄的。吉日认为需要严格控制输出,太狭隘了,那是面向过程的做法,在OO的世界里,输出类型可宽可窄,我在上面图中的第3点注释中已经解释过为什么了。

3。接口、规范原则:流入的口径、螺纹、材质,流出的口径、螺纹、材质等,都应该是有些行业规范的,否则一个装置架构完毕后,要流到下一个装置去处理,那不是流不下去了?或者可以中间用特殊的管道,把这2个设备都连接上,这个管道的2头,跟这些设备都有标准可以接,就像有些电脑的转接头一样,例如usb转啥啥的等等。

评:这是在说啥?前面说行业规范,为什么流不下去了?后面谈到的管道和USB,在设计模式中其实都是一个Adapter。哦,我明白了,吉日是想告诉我们前面作出一个A类型的对象,但是后面需要一个B类型的对象。

真的流不下去了么?如果A是从B中派生的,也流不过去么?当然可以流过去。而真正需要Adapter的,指的是A不从B中派生的情况。

4。可替换、可升级可更新原则:一个庞大的化工厂,往往会进行一些维修、升级某个装置或者某个设备的情况,你总不能要求整个化工厂全体都升级,那不是开玩笑的,好像建立一个上规模的化工厂要几百亿,这钱都是用火车拉,也能拉几节车厢的,不是想更新了就更新了,想不要了,就不要了。
    例如,我们车子的一个螺丝坏了,我可以选择A公司生产的相同型号的螺丝,也可以采用B公司生产的相同型号的螺丝,这就是因为这些螺丝,都能有相同的功能,都能满足规定的要求规范。

评:这里的可替换、可升级、可更新原则,说得就是组件的热插拔了。给新人讲这个是不负责任的,因为这里牵扯到要面向接口编程,所有的零件都要做成工厂,我们所要写的每个类都应该是:接口.方法();,而不是:具体类.方法();。试问吉日,你写过面向接口的程序么?你能分辨出什么时候用接口,什么时候用抽象类么?你做过组件的设计期行为么?

当然,我也时刻在反思,设计模式和接口委托这些技术的粒度究竟有多大?难道一定要用化工厂维修设备的这么复杂的场景来描述么?大炮打蚊子,还是杀鸡用牛刀?

 

  终于写完了,我的感受是,2个字:痛苦。读吉日的文章,错别字和病句抛开不说,经常是看着看着就跑题了,到了该深入讲解的时候,又含糊其辞,没有任何设计思想,UML图解和代码实现就更不要说了,流程图画的很炫却起不到实际意义,主观意识太重,以至于很多地方经不起推敲。

  《不懂接口》一文中,吉日观点总结如下:

1.输入输出,前者宽,后者窄(其实后者是没有定论的)

2.需要工厂为我们生成对象(工厂这个词是我说的,原文没有)

3.需要把一个几千行代码的方法拆分成职能单一的小方法(这个用不着你说,就像说煤球是黑的一样)

4.需要提供一个自动选择生成不同对象的机制(什么机制呢?没说)

5.需要USB这样的管道(怎么实现呢?没说)

  吉日此文的题目是《不懂接口、反射、委托、设计模式足足写了5年的代码 -- 写给初学者(谈美女生成器不谈代码生成器)》,但我还是那句话,初学者能从中学到啥呢?你随便找本书看看都比浪费时间看他的东西强。

  吉日是一个写了10多年程序的程序员,但是,基本停留在面向过程的基础上,听这样一个人讲接口、委托、工厂和OO,那就是一个笑话。而且他口无遮拦还真敢讲,更是滑天下之大稽。这是我花了一晚上时间,分析完他这篇文章后,对他的进一步认识。

  最后,奉劝初学者,如果你们真想看明白接口、委托之类的技术,还是买一本《你必须知道的.NET》吧,你也可以选择《CLR via C#》。这些都是语言和设计思想中最重要的概念,千万不要被吉日的这几篇水文给毁了。

  本文实例代码完整下载:program.zip

 

posted @ 2009-10-19 00:38  包建强  Views(12579)  Comments(170Edit  收藏  举报