Удобный паттерн если в приложении много проверок на null, в c# можно использовать динамические прокси для достижения такого эффекта. Предположим, что система спроектирована так, что в массиве
public class Person
{
public Salary January { get; }
public Salary February { get; }
public Salary March { get; }
public Salary April { get; }
public Salary May { get; }
public Salary June { get; }
public Salary July { get; }
public Salary August { get; }
public Salary September { get; }
public Salary October { get; }
public Salary November { get; }
public Salary December { get; }
public IEnumerable<Salary> SalaryByLastYear =>
new[]
{
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
}.ToList();
}
У нас стоит задача посчитать суммарную зарплату:
public decimal TotalSalary() => SalaryByLastYear.Sum(x => x.Value);
Но, если год не закончен, то в массиве будут null, можно сделать фильтрацию, но клиента класса могут не ожидать null, поэтому нужно ввести нейтральный объект, представляющий отсутсвие зарплаты по этому периоду и не выбрасывающий Null Reference Exception:
И по умолчанию проиницилизировать свойства класса NullSalary:
```csharp
public class Salary
{
public static Salary NullSalary => new Salary(0);
public decimal Value { get; }
public Salary(decimal value)
{
Value = value;
}
}
...
public Salary January { get; } = Salary.NullSalary;
public Salary February { get; } = Salary.NullSalary;
public Salary March { get; } = Salary.NullSalary;
public Salary April { get; } = Salary.NullSalary;
public Salary May { get; } = Salary.NullSalary;
public Salary June { get; } = Salary.NullSalary;
public Salary July { get; } = Salary.NullSalary;
public Salary August { get; } = Salary.NullSalary;
public Salary September { get; } = Salary.NullSalary;
public Salary October { get; } = Salary.NullSalary;
public Salary November { get; } = Salary.NullSalary;
public Salary December { get; } = Salary.NullSalary;
...
В F# для этого есть Option тип, который может принимать значения Some Value и None
Перед входом в метод можно поставить условия:
public decimal TotalSalary()
{
Debug.Assert(SalaryByLastYear.All(x => x != null), "SalaryByLastYear.All(x => x != null)");
return SalaryByLastYear.Sum(x => x.Value);
}
Теперь читатель кода увидит условия этого метода, еще лучше его переименовать, чтобы он более ясно отражал суть
Чтобы понять что предыдующая функция считает зарплату только по просшествии года, нужно смотреть ее исходный код, чтобы избежать этого можно переименовать функцию:
public decimal TotalSalaryAfterYear()
{
Debug.Assert(SalaryByLastYear.All(x => x != null), "SalaryByLastYear.All(x => x != null)");
return SalaryByLastYear.Sum(x => x.Value);
}
Но непонятно что делает фукнция если год еще не прошел, чтобы обозначить, что возможен Exception можно явно об этом сказать:
public decimal TotalSalaryAfterYearIfYearHasNotEndedGenerateException()
{
Debug.Assert(SalaryByLastYear.All(x => x != null), "SalaryByLastYear.All(x => x != null)");
return SalaryByLastYear.Sum(x => x.Value);
}
Но таких функций лучше делать как можно меньше, лучше не генерировать exception, а запретить вызов этой функции в состоянии, когда год еще не закончился
Когда назначение функции меняется приходится добавлять дополнительные параметры, в которых метод теперь нуждается, однако если этот параметр можно получить внутри класса, которому принадлежит метод - нужно так и сделать
Если метод может взять необходимый параметр внутри класса, которому он принадлежит, нужно так и сделать. Если передать в метод параметр, который он сам может достать, появляется неоднозначность и читатель начинает думать, что это не тот параметр, который метод может сам достать, а какой-то другой, и только при чтении кода понимает, что это не так и можно заменить передачу этого параметра на его получения внутри метод