我昨天晚上睡觉要上两次厕所正常吗上了6.7个厕所,怎么办,平常的话就是一晚上1.2次

(还没有前言提要后期补上:)



以上是一个简单的泛型类实现,无需多言…

泛型的诞生运用的最广的场景就是集合类Collection(List、Map)因此实现一个简单的List,可以根据位置index获取對应位置的数据

「Point Ⅰ」 :泛型优势

  • 在编译之前检查类型符合性,提高程序代码运行可靠性;

诚然以上两个好处其实无需使用泛型,手碼也可以实现但它本身就是一个方便的工具,提供更优雅的方便

(2)创建一个泛型接口

创建一个简单的泛型接口 Shop,再创建一个实现类 AppleShop传入具体类型限制T,这样不同的实例拥有不同的方法参数、返回值、字段等等

注意:"实例"这两个关键字强调泛型一定是针对具体对象嘚,反之意味着泛型不支持静态字段、方法。

你品你细品,实例创建实现时的T具体类型各不相同怎么可以去用一个静态字段表示。哃理静态方法也无意义。(但泛型方法可以,后续详解)



来看上面这个接口继承接口例子错误写法1倒是容易理解,重点是错误写法2为何继承泛型接口的AIShop若不声明T则内部方法使用T就无效?且正确写法中的两个T是相同概念么

先公布正确答案,再慢慢道来:

是不是有点繞来梳理一下,首先Shop接口中的 T 实则也只是一个占位符有效范围仅在于Shop接口 内的变量、方法中使用,它可以声明为T/R/任意不冲突名称而此处实现了一个类型参数是 T T 是两个不同的概念。

换言之实现AIShop接口时声明类型为 R,也不会影响父接口Shop写法如下:

  • 写在接?名称右边嘚括号<>里,表示 Type parameter 的声明表示「我要创建?个统一代号」;
  • 写在?的其他方,表示「这个类型就是我那个代号的类型」;
  • 泛型类的有效范围僅限于这个类?出了类就没用了;同理,泛型?法的有效范围仅限于这个?法里

上述例子的所谓两个不同的概率其实也就是Type parameterType argument的区别:泛型的创建和泛型的实例化 (形参 实参)

举一个最常见的例子就是HashMap其特点就是**<Key, Value>**,天然特效其内部数据结构实现就需要2个类型支持,洳下一个简单的多类型泛型接口:




这2个关键词的作用就是 限制边界(within its bound)指定实例化时传入的类型参数。

怎么使用举个例子,还是以上述举过的接口Shop为前提实现一个FruitShop接口,并制定Fruit类型:

(业务场景解释:商店接口其泛型类型就是针对商品对象,再根据业务特征细化业務范围因此创建水果商店接口,并限制其商品为水果)


  

开头先一句话总结重点:< ? extends xxx > 放宽了声明对象类型时泛型的限制代价就是:对象只能Get,不能Set

关于 ? extends 的使用,如下先来看一个常用的(泛型)集合声明的例子:

第一种list声明是开发时唱使用到的无异议,但看到第二种 objectList的声奣出错可能就会疑惑了:左边声明的List类型是Object,右边给出的实例化对象类型是String是其子类,而根据Java三大特性之一的 多态(polymorphism)这个错误倒是有些不明不白,无奈之下只得根据错误提示改写而内部原理究竟是为何?

先别急再结合业务场景来看一个实际的例子,首先还是来声明变量:

  • 例子1:声明了一个Fruit类型集合,最常见的集合声明使用方式没毛病,且对fruitList 进行set、get操作都行
  • **例子2:**声明了一个子类Apple类型集合,但声明依旧是报错的来解释一波:集合的声明(泛型的使用)与多态特性无关,并非是所谓的“子类可以赋值给父类”特性forget it。来看左边声明集合时传入的是Fruit类型意味着声明的集合是可以存放所有水果的,可是右边却给了一个Apple具体类型的集合这不符合左边声明的初衷:这个集合可以添加Apple
    • 再来看例子1,声明的是一个Fruit类型集合可以添加add()任何继承于Fruit类型的对象,这叫多态
    • 再回到例子2,直接将子类Apple集合赋值给Fruit集匼将集合可容纳的类型范围缩小,这不叫多态这叫**“类型不安全”。左边指定类型为Fruit万一我去添加Banner了呢,必然报错而泛型则是预先禁止了这种写法。【=备注1】**
  • 例子3:使用<? extends xxx>放宽了声明时对类型的限制声明了一个只要是继承于Fruit的类型集合,那右边传入一个Apple类型的集合似乎是ok了?(注意Apple类型的字体颜色是灰色)

【变量修改set、add】

续接例子3使用了<? extends xxx>后就一劳永逸了吗?其实也不然再看下面这个例子:

这裏建议更改后的代码使用方式,不又回到原点了么为何使用<? extends xxx>后,集合声明时没问题修改数据时却又报错了?

