вторник, апреля 10, 2007

Когда деревья были большими...

В последнее время приходится проводить много профессиональных интервью с программистами. Так вот, я заметил, что многих ставят в тупик вопросы об основных постулатах ООП – наследовании, инкапсуляции, полиморфизме. Складывается впечатление что раньше (несколько лет назад) с этими вещами у программистов были дела получше. Я задумался о причинах такой деградации, и вот какое объяснение пришло мне в голову.
Во всем виновато повсеместное распространение объектных языков и объектного подхода в программировании.
Да – да, именно так. Много лет назад, когда деревья были большими, компьютеры были тоже нехилых размеров. До сих пор не забуду, как на кафедру вычислительной техники доставили новый компьютер... С железнодорожной станции целый день шли Камазы груженные аппаратурой. Нашей группе досталась разгрузка ЦПУ, когда этот, с позволения сказать, процессор освободили от тары и взгромоздили подъемным краном на крыльцо учебного корпуса, мы все почувствовали благоговение перед мощью вычислительной техники. На остатках тары мы прочитали: «Масса брутто 2100 кг. Мaсса нетто 1800 кг.». Перемещали циклопический процессор по коридорам кафедры двадцать человек, опутав его веревками и установив на лист войлока толщиной полтора сантиметра. Подобно древним Египтянам мы воздвигали алтарь нового бога – ЕС 1036. С появлением этой машины, мы могли пользоваться терминалами, раньше приходилось вводить тексты программ и исходные данные на перфокартах.
Впрочем я отвлекся, пустившись в исторический опус, размеры машины тут не причем. В те времена мы изучали программирование на таких языках, как FORTRAN, ALGOL 60, PL1. Только - только появлялись смешные машинки, размером с современный компьютерный стол, с веселым названием ДВК и зашитым в ПЗУ Бейсиком, к которому все мы испытывали искреннее презрение. Тем не менее, все мы твердо усвоили, что программа состоит из двух частей: данных и инструкций по обработке этих данных. Инструкции могут объединяться в подпрограммы процедуры и функции, те с свою очередь могут составлять библиотеки. Объектный подход в корне ломал все. Оказывается можно объединить данные и процедуры по их обработке в одном объекте. Объект это не данные и это не модуль это конгломерат того и другого. Все это необходимо было осознать, пропустить через себя. Области видимости данных, отличные от локальных и глобальных, казались невероятным усовершенствованием. Далее - наследование. Это было что-то фантастическое. Впрочем, наследование, это самая естественная и понятная из парадигм ООП. Полиморфизм - это казалось шаманством чистой воды. Многим было легче понять, как работает его реализация на таблицах виртуальных методов, чем сам смысл этого явления. В общем, для того, что бы научиться объектному подходу, нам приходилось пропускать через себя все его основы.
Тем, кто сегодня начинает программировать, часто так же трудно представить себе, что функция может быть объявлена не в классе, как нам было трудно понять, как к структуре данных можно привязать процедуры и функции. Сейчас любой учебник начинается с описания того, как с помощью визарда создать обработчик нажатия кнопки на форме. И многие долгие годы пребывают в уверенности, что этот обработчик принадлежит классу кнопки а не формы! Умные IDE автоматически подставляют слово override в объявление метода, и немногие могут объяснить для чего оно нужно.
Объектные парадигмы приходят как сами собой разумеющиеся, но мало кто может их с толком использовать. Человек жалуется в форуме: «Я сделал у класса конструктор приватным, а теперь не могу создать его экземпляр, используя имя класса и технологию отражения». Хочется спросить: - «В своем ли ты уме, парень? Ты отрубил голову подозреваемому, и теперь жалуешься на то, что он молчит на допросе». Многие борются с виртуальными методами, пытаясь заставить их работать как не виртуальные...
Может стоит начинать преподавать программирование на каком ни будь АЛГОЛе, PL1 или голом С, что бы потом у людей формировалось истинное понимание сути ООП?

10 комментариев:

fatboy комментирует...

Нет, не нужно с АЛГОЛа начинать, - нужно глубже преподавать. Дело в тех самых учебниках, в которых обучают не языку программирования а, действительно, работе с визардами, и в преподавателях, которые готовят лекции по таким учебникам. Нам вот два семестра на лекциях по программированию через проэктор крутили уроки Delphi из серии "Научись сам за 5 минут". До сих пор в голове это дурацкое "Кликните левой кнопкой мышки на пункте...". Университет, правда, провинциальный был, но УНИВЕРСИТЕТ все-таки!
А вообще было время, когда я тоже думал, что начинать надо с Ассемблера =)

