每个应用程序都有其秘密武器,即它存在的理由。将秘密武器代码化是编写可维护和成功应用程序的关键。
等等。什么是代码化?请耐心听我说,我们会讲到的。
首先让我们假设一下:
你刚刚被晋升为首席软件工程师(恭喜!)。你的首席执行官的第一项任务是为公司创建一个新产品。这是一个从零开始的会计应用程序。高管们认为拥有自定义会计解决方案将使他们在竞争中获得优势。
几个月过去了,大部分横切关注点已经开发完成(太好了!)。团队现在专注于应用程序的精妙之处:业务领域(秘密武器)。这是将秘密武器代码化的开始。
代码化是围绕业务领域中的一个基本概念建立结构。
在会计中,市盈率(P/E Ratio)是衡量公司收益的指标。高市盈率表明未来收益增长潜力大。市盈率的计算方法是将每股市场价值(股价)除以每股收益(利润 - 股息 / 流通股数)。
一个简单的、我认为是天真的实现:
public class Metric
{
public string Name { get; set; }
public decimal Value {get; set}
public int Order {get; set;}
}
public class AccountingSummary
{
public Metric[] GetMetrics(decimal price, decimal earnings)
{
var priceEarningsRatio = price/earnings;
var priceEarningsRatioMetric = new Metric
{
Name = "P/E Ratio",
Value = priceEarningsRatio,
Order = 0
}
return new [] {priceEarningsRatioMetric};
}
}
如果这只在一个地方使用,这很好。但如果你在其他地方也使用市盈率呢?
比如在 PriceEarnings.cs 中
var priceEarningsRatio = price/earnings;
还有在 AccountSummary.cs 中
var priceEarningsRatio = price/earnings;
以及在 StockSummary.cs 中
var priceEarningsRatio = price/earnings;
市盈率是这个应用程序的核心,但它的实现方式(硬编码在各个地方)使得市盈率的重要性在代码的海洋中丧失了。它只是森林中的另一棵树。
你还面临在一个地方更改比率但在其他地方没有更改的风险。这可能会影响下游计算。这类错误通常很难找到。
测试人员通常会假设如果它在一个区域有效,那么在所有区域都是正确的。为什么应用程序不为整个应用程序使用相同的代码来生成市盈率呢?这不正是面向对象编程的目的吗?
我可以想象这样的错误进入生产环境,直到你的高管来访时才被发现,他要求知道为什么美国证券交易委员会的市盈率计算与公司提交的不同。这不是一个好位置。
让我们重新审视我们的市盈率实现,看看我们如何改进第一次尝试。
在会计系统中,公式是一个东西,让我们通过添加一个接口来围绕公式建立结构:
public interface IFormula
{
decimal Calculate<T>(T model);
}
现在每个公式都通过这个接口实现,给我们一致性和可预测性。
以下是我们在实现接口后改进的市盈率:
我们添加了 PriceEarningsModel 来将所需数据传递到我们的 Calculate 方法中。
public class PriceEarningsModel
{
public decimal Price {get; set;}
public decimal Earnings {get; set;}
}
使用 PriceEarningsModel,我们为市盈率创建了 IFormula 接口的实现。
public class PriceEarningsRatioFormula : IFormula
{
public decimal Calculate<PriceEarningsModel>(PriceEarningsModel model)
{
return model.Price / model.Earnings;
}
}
我们现在已经将市盈率代码化了。它是我们应用程序中的一个一级概念。我们可以在任何地方使用它。它是可测试的,一个更改会影响整个应用程序。
作为提醒,这是我们开始时的实现:
public class Metric
{
public string Name { get; set; }
public decimal Value {get; set}
public int Order {get; set;}
}
public class AccountingSummary
{
public Metric[] GetMetrics(decimal price, decimal earnings)
{
var priceEarningsRatio = price/earnings;
var priceEarningsRatioMetric = new Metric
{
Name = "P/E Ratio",
Value = priceEarningsRatio,
Order = 0
}
return new [] {priceEarningsRatioMetric};
}
}
它很简单,能完成工作。问题是市盈率(这是我们会计应用程序中的一个核心概念)并不突出。不熟悉应用程序或业务领域的工程师不会理解其重要性。
我们改进的实现使用了我们新的市盈率类。我们将 PriceEarningsRatioFormula 类注入到我们的 AccountSummary 类中。
我们用新的 PriceEarningsRatioFormula 类替换了硬编码的市盈率。
public class AccountingSummary
{
private PriceEarningsRatioFormula _peRatio;
public AccountingSummary(PriceEarningsRatioFormula peRatio)
{
_peRatio = peRatio;
}
public Metric[] GetMetrics(decimal price, decimal earnings)
{
var priceEarningsRatio = _peRatio.Calculate(new PriceEarningsModel
{
Price = price,
Earnings = earnings
});
var priceEarningsRatioMetric = new Metric
{
Name = "P/E Ratio",
Value = priceEarningsRatio,
Order = 0
}
return new [] {priceEarningsRatioMetric};
}
}
有人可能会争辩说 PriceEarningsRationFormula 比之前的实现需要做更多的工作,我同意。有一些额外的仪式,但好处远远超过代码和仪式的小幅增加。
首先,我们获得了为整个应用程序更改市盈率的能力。如果出现缺陷,我们还有一个单一的实现来调试。
最后,我们已经在应用程序中将 PriceEarningsRatioFormula 的概念代码化了。当一个新工程师加入团队时,他们会知道公式对应用程序和业务领域至关重要。
还有其他方法来代码化(封装)领域,例如微服务和程序集。每种方法都有其优缺点,你和你的团队必须决定什么最适合你的应用程序。
将关键领域概念封装在类和接口中会创建可重用的组件和概念边界。使应用程序更容易理解,减少缺陷,降低新工程师的入职障碍。
作者:Chuck Conway 是一位 AI 工程师,拥有近 30 年的软件工程经验。他构建实用的 AI 系统——内容管道、基础设施代理和解决实际问题的工具——并分享他沿途的学习成果。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTube 和 SubStack。