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
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
咒语: 先玩,确认你的假设,找点事做
这就是你的分支是沙箱的阶段。 混乱的代码是好的。 死亡的结局是好的。 开始三次以上是 罚款你不是在建大教堂 而是在用餐巾纸画画
你想弄清楚的是:
关键的观点是: 在你升公关前 你的分行是 您 的这是你的实验实验室。没有人需要看到你的假开始,你评论出来的调试代码,你的“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? 提前让他们参与进来。 快速的“ 这样的形状有意义吗? ” 对话可以节省重新工作的日子。 建立用户形象? 也许在不那么粗糙之前延缓对商业利害相关方的曝光 — — 有些人被挂在了什么上面。 外观 而不是它是否 工程。这很好;他们在第二阶段提供反馈。
使用判断力。有时你需要反馈 澄清澄清而不是批准- 规格已经这样做了 。 “ 规格上写着“ 优雅的手动错误 ” - 是意味着重试三次, 还是快速失败通知? 这是第一阶段的谈话 。
如果一个利害相关者的投入是关键, 但是他们会用粗略代码挣扎, 建立一个微不足道的演示。 一个显示这个概念的硬码快乐路径可以释放有价值的反馈, 而不会分散失踪的边缘案例的注意力 。
实事求是。目标是最佳特征,而不是纯纯化过程。
咒语: 现在你知道你在建什么了 让它 良好.
探索阶段已经完成了它的工作。你确认了接近方法的工作, 你了解问题的形式, 你可能发现了十几个边缘案例, 最初的票没有提及。
现在你把它清理干净:
// 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
};
}
这就是你:
关键是,这是非技术利益攸关方反馈的最佳时机。 功能现在已经可见且可用, 但你还没有为此投入时间写测试。 如果他们说“ 实际上, 我们想要它做X ” , 您可以在删除一个全面的测试套件时不感到心碎。
咒语: 硬化它,测试它, 使它具有弹性。
现在,直到现在,你才写你的测试。添加安全检查。对生产边缘案例进行正确的错误处理。增加监测和护栏。
为什么等到现在? 你终于知道你在测试什么了.
第一阶段写下的测试本是错误的——你还不了解问题。第二阶段写下的测试本会激怒你,你仍在精炼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);
}
这一阶段还包括:
不同的人应该在不同阶段参与
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
实际上,这意味着:
FIXME 和 TODO 各地的评论然后,在你升官之前:
PR审查员看到的是干净的职业代码,有清晰的叙事。他们没有看到六个错误的开始, 3点的“为什么这该死的工作”没有实施, 也没有看到“不撤销”的历史。
沙箱是保密的 磨光的作品是公开的
每条线都感觉永远不变 每条决定都会有结果 游戏是合法工作 第1阶段,当第2阶段的支线很便宜时,就会有反馈,而测试一旦在第3阶段写得正确,就会有反馈,因为你终于知道你在测试什么了。
"玩乐是正当的工作"
当域名被很好地理解或者你正在锁定一个已知的错误时,TDD工作非常出色。但是当问题仍然模糊时,写测试通常意味着连续三次测试错误的东西。这个方法会绕道:先探索,然后在稳定时测试。
这三阶段思维对设计原理有何影响?简言之,它会杀死教条。这就是如何改变我对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
}
}
如果您以后需要灵活性, 重构成本是便宜的。 超前工程成本昂贵, 因为您保持的抽象层无法维持 。
当我们屠杀神圣的牛群的时候, 我们来谈谈 过 过 过 过 过 这也许是我们行业中最教条化地应用的原则, 当不加思考地应用时, 会造成更多的伤害而不是好。
问题在于: 并非所有重复工作都是同样的重复工作.
当两条代码看起来相似,但目的不同时, 把它们带入一个共同的抽象夫妇, 独立发展。现在,当一个使用的案例需要改变时, 你要么是:
// "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次,你有信心 它代表 相同 概念会一起演进。 在那之前, 一点点拷贝版是好的。 这不是道德上的失败, 而是适当的谨慎 。
重复比错误的抽象要便宜得多。 当图案清晰时, 您总是可以稍后变本加厉的。 您无法轻易地解开一个通过代码库编织的错误的抽象。
这是另一头需要宰杀的圣牛: "每个班级都需要一个界面"模式.
早在 .NET 依赖性注射的早期, 有人决定, 要使代码可以测试, 你需要在任何地方输入接口。 逻辑是: 你不能嘲笑一个混凝土类, 所以每个服务都需要 IService 和 ServiceImpl。 突然间,每个 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只是你需要的服务 直接注射
"但是测试呢?" 我听见你哭泣。
对于单位测试,您可以选择:
virtual 使用模拟框架第三点至关重要: 重构工具在提取接口方面特别好.
在骑士或视觉演播室, 它的字面 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阶段.
在“让它发挥作用”和“让它变得漂亮”期间,我只写课程。没有过早的抽象抽象。没有每种类型的界面税。代码更简单,更容易导航(不跳过) IFoo 和 Foo),而且写得更快。
当我按下"锁住它" 需要写测试报告时 时当时 我决定了真正需要嘲笑哪些服务。通常比你想的要少 — — 通常只是外部整合,比如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
重点是: 停止前方的“ 以防万一” 前方的写入界面。 写入混凝土类 。 如果您以后需要测试或真正多形态的界面, 需要使用现代工具提取一个界面需要几秒钟 。
您的代码库会更简单,更通航, 而且您不会有 维持负担 保持接口同步 与执行同步
具有讽刺意味的是,这一方法更符合 原原件 与大多数我遇到的“ 敏感” 进程相比, 敏感宣言比大多数“ 敏感” 进程都简单。 快速工作软件( 第1阶段 ) , 快速应对变化( 第1-2阶段 ) 。 当特征明显可见时客户协作( 第2阶段 ) 。 没有速记点, 没有速度跟踪, 没有血腥的刻录图表 。
现代的“ Agile ” 已经被进程爱好者所捕捉, 他们增加了如此多的仪式, 已经没有时间进行探索了。这个方法带来了回放 — — 不是作为宽容,而是作为创造性工作的合法阶段。
我应该诚实地说:这并不总是正确的做法。
错误修正 : 当您正在修补已知的错误时, TDD 风格更有意义 。 写入一个复制错误的测试, 修正它, 完成 。
众所周知的问题: 如果你执行标准算法或模式 你用过一百次 你就不需要探索阶段
规定已知要求的紧凑期限: 如果你真的知道自己正在建造什么 并且当它需要运载时 你可能会跳过第一阶段的探索
高度管制的环境: 如果每个承诺都需要签收,沙箱就更难了。尽管你仍然可以在推前在当地进行勘探。
但是,为了 多数 发展功能——要求有点模糊, 技术方法并不明显, 你需要思考空间——这个方法非常有效。
如果你在职业生涯的早期, 你可能会感到压力, 总是显示“ 完美” 代码。 您不必这样做。 使用这种三阶段方法, 只需弄清楚你处于哪个阶段, 并且总是完成整个工作直到第三阶段。 如果它导致最后的代码被擦亮、 测试、 生产准备就绪, 没有人会责怪你 混乱的勘探代码 。
让它工作, 让它漂亮, 锁定它。
开始简单。 设计要有远见, 而不是投机。 循环速度很快。 只有在有回报时才增加复杂性 。
"重复比错误的抽象要便宜得多"
探索阶段不是一个有罪的秘密 — — 这是过程的一个明确部分。 您的分支就是你的沙箱, 直到您提出公关。 技术反馈提前到来, 非技术反馈到一半, 行动和安全反馈迟到。
这保持了创造力的活力,减轻了压力,并产生了规模优雅的系统,因为你了解了问题,然后才致力于解决问题。
从第1分钟开始,不要试图完美 允许自己玩
如果这引起共鸣,
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.