为啥你会相信“用OO思维从头开发程序”是正确的?
OO编程、设计模式、各种设计原则是入门级开发者经常遇到的概念。实际上被这些内容打上了思想钢印的人总会坚持如何如何写代码是正确的。但到了实际,似乎完全不是那么回事。在真的项目中应用那些“正确的理念”,总会出现这样或那样的别扭,甚至起完全相反的效果。这时,被打上钢印的人往往会认为“不是这些理念不对,而是还没学到家。如果再获取到更多“高级”的资料,就一定可以解决问题!“
然后……一次又一次的被打脸。
这类事情OO算是影响力比较大的一个。如果你足够关心周围的事情,类似的其实还有好多:
其实不过是不站在实际的角度去分析真正问题的根源罢了。
比如最基本的,你写一个网站的后端。此时,服务来了一个请求,按照一个流程图的方式实现逻辑有问题吗?用A.foo()或者foo(A)在这个全局视角下有什么本质区别吗?没有。
复杂一点,比如电商交易。订单管理,库存管理,支付渠道,物流服务是4个完全独立的部门。下一次单要完成扣库存,产生订单,扣款和发起物流流程这几个操作。这4个部门的服务是典型的“对象”,在彼此沟通完成一个完整的业务流程。如果在分布式的情况下,他们是以微服务的形式存在的,用某种RPC机制沟通。但与此同时,这里需要某个一致性的保证。
但如果他们刚好都在一个单机的服务里,可不可以是4个service class产生的spring的bean,提供某个接口呢?当然是可以的。那如果不用Java,用go呢?也没问题啊,上interface做一个“facade”表达什么接口可以公开就好。如果用C呢?也没问题啊,在C里你依然可以表达什么东西公开,什么东西隐藏。如果语法支持的不那么好,写注释啊。对方瞎用了后果自负。
如果你需要某种横切面的治理,需要在流量入口拦一道做鉴权,风控,流量路由等,就是要在流量必经之处增加逻辑。Java里有AOP,可以用。不用java,自己撸个中间件机制可以吗?当然也没问题。毕竟早期Java自己没有Annotation的时候也是弄了个标准定义了Filter才搞定这件事。你说这是模式吗?是。但看起来很高大上吗——no。他就是这个问题抽象出来最符合直觉的做法而已。
如果是搞大数据分析,那么代码设计还重要吗?可能一个研发写的就是一坨坨的SQL,Hive的SQL,Flink的SQL,Clickhouse的SQL,ES的SQL……。但这个时候如果需求方有上万个指标,要从几百个表中统计,那这时候问题是什么?是数据治理。需要对数据分层ODS,DW,DM,DA…… 要对指标进行标准化的维护,不然需求描述的混乱性就会让数据分析本身失去意义——RD实现的和需求方要的不一样还搞个锤子。此时就会发现,这种分层和维护道理上和写代码没啥区别,只不过用的不是class的继承和对象关系维护层次,而是使用数据和数据的依赖关系。指标维护使用的是“元数据表”和管理平台,而不是enum罢了。
如果是搞前端,前端天然的代码组织方式就是一个个可视化组件。每个组件维护自己的状态,并基于关联数据在UI上绘制出来。javafx的组件(一个class)、jquery的组件(一个函数)、React的一个组件(Hook 函数)……他们都是组件,都能满足上述的需要。你说javascript提供一个class和不提供class关键字有啥本质区别吗?
具体到某个业务,比如想实现Ctrl+Z撤销,那必然得有个地方记录每个操作的之前的状态和之后的状态。这样才能做回退。书上把这个叫做Command模式。但这个名字不重要,关键是本质上就得有个地方记。对比到一个支持ACID的数据库存储引擎是一摸一样的,需要Undo Log或者某个等价机制支持事务回滚。
到了任务调度领域,本质问题是维护当前可以使用的资源,并将资源最大化的利用在处理任务上。不管是操作系统的任务调度,还是大数据任务调度平台,都是如此。管理的资源不一样罢了。这时你需要一个地方知道现在都还有哪些资源,并给任务配上可用的资源。抽象一下这里就需要一个interface查询资源和分配资源罢了。至于用啥语法,模式,都可以商量。
我想我举的已经够多的了。这个市面上99%的人并不是编程语言从业者和研究人员。他们的工作目标和职责与语言风格、设计模式的关系并不大。或者说即使用了好的语言风格,设计模式,如果运气够好,能给一个微小的局部带来一点点提升(比如代码好看懂,好改)。但如果用不好,造成编码更不容易改和改动的比比皆是。但是在更全局的视角下,这些都无关紧要。这些内容甚至都不如多加点测试,多加点线上监控和问题发现机制来的实在。
所以我对很多人热衷于讨论这一套现实意义不大的东西感到非常困惑。或者说有些讨论还是有些意义和借鉴价值的,但更多的资料就是抄来抄去的空话,以及饭圈般的干架。至于要解决的问题本身,讨论的并不多。不信,比如”公司需要一个网关,要怎么设计“?想想看如何做。
至于30年前为啥有先哲折腾这些理念?因为那时编程本身的理论不成熟。缺乏”如何组织好代码“的思路和工具。那个时代用C/C++都是重要突破,smalltalk更是划时代的思路的体现。但30年后那些问题还是问题吗?
我并不反对在抽象的编程思路上做探索。但我希望所有人都能明白:当你的目标是探索的时,要明确的知道自己是在探索。探索这个事情本身就有风险。如果你拿着现实的项目做实验,拜托做好Plan B,并且有效的去观察那个方案是不是真的有效。有效就有效,没效果就没效,及时止损就好。探索并不会因为你看了“高级”的书而变得不同。
正则写的不好,不要怪没看Intel汇编手册和JVM深入浅出。
Spring的@Transactional没生效,八成是哪里配置错了或者一个bean自己调用了自己,和是否深入理解AOP思想和理论没有半毛钱关系。
差几个业务逻辑没实现对,拜托下次仔细看PRD,或者和PM更充分的沟通,顺手再补个测试。顺便再想一想你的公司上一次出现P0严重事故赔钱给用户是在什么时候?解决了吗?代码写错了,有没有CI帮你检查并报警?写了个接口限流了吗?支持熔断吗?有降级预案吗?
我也不反对挑选一个好工具。虽然同一件事情这样也可以,那样也可以。但总有一个工具是比较合适的,综合性价比是高的。go实现并发就是比java方便些;而java实现AOP就是比go舒服。根据自己的需要选个趁手的兵器天经地义。但拜托不要反过来,用工具去套需求。消毒水再好也治不了新冠不是?
如果你对某个理念深信不疑,最好可以深究一下这个信念从何而来?为啥你觉得“用OO思维从头开发程序”就是对的?是某些资料这么说就信了?还是你的同行业的人用那个理的确做成了事情?如果你的同行都不这么干,你为啥相信你想的就一定是对的?即使同行落地,想想看他是不是只把光鲜亮丽的部分讲出来,而故意隐藏了关键的负面内容?
浓缩到一句话。到底什么是【事实】,什么是【表达】,哪个更重要?有没有偏好于拿着【表达】当作【事实】?如果是,这和成天皿煮的西方媒体政客又有啥区别咧?
也许写了10年程序,踩过了各种坑,见识过了各种垃圾代码,对某个行业有了更深入的认知,才是回过头来思考如何设计代码的时候吧。