?

Log in

No account? Create an account
OOP в университете и индустрии - Поклонник деепричастий [entries|archive|friends|userinfo]
Anatoly Vorobey

[ website | Website ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Links
[Links:| English-language weblog ]

OOP в университете и индустрии [мар. 12, 2013|10:02 pm]
Anatoly Vorobey
[Tags|]

У факультета компьютерных наук в Carnegie-Mellon University очень высокая репутация, он известен как один из самых лучших в Америке - именно этот факультет, а не весь университет. Так вот, в нем, оказывется, решили не преподавать больше объектно-ориентированное программирование в вводных курсах. Те студенты, что захотят с ним познакомиться, смогут это сделать на факультативном предмете для второго курса:

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-х, а сейчас понимают многие, а может, это уже и стало общим местом, не уверен. Так что прогресс налицо.
СсылкаОтветить

Comments:
Страница 1 из 3
<<[1] [2] [3] >>
[User Picture]From: angerona
2013-03-12 08:07 pm
sophomore — это второй курс
(Ответить) (Thread)
[User Picture]From: avva
2013-03-12 08:30 pm
да, спасибо, я вечно это забываю. Сейчас исправлю.
(Ответить) (Parent) (Thread)
[User Picture]From: cmm
2013-03-12 08:11 pm
относится ли энкапсуляция к инкапсуляции как эмиграция к иммиграции?
(Ответить) (Thread)
From: asox
2013-03-13 11:39 am
Тогда это должна быть экскапсуляция...
(Ответить) (Parent) (Thread)
[User Picture]From: nec_p1us_u1tra
2013-03-12 08:17 pm
Я ни разу не, но

> Неважно, что специалист по теории языков программирования скажет, что это не OOP вообще, а тривиальный синтаксический сахар

да, сахар и не требует концепции класса или объекта. ср. перловое

{
my $var;
sub1 {};
sub2 {};
}

вполне изолированный набор из данных и кода, вдобавок данных скрытых от внешнего мира.
(Ответить) (Thread)
[User Picture]From: tlkh
2013-03-13 06:14 am
а можно таких штук создать несколько экземпляров?
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: kodt_rsdn
2013-03-12 08:25 pm
extends vs implements... проблемы начинаются, когда нужно позаимствовать готовое поведение.
Например: у всех окошек есть более-менее одинаковое оформление, функции перемещения и изменения размера, и т.д. и т.п., и у каждого - своя "бизнес-логика".
Сделать MyWindow implements IWindow и определить все методы этого IWindow, пускай даже как вызовы соответствующих системных функций-заготовок - поистине издевательство.

Для заимствования не так уж много хороших средств.
Это или миксины, или наследование.
Миксины заморачивают, а наследование развращает. Выбирайте! %)
(Ответить) (Thread)
From: huzhepidarasa
2013-03-12 08:34 pm
Да, окошки очень хорошо укладываются в парадигму ООП, даже в ее темные и опасные углы. Благодаря чему ООП и завоевало мир, кстати.
(Ответить) (Parent) (Thread)
From: huzhepidarasa
2013-03-12 08:27 pm
В STL нет наследования и по интерфейсу тоже, там другие контракты.
(Ответить) (Thread)
[User Picture]From: thedeemon
2013-03-13 04:59 pm
STL вообщне не ООП. Сам Степанов так говорил:
"STL is not object oriented. I think that object orientedness is almost as much of a hoax as Artificial Intelligence. I have yet to see an interesting piece of code that comes from these OO people."
(Ответить) (Parent) (Thread)
[User Picture]From: timur0
2013-03-12 08:28 pm
хороший анализ, спасибо
(Ответить) (Thread)
[User Picture]From: ushastyi
2013-03-12 08:34 pm
Все, что Вы написали, это все правильно, и здорово написано на самом деле, но этому как раз НЕ учат в курсах ООП. Это приходит или не приходит с опытом.

