Search K
Appearance
🍵 欢迎来到技术茶馆 🍵
这里是一个分享技术、交流学习的地方
技术札记 | 茶馆周刊 | 工具书签 | 作品展示
让我们一起品茗技术,共同成长
Appearance
"最好的代码不是一次写对的,而是不断重构出来的。" —— Martin Fowler
我所在的团队负责一个多版本拍卖业务系统,目前有十几个不同版本的拍卖模式,每个版本对应一个代码分支。每个版本都有自己独特的协议规范、业务流程和特殊要求。随着业务发展,系统逐渐的遇到了一系列问题:
技术债务不仅影响代码质量,更直接冲击了团队的研发效能。我们在日常开发中遭遇了三个核心瓶颈:
新同学加入团队后,需要2-3个月才能理解现有代码逻辑:
每次新增拍卖模式版本,都是一次"复制粘贴+魔改"的过程:
代码耦合导致"牵一发而动全身":
此时我的脑子里出现了一个想法:为了早点下班~ 必须重构了!
针对上述的三大痛点,我们很快制定了初步的重构规划:
| 痛点 | 重构目标 | 预期效果 |
|---|---|---|
| 新人上手慢 | 架构清晰化、职责单一化 | 培养周期从3周降到1周,调研成本降低70% |
| 需求效率低 | 通用逻辑可复用、新增版本可配置化 | 新版本接入成本降低70% |
| 测试成本高 | 解耦分层、单元测试覆盖率≥80% | 回归测试时间减半,Code Review时间降低80% |
目标有了,问题也来了:这是一个包含10余个活跃版本的业务系统,系统复杂,且每日有数百万请求打进系统,如何在不影响线上业务的前提下进行重构?
出于对生产环境的敬畏之心,我们不敢"一刀切"地推倒重来。经过评估,我们选择了渐进式重构策略:整体改进是一个持续的过程,目标清晰但落地需要时间。我们会逐步验证架构可行性,再慢慢进行迁移,确保每一步都是稳定可控的。
| 阶段 | 时间 | 目标 |
|---|---|---|
| 第一阶段 | 1-2周 | 架构验证:完成1个版本的规范化改造 |
| 第二阶段 | 1-2个月 | 核心迁移:完成50%版本的迁移 |
| 第三阶段 | 3-6个月 | 全量迁移:剩余版本全部迁移 |
| 第四阶段 | 持续优化 | 补充协议Handler、完善监控 |
PDCA(Plan-Do-Check-Act)是戴明环的核心思想,特别适合用于:
在与AI协作时,PDCA的价值更加明显:
| PDCA阶段 | 人的职责 | AI的职责 | 协作模式 |
|---|---|---|---|
| Plan | 架构设计、任务拆解、确定路径 | 提供设计建议、分析技术方案 | 人主导,AI辅助 |
| Do | 编写核心逻辑、Review代码 | 生成模板代码、实现细节 | 结对编程 |
| Check | 验证业务逻辑、集成测试 | 生成单元测试、代码检查 | 人验证,AI执行 |
| Act | 总结经验、沉淀规则 | 整理文档、提取模式 | 共同迭代 |
重构前,我们的代码结构是这样的:
<?php
class KaVersionBusinessStrategy extends BaseVersionBusinessStrategy
{
/**
* 获取竞价结果
*/
public function getBiddingPrice(...$params): BiddingPriceResult
{
// 1. 参数校验(50行)
// 2. 解密验签(30行)
// 3. 获取版本配置(40行)
// 4. 调用竞价服务(60行)
// 5. 计算佣金(50行)
// 6. 组装响应(30行)
// 7. 加密签名(20行)
// ... 总计500+行
}
/**
* 创建拍卖订单
*/
public function createAuctionOrder(...$params): CreateOrderResult
{
// 同样的模式,又是几百行...
}
}问题总结:
本次我们的设计,是一个基于责任链模式的新架构:
┌─────────────────────────────────────────┐
│ Facade层(统一入口) │
│ VersionUnifiedFacade │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Adapter层(版本适配) │
│ IVersionAdapter │
│ - KaVersionAdapter │
│ - AVersionAdapter │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Handler层(责任链) │
│ - DuplicationCheckHandler │
│ - VersionConfigHandler │
│ - ReportMappingHandler │
│ - OrderParamAssemblyHandler │
│ - OrderCreationHandler │
└─────────────────────────────────────────┘核心设计点:
分层解耦
可复用的Handler
DuplicationCheckHandler → 幂等性检查(通用)VersionConfigHandler → 版本配置获取(通用)ReportMappingHandler → 报告映射(通用)OrderParamAssemblyHandler → 参数组装(可定制)OrderCreationHandler → 订单创建(通用)灵活的编排机制
// A版本:标准流程
$chain = new HandlerChain();
$chain->addHandler(new DuplicationCheckHandler())
->addHandler(new VersionConfigHandler())
->addHandler(new ReportMappingHandler())
->addHandler(new OrderParamAssemblyHandler())
->addHandler(new OrderCreationHandler());
// KA版本:额外增加权限校验
$chain = new HandlerChain();
$chain->addHandler(new DuplicationCheckHandler())
->addHandler(new KaAuthCheckHandler()) // KA特有
->addHandler(new VersionConfigHandler())
->addHandler(new ReportMappingHandler())
->addHandler(new OrderParamAssemblyHandler())
->addHandler(new OrderCreationHandler());有了架构设计后,我将重构任务拆解为8个小任务:
| 任务ID | 任务描述 | 依赖关系 |
|---|---|---|
| T1 | 定义责任链上下文数据结构 | - |
| T2 | 定义IVersionAdapter接口 | T1 |
| T3 | 定义BusinessHandler接口 | T1 |
| T4 | 实现5个通用Handler | T3 |
| T5 | 实现统一对外接口 | T2 |
| T6 | 实现VersionAdapter | T2, T4 |
| T7 | 编写单元测试 | T4-T6 |
| T8 | 集成测试与灰度发布 | T7 |
关键点:每个任务都是独立可验证的,这也为后续与Cursor协作奠定了基础。
在开始编码前,我做了三件事:
1、用自然语言写了一个详细的架构说明文档
AI更擅长理解结构化信息,为了提升Cursor的代码生成质量,我首先为Cursor量身定制了一份"产品需求文档"(PRD)。有了这份文档,Cursor代码生成的有效率大大提升,大部分时间,我只需要微调业务细节,就可以直接使用它生成的代码。
包含:
2、定义好接口和数据结构
<?php
/**
* 版本上下文
*/
class VersionContext
{
private string $requestId; // 请求ID
private string $versionCode; // 版本编码
private string $action; // 业务动作
private $bizData; // 业务数据
private array $handlerResults = []; // Handler结果
public function __construct(
string $requestId,
string $versionCode,
string $action,
$bizData
) {
$this->requestId = $requestId;
$this->versionCode = $versionCode;
$this->action = $action;
$this->bizData = $bizData;
}
public function getRequestId(): string
{
return $this->requestId;
}
public function getVersionCode(): string
{
return $this->versionCode;
}
public function getAction(): string
{
return $this->action;
}
public function getBizData()
{
return $this->bizData;
}
public function putHandlerResult(string $key, $value): void
{
$this->handlerResults[$key] = $value;
}
public function getHandlerResult(string $key)
{
return $this->handlerResults[$key] ?? null;
}
public function getHandlerResults(): array
{
return $this->handlerResults;
}
}3、编写第一个Handler作为示例
我手写了一个DuplicationCheckHandler,作为Cursor的参考模板:
<?php
class DuplicationCheckBusinessHandler extends AbstractTypedBusinessHandler
{
private IAuctionOrderService $orderService;
public function __construct(IAuctionOrderService $orderService)
{
$this->orderService = $orderService;
}
protected function handleTyped(CreateOrderBizData $input, VersionContext $context): bool
{
// 1. 根据outOrderNo查询订单
$existOrder = $this->orderService->queryByOutOrderNo($input->getOutOrderNo());
// 2. 如果订单已存在,返回true(跳过后续Handler)
if ($existOrder !== null) {
\Log::info("订单已存在,跳过创建流程", [
'outOrderNo' => $input->getOutOrderNo()
]);
$context->putHandlerResult('ORDER_EXISTS', $existOrder);
return true;
}
return false;
}
public function getOrder(): int
{
return 100; // 最先执行
}
public function supports(string $action): bool
{
return $action === 'CREATE_ORDER';
}
}完成了上述步骤后,我开始与Cursor结对编程。
我的提示词:
参考
DuplicationCheckBusinessHandler的实现方式,帮我实现VersionConfigBusinessHandler,用于获取版本配置。需要满足:
- 从
ExternalVersionConfigService获取配置- 从
PricedSourceService获取竞价源配置- 将结果保存到context的handlerResults中
- order设置为200
Cursor生成的代码:
<?php
class VersionConfigBusinessHandler extends AbstractTypedBusinessHandler
{
private ExternalVersionConfigService $configService;
private PricedSourceService $pricedSourceService;
public function __construct(
ExternalVersionConfigService $configService,
PricedSourceService $pricedSourceService
) {
$this->configService = $configService;
$this->pricedSourceService = $pricedSourceService;
}
protected function handleTyped(CreateOrderBizData $input, VersionContext $context): ExternalVersionConfig
{
$versionCode = $context->getVersionCode();
// 1. 获取版本配置
$versionConfig = $this->configService->getByVersionCode($versionCode);
if ($versionConfig === null) {
throw new VersionException('VERSION_NOT_FOUND', "版本配置不存在: {$versionCode}");
}
// 2. 获取竞价源配置
$pricedSource = $this->pricedSourceService->getPricedSource(
$versionConfig->getOrgId(),
$input->getCategoryId()
);
// 3. 保存到上下文
$context->putHandlerResult('VERSION_CONFIG', $versionConfig);
$context->putHandlerResult('PRICED_SOURCE', $pricedSource);
\Log::info("获取版本配置成功", [
'versionCode' => $versionCode,
'orgId' => $versionConfig->getOrgId()
]);
return $versionConfig;
}
public function getOrder(): int
{
return 200;
}
public function supports(string $action): bool
{
return $action === 'CREATE_ORDER';
}
}可以看到,代码结构完全符合预期,异常处理到位,日志也很规范。
在Do阶段,我总结出了几个与Cursor高效协作的技巧:
技巧1:更加明确的提示
不好的提示词:
帮我实现所有的Handler
好的提示词:
先实现
DuplicationCheckHandler,用于检查订单是否已存在
技巧2:提供参考代码
参考
DuplicationCheckHandler的实现方式,生成VersionConfigHandler
技巧3:明确约束条件
要求:
- 继承
AbstractTypedBusinessHandler- order设置为200
- 必须包含日志
- 异常使用
VersionException
技巧4:及时Check
每生成一个Handler,我都会:
在Check阶段,我建立了三层验证机制:
第一层:IDE即时检查
第二层:单元测试
我让Cursor帮我生成了单元测试:
为
DuplicationCheckHandler生成单元测试,需要覆盖:
- 订单不存在的场景
- 订单已存在的场景
- 查询异常的场景
Cursor生成的测试:
<?php
use PHPUnit\Framework\TestCase;
class DuplicationCheckBusinessHandlerTest extends TestCase
{
private DuplicationCheckBusinessHandler $handler;
private IAuctionOrderService $orderService;
protected function setUp(): void
{
$this->orderService = $this->createMock(IAuctionOrderService::class);
$this->handler = new DuplicationCheckBusinessHandler($this->orderService);
}
public function testHandle_OrderNotExists(): void
{
// 构建测试数据
$input = new CreateOrderBizData();
$input->setOutOrderNo('OUT_ORDER_001');
$input->setVersionCode('TEST_VERSION');
$input->setProductId(123456);
$context = new VersionContext(
'REQ_001',
'TEST_VERSION',
'CREATE_ORDER',
$input
);
// Mock:订单不存在
$this->orderService->expects($this->once())
->method('queryByOutOrderNo')
->with('OUT_ORDER_001')
->willReturn(null);
// 执行
$result = $this->handler->handleTyped($input, $context);
// 验证
$this->assertFalse($result);
$this->assertNull($context->getHandlerResult('ORDER_EXISTS'));
}
public function testHandle_OrderExists(): void
{
// 构建测试数据 - 订单已存在的情况
$input = new CreateOrderBizData();
$input->setOutOrderNo('OUT_ORDER_002');
$input->setVersionCode('TEST_VERSION');
$input->setProductId(123456);
$context = new VersionContext(
'REQ_002',
'TEST_VERSION',
'CREATE_ORDER',
$input
);
// Mock:订单已存在
$existOrder = new AuctionOrderVO();
$existOrder->setId(999);
$existOrder->setOutOrderNo('OUT_ORDER_002');
$this->orderService->expects($this->once())
->method('queryByOutOrderNo')
->with('OUT_ORDER_002')
->willReturn($existOrder);
// 执行
$result = $this->handler->handleTyped($input, $context);
// 验证
$this->assertTrue($result);
$this->assertNotNull($context->getHandlerResult('ORDER_EXISTS'));
$this->assertEquals(999, $context->getHandlerResult('ORDER_EXISTS')->getId());
}
}第三层:集成测试
我在测试环境跑了完整的下单流程,验证Handler链的协作:
<?php
use PHPUnit\Framework\TestCase;
class VersionUnifiedFacadeTest extends TestCase
{
private IVersionUnifiedFacade $versionUnifiedFacade;
private IAuctionOrderService $orderService;
protected function setUp(): void
{
// 初始化服务...
}
public function testCreateOrder_FullProcess(): void
{
// 构建完整的版本请求
$bizData = new CreateOrderBizData();
$bizData->setOutOrderNo('TEST_OUT_ORDER_' . time());
$bizData->setVersionCode('KA');
$bizData->setProductId(123456);
$bizData->setCategoryId(1);
$bizData->setSkuId(1001);
$bizData->setUserId(888888);
$bizData->setPicList([
new Pic('https://test.com/pic1.jpg')
]);
$request = new VersionRequest();
$request->setRequestId('REQ_' . time());
$request->setVersionCode('KA');
$request->setAction('CREATE_ORDER');
$request->setBizData($bizData);
// 执行完整流程
$response = $this->versionUnifiedFacade->process($request);
// 打印结果
echo "=== 集成测试结果 ===\n";
echo "请求成功: " . ($response->isSuccess() ? 'true' : 'false') . "\n";
echo "订单ID: " . $response->getData() . "\n";
echo "响应码: " . $response->getCode() . "\n";
echo "响应信息: " . $response->getMessage() . "\n";
// 验证订单确实创建成功
if ($response->getData() !== null) {
$order = $this->orderService->getById($response->getData());
echo "订单详情: " . json_encode($order, JSON_UNESCAPED_UNICODE) . "\n";
echo "订单状态: " . $order->getOrderState() . "\n";
echo "外部订单号: " . $order->getOutOrderNo() . "\n";
}
}
}经过多次迭代,我总结出了一套可复用的提示词模板:
模板1:生成Handler
角色:你是一个PHP后端工程师,熟悉责任链模式
任务:实现一个BusinessHandler,用于[具体功能]
要求:
1. 继承AbstractTypedBusinessHandler
2. 输入类型是[P],输出类型是[R]
3. order值设置为[ORDER_VALUE]
4. 从context中获取[DEPENDENCY]
5. 将结果保存到context.putHandlerResult("[RESULT_KEY]", result)
6. 异常使用VersionException,格式:new VersionException(errorCode, message)
7. 添加日志:处理开始、处理成功、处理失败
参考代码:[粘贴参考Handler代码]模板2:生成Adapter
角色:你是一个PHP架构师,熟悉适配器模式和责任链模式
任务:实现[版本名]VersionAdapter
要求:
1. 继承AbstractIVersionAdapter
2. 注入以下Handler:[列出Handler列表]
3. 根据action构建不同的Handler链:
- CREATE_ORDER: [Handler顺序]
- UPDATE_ORDER: [Handler顺序]
4. 添加完善的日志
参考代码:[粘贴AVersionAdapter代码]在重构过程中,我发现了一个可以提取的通用模式:参数校验。
最初,每个Handler都自己做参数校验:
// DuplicationCheckHandler
if (empty($input->getOutOrderNo())) {
throw new VersionException('PARAM_ERROR', '外部订单号不能为空');
}
// VersionConfigHandler
if (empty($input->getVersionCode())) {
throw new VersionException('PARAM_ERROR', '版本编码不能为空');
}在Act阶段,我提取了一个通用的ParamValidationHandler:
<?php
class ParamValidationBusinessHandler extends AbstractTypedBusinessHandler
{
protected function handleTyped($input, VersionContext $context): void
{
// 根据action和input类型进行参数校验
$action = $context->getAction();
if ($action === 'CREATE_ORDER' && $input instanceof CreateOrderBizData) {
$this->validateCreateOrder($input);
} elseif ($action === 'UPDATE_ORDER' && $input instanceof UpdateOrderBizData) {
$this->validateUpdateOrder($input);
}
}
private function validateCreateOrder(CreateOrderBizData $input): void
{
if (empty($input->getOutOrderNo())) {
throw new VersionException('PARAM_ERROR', '外部订单号不能为空');
}
if (empty($input->getVersionCode())) {
throw new VersionException('PARAM_ERROR', '版本编码不能为空');
}
// ... 其他校验
}
public function getOrder(): int
{
return 50; // 最先执行
}
}现在,只需要在Handler链中加入这个Handler,就能统一处理参数校验了。
这篇文章分享的是我们正在进行的探索,而非一个"完美的成功案例"。
我们的重构之旅才走了一小步,还有很多坑要踩,很多问题要解决。但我们选择边做边分享,因为:
真实的经验比完美的故事更有价值。
愿你在AI时代,既能拥抱新工具提升效率,又能保持对代码质量的追求、对架构美感的坚持。