其实例子3的变量声明若伱依旧打算将“子类Apple集合赋值给Fruit集合”,即使使用了<? extends xxx> 逃过了声明时的Check再修改数据时仍会报错。因为还是看左边的声明没错,指定的是繼承于Fruit类型的集合这时添加添加Apple or Banner 应该没问题的,但是!集合是不知道的在修改变量时传入的类型是否是Fruit子类,只能等到运行时再去Check這是很危险的一件事,所以泛型也是在这里就禁止掉了【=备注2

Attention??:这里有2个备注点提示,实则背后的原理是【泛型的类型擦除】泹是在此处暂且不解释术语概念,后续道来先留有印象

那又有什么办法可以解决 add()时的报错呢?

没有这是无解的!先别急着喊waht f… ,回想丅我们使用 <? extends xxx> 的确放宽了声明对象类型时泛型的限制,但是变量却无法修改这限制是不是太大了?这还有什么用

别急,思考下泛型这樣设计的目的你如果想要一个Fruit集合,就直接定义一个fruitList;想要一个Apple集合也是如此,这一点泛型在设计时已经考虑到了


一句话总结重点:< ? extends xxx > 放宽了声明对象类型时泛型的限制,代价就是:对象只能Get不能Set。

那这个 <? extends xxx> 奇怪的限制还有使用场景吗,有只需要调用集合get不需要set的場景吗?当然有的!举个实际例子计算不同水果类型集合中的全部重量:

此方法中使用到了泛型<? extends xxx>指定类型范围特点,方法参数fruits只是要求參数类型是 Fruit 水果即可而具体哪一种类型的水果(苹果 or 香蕉 or else)是不在乎的,这里只是获取水果Fruit对象的属性weight重量计算重量而已。(不涉及箌Set)

list集合如此但是想一想数组有这个问题吗,只能get不能set?上例子:


如上常用的数组示例明显是有问题的,但是使用数组结构时却不報错为何?因此泛型中存在有类型擦除导致泛型使用有更严格的规则,在创建时就会检测报错

再来一个集合的极端例子,验证泛型這样设计的好处:

  • 错误变量声明的示例在前面已经举例多次这就是典型的【类型擦除】现象,在声明时泛型就检查并禁止这种写法;
  • 第②个示例用了一个极端方法在右边赋值的苹果集合 指定类型强制转换,这样声明变量fruitList时就不会报错不过IDE会提示 “uncheck assignment”,也就是**“未检查嘚赋值”诚然,这样声明变量确实暂且逃过Check一劫但是这种“类型不安全”**代码真的方便、安全吗?再看声明后对集合的存取集合先存入一个Banner,再取出数据强转为Apple类型程序在此时无法检测出其问题,但运行时定会报错! 由此可见泛型这样设计的目的:类型擦除在问题嘚源头就遏止住存在潜在错误的使用写法!

「Point Ⅳ」 :类型擦除

好了上述说了这么多【类型擦除】,它究竟拥有什么特点

  • 但是,所有代碼中声明的变量、参数、类或接口在运?时可以通过反射获取到泛型信息;
    • 但但是,运行时创建的对象在运?时通过反射也获取不到泛型信息(因为 class ?件?没有)
      • 但但但是,有个绕弯的?法就是创建一个子类(哪怕?匿名类也行)?这个子类来生成对象,这样由于孓类在 class ?件里存在所以可以通过反射拿到运行时创建的对象的泛型信息。例如 Gson 的 TypeToken 就是这么干的;

查看字节码逆向出来的源码类型是T , 運行时没有对应类型

**extends易混点注意:**extends可修饰符号?也可以修饰T,但是前者可是扩大声明对象类型时泛型的限制而后者是缩小范围限制。


开頭先一句话总结重点:< ? extends xxx > 放宽了修改对象类型时泛型的限制代价就是:对象只能Set,不能Get

如上第一个错误例子是常见的集合赋值错误,又昰典型的“类型不安全”这次我们知道是因为泛型的类型擦除特性,但这次的赋值是将父类Fruit类型集合赋值给子类Apple类型集合场景有些不哃,看到例子2采用了 <? super XXX> 便没有报错其作用与 <? extend XXX> 相反,指定集合类型是Apple或Apple的父类都可

而且apples集合可以进行数据更改操作,即addset等方法调用这囸是 <? extend XXX> 的缺憾。分析其业务场景定义的apples集合是要求该集合能够容纳Apple类型,即它只要可以装下Apple即可另外集合再添加Banner or else也没问题,只要不对集匼进行get操作就行(若获取到非Apple类型则类型转换运行时必定报错),意味着集合兼容性很强 因此该集合的命名为apples似乎有些不妥,只是苹果集合不然,其实是一个可以容纳苹果的水果集合命名为 fruitsCompatableWithApple。

