Рубрики
Без рубрики

Слишком или нет, чтобы ОО

Обсуждение объектно-ориентированного проектирования. Помечены объектно-ориентированными, процедурными, языками программирования, java.

(предыдущая версия этого текста была опубликована на Medium)

Примерно в 2018 году, отчасти из-за работы Джонатана Блоу над Jai и очевидного отсутствия ОО в языке, я начал переосмысливать, хорошо ли ОО или нет. В итоге я выслушал — и прочитал — различные критические замечания в адрес OO и довольно долго размышлял над этой проблемой. Я также начал следить за серией Боба Нистрома “Создание переводчиков” , вторая часть которой была написана на очень понятном и легком для понимания процедурном языке C. Это напомнило мне, насколько непосредственно нацеленным на проблему был для меня код.

До OO было просто написание кода

Я начал программировать в 1982 году. Мне было 9 лет, и программирование на домашних компьютерах почти всегда было БАЗОВЫМ (или ассемблерным, когда вы хотели получить некоторую скорость). Это была эпоха первых “домашних компьютеров”. Компьютерные журналы печатали бы списки игр и приложений. Это было частью “получения программ по дешевке” и частью “обучения программированию”.

BASIC – это не совсем структурированное программирование, по крайней мере, не те ранние версии. “Структурированное программирование” было ограничено такими утверждениями, как GOSUB 1340 . Тем не менее, вы определенно могли бы что-то построить с его помощью. Игры и приложения, все были написаны на БЕЙСИКЕ. Ограничением обычно была память компьютера (обычно 16 Кб), а не структура. Возможно, это был не очень элегантный код, но он добился цели.

В конце концов я бы выбрал Pascal, и хотя более поздние итерации BASIC улучшили бы большую часть 8-разрядных реализаций, Pascal был намного лучше. Он был более мощным, и, что более важно, было действительно легко писать четкий и структурированный код. Но даже в этом случае разработка программ не сильно отличалась от написания на ассемблере или БЕЙСИКЕ. Вы начинали с одного конца и строили вещи до тех пор, пока они не были закончены. Было действительно легко просто “довести дело до конца”.

В конце концов я немного выучил C++, но OO-часть языка в основном ускользала от меня. Только когда я добрался до Java, все изменилось — и в то время я верил, что это к лучшему…

Java и “настоящий” OO

Раньше я говорил людям, что не понимал объектно-ориентированного программирования, пока не выучил Java. Возможно, это не совсем точно, но это правда, что я не пробовал никакого “Объектно-ориентированного проектирования”, пока не выучил Java.

Java действительно заставляла вас создавать объекты. Я сам начинал, когда апплеты были новинкой, а язык все еще находился в версии 1.0.2. Это было круто и волшебно. Объекты, где эти маленькие блоки вы могли бы создавать, чтобы делать что-то, почти как программирование маленьких роботов. Вместо простого программирования вы сделали это design , где независимые объекты разговаривали и выдавали результат. Это было потрясающе. И, конечно же, это тоже была ложь.

Поскольку Java стала мейнстримом, статьи и книги о том, как делать real OO , процветали. Суть, по-видимому, заключалась в том, что следует отбросить большинство вещей, связанных с процедурным программированием. Предположительно, это было плохо так же, как неструктурированное программирование было плохо для процедурного программирования. Чем больше мы думали в терминах объектов, тем лучше все было. Было ясно, что nirvana была почти доступна для тех счастливчиков, которые могли зарабатывать на жизнь программированием Java.

Кошмары объектно-ориентированного моделирования

В свободное время я работал над следующей итерацией сложной онлайн-игры, которая изначально была написана как BBS door (BBS online games еще тогда, когда мы использовали коммутируемые модемы). Оригинальная версия была написана на QBasic, и я также сделал переписывание на Turbo Pascal, которое охватывало около 80% игры. Написание игры заняло вечера и выходные, растянувшиеся примерно на полгода.

Версия QBasic была сложной, так как вам приходилось явно передавать все глобальные данные между файлами реализации, и у каждого файла было ограничение по размеру, поэтому его приходилось разделять с помощью этого огромного объявления глобальных переменных поверх каждого файла. Версия на Паскале была намного проще в написании. Вам не нужно было явно передавать глобальные переменные, и было просто передавать данные процедурам, в то время как для QBasic вам нужно было передавать параметры и результаты в глобальных переменных, которые вы специально выделили для этой цели. Очевидно, что версия Java — с OO goodness — должна быть даже проще , чем Pascal для реализации!

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

Делать реальные вещи

Тем временем я начал профессионально работать программистом. Я писал на Perl, Java, изучал Objective-C и Ruby. С Objective-C я обнаружил, что “OO” Java/C++ был всего лишь одним из видов OO. В Java идея состояла в том, чтобы создавать крошечные классы, которые были собраны в единое целое, но в ObjC объекты вместо этого использовались в качестве высокоуровневого “клея” между более крупными компонентами, написанными внутри в прямом процедурном коде C. Мелкозернистые классы Java будут рассматриваться как полная противоположность хорошему дизайну для Objective-C. Итак, если ОО должно было относиться к процедурному программированию так же, как процедурное — к неструктурированному, почему не было даже консенсуса относительно того, как делать “правильное ОО”?

