Back to "玩游戏代码:高效的敏捷方法"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Agile Best Practices Craftsmanship Software Development

玩游戏代码:高效的敏捷方法

Sunday, 23 November 2025

为何将你的分支当作沙箱,

热拍 : 大多数开发商都试图在开发的每个阶段都做到完美, 而这正在扼杀他们的创造力和生产力。这是一个更好的方法。

一. 导言 导言 导言 导言 导言 导言 一,导言 导言 导言 导言 导言 导言

我在专业上为... 好了,让我们只是说,我记得 当"危险"是一个新想法, 而不是一个企业的嗡嗡口号 由项目经理武器化 以证明更多的站立。

多年来,我注意到一些事情: 我写过最好的代码 来自于我所感觉到的项目 播放权限最差的代码来自每个键盘都觉得被仔细检查的项目, 我试图从第1分钟开始写出“生产准备”代码。

这不是偶然的 创造力和压力不能很好地混合。

所以,我开发了一种方法, 将混乱的创造性探索 与抛光的、 现成的、 现成的送货区分开来。我称之为“让它工作,让它漂亮,把它锁起来- 但真的,这是关于允许自己 进行实验,而没有 完美的重量 挂在你身上。

三阶段

让我先说清楚:这不是关于过于草率或避免纪律。 在正确的时间运用正确的纪律.

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阶段:使其发挥作用

咒语: 先玩,确认你的假设,找点事做

这就是你的分支是沙箱的阶段。 混乱的代码是好的。 死亡的结局是好的。 开始三次以上是 罚款你不是在建大教堂 而是在用餐巾纸画画

你想弄清楚的是:

  • 这个 API 是否真的表现得如何? (Spoiler:它很少)
  • 我能用我拥有的工具来解决这个问题吗?
  • 实际需求是多少,而不是票上所写的需求?
  • 这是两个小时的问题还是两周的问题?

关键的观点是: 在你升公关前 你的分行是 您 的这是你的实验实验室。没有人需要看到你的假开始,你评论出来的调试代码,你的“TODO:这是可怕的,以后再修”评论。但请检查OFTEN-每次你有东西可以工作、作出承诺和推动。勾引你的内线管道;一个良好的管道会给你更多的信息(综合测试、打破其他地方的变化等)。检查丑陋的、几乎没有缝合的坦率代码是FINE。这就是POINT。即使作为一个三十年的老兵,我仍然这样做。它释放了我的思想。

附带利益: 常事是心跳。在远程/同步团队中,“突如其来的但进步”的稳步轨迹让每个人都确信工作正在移动,而不必在斯拉克工作生产率。

这种心理自由至关重要。 当你知道可以把一切都扔掉并重新开始时, 你就会做出更大胆的决定。 你尝试这种可能行不通的怪异方法。 有时它会非常成功。 有时你需要读一篇文章, 观看YouTube视频, 思考一段时间。 记住: 你的工作是提供好的软件和良好的决定, 而不是快速键入。 良好的管理者理解你。 不是一个类型.

// 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 };
}

这是生产代码吗?天啊,这有用吗?绝对有用。它告诉你,在你投入时间打磨没用的东西之前,你的方法是否可行。

关于反馈的说明: 您可以在下列时间获得反馈: 任何 阶段 1 点中的点- 密钥选择 人 或 人 目标是建立最佳特征,而不是遵循僵硬的进程。

建立另一团队消费的API? 提前让他们参与进来。 快速的“ 这样的形状有意义吗? ” 对话可以节省重新工作的日子。 建立用户形象? 也许在不那么粗糙之前延缓对商业利害相关方的曝光 — — 有些人被挂在了什么上面。 外观 而不是它是否 工程。这很好;他们在第二阶段提供反馈。

使用判断力。有时你需要反馈 澄清澄清而不是批准- 规格已经这样做了 。 “ 规格上写着“ 优雅的手动错误 ” - 是意味着重试三次, 还是快速失败通知? 这是第一阶段的谈话 。

如果一个利害相关者的投入是关键, 但是他们会用粗略代码挣扎, 建立一个微不足道的演示。 一个显示这个概念的硬码快乐路径可以释放有价值的反馈, 而不会分散失踪的边缘案例的注意力 。

实事求是。目标是最佳特征,而不是纯纯化过程。

第2阶段:使它漂亮

咒语: 现在你知道你在建什么了 让它 良好.

探索阶段已经完成了它的工作。你确认了接近方法的工作, 你了解问题的形式, 你可能发现了十几个边缘案例, 最初的票没有提及。

现在你把它清理干净:

// 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 原则 注重实际 (认识他们,但不要崇拜他们)
  • 给事物以适当名称
  • 添加类型提示和 XML 文档
  • 从打电话者的角度考虑 API 表面
  • 处理您在第一阶段发现的边缘案例

关键是,这是非技术利益攸关方反馈的最佳时机。 功能现在已经可见且可用, 但你还没有为此投入时间写测试。 如果他们说“ 实际上, 我们想要它做X ” , 您可以在删除一个全面的测试套件时不感到心碎。