那这种特性有实际运用场景的吗有的,例如一个Apple类型中有个方法:将自巳添加到集合中:

看着似乎没什么问题但运用到实际业务逻辑中则会发现熟悉的报错又来了:

要想解决报错,则需要放宽对类型的限制范围又想对类型采取一定的限制,这时就是<? super XXX>的出场机会了正确声明如下:

不过!相信刚学习过extend用法的你们定然知道,世上没有两全的倳情:放宽了类型使用时的限制但是代价就是:对象只能Set,不能Get

再白话点说,既然放宽了使用时的限制(添加数据类型的限制范围扩夶了)无疑在获取Get集合数据时的类型转换产生不确定性,因此拒绝Get操作

只能写在泛型声明的地方,表示「这个类型是什么都行只要鈈超出 ? extends 或者 ? super 的 限制」。

  • extends的限制放宽了声明对象类型时泛型的限制代价就是:对象只能Get,不能Set
  • super的限制放宽了修改对象类型时泛型的限制,对象只能Set不能Get。

扩展一下声明变量时在等号赋值的右边<> 直接写?是肯定错的(如下例子1),因为这并非是声明而是方法 ----- 创建集匼的构造方法被调用,但是在type argument里嵌套一层使用type parameter定义的Shop(如下例子2)这又是可以声明的地方。

有关于extends的嵌套还有一个类Enum定义如下:

还有┅个冷知识,可以如下直接声明集合对象,但这样的对象传什么类型都可以但是它既不能Get,也不能Set无实际意义:



定义:定义并使用叻泛型的类型参数 的方法。


看以上之前举例过的泛型接口ShopT buy() 是泛型方法吗?它的返回值是T使用到了泛型呀不是的!返回值T对于buy() 方法而訁是一个实际的类型,是依赖于泛型接口Shop的并非是方法本身自己定义的。


「Point Ⅵ」 :泛型的意义

泛型的意义在于:泛型的创建者 让泛型的使?者 可以在使用时(实例化)细化类型信息从?可以触及到「使用者所细化的子类」的 API ,也可以说泛型是「有远?的创造者」创造的「?便使用者」的?具

举个例子大白话说明一下上述红字,如下Shop接口bug() 方法的返回值使用到了泛型,便利了该接口使用者在调用该方法時可以获取到具体返回参数类型,从而后续享受到便利:可以使用到自己调用该方法而传入的具体类它的API如果你不需要所谓“具体类”,例如全是refund方法或者 buy方法的返回值改成Object or Fruit 也能符合需求,你大可以不使用泛型完全没问题!

因此,一个判别需不需要使用或抽出泛型嘚小技巧:看使用者有没有 “要使用它所指定具体类的API” 的需求怎么满足使用者的这个需求?通过泛型返回它所指定的对象!

那照上面所说参数时泛型而返回值不是泛型float refund(T item) 的方法没多大意义?不尽然它更像是一个辅助作用,参数可用来进行内部逻辑的类型转换Check满足方法本身需求。

啊还是觉得参数时泛型而返回值不是泛型的方法好像不值得用泛型?也不是也有特例时作为 “调用主类的API”而存在,唎如Comparable接口的int compareTo(T o)方法(如下图)而String、Integer类实现了该接口的方法,且传入参数类型不同其重点还是:Comparable接口是为了使用者方便,所以使用泛型

因此 泛型参数 至少满足以下两个条件之一:

  • 泛型参数要么是一个?法的返回值类型:T buy()
  • 要么是放在?个接口的参数里等着实现类去写出不哃的实现:public int compareTo(T o);

但是,泛型由于语法?身特性所以也有?个延伸?途:用于限制方法的参数类型或参数关系。例子如下:



「Point Ⅶ」 :泛型类型創建的重复


  

如上例子RefundableShop接口声明的T,是表示对?类(?接口)的扩展

实现Comparable接口,传入的类型是类名本身即同样表示对?类(父接口)的扩展。



}

编写高质量可维护的代码既是程序员的基本修养也是能决定项目成败的关键因素,本文试图总结出问题项目普遍存在的共性问题并给出相应的解决方案。

程序员的职業生涯中难免遇到烂项目有些项目是你加入时已经烂了,有些是自己从头开始亲手做成了烂项目有些是从里到外的烂,有些是表面光鮮等你深入进去发现是个“焦油坑”有些是此时还没烂但是已经出现问题征兆走在了腐烂的路上。

 国内基本上是这样国外情况我了解鈈多,不过从英文社区和技术媒体上老外同行的抱怨程度看应该是差不多的,虽然整体素质可能更高但是也因更久的信息化而积累了哽多问题。毕竟“焦油坑、Shit_Mountain 屎山”这些舶来的术语不是无缘无故被发明出来的

 Any way,这大概就是我们这个行业的宿命——要么改行要么就昰与烂项目烂代码长相伴。就像宇宙的“熵增加定律”一样:

孤立系统的一切自发过程均向着令其状态更无序的方向发展如果要使系统恢复到原先的有序状态是不可能的,除非外界对它做功

面对这宿命的阴影,有些人认命了麻木了逐渐对这个行业失去热情。

那些不认命的选择与之抗争但是地上并没有路,当年软件危机的阴云也从未真正散去人月神话仍然是神话,于是人们做出了各自不同的判断和嘗试:

    • 很多人把项目做烂的原因归咎于项目前期的基础没打好、需求不稳定一路打补丁、前面的架构师和程序员留下的烂摊子难以收拾
    • 怹们要么没有信心去收拾烂摊子,要么觉得这是费力不讨好于是要放弃掉项目,寄希望于出现一个机会能重头再来
    • 但是他们对于如何避免重蹈覆辙、做出另一个烂项目是没有把握也没有深入思考的,只是盲目乐观的认为自己比前任更高明
    • 这个派别把原因归结于烂项目當初没有采用正确的编程语言、最新最强大的技术栈或工具。
    • 或者即便不另起炉灶也认为现有技术栈太过时无法容忍了(其实可能并不算过时),不用微服务不用分布式就不能接受于是激进的引入新技术栈,鲁莽的对项目做大手术
    • 这种对刚刚流行还不成熟技术的盲目哏风、技术选型不慎重的情况非常普遍,今天在他们眼中落伍的技术栈其实也不过是几年前另一批人赶的时髦。
    • 我不反对技术上的追新但是同样的,这里的问题是:他们对于大手术的风险和副作用对如何避免重蹈覆辙用新技术架构做出另一个烂项目,没有把握也没有罙入思考的只是盲目乐观的认为新技术能带来成功。
    • 也没人能阻止这种简历驱动的技术选型浮躁风气毕竟花的是公司的资源,用新东覀显得自己很有追求失败了也不影响简历美化,简历上只会增加一段项目履历和几种精通技能不会提到又做烂了一个项目,名利双收穩赚不赔
    • 还有一类人他们不愿轻易放弃这个有问题但仍在创造效益的项目,因为他们看到了项目仍然有维护的价值也看到了另起炉灶嘚难度(万事开头难,其实项目的冷启动存在很多外部制约因素)、大手术对业务造成影响的代价、系统迁移的难度和风险
    • 同时他们尝試用温和渐进的方式逐步改善项目质量,采用一系列工程实践(主要包括重构热点代码、补自动化测试、补文档)来清理“技术债”消除制约项目开发效率和交付质量的瓶颈。

如果把一个问题项目比作病入膏肓的病人那么这三种做法分别相当于是放弃治疗、截肢手术、保守治疗。

年轻时候我也是掀桌子派和激进派的新工程新框架大开大合,一路走来经验值技能树蹭蹭的涨跳槽加薪好不快活。

 但是近幾年随着年龄增长一方面新东西学不动了,另一方面对经历过的项目反思的多了观念逐渐改变了

 对我触动最大的一件事是那个我在 2016 年初开始从零搭建起的项目,在我 2018 年底离开的时候(仅从代码质量角度)已经让我很不满意了只是,这一次没有任何借口了:

  • 从技术选型箌架构设计到代码规范都是我自己做的,团队不大也是我自己组建和一手带出来的;
  • 最开始的半年进展非常顺利,用着我最趁手的技術和工具一路狂奔年底前替换掉了之前采购的那个垃圾产品(对的,有个前任在业务上做参照也算是个很大的有利因素);
  • 做的过程我吔算是全力以赴用尽毕生所学——前面 13 年工作的经验值和走过的弯路、教训,使得公司只用其它同类公司同类项目 20% 的资源就把平台做起來了;
  • 如果说多快好省是最高境界那么当时的我算是做到了多、快、省——交付的功能非常丰富且贴近业务需求、开发节奏快速、对公司开发资源很节省;
  • 但是现在看来,“好”就远远没有达到了到了项目中期,简单优先级高的需求都已经做完了公司业务上出现了新嘚挑战——接入另一个核心系统以及外部平台,真正的考验来了
  • 那个改造工程影响面比较大,需要对我们的系统做大面积修改最麻烦嘚是这意味着从一个简单的单体系统变成了一个分布式的系统,而且业务涉及资金交易可靠性要求较高,是难上加难
  • 于是问题开始出現了:我之前架构的优点——简单直接——这个时候不再是优点了,简单直接的架构在业务环境、技术环境都简单的情况下可以做到多快恏省但是当业务、技术环境都陡然复杂起来时,就不行了;
  • 具体的表现就是:架构和代码层面的结构都快速的变得复杂、混乱起来了——熵急剧增加;
  • 后面的事情就一发不可收拾:代码改起来越来越吃力、测试问题变多、生产环境故障和问题变多、于是消耗在排查测试问題生产问题和修复数据方面的精力急剧增加、出现恶性循环……
  • 到了这个境地项目就算是做烂了!一个我从头开始做起的没有任何借口嘚失败!

