При разработке программ мы, к сожалению, не можем оперировать в голове всем тем количеством инструкций, из которых состоят серьёзные системы. Есть довольно старое правило, что человек может одновременно оперировать 5-7 вещами, при этом масштаб этих вещей не очень важен. Также, стоит заметить, что в основе психики человека лежит абстрагирование и обобщение, которое позволяет нам адаптироваться к новым, не знакомым нам вещам.
Разношерстные на первый взгляд инструкции можно объединить по смыслу, который они несут вкупе. Эти объединения можно собрать в более высокоуровневые абстракции. И так можно продолжать сколько угодно, пока вы не разобьете свою программу на иерархию понятных и простых в использовании сущностей.
Переход от буквального к семантическому очень важен, и чем более абстрактно программист может смотреть на происходящие в коде вещи, тем эффективней он может бороться с нарастающей при разработке сложностью системы.
Абстрагирование – это выделение существенных деталей на фоне несущественных. Например, выводя цветное сообщение в консоль, нам важно будет только какого цвета сообщение и его текст. Это один уровень абстракции, на этом уровне нам не важно как внутри этой операции (вывод цветного сообщения) происходит возвращение к исходному цвету.
При этом мы можем рассматривать другой уровень абстракции. Если у нас есть операция вывода какого-то цветного сообщения, мы можем построить на её основе более высокоуровневую операцию – вывод предупреждения в консоль. Это операции выводит некоторый текст сообщения, желтым цветом и в рамке. При использовании этой операции неважно как там создается рамка и прочее, главное тут – её семантика – предупредить пользователя о чём-то.
Любая абстракция начинается с имени. Хотя она начинается еще раньше, в нематериальном плане идей и смыслов, но своё физическое воплощение, через которое мы передаем её другим людям, она начинает именно с имени. Хорошее имя – это лишь отражение хорошей сути. Если абстракция плохая, то как не думайте, будет у вас какой-нибудь Manager.
Метод как функциональная абстракция – метод есть некое действие, совершаемое с какими-то возможными входными значениями (аргументами), которое может завершится с каким-то результатом (возвращаемое значение) и некоторыми корректными последствиями на состояние контекста (инварианты). Он как и любая абстракция описывает общую концепцию, а не конкретные примеры использования.
Мы можем как из лего строить одни функции из других тем самым создавая иерархию функций (алгоритмов), и в итоге, получая программу решающую нашу задачу эффективным и понятным читателю программы способом.
Если рассматривать метод как некий алгоритм или даже возможно действие, то мы можем говорить о двух важных аспектах: предусловия и постусловия.
- Для выполнения действия нужны какие-то данные, инструкции или особые указания. Например если говорить, что у нас есть некое действия в системе как “Покупка товара” то такому действию нужно знать как минимум о том, кто покупает и что покупает.
- У действия могут быть последствия. Например последствие покупки товара, информация об успешной или неудачной покупки + изменения баланса пользователя. Это есть постусловие.
При соблюдении предусловий, метод обязуется соблюдать свои постусловия. Это эмпирически понятно и обычно описывается словом “Контракт”. Если вы дадите какой-то операции некое A, то она точно отдаст некое B.
В C# мы используем такой формализм как “формальная система типов” который позволяет описывать предусловия и постусловия с помощью декларации типов. Так предусловия мы можем выразить через типы параметров а постусловие через тип возвращаемого значения. Хоть это и не даёт возможности полностью следовать идеологии контрактного программирования, но это лучше чем ничего.
Спуск на один шаг
Имя функции должно находится на неком уровне абстракции N, весь код внутри функции должен располагаться чётко на уровне N – 1.
Пример когда функция уходит слишком вниз в своей реализации:
private static string MakeHtmlReport(Report report)
{
string fileBasePath = Server.Request.ApplicationRunningPath;
string html = $"<img src='{fileBasePath}/report_logo.png'/>";
html += $"<div>{report.Text}</div>";
}
Во-первых этой функции абсолютно не нужно знать о путях, во вторых функции очень нежелательно вручную собирать html.
Как бы выглядела эта функция если её реализации находилось бы на уровне ниже? Представим что на высоком уровне у нас Отчёт, а ниже находится абстракция “Ресурс” и “Формат”.
private static string MakeHtmlReport(Report report)
{
var page = htmlFormat.MakePage();
page.Append(Resources.ReportLogo);
page.Append(report.Text);
return page.ToString();
}
Теперь в функции осталось самое важное выбор формата и передача через него данных. Но можно двинутся и дальше, например представить функцию MakeHtmlReport часть некоего полиморфного типа и у нас останется только это.
protected override string Make(Report report)
{
var page = _format.MakePage();
page.Append(Resources.ReportLogo);
page.Append(report.Text);
return page.ToString();
}
У нас в сущности останется только шаблон и мы введём такую абстракцию в системе “Шаблон отчёта” и в результате будет что-то такое.
protected override void Draw(Report report, IFormatedPage page)
{
page.Append(Resources.ReportLogo);
page.Append(report.Text);
}