10种软件开发中 over-engineering 的错误套路
别把「不要过度使用 Generic」误解成「不用 Generic」,也别把「不要写一些不必要的 Wrapper」误解成「不写任何 Wrapper」。我只是在讲 over-engineering 这个事,只是在提倡不搞野路子编程。
套路1:攻城狮自认为比业务人员聪明
攻城狮觉得自己最聪明,因为东西是他们写出来的嘛!然而这经常就是 over-engineering 的根源。即使我们考虑好100件事情,还是会有第101件我们没想到的事情冒出来。就算我们搞定了1000个问题,还是会带出10000个新问题。我们觉得一切尽在掌握,但是事实是要打脸的。
在我15年的 coding 生涯里,我还从来没见着哪块业务光用需求就能概括清楚了。业务总是会变得五花八门,这不是业务人员的错,这是业务的天性使然。
长话短说:别和业务争,他们是赢定了的
温馨提示:如果你时间不多,这篇文章 Get 到上面这点就够了
套路2:过度复用业务逻辑
业务需求越来越多时(早就料到有这么一天),我们的反应是:
我们挖空心思搞抽象,搞复用,结果搞出一堆 Fat Models Fat Controllers。然并卵,业务需求从来不知足,只会越来越复杂,这个套路 hold 不住。
所以,应该反其道而行之:
在合理的系统中,共享逻辑会随着时间沉淀下来,即使外部功能变多,也依然保持「身材」。如果你看到的情况与此相反,恭喜你,你手上的系统早晚难逃被全部重写的命运,因为实在难以做局部的改进了。
在水平划分业务之前,最好先考虑垂直划分。比如不相干的服务,流程相关的逻辑,语言相关的模块,等等。这样一来改个局部的地方就比较容易,不用想太多。
长话短说:不要搞组合共享,要隔离
温馨提示:去代码库里挑一个和外部系统(Endpoint/Page/Job/etc)打交到的 Action 来看看,算一算这段代码的执行过程中需要切换多少个 Context?
套路3:任何东西都要 Generic
(本套路有时候是与套路2一起发作的,有时也单独发作)
- 连接数据库?写一个 Generic Adapter
- 查询数据库?Generic Query
- 传参数?Generic Params
- 构建参数?Generic Builders
- Map the Response?Generic Data Mapper
- 处理用户请求?Generic Request
- 干所有的杂活?Generic Executor
- 等等等等
有时候攻城狮就是管不住自己的欲望:不先去解决具体的业务问题,而是要浪费时间搞一个完美的抽象。
设计只能是追着真实生活的需求跑,所以哪天即时我们吃了狗屎运搞出个完美的设计,完成的时间也就是过期的时间——业务变了。最吼的设计就是易于重构的设计。这篇文章提倡的是大家应该写删了不心疼的代码,而不是老是想着被扩展的代码
长话短说:错误的抽象比重复还要糟糕
如果要做合理的抽象,适度的重复是必须的,因为只有我们从足够多的重复中总结的抽象才是好抽象(别一开始没多少重复就着急搞抽象)。抽象的质量,往往是系统里最薄弱的一环。
正经提示:在微服务之间搞抽象简直是灾难,会导致分布式巨无霸怪胎产生
套路4:影子封装器(Wrapper)
以前用到外部库时,总要包装包装,但往往只是影子封装法。我们也就只在实现功能和打包第三方库之间玩玩小把戏。所以呢,我们所谓的封装其实是和底层实现绑得死死的(有时候只是1:1的搬功能,有时候甚至花10倍功夫来包装,而只实现了底层库1/10的功能)。如果以后我们把底层库换了,包装器就得跟着改。有时候还在包装器里带点业务逻辑,搞成个四不像,最后成了个懵逼胶水层。
现在是2016年了,醒一醒吧!第三方库都已经做得又快又好了。写这些库的人都很聪明:质量好、测试好、有钱有闲写得好。用这些库的时候,就只管按照 Initialize-Instrument-Implement 的模式来用就行了。
长话短说:写 Wrapper 属于意外事件,不要当作常态。不要为了包装而包装
温馨提示:不是开玩笑,有人就是想搞能「海纳百川」的 Wrapper,理由就是,可以随时更换实现库啊!这是一种病,参照后文中的「可X性」套路
套路5:用工具思维对待质量问题
盲目地套用代码质量概念,并不能实际提高代码质量,比如:把所有变量都写成 private final,给所有的服务类都加一个 interface,等等。
看下 Hello World 企业版 或者 FizzBuzz企业版 ,代码写得巨多,且搞笑。细看每个类都严格遵守实现准则了,设计模式也用得蛮多(工厂,构建器,策略,等等),编码技巧上也下了苦功夫(泛型,枚举等等)。拿工具一检测,代码质量给满分。
但是,从大处一看,这货也就打印一句废话。
长话短说:别迷恋细节,要从大局出发
自动化代码质量检测工具能帮我们盯覆盖率这些东西,但是分辨不出测试是不是测了需要测的东西。性能检测工具能测性能,也不知道程序是并行还是串行跑出来的。只有人才把握得了大局。
否则,就会催生出千层饼现象:把代码逻辑分割到10到20层面饼里面,但每一层对全局的了解都是懵逼状态。为什么会酱纸?就是因为我们单纯追求代码可测试性,或者单一职责原则这些东西,忽视了大局观。
在以前,大家喜欢搞继承。A继承B继承C继承D等等。
在现在,大家还是喜欢搞这一套,只不过加上了配套的接口和实现。每一层都有接口和实现去调下一层,因为这很规范嘛。
有一些很好的原则,就是用来对付继承和其他OOP滥用的现象的。很多攻城狮不知道这些原则的来源,只管照本宣科,是有问题的。
长话短说:相同的概念在不同的领域有不同的含义。不要见得风就是雨,拿着锤子眼里只有钉子
在别的领域,要学会说人话。好的开发者要靠这个混。新瓶装老酒是行不通的。永远不要以套用概念的名义把设计搞乱。
套路6:Adopter 狂热症
无处不在的泛型。这个世道上,HelloWorldPrinter 都要写成 HelloWorldPrinter< String, Writer >
如果问题明显能用普通的方法、专门的数据类型来解决,就不要乱搞泛型。
无处不在的策略模式。连 if 都想用策略模式来做。
脑子有毛病?
无处不在的 DSL。想用 DSL重写所有东西。
不知所云……
喜欢用 Mock。测试时任何东西都想 mock 一下。
如果玩砸了……
元编程好屌,什么地方都搞一搞。
能讲讲理由么!
枚举好屌,扩展方法好屌,Traits好屌,好屌好屌,用起来!
骚年,这是不对的!
长话短说:不要什么地方都长话短说
套路7:痴迷「可…性」
- 可配置性
- 可伸缩性
- 可维护性
- 可扩展性
- ……
瞎搞。不可理喻。顽冥不化。
栗子1:来做个可扩展性屌屌的 CMS 吧!业务人员可以随心所欲地给实体加字段。
结果:业务人员从来不用。如果非用不可,他们会抓一个码农来帮他们用。本来就想要几个常用字段,快点出结果。结果你给我搞一个狂拽酷炫然并卵的界面出来。
栗子2:设计一个可配置性极好的数据库。改个文件就能自由切换底层数据库哦!
结果:10年以后,我只见过1家公司动真格去换底层数据库。到了真换的时候,那种所谓的配置隔离文件起不了什么作用。有太多运维工作要做了,不兼容性、功能不匹配,等等。等到我们的客户要求我们光换个配置文件就把半数表格换到 NoSQL 去,我们就疯了:配置文件只是千千万万要改动的地方之一,客户你玩儿我呢?
今天根本没有办法光凭一个配置层就能把传统数据库和新的 文档/KV 数据库(Redis CouchDB DynamoDB)统一起来。甚至不同的传统关系数据库之间也不行(MySQL Postgres SQLite)(这一段我不想翻译了,我觉得这个是个常识)
长话短说:不要对「可…性」的痴迷坐视不管。要清晰地定义问题场景/用例/需求/解法
套路8:不务正业的 「玩具小发明」
从头开始做东西总是让人感觉良好。但是这些东西很快就没人用了。几个栗子:
- 玩具程序库(HTTP,迷你 ORM/ODM,缓存,配置,等等)
- 玩具框架(CMS,事件流,并发,后台任务,等等)
- 玩具工具(构建、部署工具,等等)
不要忘了:
- 熟悉问题域不是一件轻松的事,要花大量的精力和技巧。一个成功的 Service Runner 库是建立在对 daemon work、进程管理和I/O等等的专业知识深入了解的基础上的。一个 CMS 也不是显示几个字段那么简单,这些字段有内在的联系,还要校验、向导、适配显示等问题要解决。一个看起来很简单、就只做 「重试」这么一件事的程序库,事实上也不是那么容易做好的。
- 维护这些个玩具库需要持续花费时间和精力。再小的开源库要维持运转也是一个容易的事情。
- 就算把这些东西开源了,除了你自己和那些要靠它们吃饭的人,不会再有别人去看的。
- 那帮人开源了这些东西,心满意足地在简历里写上「XXX创始人」之后,一般就会慢慢撒手不管了。
- 维护当前的东西需要在当下花时间。但是创造新东西是在透支未来的时间。(译者注:向未来的人借时间债)
长话短说:复用已有的!复制优秀的!学会贡献!学会三思!
最后,如果真要不得不去做一些超前的事,要带着创业心态去做。要和现有的同行竞争。要争取内部同事的信任和支持。不要觉得都是自己人就无所作为,把一切看作理所当然了。
套路9:安于现状
如果有些东西一开始以某种方式去实现了,那么所有人会默认按照这种思路接着做下去。没有人会去质疑一下原因。能干活的代码就是「好代码」。即使实现的过程有些迫不得已,大家也会绕着弯去适应现状。
一个健康的系统应该是持续演进的。不健康的系统特色就是一直在修修补补。如果一块代码很久都没人提交了,说明这块代码已经开始变臭了。系统的每一部分都要与时俱进。这篇文章写得很好:不要把重构任务一直放在 backlog 里!
一个团队每天实际迭代的状况,和他们理想中的迭代方式对比:
长话短说:重构是每天日常工作的一部分。没有什么代码是不能改的。
套路10:胡乱估计
真心的,经常会见到素质还不错的攻城狮/团队却写出了一坨屎。看到他们写的代码,我们会想,卧槽,这真是那个号称很牛逼的人做出来的么?!
质量不光靠技巧,还要靠时间来凑。越聪明人的人,越容易自信满满导致估计失控。(WTF?)最后呢,只好靠一堆 hack 技巧和自杀性工作时长来把事情了结。
长话短说:还没写代码之前,错误的估计就能毁了工程质量
如果你能耐心看到这里,我谨在此表示由衷的感谢!还是别忘了,我只是在讲 over-engineering 这个事,只是在提倡不搞野路子编程。
10种软件开发中 over-engineering 的错误套路的更多相关文章
- 软件开发中oracle查询常用方法总结
上次新霸哥和大家讲解了一些关于oracle的知识发现大家对oracle还是比较感兴趣的,下面新霸哥就大家比较关系的oracle中常用的查询有哪几种?做个和oracle相关的开发的朋友可能会知道答案,但 ...
- Atitit 软件开发中 瓦哈比派的核心含义以及修行方法以及对我们生活与工作中的指导意义
Atitit 软件开发中 瓦哈比派的核心含义以及修行方法以及对我们生活与工作中的指导意义 首先我们指明,任何一种行动以及教派修行方法都有他的多元化,只看到某一方面,就不能很好的评估利弊,适不适合自己使 ...
- 关于软件开发中兼容win7注册表的解决方案
关于软件开发中兼容win7注册表的解决方案 编写人:CC阿爸 2014-3-14 l 近来在开发一winform程序时,发现在xp 系统访问注册表一切正常.可偏这个时候,微软又提醒大家.Xp今年 ...
- Atitit. 软件开发中的管理哲学--一个伟大的事业必然是过程导向为主 过程导向 vs 结果导向
Atitit. 软件开发中的管理哲学--一个伟大的事业必然是过程导向为主 过程导向 vs 结果导向 1. 一个伟大的事业必然是过程导向为主 1 1.1. 过程的执行情况(有明确的执行手册及标准) ...
- UML在软件开发中各个阶段的作用和意义
经典的软件工程思想将软件开发分成5个阶段:需求分析,系统分析与设计,系统实现,测试及维护五个阶段. 之所以如此,是因为软件开发中饣含了物和人的因素,存在着很大的不确定性,这使得软件工程不可能像理想的, ...
- 四种软件开发模式:tdd、bdd、atdd和ddd的概念
看一些文章会看到TDD开发模式,搜索后发现有主流四种软件开发模式,这里对它们的概念做下笔记. TDD:测试驱动开发(Test-Driven Development) 测试驱动开发是敏捷开发中的一项核心 ...
- 软件开发中的完整测试所包括的环节UT、IT、ST、UAT
软件开发中的完成测试环境所包括的环节包括:UT.IT.ST.UAT UT = Unit Test 单元测试 IT = System Integration Test 集成测试ST = System T ...
- 软件开发中 SQL SERVER 任务的用法
在软件开发中,经常性会用到定时任务.这个时候你可能会想到线程.但是事实中,线程方法比较麻烦.容易出错,资源竞争等问题,设计起来让你很头痛. 现在给大家提供一个新的思路,用SQL SERVER 的任务管 ...
- Java软件开发中迭代的含义
软件开发中,各个开发阶段不是顺序执行的,而各个阶段都进行迭代并行执行的,然后在进入下一个阶段的开发. 这样对于开发中的需求变化,及人员变动都能得到更好的适应. 软件开发过程汇总迭代模型如下图所示:
随机推荐
- Javascript的异步和回调
介绍JavaScript的一些同步.异步.单线程多线程,回调基本概念:https://segmentfault.com/a/1190000002999668
- mysql修改root用户密码
自我总结,欢迎拍砖! 目的:若root用户密码忘记,则需要重新设置root用户的密码. 步骤: 1.找到mysql安装目录下的 my.ini 文件,找到[mysqlId]一行,在下方添加语句:skip ...
- GO开发[六]:golang反射(reflect)
反射 反射:可以在运行时动态获取变量的相关信息 Import ("reflect") reflect.TypeOf,获取变量的类型,返回reflect.Type类型 refle ...
- python数据类型之元组、字典、集合
python数据类型元组.字典.集合 元组 python的元组与列表类似,不同的是元组是不可变的数据类型.元组使用小括号,列表使用方括号.当元组里只有一个元素是必须要加逗号: >>> ...
- 大白话说Java反射:入门、使用、原理
文章首发于[博客园-陈树义],点击跳转到原文<大白话说Java反射:入门.进阶.原理> 反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释. 一般情况下,我们使用某个类时 ...
- BZOJ 4513: [Sdoi2016]储能表 [数位DP !]
4513: [Sdoi2016]储能表 题意:求\[ \sum_{i=0}^{n-1}\sum_{j=0}^{m-1} max((i\oplus j)-k,0) \] 写出来好开心啊...虽然思路不完 ...
- SDP(5):ScalikeJDBC- JDBC-Engine:Streaming
作为一种通用的数据库编程引擎,用Streaming来应对海量数据的处理是必备功能.同样,我们还是通过一种Context传递产生流的要求.因为StreamingContext比较简单,而且还涉及到数据抽 ...
- 原生js贪吃蛇
<!DOCTYPE html> <html> <head> <title></title> <meta charset="u ...
- WPF Effect 造成的字体模糊
WPF 里面有个Effect ,暂且可以理解为 "特效" 分类. 但是有时候使用不恰当,容易出现各种毛病. 例如: 代码如下: <StackPanel HorizontalA ...
- 【翻译】我如何使用CSS来制作bitsofcode Logo动画
翻译文章,翻译不好,还望大家指出 原文地址:How I Animated the bitsofcode Logo with CSS 我是css动画的新手,这样说是因为我只在有限的案例中使用过他们,而且 ...