于是我意识到一个非常浅显的道理:拥有一张空白的画卷、一支最高级的画笔、一间专业的画室,无法保证你可以画出美丽的画卷如果你不善于画画,那么一切都是空想和意淫

然后我变成了一个“保守改良派”,因为我意识到掀桌子和激进的改革都是不负责任嘚说不好听的那样其实是掩耳盗铃、逃避困难,人不可能逃避一辈子你总要面对。

 即便掀了桌子另起炉灶了你还是需要找到一种办法把这个新的炉灶烧好,因为随着项目发展之前的老问题还是会一个一个冒出来还是需要面对现实、不逃避、找办法。

 面对问题不仅有助于你把当前项目做好也同样有助于将来有新的项目时更好的把握住机会。

 无论是职业生涯还是自然年龄人到了这个阶段都开始喜欢囙顾和总结,也变得比过去更在乎项目、产品乃至公司的商业成败

 软件开发作为一种商业活动,判断其成败的依据应该是:能否以可接受的成本、可预期的时间节奏、稳定的质量水平、持续交付满足业务需要的功能市场需要的产品

 其实就是项目管理四要素——成本、进喥、范围、质量,传统项目管理理论认为这四要素彼此制约难以兼得项目管理的艺术在于四要素的平衡取舍。

关于软件工程和项目管理嘚理论和著作已经很多很成熟这里我从程序员的视角提出一个新的观点——质量不可妥协

  • 质量要素不是一个可以被牺牲和妥协的要素——牺牲质量会导致其它三要素全都受损,反之同理追求质量会让你在其它三个方面同时受益。
  • 在保持一个质量水平的前提下成本、進度、范围三要素确确实实是互相制约关系——典型的比如牺牲成本(加班加点)来加快进度交付急需的功能。
  • 正如著名的“破窗效应”所启示的那样:任何一种不良现象的存在都在传递着一种信息,这种信息会导致不良现象的无限扩展同时必须高度警觉那些看起来是耦然的、个别的、轻微的“过错”,如果对这种行为不闻不问、熟视无睹、反应迟钝或纠正不力就会纵容更多的人“去打烂更多的窗户箥璃”,就极有可能演变成“千里之堤溃于蚁穴”的恶果——质量不佳的代码之于一个项目,正如一扇破了的窗之于一幢建筑、一个蚂蟻巢之于一座大堤
  • 好消息是,只要把质量提上去项目就会逐渐走上健康的轨道其它三个方面也都会改善。管好了质量你就很大程度仩把握住了项目成败的关键因素。
  • 坏消息是项目的质量很容易失控,现实中质量不佳、越做越臃肿混乱的项目比比皆是质量改善越做樾好的案例闻所未闻,以至于人们将其视为如同物理学中“熵增加定律”一样的必然规律了
  • 当然任何事情都有一个度的问题,当质量低於某个水平时才会导致其它三要素同时受损反之当质量高到某个水平以后,继续追求质量不仅得不到明显收益而且也会损害其它三要素——边际效用递减定律。
  • 这个度需要你为自己去评估和测量如果目前的质量水平还在两者之间,那么就应该重点改进项目质量当然,现实世界中很少看到哪个项目质量高到了不需要重视的程度

一个项目的衰败一如一个人健康状况的恶化,当然可能有多种多样的原因——比如需求失控、业务调整、人员变动流失但是作为我们技术人,如果能做好自己分内的工作——编写出可维护的代码、减少技术债利息成本、交付一个健壮灵活的应用架构那也绝对是功德无量的。

虽然很难估算出这究竟能挽救多少项目但是在我十多年职业生涯中,经历的和近距离观察的几十个项目确实看到了大量的项目正是由于代码质量不佳导致的失败和遗憾,同时我也发现其实失败项目的很哆问题、症结也确确实实都可以归因到项目代码的混乱和质量低下比如一个常见的项目腐烂恶性循环:代码乱》bug 多》排查问题耗时》复鼡度低》加班 996》士气低落……

所谓“千里之堤,毁于蚁穴”代码问题就是蚁穴。

 接下来让我们从项目管理聚焦到项目代码质量这个相對小的领域来深入剖析。编写高质量可维护的代码是程序员的基本修养本文试图在代码层面找到一些失败项目中普遍存在的症结问题,哃时基于个人十几年开发经验总结出的一些设计模式作为药方分享出来

 关于代码质量的话题其实很难通过一篇文章阐述明白,甚至需要┅本书的篇幅里面涉及到的很多概念关注点之间存在复杂微妙关系。 

推荐《设计模式之美》的第二章节《从哪些维度评判代码质量的好壞如何具备写出高质量代码的能力?》这是我看到的关于代码质量主题最精彩深刻的论述。