Мой любимый проект все равно провалился. Я все еще пытался моделировать вещи способом Java, и у меня все время ничего не получалось. Но потом я попытался написать это на Ruby, и произошло нечто неожиданное.

Доводить дела до конца

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

С другой стороны, в Ruby написание класса тривиально. Даже моделирование многих из них – не такая уж большая работа. Если вы ленивы, вы даже можете сгенерировать их кучу с помощью метапрограммирования. Ruby очень, очень быстро создает прототипы.

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

В то время я профессионально работал на покерных серверах, и было ясно, что экземпляр игры в покер – это просто структура данных с колодой, текущими ставками и игроками. Действия игрока были просто командами, действующими на основе этих данных в соответствии с определенными правилами. Может быть, эта идея могла бы сработать…? В качестве прототипа в моем коде Ruby я просто использовал вложенную хэш—карту – никаких объектов модели вообще. Каждое действие игрок просто вызывал бы соответствующий метод, который непосредственно редактировал бы значения ветвей карты. Очень процедурный подход — хотя в то время я об этом так не думал.

Этот дизайн сразу же принес несколько хороших результатов: было очень просто добавить функцию “отменить”, при которой изменения в данных можно было откатить. Дерево данных, естественно, было очень просто сериализовать и десериализовать (сравните головную боль моих старых проектов, где каждый отдельный класс должен был реализовывать сериализацию/десериализацию самостоятельно), и я также мог точно отслеживать, какие изменения были внесены, чтобы собирать обновления для зарегистрированных игроков.

Я решил свою большую проблему OO … приняв более процедурный подход.

Не совсем там пока

Несмотря на решение проблемы, я на самом деле не осознавал что проблема была в OO. Я просто подумал, что нашел очень умное решение.

Профессионально, несмотря на то, что я мог бы создавать все “причудливые” OO-решения с отражением, полиморфизмом и т.д., Мой собственный стиль OO, как правило, предпочитал простые, явные и очевидные решения. Но я бы выбрал решения, потому что мой опыт показал мне, что они были лучшими, а не потому, что я понял, что есть какие-то проблемы с OO.

Новая проблема

Я построил свой игровой сервер, и все было в порядке. Расширение было тривиальным, а добавление дополнительных функций – предельно простым. Была только одна проблема: клиент.

Я и раньше писал клиентам, но на меньшем. Пока у вас есть не более, скажем, 10-15 экранов, вам может сойти с рук большинство дизайнов. В моем игровом клиенте было более 50 различных диалоговых экранов и множество различных состояний. Все становилось запутанным.

У меня была моя модель, мои представления и мои контроллеры, и все же я чувствовал, что у меня там нет никакого контроля везде было так много состояния . Потому что в этом суть OO в стиле Java/C++: разделите состояние на небольшие части и позвольте каждому объекту управлять своей конкретной частью состояния приложения. Это действительно невероятно плохая идея, поскольку сложность примерно соответствует квадрату различных взаимодействующих состояний. Кроме того, очень заманчиво просто позволить состоянию быть неявным в некоторых (комбинации) значениях переменных-членов. “Зачем иметь явное состояние, когда вы можете проверить значение переменной, чтобы выяснить это?”

Вывод

В процедурном программировании вы склонны сохранять свое состояние там, где вы его видите. В отличие от OO, где вам предлагается разделить состояние и скрыть его, вы в значительной степени должны сохранять его явным, и это действительно хорошо. Это не значит, что использование объектов обязательно плохо. С одной стороны, это очень мощный инструмент для построения иерархий рендеринга пользовательского интерфейса, а пространство имен вместе с цепочкой может создавать очень плавный и читаемый код, сравните: urlescape(substr(string, 0, strlen(string) - 2) с string.substr(0, -2).urlescape() (хотя все еще может быть аргумент в пользу того, что первое более понятно!). Однако объектно—ориентированный дизайн с объектами, которые сохраняют состояние или воздействуют на другие объекты, – вот где OO идет не так.

Существует также (в основном забытый) стиль OO Objective-C, который оказывается даже лучше для создания графических интерфейсов, чем Java/C++, поскольку поздняя привязка диспетчеризации и среды выполнения значительно приближает его к языку сценариев. К сожалению, Apple, бывшие чемпионы Objective-C, в значительной степени забыли, в чем на самом деле заключалась идея ObjC, и теперь заменяют ее Swift, который использует OO-стиль Java/C++.

Тем не менее, есть языки, пытающиеся вернуться к основам. Golang – один из них, и многие другие новые “языки системного программирования” также подпадают под эту категорию. Go, в частности (несмотря на мои оговорки относительно языка), опровергает миф о том, что “невозможно создавать крупномасштабные продукты с помощью процедурного программирования”. Растущая популярность языка может создать трещину в идее о том, что OO “неизбежен”.

Тем не менее, OO в стиле Java глубоко укоренился и не проявляет особой склонности к исчезновению в ближайшее время. Будет интересно посмотреть, что принесет нам будущее.

Оригинал: “https://dev.to/lerno/to-oo-or-not-to-oo-5dm5”