OOP в университете и индустрии
![топ 100 блогов](/media/images/default.jpg)
Object-oriented programming is eliminated entirely from the introductory curriculum, because it is both anti-modular and anti-parallel by its very nature, and hence unsuitable for a modern CS curriculum. A proposed new course on object-oriented design methodology will be offered at the sophomore level for those students who wish to study this topic.
Этой новости уже два года - не знаю, как там у них с этим все вышло. В комментариях к этой блог-записи есть умеренно интересный спор о том, действительно ли ООП "anti-modular and anti-parallel by its very nature", в котором присутствует много академического жаргона.
Я не знаю, что думать конкретно об идее "не преподавать ООП на первом курсе CS". Профессор CMU утверждает, что одной из причин их решения было то, что они встретились с выпускниками CMU, работающими сейчас в Facebook, и те им сказали, что больше всего в университете им помог курс функционального программирования, так что они решили давать побольше этого. Проблемы с этим аргументом очевидны: FP необязательно должно целиком вытеснять OOP, а главное, эту информацию они получили от людей, которые будучи студентами как раз начинали с OOP-языка. Но с другой стороны, есть резон и в том, что факультет CS должен давать студентам теоретические начала, а также учить их думать и понимать, а не только обучать писать программы, и на престижном факультете, куда идут абитуриенты высокого уровня, может и лучше это делать с помощью императивных и функциональных языков, а OOP считать чем-то, что можно потом подучить.
Поскольку я вижу обе стороны этого спора, и не могу между ними решить (недостаточно данных), я напишу вместо этого о применении OOP в индустрии, которое хоть и повсеместно - это глупо отрицать, но довольно значительно изменилось за последние 20 лет. Мне кажется, что в этом - в том, как применять OOP - мы коллективно многому научились, хоть этот опыт плохо согласуется с "философией" OOP, скажем так.
Я думаю, что развитие OOP-языков и то, как они применяются на практике, особенно в больших проектах, показало за последние 20 лет следующую тенденцию. Главная польза OOP была и остается в том, как эта практика помогает модуляризации исходного кода. Под модуляризацией я понимаю в широком смысле все, что помогает программисту, с одной стороны, держать связанные друг с другом вещи вместе (как на экране, так и в уме), и с другой стороны, иметь дело с менее тесно связанными вещами раздельно. При этом "помогает" означает не только "делает возможным", но также - и даже более важно! - "делает обычным, очевидным, самым простым выбором".
Это может показаться на первый взгляд тривиальным утверждением, но не думаю, что оно совсем тривиально. Когда говорят о достоинствах OOP, упоминают всякие разные вещи. Например:
- моделирование проблемы или решения в виде иерархии объектов и их методов
- code reuse: использование уже написанного кода, который надо лишь немного изменить (или добавить в него), с помощью наследования
- энкапсуляция, скрытие внутренней имплементации от внешних пользователей
Я считаю, что эти вещи сами по себе важны намного меньше, чем то, насколько они помогают или мешают главной цели: модуляризации исходного кода. Именно это в конце концов решает то, стоит ими пользоваться или нет, и к этому в конечном итоге, путем проб и ошибок, приходит вся индустрия.
В свете этого утверждения давайте посмотрим на основные черты и особенности OOP, и я попробую проиллюстрировать вышесказанное на их примере.
1. Начнем с самого основного: соединения кода и данных в одном классе. Это оказалось невероятно полезной идеей, которая, возможно, отвечает за львиную долю всей пользы от OOP вообще. При этом согласно академическим или пуристическим понятиям это даже необязательно OOP вообще, но это неважно. Представьте себе C++ без виртуальных функций, наследования и полиморфизма, просто с классами, в которых есть данные и методы. Неважно, что специалист по теории языков программирования скажет, что это не OOP вообще, а тривиальный синтаксический сахар (а что-то типа CLOS в Лиспе, где нет четкой привязки методов к классам, наоборот, самый что ни на есть OOP). Даже этот тривиальный синтаксический сахар сам по себе уже дает огромную пользу в смысле модуляризации. Оказывается, огромное количество решений, которые приходится писать на практике, естественным образом выглядят так: небольшое количество связанных друг с другом данных, и функции для работы именно с этими данными. Понятие класса помогает держать эти связанные вещи вместе, а не связанные вещи - отдельно, в другом классе (и другом файле обычно).
Конечно, для этого не обязательно иметь OOP или понятие класса. Мне приходилось иметь дело с большими проектами, написанными на C; в них обычно оказывалось, что во многих исходных файлах определена одна-две небольших структуры, хранящие связанную друг с другом информацию, и рядом - функции для работы с этой структурой. Имена функций и структур подчеркивают их связь; часто складывается конвенция, что функции получают указатель на структуру первым аргументом. Все это работает, но чтобы это поддерживать, нужно стараться. Если не стараться (или если поручить дело слабенькому программисту), со временем функции для работы с этой структурой растекутся в другие файлы, имена будут не очень ясные, итд. Классы дают эту модуляризацию практически бесплатно и поддерживают ее тоже почти бесплатно. В одном этом уже - огромное их преимущество. Это также главная причина, по которой C++ победил C.
2. Наследование по интерфейсу помогает модуляризации. Определяется контракт, и он разделяет в пространстве (исходного кода) и уме (программиста) имплементацию и использование этого контракта. В больших проектах это незаменимо. Должен быть какой-то способ решить: я делаю это, а ты делаешь это, и мы потом вот по этой линии стыкуемся. Эта линия будет контрактом, как ее ни назови; но моделировать ее в виде интерфейса или абстрактного класса очень удобно (в динамических OOP-языках происходит то же самое, просто контракт задается неявно или в документации, а не в виде типа). Кстати, в небольших проектах это тоже необходимо, даже если есть только один программист: "я" и "ты" просто означает "я сегодня" и "я завтра". Должна быть возможность думать только о части системы.
Полиморфизм помогает модуляризации в той мере, в какой он обеспечивает удобную работу с контрактом, т.е. наследованием по интерфейсу.
3. Наследование кода (implementation inheritance) мешает модуляризации, и поэтому почти всегда оказывается плохой идеей. Я не могу окинуть взглядом одну штуку (класс) и понять ее поведение, мне нужно учитывать другие штуки (классы), которые написаны обычно в другом месте, в другом файле. Одного этого достаточно, чтобы навредить модуляризации. Этот вред обычно оказывается значительнее, чем польза от code reuse. Говоря в терминах Джавы, implements намного полезнее extends. Множественное наследование (multiple inheritance) в этом смысле еще хуже, и вреда от него - в реальном мире и с реальными программистами, а не в идеальном мире, населенном гуру - намного больше, чем пользы. Решение Джавы отказаться от множественного наследования кода вообще в этом смысле закономерно. Триумф STL, в которой практически нет наследования кода, над другими потенциальными стандартными библиотеками для C++ - закономерен.
Все это про интерфейсы, наследование итд. никто не понимал в начале 90-х, а сейчас понимают многие, а может, это уже и стало общим местом, не уверен. Так что прогресс налицо.
|
</> |