А вот функциональное программирование очень хорошо учит именно программировать и думать. Поэтому не случайно, что классический MIT-овский курс по программированию был функциональным (http://mitpress.mit.edu/sicp/)

P.S. А к Carnegie-Mellon у меня сложное отношение. В Москве преподаватели оттуда читают иногда треннинги, и я был на нескольких. Правильные, хорошие. Но по большей части бесполезные в практической деятельности. Теоретики.
(Ответить) (Thread)
[User Picture]From: javax_slr
2013-03-12 08:50 pm
По моему опыту Бен гуриона, за редким исключением все преподаватели на компьютерах - теоретики, не знающие индустрии. Потому что те, кто могут - зарабатывают деньги в индустрии, а не читают курсы. Исключения есть, но их мало.
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: Alexandre Sorokine
2013-03-12 08:45 pm
Я думаю, что основная причина отказа от курса в отсутствии хорошей академической теории OOP какова, например, есть у функционалчного программирования (lambda calculus). Inheritance, incapsulatiоn, code reuse это скорее набор пожеланий сильно по-разному реализованный в разных языках.
(Ответить) (Thread)
[User Picture]From: migmit
2013-03-12 09:42 pm
Гм... Theory of Objects, Абади-Карделли?
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: link0ff
2013-03-12 08:48 pm
OOP is anti-modular and anti-moral by its very nature - http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html
(Ответить) (Thread)
[User Picture]From: avva
2013-03-13 02:04 am
Это против Джавы более, чем против OOP.
(Ответить) (Parent) (Thread)
[User Picture]From: anton_solovyev
2013-03-12 08:54 pm
Мне кажется, уже стало общим местом. В тот момент, когда design patterns внезапно заменились просто refactoring.

И таки я абсолютно согласен -- ООП failed как его подавали вначале, и succeeded (практически) в той части где оно просто добавляет дополнительные scopes.
(Ответить) (Thread)
[User Picture]From: xeno_by
2013-03-13 08:07 am
Я бы хотел упомянуть, что качественное ООП это не просто scopes для термов (то, что обычно понимается под инкапсуляцией), а еще и scopes для типов (т.е. экзистенциальные типы): http://avva.livejournal.com/2581700.html?thread=93742788#t93742788.
(Ответить) (Parent) (Thread)
From: A R
2013-03-12 08:55 pm

2 cents

1. Энкапсуляция очень важна - когда 1000+ программистов на четырех континентах курочат общий код - очень помогает что некоторые вещи не могут быть сломаны без измения интерефейса - которое куда проще отследитью

2. Если не увлекаться хиерархиями - использование platform-independent base classes + platform-dependent derived ones, позволяет software пережить несколько поколений hardware.

3. Гарантированное выполнение деструктора (c++) очень очень большое дело для тех кто понимает как этим пользоваться.

Главная проблема - даже минимально приемлемый уровень реального знакомаства с тем же с++ не под силу примерно половине С программистов, что ставит практический крест на применении в больших организациях.
(Ответить) (Thread)
[User Picture]From: kozodoev
2013-03-13 01:24 am

Re: 2 cents

Гарантированные выполнени деструктора и следующее из него raii - это, конечно, круто, но к ооп не имеет ну совсем никакого отношения.

Edited at 2013-03-13 01:27 (UTC)
(Ответить) (Parent) (Thread)
[User Picture]From: zumba
2013-03-12 09:09 pm
Пункт 3 - очень правильный.
Использование наследования обычно заканчивается на примерах из обучающей литературы.
Редкое исключение - инфраструктура, например Socket->TcpSocket->AsyncTcpSocket.
Множественное наследование - вообще ад.
А интерфейсы - как раз весьма полезная вещь. В особенности для выработки дисциплины чёрного ящика.
(Ответить) (Thread)
[User Picture]From: xeno_by
2013-03-13 08:08 am
А что делать с интерфейсами, рядом с которыми хочется положить еще и методы с конкретными реализациями?
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: golos_dobra
2013-03-12 09:17 pm
приплюснутый си хорош только для эксплуатации человеком человека, экспроприации прибавленной стоимости, как средство крепко держать корпоративного
раба на цепи.
(Ответить) (Thread)
[User Picture]From: lyuden
2013-03-12 09:19 pm
Взгляд дилетанта.

У меня нет специального образования. Я не знаю четыре главных слова и т.д. Программировать я учился на Паскале, и до ООП мы не дошли. Потом в аспирантуре я писал в основном рассчетные программки на Питоне. Сейчас работаю фрилансером, конкректно сейчас перевожу разработанные математические модели на Питон, так чтобы они быстро и параллельно считались.


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

По поводу интерфейсов, возражение примерно такое же как и при холиваре cтатическая / динамическая типизация. Используя юнит тесты можно проверять соответсвие протоколу в критичных местах, и все. Поддерживать протокол везде - "неэкономично"

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

Изредка у меня встречаются dict с функциями, этакие легковесные объекты, и под одними и теми же ключами у меня иногда оказываются совершенно разные функции, когда я думаю, о том коде которы бы мне понадобился в ООП чтобы этого добиться, я собственно даже плохо представляю ООП структуру которая бы позволяла иметь все комбинации из 4 функций у каждой из которых есть два варианта и они нигде не близко к

{"f1":f1v1,"f2":f2v1,"f3":f3v2,"f4":f4v1}

а то что они стоят на своих местах проверяется юниттестами тривиально.

Своих племянников я ООП учить не буду, до последней возможности.






(Ответить) (Thread)
[User Picture]From: _winnie
2013-03-12 09:22 pm
> Триумф STL, в которой практически нет наследования кода, над другими потенциальными стандартными библиотеками для C++ - закономерен.

Забудем про iostream, посмотрим на реализацию контейнеров в STL.

Там наследование для code reuse в полный рост, что бы четыре раза не реализовывать set, map, multiset, multimap. Не знаю почему именно через наследование.

Заглянул в lib/gcc/i686-pc/4.5.3/include/c++/unordered_set

unordered_set унаследован от __unordered_set,
__unordered_set от _Hashtable,
_Hashtable множественно унаследован аж от трёх классов _Rehash_base, _Map_base, _Equality_base

Т.е. унаследовать автомобиль от рулевого колеса и багажника в STL - это самое обычное дело
(Ответить) (Thread)
[User Picture]From: meshko
2013-03-12 09:25 pm
Оно там вроде внутри, а наружу не пускают.
(Ответить) (Parent) (Thread) (Развернуть)
Страница 1 из 3
<<[1] [2] [3] >>