代码重构之旅(一) 项目结构

起因

之前说过自己被分配了一个重构代码的任务:

这是一个有6、7年历史,多人经手的老系统,PHP 语言,分布在50台机器上运行。代码使用最简单的结构,没有使用任何完整框架,甚至有三四千行代码的文件,修改时最新的 IDE 都无法帮得上忙,发生问题时排查困难。特别是程序员在写代码时,想引用之前的方法找不到,自己定义新的方法又无处放,只好在已经很杂乱的文件上堆砌。

由于此系统一直在运行中,甚至各机器日顶峰QPS有近1000,而且作为一个业务系统,之前甚至没有CR,里面遍布一些比较“奇怪”的逻辑和写法,看得人头皮发麻,迁移这个系统无异于给行驶的汽车换轮子。

写跟此项目迁移相关的文章,不仅是为了分享经验,更是为了在迁移的步骤中能梳理一下思绪,尽量少踩坑,也顺便沉淀一下知识。

文章经常被人爬,而且还不注明原地址,我在这里的更新和纠错没法同步,这里注明一下原文地址:https://cloud.tencent.com/developer/user/1148723/activities

问题

首先是定位问题,之前的代码有以下问题:

  • 文件:代码组织逻辑不明显,手动加载文件不方便,且文件代码行数很多,有很多不必要的引用。
  • 代码:函数定义逻辑不清,根据功能找函数不方便;且代码耦合度高,导致复用率低;多处定义全局变量,很可能会被某处引用并修改,引发异常。
  • 配置:配置分散在各文件中,引用不易查,改动时无法保证完全改动。
  • 测试:无测试项,改动后风险不可控。

针对这些问题,考虑将代码迁移到 Yaf 框架下,将其重构:

  • 使用 Yaf 框架管理代码组织,使用命名空间实现易加载、按需加载。
  • 使用命名空间和类从逻辑上聚合方法,避免全局变量风险;代码分层,分离数据和逻辑,提高数据代码和部分逻辑代码的复用率;
  • 配置数据统一管理,避免多处依赖,降低配置修改风险;
  • 添加 phpunit 单元测试,降低代码修改风险;

谈谈框架

框架

我们在多人合作开发大型项目时,必然要考虑到如何使代码复用率最高,如何让一个开发者可以在庞大的项目里迅速找到自己想要的方法。时间久了,大家会总结出一个套路解决上述问题,每次面对开发任务时都按照同样的方式开发。一些有经验的高手开发者会抽象出这个套路,整理并实现为框架。

所以框架是为解决一个开放性问题而设计的具有一定约束性支撑结构。从其定义的几个方面来分析:

  • 解决问题:框架要解决的问题是开发规范和效率问题,使用同一种规则,能大大降低开发者决定很多策略时的心智负担。
  • 约束性:无规矩不成方圆,既然是规范,那么一定有其约束性。框架一般会对文件、方法、命名、类等进行约束。
  • 支撑结构:框架只是一个支撑结构,适用性广,它像一个货架,开发者把代码货物摆到对应的地方即可。

为了深入了解框架的思想,我之前也写过一个自己的PHP框架:GitHub-zhenbianshu-Sqire_Framwork,还有配套的博客三篇:搭建自己的PHP框架心得

框架只是实现了 MVC 的设计模式和 简单的路由,有对此感兴趣的同学可以 fork 下看一下。

Yaf

Yaf 学 PHP 应该都有所了解,这里不过多介绍。 它作为用 C 编写的PHP扩展存在,效率自然不用多说,选用 Yaf 更多是因为它作为框架的“自由”。

Yaf 最大限度地给开发者自由,开发者可以定义代码结构,在路由各步骤间定义个性化需求。而Yaf 只在最适合的时候提供一些帮助,恰好足够满足开发需求,又不会添加多余的规则和限制。

就如同我们在使用导航软件时,传统框架一般会在地图标出一条路线,这条路线可能会为了你并不需要饭店或宾馆而绕远路,而开发者必须沿着这条路走;Yaf则只会指明方向,走直线或弯路全凭自己实现。

当然我们也要为自由付出一些“代价”,缺少了框架的指导,项目分层和结构这些纠结的事就要自己来确定了。

结构

代码结构是我来设计的,参考了几个已有项目的结构,也尽量兼容当前项目的写法,让同事尽量容易接受。不敢说是最好的,至少目前来看是最适合的了。

整体结构

作为一线开发者,为了整理出最适合开发者开发的代码结构我做了很多尝试。最后结合 MVC 和三层架构(三层架构:UI 表示层、BLL 业务逻辑层、DAL 数据访问层)整理出了目前的四层代码结构:

考虑到MVC中的M层会因为业务扩展,变得逻辑复杂,最后臃肿得不好维护;而三层架构中表示层太单薄,View不易控制。最后修改为 BLL/DAL/V/C;

由上至下为:

  • V: 接口数据的输出、日志、文件、view页面;
  • C: controllers 控制器、后台脚本;
  • BLL: 业务逻辑 Service;
  • DAL: 数据访问层,包括内部数据的访问:Db, 和外部接口数据的访问:Api。

支持层

在四层代码结构之外,预留了两块结构作为全局支持:

Tools:由于禁止跨层调用的限制,一些函数的调用可能会很麻烦。于是提供全局可用的工具,开发者可以在各层按需加载这些工具。

config:提供集中的文件配置,通过划分清楚的文件夹可以帮助快速找到并修改配置。

除此之外,将一些很常用的方法和常量注册为全局,省去了不必要的频繁加载;同时也借用了Yaf 内置全局变量提供了公共数据透传功能。

文件结构

下面是被我删减到无法再删减的文件结构,供人参考,也希望有人能提出改进意见。

├── app
│   ├── Bootstrap.php
│   ├── controllers
│   │   ├── Error.php
│   │   └── Mobile
│   │       └── Search.php
│   └── library
│       ├── Api
│       │   └── Util.php
│       ├── Db
│       │   └── User
│       │       └── Addr.php
│       ├── Service
│       │   └── Order
│       │       └── Count.php
│       └── Tools
│           └── Http.php
├── config
│   ├── application.ini
│   ├── error
│   │   └── api.php
│   └── rewrite.php
├── public
│   └── index.php
├── scripts
└── tests
    ├── controllers
    │   └── UserSortTest.php
    └── phpunit.xml

思考

除此之外,还有些比较纠结的问题:

Cache 和 Db

在当前代码结构中,我把 Redis Cache 和 Mysql Db 整合放到了同一层 Db 层。

主要考虑到:

  • 项目稳定,数据库类型可控,不会再扩展了;
  • 大部分数据直接使用 Redis 作 Db,即单一数据库;
  • 中间再添加一层数据调控层的话开发为了遵守不跨层的规范会写很多无意义的代码;

不知道会不会埋坑,不过即使后期复杂,将这一层再拆分也不会有较大风险。

静态方法 or 类方法

项目中绝大部分逻辑都是增删改查或数据处理,于是我在底层方法普遍使用静态方法,由于静态方法不需要实例化对象,无论是在开发还是运行都比使用类方法效率高。

当然类方法是完全支持的,适用一些复杂的长流程数据处理。

业务逻辑划分,实体 or 逻辑

业务逻辑层中,文件分类是最纠结的事。如用户操作订单的相关逻辑:

  • 如果按照实体来拆分,用户类和订单类都无法完全精确地表示。
  • 而如果按照逻辑来拆分,多种多样的操作逻辑也同样让人抓狂。

目前主要使用了按照实体拆分,只考虑被操作对象,如查询用户订单的逻辑放在用户类,而用户删除订单的逻辑放在订单类。

另外也会提取一些专有且复杂的逻辑实现一些逻辑类,如对订单有多种类型的排序,都放在订单类中基本不会被复用到,则抽象出一个订单排序类。

小结

现在项目刚确定了代码结构,重构了基础方法,业务代码还在持续迁移中,下次会聊一聊怎么通过 框架路由 和 Nginx 配置进行灰度测试。

由于是业务部门,业务的开发是头等大事,代码迁移工作只能排在业务需求后面,而且开发人员也不足,整理之前的奇葩逻辑花费太多时间,所以迁移进行得很慢,此系列文章偶有更新,欢迎有相同经历的同学关注或发现意见。

关于本文有什么问题可以在下面留言交流,如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我,一直在更新,欢迎 关注

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java技术

优秀的代码都是如何分层的?

说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多...

952
来自专栏小程序·云开发专栏

你不知道的Node.js性能优化

仅仅是简单的升级 Node.js 版本就可以轻松地获得性能提升,因为几乎任何新版本的 Node.js 都会比老版本性能更好,为什么?

7.4K4
来自专栏芋道源码1024

你的项目应该如何正确分层?

说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多...

662
来自专栏Java架构

每个 JavaScript 工程师都应当知道的 10 个面试题以人为本1. 能说出来两种对于 JavaScript 工程师很重要的编程范式么?2. 什么是函数式编程?3. 类继承和原型继承有什么区别?

1996
来自专栏铭毅天下

Elasticsearch全文检索实战小结——复盘我带的第二个项目

一、项目概述 这是一个被我称之为“没有枪、没有炮,硬着头皮自己造”的项目。项目是和其它公司合作的三个核心模块开发。 使用ES的目的是: 1)、采集数据、网...

56110
来自专栏Java社区

前后端数据对接的思考及总结

2333
来自专栏Java技术栈

Java 9、10、11,哪个才是 Java 程序员的本命?

之前,我们在《Java 10无跳票发布,主推的新特性引争议》的文章中做了一个小的调查,主要是调查现在的Java程序员都在使用哪个版本的Java?根据调查结果,绝...

1553
来自专栏Linyb极客之路

你的项目应该如何正确分层?

说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多...

1183
来自专栏京东技术

多级缓存设计详解 | 给数据库减负,刻不容缓!

物流研发部架构师,GIS技术部负责人,2012年加入京东,多年一线团队大促备战经验,负责物流研发一些部门的架构工作,专注于低延迟系统设计与海量数据处理。曾负责青...

1525
来自专栏微信公众号:Java团长

分布式之消息队列复习精讲

庆幸的是两位朋友都很有上进心,于是博主写这篇文章,帮助他们复习一下关于消息队列中间件这块的要点

782

扫码关注云+社区