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

起因

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

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

相关文章

来自专栏腾讯云技术沙龙

黄文俊:Serverless架构及场景介绍

大家好,自我介绍一下,目前我是腾讯云无服务器云函数产品负责人。我做了很多年后端开发。今天是从一个程序员角度讲解一下我们怎么样用Serverless架构。

2516
来自专栏架构师之路

秒杀系统架构优化思路

一、秒杀业务为什么难做 1)im系统,例如qq或者微博,每个人都读自己的数据(好友列表、群列表、个人信息); 2)微博系统,每个人读你关注的人的数据,一个人读多...

38310
来自专栏互联网杂技

JavaScript就要统治世界了?

" JavaScript 可以……" "嘛,不就是操作一下 DOM,可以让元素飞来飞去吗" "JavaScript 是……" "不就是用 jQuery 让网页动...

2916
来自专栏悦思悦读

码农的瑞士军刀-脚本语言

首先声明,这里关于语言间的比较仅仅是为了说明问题。世界上没有一个开发语言有绝对的好坏,只有是否适合你当前的环境。 很多码农工作的非常辛苦,每天加班。一般情况,团...

36310
来自专栏EAWorld

代码实战:从单体式应用到微服务的低风险演变

作者:Christian Posta 译者:海松 原题:Low-risk Monolith to Microservice Evolution Part I...

3345
来自专栏DevOps时代的专栏

软件测试金字塔

? “测试金字塔”是一个隐喻,它告诉我们将软件测试分成不同颗粒度的桶,也给出了我们应该在这些组中进行多少次测试的想法。尽管测试金字塔的概念已经存在了一段时间,...

3235
来自专栏云计算

OpenShift的容器镜像(第1部分):目标

本文来源于2017 EMEA (Europe, the Middle East and Africa,欧洲,中东和非洲) 红帽技术交流会议的会议记录,与会者包括...

1966
来自专栏用户2442861的专栏

分布式统一框架的设计与实现(数据库)

我们设计并开发了内容中心统一的分布式开发框架。我们把它取名为albian, albian是基于java的(故以下简称albianj)。他主要是面向海量数据处理...

591
来自专栏架构师小秘圈

那些年不加班的开发团队的秘密,原因竟是因为持续集成!

持续集成,让很多开发团队又 「 爱 」 又 「 恨 」 。爱,在于整个流程对项目的交付价值大有裨益,尽最大可能地减少不必要的加班;恨,在于成本过大,部署的困难、...

3815
来自专栏GreenLeaves

FactoryMethod工厂方法模式(创建型模式)

整个抽象的游戏设施建造系统相对变化较慢,本例中只有一个Build的创建方法,而Build内部的方法实现,该实现依赖与各种具体的实现,而这些实现变化的非常频繁,现...

482

扫码关注云+社区