# 玩游戏代码=:}高效敏捷方法

<datetime class="hidden">2025-11-23T10:00</datetime>

<!-- category -- Software Development, Agile, Best Practices, Craftsmanship -->
**为何当你的分行是沙箱? —? 和作为合法工作玩耍?**

> **热辣辣椒** 多数开发商都试图在发展的各个阶段做到完美,

## 一. 导言导言 导言 导言 导言

I' 一直在为"... 好了,,"而专业地建立软件 只是说我记得 当""Agile\""是一个新想法

这些年来,, I' 已经注意到一些事情 : *播放权限*最差的代码来自每个键盘都觉得被仔细检查过的项目 , 我试图从第一分钟开始 写"

这是意外事件 . **创意与压力**

因此,I'已经开发出一种方法 将乱七八糟的,创意探索与光滑的,生产区分开来[让它运作起来,让它漂亮一点](/blog/makeitworkthenmakeitpretty)@" @ @ - @ 但其实是, @ it@'}关于允许自己在没有完美重量的情况下进行实验,

[TOC]

## 三阶段

让我先说清楚 : 这是关于草率或避免纪律的 *在适当的时候运用正确的纪律*.

```mermaid
graph LR
    subgraph "Phase 1: Make It Work"
        A[New Requirement] --> B[Explore & Experiment]
        B --> C{Does It Work?}
        C -->|No| D[Try Different Approach]
        D --> B
        C -->|Yes| E[Basic Solution]
    end

    subgraph "Phase 2: Make It Pretty"
        E --> F[Refactor & Clean]
        F --> G[Apply SOLID Pragmatically]
        G --> H[Get Stakeholder Feedback]
        H --> I[Polished Solution]
    end

    subgraph "Phase 3: Lock It Down"
        I --> J[Add Tests]
        J --> K[Security Review]
        K --> L[Add Monitoring]
        L --> M[Production Ready]
    end

    style A stroke:#f59e0b,stroke-width:3px
    style E stroke:#0ea5e9,stroke-width:3px
    style I stroke:#8b5cf6,stroke-width:3px
    style M stroke:#10b981,stroke-width:4px
```

### 阶段"1: 使它发挥作用

**咒语:** 首先播放“.”确认您的假设 “.” 获取正在运行的“MS K2”

这就是你的分支是沙箱的阶段 MSMK0 误差代码是好的 . *罚款*“. ”您不是在这里建教堂吗?“;” 您是在餐巾纸上画的草图

你试图找出什么 :

- 此 API 实际上是否表现得如何, 医生们声称 @? @ @ ( @Spoiler@: @ 它很少做 @MS K3}
- 我能用我拥有的工具来解决这个问题吗?
- 实际要求是多少?
- 这是两个时间问题还是两周问题?

**关键洞察力** 直到你升起一个PL, 你的分行就是 *您 的*I',但每次在Optechnology , 承诺和推动. Hook 进入你的CI输油管时 没有人需要看到你的假启动吗? , 你的评论=-}解调代码@, *你的评论"*TODO}这太可怕了

**附带利益=:** 常务是心跳.在远程MS K1async队}♪,穩定的足迹"{"但进展中〞"承诺让每个人都放心,

这个心理自由是必需的 @. 当你知道你可以把一切都扔掉, 并开始新鲜的@,}你就会做出更大胆的决定. 你尝试那种可能赢得'_t working}.}有时它会很成功 **不是一个特质**.

```csharp
// Phase 1 code looks like this - and THAT'S OKAY
public async Task<Result> ProcessThing(Request req)
{
    // TODO: what if this is null?
    var data = await _api.GetSomething(req.Id);

    // This probably isn't right but let's see what happens
    var transformed = data.Items
        .Where(x => x.Status != "deleted") // Is this the right filter?
        .Select(x => new Thing { Name = x.Name }); // Missing loads of fields

    // HACK: hardcoded for now
    return new Result { Items = transformed.ToList(), Total = 42 };
}
```

这是生产代码吗? ?? 上帝没有! 它有用吗?

**关于反馈的说明*:** 您可以在下列时间获得反馈 *任何* 正在选择的密钥 *人 或* 目标是建立最佳功能... ...,,不要遵循僵硬的流程.

建立另一个团队将消费的API, 另一个团队将会消耗#?}让它们参与进来 早点做一个. 一个快速的“"%does”这个形状就有意义了..."?"}对话可以节省重新工作的日子 *外观* 而不是它是否 *工程*@.}他们在「2.」的阶段里提供反馈。

使用您的判断@. @% 有时您需要反馈 **澄清**@ , @ 不批准@ MS K1 @ spec 已做此决定: @ . @ @ I"_ spect 上面写着“ '} handle 差错 优雅地” @'— @does, 意思是重试三次+, @ 或快速失败, 然后通知@?"@ that'_ 是一个阶段 @ MPK9# talking MSG10 @

如果一个利益攸关方@' @%s 输入是关键, 但是他们与粗略代码“ ,” 的争斗会构建一个微不足道的演示 < . > a hardcoded happy*-path, 它能显示这个概念可以解开有价值的反馈, 而不会分散缺失边缘案例的注意力}{.}

切换实用性:.+B 目标为最佳特征 @,_BARBAR_纯度进程=.}

### 阶段2: 使它美丽

**咒语:** 现在你知道自己在建什么了了吗? *良好*.

探索阶段已经完成了它的工作 “.你 '你确认了这个方法的功效 你理解问题的形状“,” 而您可能发现了十几个边缘病例 最初的票号是"'T提到'.′

现在你把它清理干净吧 :

```csharp
// Phase 2: Same logic, but actually maintainable
public async Task<ProcessingResult> ProcessItemsAsync(
    ProcessingRequest request,
    CancellationToken cancellationToken = default)
{
    ArgumentNullException.ThrowIfNull(request);

    var items = await _itemRepository.GetActiveItemsAsync(
        request.AccountId,
        cancellationToken);

    var processedItems = items
        .Where(item => item.Status != ItemStatus.Deleted)
        .Select(item => _mapper.ToProcessedItem(item))
        .ToList();

    return new ProcessingResult
    {
        Items = processedItems,
        TotalCount = processedItems.Count,
        ProcessedAt = _timeProvider.UtcNow
    };
}
```

这里就是你的 :

- 适用SOLID原则 *注重实际* {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}但不要崇拜他们
- 给事物以适当名称
- 添加类型提示和 XML 文档
- 从调用器@'s 角度考虑 API 表面
- 处理您在“1”阶段发现的边缘病例

**关键地说,,这是非-的最佳时机 技术利益攸关方反馈=.** 该功能现已可见且可用@ , @ {但您已经有'} 还没有投入时间来为它进行写作测试 MS K2} 如果他们说“ "” 其实是 @ MSSK4} 我们想要做 X 而不是 “,"” 您可以在删除一个综合测试套件时不心碎地跳过

### 阶段3:锁定它

**咒语:** 让它具有弹性 .

Now — @ @ @ and only now @—}你才寫下你的測試.#新增安全檢查@. 實施生产邊緣病例的正確錯誤處理Q.}新增監控與守護列 *.}

为什么等到现在 **你终于知道自己在测试什么**.

在“1”阶段中写作的测试本会是错误的 @—}你没有理解问题 但是在 “2” 阶段里写的._Tests *正确*@,#因为执行稳定 #.#

```csharp
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(100)]
public async Task ProcessItemsAsync_WithValidRequest_ReturnsCorrectCount(
    int itemCount)
{
    // Arrange
    var items = _fixture.CreateMany<Item>(itemCount).ToList();
    _mockRepository
        .Setup(r => r.GetActiveItemsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(items);

    // Act
    var result = await _sut.ProcessItemsAsync(
        new ProcessingRequest { AccountId = Guid.NewGuid() });

    // Assert
    result.TotalCount.Should().Be(itemCount);
    result.Items.Should().HaveCount(itemCount);
}

[Fact]
public async Task ProcessItemsAsync_WithNullRequest_ThrowsArgumentNullException()
{
    // Act & Assert
    await _sut.Invoking(s => s.ProcessItemsAsync(null!))
        .Should().ThrowAsync<ArgumentNullException>();
}

[Fact]
public async Task ProcessItemsAsync_ExcludesDeletedItems()
{
    // Arrange
    var items = new[]
    {
        new Item { Status = ItemStatus.Active },
        new Item { Status = ItemStatus.Deleted },
        new Item { Status = ItemStatus.Active }
    };
    _mockRepository
        .Setup(r => r.GetActiveItemsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(items);

    // Act
    var result = await _sut.ProcessItemsAsync(
        new ProcessingRequest { AccountId = Guid.NewGuid() });

    // Assert
    result.Items.Should().HaveCount(2);
}
```

