Сложность и абстракция

Изобретаем ООП.

Большая часть кода, который мы до этого момента рассматривали, была написана в процедурном стиле: мы использовали переменные и код, их изменяющий. В этом курсе мы научимся писать объектный код. Объектно-ориентированное программирование (ООП) расширяет процедурное программирование, которое является ступенью развития императивного программирования. Я позволю себе маленький экскурс в историю и по шагам подведу вас к самой идее ООП.

Итак, императивное программирование — описание в виде кода последовательности действий, выполнив которые, компьютер выдаст нужный нам результат. Действия выполняются друг за другом, данные от действия к действию передаются с помощью переменных.

В какой-то момент появляется необходимость выбирать между несколькими путями достижения результата или же повторить некий набор действий несколько раз. Тут на сцену выходит структурное программирование, которое предоставляет нам управляющие структуры — циклы, условные конструкции.

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

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

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

Теперь мы имеем процедуры и сущности, которыми они оперируют. И довольно часто одно неотделимо от другого. Процедуры, работающие с персонажем игры работают только с ним, но не применимы к товарам в корзине. К тому же нам всё время нужно где-то хранить эти самые структуры. Вот тут-то и появляется ООП со словами: "вы можете объединить процедуры и данные в единое целое, которое мы назовём объект!".

Конечно, в рамках страницы текста нельзя описать все перечисленные подходы к программированию со всеми их особенностями, но общее представление о каждом я, надеюсь, помог составить :)

Сложность, абстракция, слои, протекание.

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

Введение абстракций, так же как и добавление в язык управляющих структур и процедур, позволяет управлять сложностью. Если взять одну большую задачу и сравнить два решения: структурную программу и написанную в объектном стиле, то строчек кода в первой может оказаться даже меньше! Но каждый отдельно взятый кусок объектной программы будет проще понять, ведь в структурной программе везде видны детали реализации и за ними очень сложно увидеть общую логику.

Разница в удобстве написания и чтения кода будет видна и при сравнении структурного кода с процедурным. Структуры данных тоже упрощают написание сложного кода. Ведь процедуры и структуры данных — такие же средства абстрагирования!

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

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

Увы, как говорил классик Джоэл Спольски, "все нетривиальные абстракции протекают". Но то, насколько сильно будет течь, зависит от вас :) Если введение какой-то абстракции способно уменьшить сложность в одном месте без сильного увеличения сложности программы в целом, то стоит подумать о том, чтобы абстракцию ввести. В этом обдумывании и заключается управление сложностью — важная часть работы программиста!

Разные сложности.

Выше я много раз использовал слово "сложность". Считаю важным уточнить: в этом уроке речь не шла о сложности выполнения программы с точки зрения компьютера! Компьютеру тем проще выполнять программу, чем проще код, поэтому с его точки зрения идеальная программа — императивная, без всех этих объектов и процедур. Вот только код гораздо чаще читают люди, чем компьютер! Поэтому нам с вами нужно думать о сложности кода для понимания человеком.

О вычислительной сложности тоже иногда нужно думать, но в таких случаях речь обычно идёт о сложности каких-то конкретных алгоритмов, представляющих собой небольшую часть больших (и сложных!) программ, которые читает человек :)

Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Javascript, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →