Описываемая проблема в статье давно и хорошо известна, поэтому она по большей части для новичков, которые не знакомы с темой.
В ПО, которое разрабатывает наша команда используются денежные значения в рублях и копейках. Мы изначально знали, что использование примитивов для выражения денежных значений — это антипаттерн. Однако по мере разработки приложения мы всё никак не могли наткнуться на проблемы связанные с использованием примитивов, нам, видимо, везло и всё было нормально. До поры до времени.
Мы совсем забыли про эту проблему и использование примитивов типа int и decimal расползлось по всей системе. И теперь, когда мы написали первый метод, в котором прочувствовали проблему, пришлось вспомнить про это технический долг и переписать всё на использование денежной абстракции вместо примитивов.
Хочется добавить, что в целом данный антипаттерн — это «одержимость примитивами», который встречается достаточно часто, например: string для представления IP-адреса, использование int или string для ZipCode.
А вот говнокод, который был написан:
public bool HasMismatchBetweenCounters(DispensingCompletedEventArgs eventArgs, decimal acceptedInRub) {
decimal expectedChangeInRub = eventArgs.ChangeAmount.KopToRub();
int dispensedTotalCashAmountInKopecs = expectedChangeInRub.RubToKop() - eventArgs.UndeliveredChangeAmount;
if (dispensedTotalCashAmountInKopecs != eventArgs.State.DispensedTotalCashAmount) {
return true;
}
if (acceptedInRub != eventArgs.State.AcceptedTotalCashAmount.KopToRub()) {
return true;
}
return false
}
public struct Money : IEqualityComparer<Money>, IComparable<Money> {
private const int KopecFactor = 100;
private readonly decimal amountInRubles;
private Money(decimal amountInRub) {
amountInRubles = Decimal.Round(amountInRub, 2);
}
private Money(long amountInKopecs) {
amountInRubles = (decimal)amountInKopecs / KopecFactor;
}
public static Money FromKopecs(long amountInKopecs) {
return new Money(amountInKopecs);
}
public static Money FromRubles(decimal amountInRubles) {
return new Money(amountInRubles);
}
public decimal AmountInRubles {
get { return amountInRubles; }
}
public long AmountInKopecs {
get { return (int)(amountInRubles * KopecFactor); }
}
public int CompareTo(Money other) {
if (amountInRubles < other.amountInRubles) return -1;
if (amountInRubles == other.amountInRubles) return 0;
else return 1;
}
public bool Equals(Money x, Money y) {
return x.Equals(y);
}
public int GetHashCode(Money obj) {
return obj.GetHashCode();
}
public Money Add(Money other) {
return new Money(amountInRubles + other.amountInRubles);
}
public Money Subtract(Money other) {
return new Money(amountInRubles - other.amountInRubles);
}
public static Money operator +(Money m1, Money m2) {
return m1.Add(m2);
}
public static Money operator -(Money m1, Money m2) {
return m1.Subtract(m2);
}
public static bool operator ==(Money m1, Money m2) {
return m1.Equals(m2);
}
public static bool operator >(Money m1, Money m2) {
return m1.amountInRubles > m2.amountInRubles;
}
public override bool Equals(object other) {
return (other is Money) && Equals((Money) other);
}
public bool Equals(Money other) {
return amountInRubles == other.amountInRubles;
}
public override int GetHashCode() {
return (int)(AmountInKopecs ^ (AmountInKopecs >> 32));
}
}
var money = new Money(200); //что это: 200 рублей или 200 копеек=2руб.?
Money a = 200; //что это: 200 рублей или 200 копеек=2руб.?
Money b = 200m; //казалось бы это рубли, но кто его знает?
К сожалению, не доступен сервер mySQL