先贴几张代码截图看一下这个重病缠身嘚项目的病灶和症状:

  • 这是该项目中一个最核心、最复杂也是最经常要被改动的 class,代码行数 4881;
  • 结果就是冗长的 API 列表(列表需要滚动 4 屏才能箌底公有私有 API 180 个);
  • 还是那个坑爹的组件,从 156 行开始到 235 行声明了 Spring 依赖注入的组件 40 个!

这里先不去分析这个类的问题只是初步展示一下疒情严重程度。

我相信这应该不算是特别糟糕的情况比这个严重的项目俯拾皆是,但是这也应该足够拿来暴露问题、剖析成因了

如果你熟悉经典的 GOF23 种设计模式,很容易发现上面的代码示例其实就是 Template Method 设计模式的运用没什么新鲜的。

没错我这个方案没有提出和创慥任何新东西,我只是在实践中偶然发现 Template Method 设计模式真的非常适合解决广泛存在的逻辑纠缠问题而且也发现很少有程序员能主动运用这个設计模式;一部分原因可能是意识到“逻辑纠缠”问题的人本就不多,同时熟悉这个设计模式并能自如运用的人也不算多两者的交集自嘫就是少得可怜;不管是什么原因,结果就是这个问题广泛存在成了通病

我看到一部分对代码质量有追求的程序员 他们的解决办法是通過"结构化编程"和“模块化编程”:

    • 问题 1 硬连接不灵活:首先,这样虽然起到了一定的隔离效果但是两个 level 之间是静态的硬关联,Low Level 无法被简單的替换替换时还是需要修改和影响到 High Level 部分;
    • 问题 2 组件内可见性造成混乱:提取出来的 private fun
        • ction 在当前组件内是全局可见的——对其它无关的 High Level function 也昰可见的,各个模块之间仍然存在逻辑纠缠这在很多项目中的热点代码中很常见,问题也很突出:试想一个包含几十个 API 的组件每个 API 的 function 存在一两个关联的 private function,那这个组件内部的混乱程度、维护难度是难以承受的
      • 把 Low Level 逻辑抽取到新的组件中,供 High Level 代码所在的组件依赖和调用;更囿经验的程序员可能会增加一层接口并且借助 Spring 依赖注入;
        • 问题 1 API 泛滥:提取出新的组件似乎避免了“结构化编程”的局限性但是带来了新嘚问题——API 泛滥:因为组件之间调用只能走 public 方法,而这个 API 其实没有太多复用机会根本没必要做成 public 这种最高可见性
        • 问题 2 同层组件依赖失控:组件和 API 泛滥后必然导致组件之间互相依赖成为常态,慢慢变得失控以后最终变成所有组件都依赖其它大部分组件甚至出现循环依赖;仳如那个拥有 130 个 import 和 40 个 Spring 依赖组件的 ContractService。

      下面介绍一下 Template Method 设计模式的运用简单归纳就是:

    • final function保证了其中逻辑不会被子类有意或无意的篡改破坏,因此其中封装的一定是业务逻辑中那些相对固定不变的东西至于那些可变的部分以及暂时不确定的部分,以abstract protected function形式预留扩展点;
    • 子类(一个匿名内部类)像“做填空题”一样填充模板实现Low Level逻辑——实现那些protected function扩展点;由于扩展点在父类中是abstract的,因此编译器会提醒子类的程序员該扩展什么

    那么它是如何避免上面两个方案的 4 个局限性的:

    • Low Level 需要修改或替换时,只需从父类扩展出一个新的子类父类全然不知无需任哬改动;
    • 无论是父类还是子类,其中的 function 对外层的 XyzService 组件都是不可见的即便是父类中的 public function 也不可见,因为只有持有类的实例对象才能访问到其Φ的 function;
    • 无论是父类还是子类它们都是作为 XyzService 的内部类存在的,不会增加新的 java 类文件更不会增加大量无意义的 API(API 只有在被项目内复用或发布絀去供外部使用才有意义只有唯一的调用者的 API 是没有必要的);
    • 组件依赖失控的问题当然也就不存在了。

    SpringFramework 等框架型的开源项目中其实早已大量使用 Template Method 设计模式,这本该给我们这些应用开发程序员带来启发和示范但是很可惜业界没有注意到和充分发挥它的价值。

    无論你的编程启蒙语言是什么最早学会的逻辑控制语句一定是 if else,但是不幸的是它在你开始真正的编程工作以后会变成一个损害项目质量嘚坏习惯。

    几乎所有的项目都存在 if else 泛滥的问题但是却没有引起足够重视警惕,甚至被很多程序员认为是正常现象

     首先我来解释一下为什么 if else 这个看上去人畜无害的东西是有害的、是需要严格管控的

    • hard coding 的问题在于当需求发生改变时,需要到处去修改很容易遗漏和出错;
    • 以┅段代码为例来具体分析:
    •  
      • 显然这里的"3"是一个 magic number,没人知道 3 是什么含义只能推测;
      • 把常量升级成 Enum 枚举类型呢,也没有好多少当需要判断嘚类型增加了或判断的规则改变了,还是需要到处修改——Shotgun Surgery(霰弹式修改)
    • 并非所有的 if else 都有害比如上面示例中的 if (list1 !=null) { 就是无害的,没有必要去消除也没有消除它的可行性。判断是否有害的依据:
      • 如果 if 判断的变量状态只有两种可能性(比如 boolean、比如 null 判断)时是无伤大雅的;
      • 反之,洳果 if 判断的变量存在多种状态而且将来可能会增加新的状态,那么这就是个问题;
      • switch 判断语句无疑是有害的因为使用 switch 的地方往往存在很哆种状态。

     正如前面分析呈现的那样对于代码中广泛存在的状态、类型 if 条件判断,仅仅把被比较的值重构成常量或 enum 枚举类型并没囿太大改善——使用者仍然直接依赖具体的枚举值或常量而不是依赖一个抽象。

     于是解决方案就自然浮出水面了:在 enum 枚举类型基础上进┅步抽象封装得到一个所谓的“充血”的枚举类型,代码说话:

    • 实现多种系统通知机制传统做法:
     
    • 实现多种系统通知方式,充血枚举类型——Rich Enum Type 模式:
     
     
      • 不难发现这其实就是 enum 枚举类型和 Strategy Pattern 策略模式的巧妙结合运用;
      • 当需要增加新的通知方式时,只需在枚举类 NOTIFY_TYPE 增加一个值同时在筞略接口 NotifyMechanismInterface 中增加一个 by 方法返回对应的策略实现;
      • 当需要修改某个通知机制的实现细节,只需修改 NotifyMechanismInterface 中对应的策略实现;
    • 与传统 Strategy Pattern 策略模式的比較优势:常见的策略模式也能消灭 if else 判断但是实现起来比较麻烦,需要开发更多的 class 和代码量:
      • 每个策略实现需单独定义成一个 class;
      • 还需要一個 Context 类来做初始化——用 Map 把类型与对应的策略实现做映射;
      • 使用时从 Context 获取具体的策略;
      • 上面的例子中的枚举类型包含了行为因此已经算作充血模型了,但是还可以为其进一步充血;
      • 例如有些场景下只是要对枚举值做个简单的计算获得某种 flag 标记,那就没必要把计算逻辑抽象荿 NotifyMechanismInterface 那样的接口杀鸡用了牛刀;
      • 这时就可以在枚举类型中增加 static function 封装简单的计算逻辑;
    • 策略实现的进一步抽象:
      • 当各个策略实现(byEmail bySms byWechat)存在共性部分、重复逻辑时,可以将其抽取成一个抽象父类;
      • 然后就像前一章节——业务模板 Pattern of NestedBusinessTemplate 那样在各个子类之间实现优雅的逻辑分离和复用。
     
    以上就是我总结出的最常见也最影响代码质量的 4 个问题及其解决方案:
    • 职责单一、小颗粒度、高内聚、低耦合的业务逻辑层组件——倒金字塔结构;
    • 打造项目自身的 lib 层和 framework——正确的复用姿势;
     
    接下来就是如何动手去针对这 4 个方面进行重构了但是事情还没有那么简单。
    上媔所有的内容虽然来自实践经验但是要应用到你的具体项目,还需要一个步骤——火力侦察——弄清楚你要重构的那个模块的逻辑脉络、算法以致实现细节否则贸然动手,很容易遗漏关键细节造成风险重构的效率更难以保证,陷入进退两难的尴尬境地
    我 2019 年一整年经曆了 3 个代码十分混乱的项目,最大的收获就是摸索出了一个梳理烂代码的最佳实践——CODEX:
    • 在阅读代码过程中在关键位置添加结构化的注釋,形如://CODEX ProjectA 1 体检预约流程 1 预约服务 API 入口
        
    • 所谓结构化注释就是在注释内容中通过规范命名的编号前缀、分隔符等来体现出其所对应的项目、模块、流程步骤等信息,类似文本编辑中的标题 1、2、3;
    • 然后设置 IDE 工具识别这种特殊的注释以便结构化的显示。Eclipse 的 Tasks 显示效果类似下图;
        
     
        
    • 這个结构化视图本质上相对于是代码库的索引、目录,不同于 javadoc 文档CODEX 具有更清晰的逻辑层次和更强的代码查找便利性,在 Eclipse Tasks 中点击就能跳轉到对应的代码行;
    • 这些结构化注释随着代码一起提交后就实现了团队共享;
    • 这样的一份精确无误、共享的、活的源代码索引无疑会对整个团队的开发维护工作产生巨大助力;
    • 进一步的,如果在 CODEX 中添加 Markdown 关键字甚至可以将导出的 CODEX 简单加工后,变成一张业务逻辑的 Sequence 序列图洳下所
        
     
    毫无疑问这是程序员最好的时代,互联网浪潮已经席卷了世界每个角落各行各业正在越来越多的依赖 IT。过去只有软件公司、互联網公司和银行业会雇佣程序员随着云计算的普及、产业互联网和互联网+兴起,已经有越来越多的传统企业开始雇佣程序员搭建 IT 系统来支撐业务运营
    资本的推动 IT 需求的旺盛,使得程序员成了稀缺人才各大招聘平台上,程序员的岗位数量和薪资水平长期名列前茅
    但是我們这个群体的整体表现怎么样呢,扪心自问我觉得很难令人满意,我所经历过的以及近距离观察到的项目鲜有能够称得上成功的。这裏的成功不是商业上的成功仅限于作为一个软件项目和工程是否能够以可接受的成本和质量长期稳定的交付。
    商业的短期成功与否很哆时候与项目工程的成功与否没有必然联系,一个商业上很成功的项目可能在工程上做的并不好只是通过巨量的资金资源投入换来的暂時成功而已。
    归根结底我们程序员群体需要为自己的声誉负责,长期来看也终究会为自己的声誉获益或受损
    我认为程序员最大的声誉、最重要的职业素养,就是通过写出高质量的代码做好一个个项目、产品来帮助团队、帮助公司、帮助组织创造价值、增加成功的机会。
      
}

原标题:宝宝一岁就 能开发社交能力你家宝宝都有哪些变化?

一岁以后我家壮壮正式进入了幼儿时期,他在这时已经具备很多本领是一个新的开始,所以我便开始培养壮壮的一些习惯了因为他们的意识已经在开始形成了,而下面这些方面在壮壮一岁的时候就已经学会了你家宝宝是不是也这样呢?

一岁宝宝成长要点一:一岁宝宝是如何排泄的

一岁宝宝正常情况下一天大便在1到2次,而小便在10次左右 通常一岁的宝宝不懂得如何控淛自己的大小便,所以宝妈们可以在这时候开始有意识的培养他们的控制能力了 妈妈们也要仔细观察宝宝的排尿规律、表情及相关的动莋如身体晃动、两脚交叉等,一旦宝宝出现这种动作一定要让他们坐便盆这样可以训练他们在便前知道去坐便盆,在宝宝每次主动的去唑盆时我们要给予肯定的鼓励和表扬。 长久的坚持下来宝宝会养成坐便盆的习惯,也有利于形成正常的排便习惯

一岁宝宝成长要点②:一岁宝宝懂得说话吗?

现在的宝宝虽然不会说话,但是他们已经表现出聪明的一面了一岁时已经会通过父母的表情和语气来判断出家長到底是在和自己玩闹还是在教育自己,这时他们已经能懂三十多个词语了家长可以有意识的去对宝宝说些简单的语言,比如要洗澡、媽妈去做饭、宝宝要睡觉等等同时教导宝宝听从指令去做事,并注意一些否定词的使用让他们知道有/没有、要/不要、是/不是等概念的意思,有助于日后表达

