Посты
Кодификация секретного ингредиента
16 сентября 2019 г. • 5 мин чтения
Каждое приложение имеет свой секретный ингредиент, причину своего существования. Кодификация секретного ингредиента является инструментальной в написании поддерживаемых и успешных приложений.
Подождите. Что такое кодификация? Терпение, мой друг, мы туда доберемся.
Сначала давайте выдвинем гипотезу:
Вас только что повысили до главного инженера-программиста (Поздравляем!). Первая задача вашего генерального директора — создание нового продукта для компании. Это бухгалтерское приложение с нуля. Руководство считает, что наличие пользовательского решения для бухгалтерского учета даст им преимущество перед конкурентами.
Прошло несколько месяцев, большинство сквозных проблем решены (ура вам!). Команда теперь сосредоточена на самом вкусном в приложении: на бизнес-домене (секретном ингредиенте). Именно здесь начинается кодификация секретного ингредиента.
Кодификация — это придание структуры существенной концепции в бизнес-домене.
В бухгалтерском учете коэффициент цена (P) к прибыли (E) (коэффициент P/E) — это измерение прибыли компании. Высокий коэффициент P/E предполагает высокий рост прибыли в будущем. Коэффициент P/E рассчитывается путем деления рыночной стоимости на акцию (цена акции) на прибыль на акцию (прибыль – дивиденды / количество выпущенных акций).
Простая, и я бы сказал, наивная реализация:
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};
}
}
Если это используется только в одном месте, это нормально. Что если вы используете коэффициент P/E в других областях?
Вот здесь в PriceEarnings.cs
var priceEarningsRatio = price/earnings;
И здесь в AccountSummary.cs
var priceEarningsRatio = price/earnings;
И вот здесь в StockSummary.cs
var priceEarningsRatio = price/earnings;
Коэффициент P/E является основным для этого приложения, но то, как он реализован, жестко закодирован в различных местах, делает важность коэффициента P/E потерянной в море кода. Это просто еще одно дерево в лесу.
Вы также рискуете изменить коэффициент в одном месте, но не в другом. Это может нарушить последующие расчеты. Такие типы ошибок печально известны своей сложностью в обнаружении.
Часто тестировщики предполагают, что если это работает в одной области, то это правильно во всех областях. Почему бы приложению не использовать один и тот же код для генерации коэффициента P/E для всего приложения? Разве это не суть объектно-ориентированного программирования?
Я могу представить себе ошибку, которая попадает в production и не обнаруживается до визита вашего руководителя, который требует узнать, почему расчеты коэффициента P/E SEC отличаются от того, что компания подала. Это не очень хорошее место.
Давайте пересмотрим нашу реализацию коэффициента P/E и посмотрим, как мы можем улучшить нашу первую попытку.
В системах бухгалтерского учета формулы — это реальность, давайте придадим структуру формулам, добавив интерфейс:
public interface IFormula
{
decimal Calculate<T>(T model);
}
Каждая формула теперь реализуется с этим интерфейсом, что дает нам согласованность и предсказуемость.
Вот наш улучшенный коэффициент P/E после реализации нашего интерфейса:
Мы добавили PriceEarningsModel для передачи необходимых данных в наш метод Calculate.
public class PriceEarningsModel
{
public decimal Price {get; set;}
public decimal Earnings {get; set;}
}
Используя PriceEarningsModel, мы создали реализацию интерфейса IFormula для коэффициента P/E.
public class PriceEarningsRatioFormula : IFormula
{
public decimal Calculate<PriceEarningsModel>(PriceEarningsModel model)
{
return model.Price / model.Earnings;
}
}
Мы теперь кодифицировали коэффициент P/E. Это концепция первого класса в нашем приложении. Мы можем использовать его где угодно. Это тестируемо, и изменение влияет на все приложение.
Напомним, вот реализация, с которой мы начали:
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};
}
}
Это просто и выполняет свою работу. Проблема в том, что коэффициент P/E, который является основной концепцией в нашем приложении бухгалтерского учета, не выделяется. Инженеры, незнакомые с приложением или бизнес-доменом, не поймут его важность.
Наша улучшенная реализация использует наш новый класс коэффициента P/E. Мы внедряем класс PriceEarningsRatioFormula в класс AccountSummary.
Мы заменяем наш жестко закодированный коэффициент P/E нашим новым классом 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 требует немного больше работы, чем предыдущая реализация, и я согласен. Есть немного больше церемонии, но преимущества стоят небольшого увеличения кода и церемонии.
Во-первых, мы получаем возможность изменить коэффициент P/E для всего приложения. У нас также есть единственная реализация для отладки, если возникнут дефекты.
Наконец, мы кодифицировали концепцию PriceEarningsRatioFormula в приложении. Когда новый инженер присоединится к команде, он будет знать, что формулы являются существенными для приложения и бизнес-домена.
Существуют другие методы кодификации (инкапсуляции) домена, такие как микросервисы и сборки. Каждый подход имеет свои плюсы и минусы, вам и вашей команде придется решить, что лучше всего для вашего приложения.
Заключение ключевых концепций домена в классы и интерфейсы создает переиспользуемые компоненты и концептуальные границы. Это делает приложение более понятным, снижает количество дефектов и снижает барьеры для адаптации новых инженеров.
Автор: Chuck Conway — инженер AI с почти 30-летним опытом разработки программного обеспечения. Он создает практические системы AI — конвейеры контента, агенты инфраструктуры и инструменты, которые решают реальные проблемы — и делится тем, что он узнает на этом пути. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube и на SubStack.