这一阶段还包括:

- 安全审查: @(@auditation_,}授权,}输入验证=)}
- 组合货币因素 @(}在负载下发生的一切*?)
- 失败模式分析@( @ what if the database is slow #?}如果 API 乘以% ?)#
- 监控我们如何知道这种生产中断?

## 反馈编程

不同的人应该参与到不同阶段... ..."."你搞错了,你会沉溺在摩擦中

```mermaid
graph TB
    subgraph "Phase 1: Technical Voices"
        P1[Make It Work] --> T1[Senior Dev Review]
        T1 --> T2["Does the approach<br/>make sense?"]
        T2 --> T3["Any obvious<br/>architectural issues?"]
    end

    subgraph "Phase 2: Non-Technical Voices"
        P2[Make It Pretty] --> N1[Product Owner Demo]
        N1 --> N2["Does it meet<br/>the requirement?"]
        N2 --> N3["Is the UX<br/>intuitive?"]
    end

    subgraph "Phase 3: Ops/Security Voices"
        P3[Lock It Down] --> O1[Security Review]
        O1 --> O2[Operations Review]
        O2 --> O3["Will it hold up<br/>in production?"]
    end

    T3 --> P2
    N3 --> P3

    style T1 stroke:#3b82f6,stroke-width:2px
    style N1 stroke:#8b5cf6,stroke-width:2px
    style O1 stroke:#ef4444,stroke-width:2px
```

**阶段1:** 科技界的利益攸关方会因粗略代码而惊慌并询问按钮颜色,

**阶段2:** 非- 技术声音欢迎@. @% 此功能可用@ , @ buttons do things @ MS K3} 让产品所有者和设计者参与进来吧$—#changes are still cheap *,}你已经拥有了'_ t书面测试@.}

**阶段3:** 若有人通过恶意输入 ?" 仅当功能真正有效时才重要 @.

## 作为沙箱的分支

这里的' 改变了我的发展生活 **直到你升起一个PR,, 你的分行是完全私人的**.

但我看到开发商会处理每件事情 就像它一样... '...

我要你将你的特色分支 当作一个沙箱. 一个操场•. 一个实验室 那里预计会发生爆炸 没有人会受到伤害

```mermaid
graph TD
    A[Feature Branch Created] --> B[Experiment Freely]
    B --> C{Did it work?}
    C -->|No| D[Try Something Else]
    D --> B
    C -->|Yes| E[Clean Up the Mess]
    E --> F[Interactive Rebase]
    F --> G[Squash to Clean Commits]
    G --> H[Raise PR]
    H --> I[Public Code Review]

    style B stroke:#f59e0b,stroke-width:3px
    style D stroke:#f59e0b,stroke-width:2px
    style H stroke:#10b981,stroke-width:3px

    subgraph "Private - Go Mental"
        B
        C
        D
        E
    end

    subgraph "Public - Be Professional"
        H
        I
    end
```

在实践中,这代表着:

- 经常提交@ , @ {} 即使代码是'_ t编译的
- 写入 `FIXME` 和 `TODO` 各地的评论
- 您正在探索时请保留调试代码@ ' @ label
- 拥有多个@ "_ 尝试此方法 @ MS K1\ 承诺
- @ '_ 不要担心发送信件@ MS K1_you'_ll global later @)}

在你升起PR:之前

1. 清理代码“(”阶段 “2)”
2. 添加测试@( @ 阶段 @ 3)}
3. 交互反弹以粉碎所有混乱的历史
4. 写入正确的承诺信件

PR审查员看到的是干净的, 专业代码,并有明确的叙事性. 他们没有看到6个错误开头吗?

> **沙箱保持私人状态 . 抛光工作公之于众**

## 为何如此减轻压力

