понедельник, 30 января 2017 г.

Абстрактный код

Как абстракции высокого уровня могут помочь в борьбе со сложностью
или почему модуль, похожий на "трубопроводный узел" чрезвычайно хорош?

В этом посте речь пойдет о важности абстракций при разработке сложных систем. Сперва небольшой оффтоп: зачем вообще нужны абстракции? Часто работать с конкретикой гораздо удобнее, чем с некой абстрактной сущностью, детали которой размазаны и не ясны до конца. Но на самом деле, нет ничего кроме абстракций как в окружающем мире, так и в разработке ПО(тем более в разработке ПО). Вопрос лишь в том, на каком уровне абстракции рассматривается конкретный предмет. Уровней абстракций может быть бесконечно много. Все это звучит слишком аБсТрАкТнО, так что пора привести несколько примеров. 

Взять к примеру, растение. Каждому ясен смысл этой абстракции, и у каждого в голове мелькнет картинка с элементами зеленого цвета и мысли о фотосинтезе, но детали будут отличаться. Но вопрос не в этом. Вопрос в том, какими уровнями абстракций обладает объект "растение". И эти уровни будут зависеть от цели. Если цель - "полить растение", то абстракция "растение" останется практически неизменной, если же "исследовать полезные свойства стволовых клеток", то абстракция "растение" растворится в изобилии деталей клеточного уровня абстракций. Между этими двумя уровнями, опять же, бесконечность других - все зависит лишь от того, какой уровень позволит достичь той или иной цели максимально эффективно.

Теперь пора возвратиться к абстракциям, помогающим разрабатывать сложные системы. "Растения" там заменяются программными модулями. И все же еще не до конца ясно, как абстракции могут помочь. В дальнейшем под абстракцией я буду понимать интерфейсы и абстрактные классы.

Главная задача, которую выполняют абстракции - это борьба с множественными зависимостями. Вот как выглядит типичный модуль сложной системы со всеми своими зависимостями:



Вот, что позволяет сделать абстракция (класс, интерфейс - не важно):


Обратная стрелка здесь - это реализация конкретной абстракции (имплементация интерфейса, наследование от абстрактного класса). А облако - сама абстракция. Обратите внимание, что изначальная связь как бы разорвалась.

Таким образом количество зависимостей становится равным не количеству всех стрелок(18) в иерархии, а только количеству стрелок от модуля к абстракциям(3). А сложность модуля(системы) и определяется именно количеством таких зависимостей.

Теперь модуль можно рассматривать обособленно от всей остальной системы, держа в уме лишь зависимости от абстракций:


Это напоминает устройство компьютерного железа(или водопроводного узла) - слот(абстракция) один, а конкретных объектов, подходящих к слоту - сколь угодно много. А все потому, что на уровне железа умные люди уже давно реализовали все зависимости на уровне абстракций. Детали для механических устройств(самолетов, автомобилей) - тоже абстракции. Но вот при построении сложной системы большинство специфичных абстракций приходится изобретать архитектору почти с нуля.

В общем,  программируйте на уровне интерфейсов и зависимостей между ними и лишь потом переходите к разработке специфичных деталей. Может показаться, что это работа чисто архитектора, но на самом деле архитектура и кодирование неразрывно связаны, так что слепо писать код получается редко. 

Таким образом, управление абстракциями - это управление сложностью системы.


среда, 25 января 2017 г.

Использование "черных ящиков" в борьбе со сложностью

Почему "черные ящики" - это хорошо.
Или в чем суть инкапсуляции.

В этом небольшом сообщении я хотел бы рассмотреть проблему сокрытия данных в масштабе крупной системы. Причем под "сокрытием" я понимаю не использование приватных полей/методов, а сокрытие логики, сокрытие того "как" что-то делается.

Если вы можете провести точную грань между тем "что" и "как", то это хорошая предпосылка создания качественного интерфейса, формирующего целостную абстракцию (о важности абстракций мы еще как-нибудь поговорим).

Почему вообще важен качественный интерфейс? Качественный интерфейс - это сигнал о том, что для конкретного элемента вашей сложной системы вы грамотно и, скорее всего, правильно определили область ответственности. Элемент(класс, если угодно, хотя и необязательно) делает одну вещь и делает это хорошо - вот это истинная цель, которая достойна преследования.

Но вернемся к тому "что" и "как". Именно это разделение открывает перед разработчиком компонента системы возможности сокрытия всех деталей, оставляя пользователю элемента лишь вкусную абстракцию, с которой приятно работать. Иными словами, "что" инкапсулирует все шаги, известные "как".

Но здесь есть один нюансик. Нередко, особенно на начальных порах, возникает иллюзия, что вы нашли границу между "что" и "как". Эта иллюзия в самом худшем случае приводит к появлению множественных геттеров(гет-методы) для защищенных данных(приватных полей) объекта.

Почему это плохо? 
Представьте себе: вы пришли в кабинет к стоматологу, который обладает всем необходимым для качественного лечения зуба. Но вместо того, чтобы садиться в удобное(хотя не всегда) кресло и не думать о том, что происходит в вашей ротовой полости, вы просите у стоматолога все инструменты, которые только можно запросить(через гет-методы), после чего стоматолог сам по себе(пошел пить чай), а вы с грудой инструментов и больным зубом - сами по себе.
Ситуация не из приятных. Было бы гораздо удобнее "попросить" стоматолога все сделать за вас, не вникая в детали его работы, ведь он в этой сфере мастер.

Именно таким образом в сложной системе и стоит создавать связи между слабозависимыми компонентами - один должен просить другого "что"-либо сделать, не вникая в детали того, "как" он это сделает, ведь главное - результат(а не участие в процессе).

В дальнейшем инкапсуляция деталей(информационных полей и большинства методов) поможет добавлять объектам новую ответственность, менять и улучшать старую, оставаясь при этом в пределах ответственного объекта.
Вы можете прийти к стоматологу, и он полечит ваш зуб разными способами, которым он научился где-то и как-то, но ваш запрос будет при этом оставаться неизменным - "полечите мне зуб!".

Грамотная инкапсуляция имеет мало общего с приватными и защищенными спецификаторами на уровне конкретных языков. Поэтому используйте инкапсуляцию грамотно.