Анонимный комментирует...

не надо Алголов, просто учить надо "правильнее". Сам в последнее время провел кучу интервью, и если многие могут сказать на уровне определений, что такое полиморфизм, наследование, инкапсуляция, то стоит копнуть чуть дальше, спросить про абстрактные классы, интерфейсы, виртуальные функции, то все алесс капут. Про такие вещи как IоC, принцип LSP, принципы закрытия-открытия, то это просто бесполезно.

Анонимный комментирует...

Простите, а где можно почитать про "принцип LSP", "принципы закрытия,открытия"?

Sergey Rozovik комментирует...

>Простите, а где можно почитать про "принцип LSP", "принципы закрытия,открытия"?

Поищи в Google "принцип Лискоу". Это все правила объектно ориентированного дизайна.

Анонимный комментирует...

Спасибо. Вот хорошая прямая ссылка:

http://nvoynov.blogspot.com/2007/02/blog-post.html

Mike Chaliy комментирует...

Про ошибку. Все можно, там же четко написано про рефлексию. Вы даже не узнали по какой причине человеку это надо.


ConstructorInfo ctor = typeof(Test).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);

Object o = ctor.Invoke(new object[] { });

MethodInfo m = typeof(Test).GetMethod("Go", BindingFlags.NonPublic | BindingFlags.Instance);

m.Invoke(o, null);

Console.WriteLine(o);

public class Test
{
private Test()
{
}

private void Go()
{
Console.WriteLine("Test");
}
}

Sergey Rozovik комментирует...

>Вы даже не узнали по какой причине человеку это надо.
Отчего же. Смысл проблемы понятен вполне, и именно об этом я и пытаюсь сказать. Тестирование приватных методов - ноги у этой проблемы растут из непонимания основ объектного подхода. Ключевое слово - инкапсуляция. Модульный тест должен тестировать внешний контракт класса, что там внутри - никого не касается (на это направлен упоминавшийся здесь принцип закрытия - открытия). Рефлексия позволяет нарушить этот постулат. Для чего это было сделано в .Net можно поговорить отдельно. Разработчики платформы решали свои проблемы, а нам достались такие возможности вроде BindingFlags.NonPublic. И теперь все это прождает соблазны навернуть какой нибудь фэйк вместо того чтобы немного подумать.

Mike Chaliy комментирует...

Спасибо что объяснили.. ;)).

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

Кстати за примерами ходить далеко не надо.
1) System.Web.UI.WebControls.Style.RegisteredCssClass. Для установки надо вызвать интернал метод SetRegisteredCssClass.
2) (Table)this.Controls[0] в GridView если надо расширить функционал. Тут может быть и коллекция пуста, и первый элемент не тейбл.

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

Кстати слегка обидело про "деградирующих" программистов, я например, на интервью не смогу вспомнить определение полиморфности.

Sergey Rozovik комментирует...

to Mike Chaliy
А чего обижаться то? На зеркало не обижаются. Есть вещи, которые надо знать, просто для того чтобы писать грамотный код. Например, надо знать булевы функции и алгебру логики для того, чтобы грамотно составить сложное условие в операторе if(). Надо знать постулаты ООП просто потому, что это помогает создавать надежный, устойчивый, поддерживаемый и расширяемый код. Никто не требует заучивать определения. Но знать как работет полиморфизм надо, иначе в своем коде ты не сумеешь воспользоваться преимуществами, которые он дает.

Яприведу простой пример. Работа слесаря очень проста - крути шурупы, забивай гвозди. Иногда приходится браться за отвертку и молоток, чтобы починить что либо, например дверную ручку. И тут с удивлением обнаруживаешь, что шурупы вместо того, чтобы быть завернутыми отверткой были кем-то забиты молотком, как гвозди. Снаружи не заметно, а ручка быстро отваливается и прикрутить ее назад на тоже место практически не возможно.
Ваши примеры использования рефлекшена походи на забивание шурупов молотком. Конечно, всегда есть оправдания, почему так надо былосделать, конечно - все работает, но проблемы возникают потом, и практически неизбежно.

apoudy комментирует...

Мне кажется объяснение гораздо проще - эти вопросы пропали из постов о вопросах на собеседовании, как и из самих собеседований. Так же как reflection и remoting. Поэтому когда народ прочесывает форумы и блоги на предмет "что спрашивают на собеседовании" они там ничего про основам ООП не находят.