传统"在任何时候都是专业的 "发展正在粉碎\." 每条线都感觉永久"MS K3" 每个决定都会感到后果. 通过抑制混乱来消除这种压力 **游戏是合法工作** 在“1,”阶段中,当轴线在“2,”阶段是廉价的时会收到反馈,测试一旦正确写入了 “3” 阶段,因为你终于知道什么是你''re testation\".}

> **"Play是合法的工作**

当域名被很好地理解或您're 锁定一个已知的错误时, TDD工作非常出色。 但是当问题仍然不明朗的时候, ,写测试通常意味着在行中三次检测错误的东西。

## 柔软的固态(和DRY'"肮脏的秘密)"

所以这三种“-”阶段的思维模式 对你的设计原理有什么影响呢? “?” 在简而言之, 它杀死了狗狗. 这里它如何改变我对 SOLID’, DRY,}和CQ#.中的界面的想法

我提到在“2.”阶段应用了SOLID原则,

SOLID是一套好的原则 我教他们.我使用它们 但I'曾见过球队 以"单一责任"或 Open\/的名号 从抽象兔子洞中消失

在这里,' ~这是我的奢侈 ~: ~

**当“:” 时应用 SOLID**

- 您有证据您需要灵活度
- 抽象使代码更加清晰
- 您正在建造其它用的东西

**Doná't 使用 SOLID 当 @ :**

- 你怀疑未来的需求
- 抽象性增加了复杂性,而没有清晰
- 您正在建立只有您维持的内部代码

三个相似的代码线比一个不成熟的抽象线更好@ .} 当您看到这个模式清晰可见时, 您总是可以稍后提取 .\ _ 您可以 ' 无法轻易地解开在您的架构中烤熟了的抽象元素“ MS K3”\ Name.

```csharp
// Over-engineered SOLID worship
public interface IThingProcessor { }
public interface IThingProcessorFactory { }
public class ThingProcessorFactory : IThingProcessorFactory { }
public class ThingProcessor : IThingProcessor { }
public class ThingProcessorDecorator : IThingProcessor { }
public class ThingProcessorValidationDecorator : IThingProcessor { }

// What you probably actually need
public class ThingProcessor
{
    public async Task<Result> ProcessAsync(Thing thing)
    {
        // Just do the thing
    }
}
```

如果您需要灵活性, 稍后@ , @ 重新设置是便宜的 @ MS K1+% Over-# 工程设计前方成本昂贵, 因为您在保留抽象层( 正在维持) '} # 没有赚钱来保持@ I. @ *

### 稻草陷阱

当我们屠杀神圣的牛群时 让我们谈论 **试** “(Don'不重复你自己). ItMS K3}或许是我们行业中最教条性应用的原则 ‘,’当不加思索地运用它时, 它造成的伤害比好的还大。”

问题是这个: **并非所有重复工作都是同样的重复工作**.

当两种代码看起来相似,但用途不同时 , 将它们带入一个共同的抽象夫妇中 应该独立进化的东西

1. 添加处理这两个案件的参数和条件
2. 打破另一个使用大小写
3. 将共享代码复制出来并编辑@(_Admitting DRY是错误的 @)}这是错的

```csharp
// "DRY" solution - looks clever, causes pain
public async Task<Result> ProcessEntity<T>(
    T entity,
    bool validateFirst = true,
    bool sendNotification = false,
    Func<T, bool>? customFilter = null,
    Action<T>? preProcess = null,
    Action<T>? postProcess = null) where T : IEntity
{
    // 50 lines of conditional spaghetti trying to handle
    // "processing" for Users, Orders, and Products
    // because they all had 3 similar lines once
}

// What you probably actually need
public async Task<Result> ProcessUser(User user) { /* 15 clear lines */ }
public async Task<Result> ProcessOrder(Order order) { /* 15 clear lines */ }
public async Task<Result> ProcessProduct(Product product) { /* 15 clear lines */ }
```

是的.}但每种方法都是:

- 孤立地容易理解
- 容易修改,无副作用
- 当该实体类型消失时容易删除
- 容易用明确的投入和产出进行测试

DRY 版本是一个要维持的噩梦, 因为它“'”试图成为所有呼叫者的所有事物。 “. ” 每一个变化都需要理解所有的使用案例 ‘. ’ 每个错误修正都有可能打破其它东西 ’. }