第3阶段:锁定下锁

咒语: 硬化它,测试它, 使它具有弹性。

现在,直到现在,你才写你的测试。添加安全检查。对生产边缘案例进行正确的错误处理。增加监测和护栏。

为什么等到现在? 你终于知道你在测试什么了.

第一阶段写下的测试本是错误的——你还不了解问题。第二阶段写下的测试本会激怒你,你仍在精炼API。第三阶段写出来的测试是 正确,因为执行是稳定的。

[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);
}

这一阶段还包括:

  • 安全审查(认证、授权、输入验证)
  • 货币因素(负载下会发生什么? )
  • 失败模式分析( 如果数据库缓慢? 如果 API 取消 ? )
  • 监测(我们如何知道生产是否中断? )

反馈编程

不同的人应该在不同阶段参与

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阶段: 只有技术声音 — 高级Devs, 建筑师。 他们可以看到解决方案的形状。 非技术利益攸关方会惊慌于粗略代码,并在你仍在寻找数据模型时询问按钮颜色。

第2阶段: 非技术性声音受到欢迎。 功能是可以使用的, 按钮可以发挥作用。 现在让产品所有者和设计者参与进来, 改变仍然便宜, 你还没有写测试。

第3阶段: “如果这有10倍的流量呢?” “如果有人通过恶意输入呢?” 这些只涉及功能一旦实际有效。

作为沙箱的分支

以下是改变我发展生活的东西: 直到你升公关, 你的分行是完全私人的.

这听起来可能听起来很明显,但我看到开发商对待每个承诺,就像对待他们永久记录一样。他们害怕实验。害怕写出丑陋的代码。害怕破坏东西。

我要你把你的特长当作沙箱,一个操场,一个预期爆炸的实验室,没有人受到伤害。

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

实际上,这意味着:

  • 经常提交,即使代码没有编译
  • 写写 FIXMETODO 各地的评论
  • 在探索时请将调试代码留在原地
  • 具有多个“ 尝试此方法” 承诺
  • 不要担心发送信息(待会会会压扁)

然后,在你升官之前:

  1. 清理代码( 第2阶段)
  2. 添加测试(第3阶段)
  3. 互动重置基础 以粉碎所有混乱的历史
  4. 写入正确的承诺信件

PR审查员看到的是干净的职业代码,有清晰的叙事。他们没有看到六个错误的开始, 3点的“为什么这该死的工作”没有实施, 也没有看到“不撤销”的历史。

沙箱是保密的 磨光的作品是公开的

为何这减轻了压力

每条线都感觉永远不变 每条决定都会有结果 游戏是合法工作 第1阶段,当第2阶段的支线很便宜时,就会有反馈,而测试一旦在第3阶段写得正确,就会有反馈,因为你终于知道你在测试什么了。

"玩乐是正当的工作"

当域名被很好地理解或者你正在锁定一个已知的错误时,TDD工作非常出色。但是当问题仍然模糊时,写测试通常意味着连续三次测试错误的东西。这个方法会绕道:先探索,然后在稳定时测试。

柔性固态(和DRY的肮脏秘密)

这三阶段思维对设计原理有何影响?简言之,它会杀死教条。这就是如何改变我对SoliID、DRY和C#界面的想法。

我提到在第二阶段应用SOLID原则“实实在在地”让我具体说明我指的是什么。

SOLID是一套好的原则。我教他们。我用它们。但我看到球队以单一责任或开放/关闭的名义 消失在抽象的兔子洞中, 创建了他们无法理解的灵活系统。

这是我的功劳论:

当 :

  • 你有证据,你需要灵活
  • 抽象使代码更加清晰
  • 你正在建造别人会用的东西

在下列情况下,不要使用固态固态:

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

三个相似的代码线比一个过早的抽象线要好。 当您清楚地看到模式时, 您总是可以稍后提取。 您无法轻易地解开您结构中的抽象线 。

// 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
    }
}

如果您以后需要灵活性, 重构成本是便宜的。 超前工程成本昂贵, 因为您保持的抽象层无法维持 。

稻草陷阱

当我们屠杀神圣的牛群的时候, 我们来谈谈 过 过 过 过 过 这也许是我们行业中最教条化地应用的原则, 当不加思考地应用时, 会造成更多的伤害而不是好。

问题在于: 并非所有重复工作都是同样的重复工作.

当两条代码看起来相似,但目的不同时, 把它们带入一个共同的抽象夫妇, 独立发展。现在,当一个使用的案例需要改变时, 你要么是:

  1. 增加处理这两个案件的参数和条件(使抽象情况更糟)
  2. 打破另一个使用大小写
  3. 复制共享代码并编辑它(承认 DRY是错误的)
// "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 版本是一个要维持的噩梦, 因为它试图成为所有呼叫者的一切事物。 每一次改变都需要理解所有使用案例。 每个错误修正都有可能打破其它东西 。

