Посты
Кодификация секретного соуса
16 сентября 2019 г. • 5 мин чтения

У каждого приложения есть свой секретный соус, своя причина существования. Кодификация секретного соуса является ключевым элементом в написании поддерживаемых и успешных приложений.
Подождите. Что такое кодификация? Терпение, мой друг, мы до этого дойдем.
Сначала давайте выдвинем гипотезу:
Вас только что повысили до ведущего инженера-программиста (Поздравляем!). Первая задача от вашего генерального директора — создать новый продукт для компании. Это бухгалтерское приложение с нуля. Руководители считают, что наличие индивидуального бухгалтерского решения даст им преимущество перед конкурентами.
Прошло несколько месяцев, большинство сквозных задач разработано (ура вам!). Теперь команда сосредоточена на самом вкусном в приложении: бизнес-домене (секретном соусе). Именно здесь начинается кодификация секретного соуса.
Кодификация — это создание структуры вокруг ключевой концепции в бизнес-домене.
В бухгалтерском учете соотношение цены (P) к прибыли (E) (P/E Ratio) является показателем прибыли компании. Высокое соотношение 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 для всего приложения? Разве не в этом суть объектно-ориентированного программирования?
Я могу представить, как подобная ошибка попадает в продакшн и не обнаруживается до визита вашего руководителя, который требует знать, почему расчеты 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 в приложении. Когда новый инженер присоединится к команде, он будет знать, что формулы важны для приложения и бизнес-домена.
Существуют другие методы кодификации (инкапсуляции) домена, такие как микросервисы и сборки. У каждого подхода есть свои плюсы и минусы, вам и вашей команде придется решить, что лучше для вашего приложения.
Заключение ключевых доменных концепций в классы и интерфейсы создает переиспользуемые компоненты и концептуальные границы. Это делает приложение более понятным, снижает количество дефектов и понижает барьеры для адаптации новых инженеров.
Автор: Чак Конвей специализируется на разработке программного обеспечения и генеративном ИИ. Свяжитесь с ним в социальных сетях: X (@chuckconway) или посетите его на YouTube.