**我的规则 :** 保持重复,直到你看到 *相同* 你相信它代表着 *相同* 将会一起进化的理念 到那时为止 , 来点拷贝 MS K2\paste是好的 它不是道德上的失败

重复比错误的抽象更便宜@ . @ 您可以稍后在模式清晰时进行复制 .} 您可以 MS K2 不能轻易地解开一个错误的摘要, 它通过您的代码库编织了\ I'%s

### C# 中的界面问题

这是又一头需要宰杀的圣母牛 **每个班级都需要一个接口\ "}模式**.

在“.NET 依赖性注射”的早期, 有人决定要制作代码测试@,}您需要在每个地方输入界面。逻辑是 @: 您可以模拟一个混凝土类\,}所以每个服务都需要 `IService` 和 `ServiceImpl`@. 忽然每个 C# 代码库都堆满了纯用于测试@.}的单一#-实施接口

```csharp
// The interface tax - early 2010s style
public interface IUserService { }
public interface IOrderService { }
public interface IEmailService { }
public interface INotificationService { }
public interface IPaymentProcessor { }
public interface IInventoryManager { }

// Each with exactly ONE implementation
public class UserService : IUserService { }
public class OrderService : IOrderService { }
// ... you get the idea
```

这是货物邪教编程... .... 我们为此缴纳了复杂税 *理论理论* 可检验性和可转让性 *理论理论* 很少实现的未来灵活性 @.

**在这里,'的东西\ :}你不需要这个了?**

现代模拟框架等 [替代物](https://nsubstitute.github.io/) 和 [摩卡](https://github.com/moq/moq4) 以虚拟方法模拟混凝土类@(}更重要的是#,}ASPQ.NET Core{'}DI容器使用混凝固型態做得很好

```csharp
// Modern approach - just register the concrete type
services.AddScoped<UserService>();
services.AddScoped<OrderService>();
services.AddScoped<EmailService>();

// In your controller or service
public class OrderController(OrderService orderService, UserService userService)
{
    // Primary constructor injection - clean and simple
}
```

无接口@. @ 否 `IOrderService`{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你需要什么服务?

**"但是测试呢??"** 我听到你哭泣的叫声

单位测试@, 您有选项 @:

1. 制作密钥方法 `virtual` 使用模拟框架
2. 使用测试数据库的实际服务@(集成检测通常更有价值,反之亦然 @)#
3. 添加接口 *当您实际需要测试时*

第三点是关键 **重构工具在提取接口方面特别好**.

在骑士或视觉演播室 , * It'* *字面上 `Ctrl+.` →{"_Extractime}" 和您MS K3re}. 每套方法都得到解析 , 班级得到更新 以实施它MSKO6 你能找到\/ 替换所有需要的用法

```csharp
// Phase 1 & 2: Just write the class
public class OrderService
{
    public async Task<Order> CreateOrderAsync(CreateOrderRequest request) { ... }
    public async Task<Order?> GetOrderAsync(int orderId) { ... }
    public async Task CancelOrderAsync(int orderId) { ... }
}

// Phase 3: Need to mock it for testing? Extract interface in 2 seconds
public interface IOrderService
{
    Task<Order> CreateOrderAsync(CreateOrderRequest request);
    Task<Order?> GetOrderAsync(int orderId);
    Task CancelOrderAsync(int orderId);
}

public class OrderService : IOrderService { ... }
```

我的工作流程现在\: **介面在 3,}不是阶段 *1**.

在“"”期间, 让它正常工作。 “"”和“"”使它漂亮一点。 我只是写了几堂课。“.”没有过早的抽象性;“MS K5” 没有对每种类型征收界面税。” `IFoo` 和 `Foo`@),和更快写@.#

当我打"的时候 把它锁住 " 然后需要写测试报告 *时当时* 我决定哪些服务实际需要嘲笑@. @ 通常需要它='}少于您 @'d think_ —}通常只是外部整合, 如 HTTP 客戶端@MS K4}数据库@,和信件队列@MSSK6{

对于内部商业逻辑服务来说 ? 一半时间我只是直接用真正的依赖性测试它们 *思考* 行为应该为“.”

```csharp
// Services I typically DO extract interfaces for (external boundaries)
public interface IPaymentGateway { }      // Third-party API
public interface IEmailSender { }         // External service
public interface IBlobStorage { }         // Cloud storage

// Services I typically DON'T need interfaces for (internal logic)
public class OrderValidator { }           // Pure logic, test directly
public class PriceCalculator { }          // Pure logic, test directly
public class OrderService { }             // Test with real repo + in-memory DB
```

**要点是:** 停止前端的“"”接口@ just in case @". 写入混凝土类\ . 如果您需要测试或真正的多形态学界面, 稍后@,}用现代工具抽取一秒

