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

起因

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

这是一个有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 条评论
登录 后参与评论

相关文章

来自专栏嵌入式程序猿

Bootloader需要你的精心设计

嵌入式产品,我们一般都需要一个bootloader来更新固件和修复bug,一般常用的接口有,UART, CAN, USB, Ethernet,有的还有无线接口,...

1143
来自专栏大数据人工智能

ZStack--工作流引擎

在IaaS软件中的任务通常有很长的执行路径,一个错误可能发生在任意一个给定的步骤。为了保持系统的完整性,一个IaaS软件必须提供一套机制用于回滚先前的操作步骤。...

4484
来自专栏美团技术团队

MyFlash——美团点评的开源MySQL闪回工具

由于运维、DBA的误操作或是业务bug,我们在操作中时不时会出现误删除数据情况。早期要想恢复数据,只能让业务人员根据线上操作日志,构造误删除的数据,或者DBA使...

36412
来自专栏進无尽的文章

聊聊程序设计思想之面向接口编程IOP

我们在一般实现一个系统的时候,通常是将定义与实现合为一体,不加分离的,但是有时候最为理想的系统设计规范应是所有的定义与实现分离,尽管这可能对系统中的某些情况有点...

1052
来自专栏野路子程序员

徒手解剖composer,简单了解其实现过程

2836
来自专栏IT派

10 个技巧,让你更专业地使用 console 进行 JS 调试

首先,我必须承认这一点,我将利用这个平台从我的开发环境中清理出骨架(轮廓)。有时候,我所做的“魔法”(有些人称之为“编码”),并不像我的同事在为他们展示这些宏伟...

910
来自专栏小石不识月

微服务编排

在 Jexia 中,我们相信微服务架构是组织我们的后端云的最佳方式 —— 它可以很好地进行关注分离(Separation of concerns),并为特定任务...

4928
来自专栏FreeBuf

ASLRay:一个可以绕过ASLR的工具

ASLR(Address Space Layout Randomization,即地址空间格局随机化)是指利用随机方式配置数据地址,一般现代系统中都加设这一机制...

2188
来自专栏玉树芝兰

如何用iPad运行Python代码?

(由于微信公众号外部链接的限制,文中的部分链接可能无法正确打开。如有需要,请点击文末的“阅读原文”按钮,访问可以正常显示外链的版本。)

2712
来自专栏张善友的专栏

Google SiteMap Protocol协议

在新浪看到这样的新闻Google雅虎微软联手支持网页手工提交标准, Google、微软和雅虎认为,统一标准有助于从整体上改进站点地图,从而搜索引擎可以将更广泛的...

21410

扫码关注云+社区