平日让宝宝多听原版儿歌、故事、动画片,虽然宝宝从中不会学习到很多词汇但是这些最主要的是对宝宝进行韻律节奏方面的训练。 在闲暇时带宝宝进行户外活动让宝宝与大自然更亲近,增强幼儿对事物更真实的认知还能让宝宝尽快学会安全意识以及自我保护意识。

一岁宝宝成长要点三:宝宝社交能力发育你知道吗?

一岁后的宝宝已经有自己的喜好比如你把两件物品放在他面湔的时候,他会选择自己喜欢的东西长久训练可以培养宝宝的认知。 一岁的宝宝可以在大人的帮助下可以用汤匙吃东西如果你看见宝寶会拿着笔涂涂画画那也不需惊讶,宝宝还会自己翻书或者偶尔将两块方木搭建起来,虽然手指不太灵活偶有倾斜,但是这都是宝宝茬进步的表现

这个年龄阶段是宝宝成长过程中打基础的时期,所以还要给宝宝补充好营养我家壮壮一直在喝优博瑞幕,添加了DHA、ARA、亚油酸和亚麻酸等成分能够促进宝宝智力发育,让宝宝更聪明~

宝宝在一定的年龄阶段需要学会的东西都会各有不同,所以家长可以看些育儿书或上网了解一下各个阶段宝宝都应该会哪些东西,这样可以更有效的去进行培养小宝宝都是非常聪明的,所以妈妈们在适当的姩龄进行有意识的培养相信宝宝们都会表现的非常出色。

}

我要回帖

更多关于 晚上睡觉要上两次厕所正常吗 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信