您的代码库将更简单@ , @ 更多的导航@ I, 并赢得了您的维护重任, 让接口与执行同步, 没有好处@ MPK3}

## Agile 连接

具有讽刺意味的“,”这个方法更符合 *原原件* @(Q 阶段@1).Q 响应变化:#(-阶段 @1-2).Q客户协作, 当功能明显可见时, 请与客户合作 {(_ 阶段+2).}没有指纹点\,} 没有速度追踪>,* 没有血腥的烧焦图表 *.

现代的“" @Agile"}”被进程爱好者所捕捉,他们加了如此多的仪式。 “'_没有时间进行探索”,这让大家回想起“MS K5{不是作为一种宽容,而是作为一个创造性工作的合法阶段 ‘.’

## 当不使用此方法时

我应该诚实地说 这是' 总是正确的做法

**错误修正@: @%** 当您@ '_ 正在修补已知的bug@ MPK1} TDD 风格更有意义 @ MS K2} 写一个测试来复制 bug@ ,} 修正它\ , done @ OMK5}

**好了,好了。 明白了问题。** 如果您正在实施标准算法或模式, 您已经使用过百倍的 ,您不需要一个勘探阶段@.}

**有已知要求的紧凑期限@: @** 如果您真的知道您正在建造什么 're 以及何时需要发运什么 ,, 您可能会跳过“ MS K2 探索” 的阶段@.

**高度受监管的环境 @:** 如果每个承诺需要签名“-@off @,}沙箱更难 #.}尽管您仍然可以在推前在本地进行勘探_.}

但对于 *多数* 要求有点模糊的, 技术方法是 ' t显而易见\ I,\ 并且你需要空间去思考\ -—\ 这种方法非常管用

## 给初级开发者的说明

如果您在职业生涯的早期 '@,}你可能会感到压力 要显示""\perfect @"}代码 您必须使用这三种“.”来表示 “-_Phase Projession” MSk7}—}只是要弄清楚你在“MS K9>re in,’中是哪个阶段,并且总是完成整个工作直到“MSC11”阶段 如果导致在末端“MSG15”上擦亮了‘,’测试了“MSHK13 生产<-{\resident 代码”) 没人会责怪您乱糟蹋勘探代码

## 底线

**让它运作起来 使它漂亮点 关上门**

启动简单@ . @ 设计前瞻而非猜测@ I. @ Iterate 快速@ O.} 只给钱时添加复杂性@ MPK3 @

> **@"@Dexplication 远比错误的抽象更便宜 #."#**

探索阶段是' 一个有罪的秘密 '—iit ''s a confident part of the process '."你的分支就是你的沙箱 直到你提出一个 P."技术反馈

这保持了创造力的活力 , 减少了压力 MS K1 并产生了规模优雅的系统

> **停止尝试从第1分钟开始完美到完美的状态 @ . @ 允许自己播放@ MS K1} @**

---


## 相关阅读

若此回响响应@,, 您可能也会享受这些相关文章 @:#

- **[我如何构建软件](/blog/makeitworkthenmakeitpretty)** “-”更深入地探讨这一方法背后的测试理念“,”,并附有三个阶段的详细代码示例:“.”
- **[每天的起立都是胡说八道 除非他们赚到薪水](/blog/agile-standups-ceremony-tax)** 以及如何评估你们的敏捷仪式是否在赢得他们的保护?
- **[拼图拼图@: @ 写入实际效果的特征描述](/blog/writingfeaturespecs)** @-_# 因为这些图纸是工具@,}不是圣经 —}作为活文件对待它们 是构建正确事物的唯一方法
- **[遗产法典库工作](/blog/workingonlegacysystems)** 包括如何运用这些原则, 当你学习了一种你曾经建立过的系统时... ...'_t building\...