当前位置: 首页 > 后端技术 > PHP

三年前旧代码的重构、总结与反思

时间:2023-03-29 17:54:09 PHP

目前正在维护一份三年前的旧代码,使用的是laravel框架。这段代码在某些方面比较标准,为了“在规定时间内完成相关功能”,同时“程序员水平不高”和“经过大量优化”,就变成了特别糟糕。但其中,程序员的水平和态度是最重要的,其他相对次要。当然,我是那几个程序员中的一员,所以我可以随意说自己的坏话。另外,本文会多次提到语言之间的比较,当然本文的目的不在这里。无论从过去还是现在来看,Laravel的框架理念和结构都堪称“现代PHP”的典范。Laravel之于php就像springboot之于java。Laravel为http请求引入了中间件。通过一些配置,您可以轻松使用类servlet拦截器,它比servlet强大得多。针对ORM类的需求,创建了eloquentframework,使用的简单性堪称一流。至于安装、配置、部署、依赖等等,laravel也提供了一个完全通用的解决方案,这就惨了。可以说,如果我们完全按照laravel的架构和laravel的文档来写,即使写出来的代码不会很优雅,但也绝对不会特别可怜。当然,laravel的缺点也很明显。最重要的缺点是性能。作为一个PHP框架,laravel在性能上的无意识尤为滞后。我们这里没有详细的数据,但是可以提供大概的数据:最简单的路由,只有Redis::set的操作,没有任何中间件和计算逻辑。24C64G的机器只能支持300+QPS左右,即使开启opcache也没有质的提升。至于php7,2015年12月发布了第一个7.0版本,项目上线四个月了,别说等laravel支持PHP7了,更何况php7还不能满足性能要求。这就是为什么我们的第一个版本的代码在两种语言中已经是异构的。PHP部分用于处理正常的业务请求,Go部分用于处理心跳等其他请求。在技??术和能力上,未来几年我们也会反思这个问题。如果我们使用tcp协议来传输数据,服务器也使用Go,那么我们可以做很多“看起来很酷”的事情,例如:我们可以到处实现和宣传C10K和C100K,我们可以宣传实现无处不在1000亿MPS(Messagepersecond),所有任务结果可以流式传输到服务器,并同步返回结果,可以让用户体验更好。但如果这样做的话,无状态、流量、日志存储等都是需要考虑的新问题。这可能会把我们培养成技术流,但也可能让我们的项目变成新的“技术毒瘤”。现在回过头来看,更重要的问题是,我们可以肯定的是,没有一个与会者敢于提出这样的方案,也没有人能左右。维护成本维护成本无疑是后期最大的成本。早期,我们依赖supervisord。早期我们经历过supervisord和docker的supervisord冲突失败。后来我们也遇到过其他项目也依赖supervisord的失败,其他项目因为配置原因停止。如果是错的,那是我自己的错,也没什么好说的。但其他维护成本更高。能想到的,比如agentkeepingalive,是比较常见的问题。几十个、上百个agent之间,总会出现一两个问题。我们都对此习以为常,甚至自己做自动修复也能解决问题。.你想不到的是,有些人还把环境相关的工作算在了你的头上。有的人会因为“为什么你的系统在这个环境下有这个问题”,查了半天,对方的端口却没有打开。这在我们中间件相关的项目中比较常见。通常他们会上来问为什么这个中间件会出现这样的问题。其实让他们把完整的堆栈发出来之后,告诉他们“明明是causedby,unknownhost,就是说你的XXX域名没有配置好。嗯”。每天有四五个人问这种问题,也会给回答方带来很大的压力。在我们的其他工作中,我们也在不断探索如何降低教育成本。常见问题解答和培训似乎没有效果。假装喷人看似有效,比如“你在XX又开启了远程调试了吗(这里请意大利炮头脑风暴一下)”,但这种操作不能天天做。如何降低教育成本,让开发者拥有自己解决问题的能力,一直是我们工作的重点。但到目前为止,我们在这方面似乎收效甚微。需求、功能、BUG、变更我们在这个项目上承受了很大的压力,没有接到新的需求,也没有增加新的功能。我们当时的理由是“他只是一个任务的服务器”,“你只要把这个任务写成这样就可以实现这个功能”。但是后续的系统改动是我们当时没有考虑到的。随后,我们有了新的任务管理界面,新的统一登录界面,CMDB界面也进行了多次改动。最终只剩下API每天在这个系统中辛苦劳作。按理说,没有人访问的接口和接口应该直接下载。不过至少这也是自己天生的BUG,自己也是心软,也没办法。根据其他系统的经验,有时我们不得不添加一些功能,而这部分功能可能会引入很多问题。比如有人抱怨为什么大公司的代码这么烂。一个项目中有四个版本的httpclient。这件事是可以理解的。例如,在早期阶段,HttpURLConnection可能仅用于get请求。中期为了支持post请求,支持参数,支持超时,单独封装了HTTPClient。后来因为引入了JWTs,又进行??了封装。代码冗余变高,但既然“系统运行良好”,就“没必要再简化”。可以理解,但不能接受。代码冗余一直是重构内部项目时的通病。通常,为了不影响原代码的执行,复制已有代码,改名,修改为新的接口调用。Java等静态语言合并冗余代码相对简单,编译成功可以保证大部分功能可用。但是在php等动态语言中我们是不敢这么干的。PHP做不到“编译成功,基本没有问题”。从这个例子来看,一方面是开发者对HttpClient的认知不够,另一方面是开发者对代码的抽象能力不够,没有留下合适的接口来满足未来的需求,所以“httpclientin一个项目有四个版本”的噩梦。一些内部制度会和我们早期一样,先做成果,再追求更高的层次。但这不是一个技术人应该有的态度。一个优秀的程序员的价值不在于掌握的那几条屠龙术,而在于细节。如果我们能够一次就做对,并且做好,尽可能做到精益求精,何乐而不为呢?一定要有结果,但一个技术人要有对职业的自尊,对自我价值的追求,对卓越的理解和渴望。完美有多远?我不知道,但我以后肯定会多走几步。单元测试和语言并发控制其实都是很痛苦的问题。夸张的说,当时的PHP还不能做到特别容易的并发,甚至是并发。我们现在的服务器其实只是做任务转发,用一些套路来实现并发(curl_multi),并不能实现并发控制等功能。至于多线程(pthreads)和多进程(pcntl)方案,在实测中并不稳定,在测试阶段会产生coredumps。并且经过多次调优,我们终于解决了curl_multi的性能问题,可以达到几万并发,性能还不错。现在回顾一下,如果你使用Go,你可以很容易地用5-6行代码来增加并发控制。Go语言本身的性能不错,并发性也很好。Go语言的一个特点是它自带单元测试。这个类似于maven,但是Go是为数不多的为语言层提供测试工具链的语言之一。静态语言相对于动态语言的优势之一就是安全。可以夸张的说,静态语言一旦编译成功,除非出现RuntimeException,否则基本不会出问题。但是像PHP这样的动态语言就比较痛苦:不仅写的时候可能会出问题,很多IDE都无法意识到你是否写了一个bug,甚至几年后再回来看代码,即使你的代码我参与过的项目读起来也很痛苦。PHP也意识到了这一点,从PHP7开始引入类型声明也可以缓解这个问题。在使用Go语言之前,我的习惯是不写单元测试。使用Go语言后,我开始养成了为所有功能编写单元测试的习惯。我们在本文中多次提到了Go语言。其实语言对项目影响不大。真正唱主角的是人。标准化运维规范对本项目影响不大,主要是开发规范。在后续的工作中,我不止一次告诫业务开发,我们现在所有的规范,不管是运维规范,数据库开发规范,还是任何代码开发规范,都是我们一次次踩坑铺出来的路.如果当初我们有数据库开发规范,表结构就不会这么坑了。就像laravel框架,我们按照规范去写,这样代码不会升一级,但不会让代码烂掉。在这里我们强烈推荐《阿里巴巴Java开发手册》给小公司和开发者。不仅有开发规范,还有表结构规范,对开发和公司都有好处。语言对项目影响不大,真正起主导作用的是人。如果人的平均素质不能优秀,那么完善的流程和规范将极大地影响一个项目的质量。总结教育成本是后期维护的主要成本之一。我们也一直在尝试赋予开发者自己解决问题的能力,虽然很难。人的素质,无疑可以直接决定一个项目的好坏。当然,对于普通公司和平均素质不高的新人来说,一个完善的流程和规范在很大程度上保证了一个项目的质量。静态语言和单元测试等方法是保证项目健壮性的重要途径。