我的规则: 在你看到 相同 3次,你有信心 它代表 相同 概念会一起演进。 在那之前, 一点点拷贝版是好的。 这不是道德上的失败, 而是适当的谨慎 。

重复比错误的抽象要便宜得多。 当图案清晰时, 您总是可以稍后变本加厉的。 您无法轻易地解开一个通过代码库编织的错误的抽象。

C # 中的界面问题

这是另一头需要宰杀的圣牛: "每个班级都需要一个界面"模式.

早在 .NET 依赖性注射的早期, 有人决定, 要使代码可以测试, 你需要在任何地方输入接口。 逻辑是: 你不能嘲笑一个混凝土类, 所以每个服务都需要 IServiceServiceImpl。 突然间,每个 C# 代码库都充满了纯用于测试的单一实施界面。

// 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

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

事情是这样的 你不再需要这个了

现代模拟框架等 替代物Moq 摩卡 更重要的是,ASP.NET Core的DI集装箱与具体类型完全一样:

// 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只是你需要的服务 直接注射

"但是测试呢?" 我听见你哭泣。

对于单位测试,您可以选择:

  1. 制作密钥方法 virtual 使用模拟框架
  2. 使用测试数据库的实际服务(综合测试往往更有价值)
  3. 添加接口 当您实际需要测试时

第三点至关重要: 重构工具在提取接口方面特别好.

在骑士或视觉演播室, 它的字面 Ctrl+. “ 解开接口” , 你就可以完成。 每一个方法都会被解开, 班级会得到更新来实施它, 如果需要, 您可以找到/ 替换所有用法 。 它需要几秒时间 。

// 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阶段.

在“让它发挥作用”和“让它变得漂亮”期间,我只写课程。没有过早的抽象抽象。没有每种类型的界面税。代码更简单,更容易导航(不跳过) IFooFoo),而且写得更快。

当我按下"锁住它" 需要写测试报告时 时当时 我决定了真正需要嘲笑哪些服务。通常比你想的要少 — — 通常只是外部整合,比如HTTP客户端、数据库和信息队列。

内部商业逻辑服务? 内部商业逻辑服务? 一半时间,我只是直接用实际依赖性测试它们。 测试更有价值,因为测试的是实际行为,而不是模拟我的行为。 思考思考 行为应该是。

// 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

重点是: 停止前方的“ 以防万一” 前方的写入界面。 写入混凝土类 。 如果您以后需要测试或真正多形态的界面, 需要使用现代工具提取一个界面需要几秒钟 。

您的代码库会更简单,更通航, 而且您不会有 维持负担 保持接口同步 与执行同步

Agile 连接

具有讽刺意味的是,这一方法更符合 原原件 与大多数我遇到的“ 敏感” 进程相比, 敏感宣言比大多数“ 敏感” 进程都简单。 快速工作软件( 第1阶段 ) , 快速应对变化( 第1-2阶段 ) 。 当特征明显可见时客户协作( 第2阶段 ) 。 没有速记点, 没有速度跟踪, 没有血腥的刻录图表 。

现代的“ Agile ” 已经被进程爱好者所捕捉, 他们增加了如此多的仪式, 已经没有时间进行探索了。这个方法带来了回放 — — 不是作为宽容,而是作为创造性工作的合法阶段。

当不使用此方法时

我应该诚实地说:这并不总是正确的做法。

错误修正 : 当您正在修补已知的错误时, TDD 风格更有意义 。 写入一个复制错误的测试, 修正它, 完成 。

众所周知的问题: 如果你执行标准算法或模式 你用过一百次 你就不需要探索阶段

规定已知要求的紧凑期限: 如果你真的知道自己正在建造什么 并且当它需要运载时 你可能会跳过第一阶段的探索

高度管制的环境: 如果每个承诺都需要签收,沙箱就更难了。尽管你仍然可以在推前在当地进行勘探。

但是,为了 多数 发展功能——要求有点模糊, 技术方法并不明显, 你需要思考空间——这个方法非常有效。

给初级开发者的说明

如果你在职业生涯的早期, 你可能会感到压力, 总是显示“ 完美” 代码。 您不必这样做。 使用这种三阶段方法, 只需弄清楚你处于哪个阶段, 并且总是完成整个工作直到第三阶段。 如果它导致最后的代码被擦亮、 测试、 生产准备就绪, 没有人会责怪你 混乱的勘探代码 。

底底线

让它工作, 让它漂亮, 锁定它。

开始简单。 设计要有远见, 而不是投机。 循环速度很快。 只有在有回报时才增加复杂性 。

"重复比错误的抽象要便宜得多"

探索阶段不是一个有罪的秘密 — — 这是过程的一个明确部分。 您的分支就是你的沙箱, 直到您提出公关。 技术反馈提前到来, 非技术反馈到一半, 行动和安全反馈迟到。

这保持了创造力的活力,减轻了压力,并产生了规模优雅的系统,因为你了解了问题,然后才致力于解决问题。

从第1分钟开始,不要试图完美 允许自己玩


相关阅读

如果这引起共鸣,

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.