刚接触后端开发那会儿,我接手了一个 PHP 项目。打开代码的那一刻,我以为自己在考古。3000 行的 index.php,SQL 语句和 HTML 混在一起,全局变量满天飞。这不是代码,这是一碗意大利面。
一个真实的噩梦
让我带你看看那份代码长什么样:
<?php
$conn = mysql_connect("localhost", "root", "123456");
mysql_select_db("mydb", $conn);
if ($_GET['action'] == 'list') {
$sql = "SELECT * FROM users WHERE status = 1";
$result = mysql_query($sql);
echo "<html><body><table>";
while ($row = mysql_fetch_array($result)) {
echo "<tr><td>" . $row['name'] . "</td>";
echo "<td>" . $row['email'] . "</td>";
if ($row['role'] == 'admin') {
echo "<td><a href='?action=delete&id=" . $row['id'] . "'>删除</a></td>";
}
echo "</tr>";
}
echo "</table></body></html>";
} elseif ($_GET['action'] == 'delete') {
$sql = "DELETE FROM users WHERE id = " . $_GET['id'];
mysql_query($sql);
header("Location: ?action=list");
}
// ... 还有 2900 多行
?>
问题清单:
| 问题 | 危害 |
|---|---|
| SQL 注入漏洞 | $_GET['id'] 直接拼接 SQL,可以删库 |
| 明文密码 | 数据库密码硬编码在代码里 |
| 无分层 | 数据库、业务、视图全部混在一起 |
| 无复用 | 每个页面都重复写连接数据库 |
| 难测试 | 想测试?只能打开浏览器点点点 |
这不是个例。这是那个时代许多小公司的常态。
混沌的特征
什么是「混沌」?不是代码少,是代码没有边界。
混沌的本质:任何代码可以访问任何数据,任何改动可能影响任何地方。
特征一:全局变量满天飞
// config.php
$db_host = "localhost";
$db_user = "root";
$current_user = null;
$is_admin = false;
$cart_items = array();
$error_message = "";
每个文件都 include 'config.php',每个文件都能修改这些变量。
结果:你永远不知道变量在哪里被改了。调试一个 bug,可能要翻遍整个项目。
特征二:复制粘贴编程
需要在另一个页面显示用户列表?复制一份。
需要在后台也显示?再复制一份。
三个月后,项目里有 17 份「略有不同」的用户列表代码。改一个 bug,要改 17 个地方。漏掉一个,就是新 bug。
复制粘贴是技术债务的复利。
特征三:业务逻辑散落各处
想知道「用户下单」的逻辑?你需要看:
order.php里有一部分cart.php里有一部分payment.php里有一部分email.php里有发送通知- 数据库触发器里还有一部分
没有人知道完整的流程。 包括写这些代码的人。
特征四:改动恐惧症
每次改代码都像拆炸弹:
- 这个函数被谁调用了?不知道
- 改这个变量会影响什么?不知道
- 这段代码为什么这样写?不知道(注释说的是另一回事)
最安全的策略是:不改。 但需求不会停。于是你选择:在外面包一层。
六个月后,包了六层。代码变成了俄罗斯套娃。
混沌是怎么形成的?
没有人故意写烂代码。混沌是日积月累的结果。
阶段 1:原型期(一切都很美好)
项目刚开始,需求简单,一个文件搞定一切。代码清晰、直接、高效。
「不需要过度设计,先跑起来再说。」
阶段 2:功能膨胀(开始变味)
需求越来越多。文件从 100 行变成 500 行,从 500 行变成 2000 行。
「没时间重构,先加上这个功能。」
阶段 3:人员更替(开始失控)
原作者离职了。新人接手,看不懂代码,不敢改。于是:
- 能复制就复制
- 能绕过就绕过
- 能不碰就不碰
阶段 4:死亡螺旋(积重难返)
代码越来越难改 → 改动越来越慢 → 压力越来越大 → 越来越没时间重构 → 代码更难改
技术债务会产生利息。而且是复利。
一个类比:房子的装修
想象你买了一套毛坯房。
第一年:简单装修,能住就行。线路裸露在外,但能用。
第二年:加装空调,电线不够,接个插线板。
第三年:加装洗碗机,水管不够,从厨房接一根管到阳台。
第四年:加装新风系统,没地方走管,凿墙。
第五年:想改造?水电工打开墙:「这是谁接的线?我不敢动,怕整栋楼停电。」
这就是混沌代码的真实写照。
每一次「临时方案」都在增加复杂度。当复杂度超过某个临界点,系统就变成了「遗留系统」——能跑,但没人敢碰。
混沌的代价
代价一:开发效率断崖式下降
| 阶段 | 新功能开发时间 |
|---|---|
| 第 1 个月 | 2 天 |
| 第 6 个月 | 1 周 |
| 第 12 个月 | 2 周 |
| 第 24 个月 | 1 个月 |
同样的功能,后期开发时间可能是前期的数倍甚至十几倍。 具体倍数因项目而异,但趋势是明确的:技术债务会拖慢一切。
代价二:Bug 率指数级增长
改一个 bug,引入两个新 bug。因为你不知道改动会影响什么。
代价三:人员流动加剧
混沌代码会加速人员流动。新人上手困难,老人疲于应付。
当维护成本超过重写成本,团队士气会受到影响。
代价四:创新停滞
「这个功能做不了,代码不支持。」
「这个改动风险太大,下个版本再说。」
技术开始拖业务的后腿。
当时的「解决方案」
面对混沌,那个年代的程序员们也尝试过一些办法:
方案 1:文件夹分类
/project
├── user/
│ ├── list.php
│ ├── add.php
│ └── delete.php
├── order/
│ ├── list.php
│ └── create.php
└── admin/
└── dashboard.php
结果:文件是分开了,但代码风格还是一样。每个 list.php 里还是数据库、业务、HTML 混在一起。
换汤不换药。
方案 2:include 大法
<?php
include 'header.php';
include 'db.php';
include 'functions.php';
// 业务逻辑...
include 'footer.php';
?>
结果:functions.php 变成了垃圾场。什么函数都往里扔,最后变成 5000 行的「工具类」。
方案 3:「框架」
技术总监说:「我们需要一个框架。」
于是他写了一个「框架」:
<?php
class Controller {
function run() {
$action = $_GET['action'] ?? 'index';
$this->$action();
}
}
class UserController extends Controller {
function index() {
// 还是数据库、业务、HTML 混在一起...
}
}
?>
有了 Controller,但没有 Model,没有 View。 披着框架的皮,干着面条的事。
走出混沌的希望
2005 年,Ruby on Rails 发布了。它带来了一个革命性的理念:
Convention over Configuration(约定优于配置)
Rails 告诉你:
- 模型放
app/models/ - 控制器放
app/controllers/ - 视图放
app/views/ - 数据库操作用 ActiveRecord,不要写 SQL
不是你决定代码怎么组织,是框架告诉你怎么组织。
同一年,Django(Python)发布。同样的理念。
再后来,Laravel(PHP)发布,把现代框架的理念带回了 PHP 世界。
混沌的终结,需要自律与约束的结合。
良好的开发习惯(自律)和框架强制的规范(约束)缺一不可。框架提供外部约束,但团队的工程文化同样重要。
从混沌中学到的教训
回顾那段混沌时期,我学到了几个重要的教训:
教训 1:代码需要边界
任何代码都应该有明确的职责。一段代码做一件事,做好一件事。
教训 2:「先跑起来」是陷阱
快速原型可以不讲究,但原型应该被扔掉,而不是演变成产品。
教训 3:技术债务会吃人
现在省下的时间,未来要十倍偿还。而且是带利息的。
教训 4:框架不是可选项
不是「要不要用框架」的问题,而是「用什么框架」的问题。对于通用 Web 开发,成熟的开源框架通常是更好的选择。自研框架在特定领域(如高频交易、游戏引擎)可能是合理的,但需要充分评估成本。
总结
混沌时代的本质:
- 没有边界,代码可以访问任何东西
- 没有分层,业务和技术混在一起
- 没有约束,想怎么写就怎么写
混沌的代价:
- 开发效率断崖式下降
- Bug 率指数级增长
- 人才流失、创新停滞
走出混沌的关键:
- 需要边界(分层)
- 需要约定(规范)
- 需要框架(约束)
混沌不是错误,是演进的起点。没有经历过混沌,就不会珍惜秩序。
下一篇,我们来看 Django/Rails 带来的 MVC 革命——框架如何用「约定」拯救了混乱的代码世界。
下一篇:MVC 启蒙:框架带来的秩序
本系列: