![]() |
◊ |
www.udomlya.ru | Медиа-Центр | Удомля КТВ | Старый форум |
![]() |
#1 |
Местный
Регистрация: 26.04.2006
Адрес: Удомля, гдежещё
Сообщений: 1,986
Вы сказали Спасибо: 676
Поблагодарили 257 раз(а) в 167 сообщениях
|
![]()
Теология ООП
28-Oct-2004 В последние годы появились весьма неочевидные прогнозы, и даже чуть ли не констатация факта о провале объектной парадигмы, ООП. Крайнюю позицию высказал Ричард Гейбриэл в своем выступлении на OOPSLA под прямым названием "Объектная парадигма провалилась". Другой, более сдержанный и взвешанный прогноз - это Пол Грэм со своей статьей "Языки программирования через сто лет". Объединяет этих двух людей то, что они приверженцы функционального программирования и языка Lisp. "А, да это брюзгливые математики, для которых высочайшим достижением программирования является интерпретатор языка Lisp на языке Lisp в 20 строк" - так бы я отреагировал на эти статьи годами раньше. Но в статье Ричарда Гейбриэла есть слишком много правды, и в ответах оппонентов (например здесь) слишком мало рационального, чтобы можно было не задумываясь пройти мимо этой дискуссии. Оппоненты приводят статистику о том, что все сегодня пишут на ОО языках и используют эти методы прямо во всю. Но это самый глупый аргумент в пользу чего-либо вообще, потому что все каких-нибудь 500 лет назад были уверены, что Земля плоская. Людям всегда нужна какая-то красивая идейная ниша, в которой они будут чувствовать себя комфортно. Находясь в нише ООП, так же как и в любой другой нише-религии, очень трудно заметить что в ней рационально, а что нет. Главное - это комфорт. *** Вместе с миллионами других программистов и я тоже в свое время испытал радость читая первые статьи об объектном программировании, о том, что методы ООП позволяют создавать иерархии классов объектов так, как это можно делать с объектами реального мира. Есть класс плодов, от него производится класс фруктов, от него - класс цитрусовых, затем апельсины, и тут можно уже создавать объекты класса апельсин. Есть что-то общее для всех плодов, для всех фруктов, затем цитрусовых и для всех апельсинов. Спускаясь вниз по иерархии вы добавляете конкретику, когда как вверхах все ужасно абстрактно. А теперь берем абстрактный GUI объект, определяем абстрактный метод paint() - и вперед с песней. Помните? Но именно здесь, на этапе объяснения самой парадигмы нас и ждал подвох. Вернитесь к этим текстам теперь, когда вы уже состоявшийся гуру ООП, и перечитайте их внимательно. Во-первых, тут явно попахивает одной очень старой теорией искуственного интеллекта, согласно которой знания человека можно представить в виде дерева - откуда видимо и вышла собственно парадигма ООП. Как известно, никто с тех пор так и не смог создать "мыслящей" машины на основе такого представления, потому что оно нелепо. Поняв это, создатели некоторых языков и ОО платформ попытались исправить положение (сломать дерево, так сказать) введя множественное наследование. Потом его убрали (Джава), сказав, что это слишком сложно и путает как программиста, так и сам компилятор. А потом снова вернут? Во-вторых. Эти школьные примеры с GUI объектами на самом-то деле и есть область применения ООП. В мире реального программирования многое можно, а иногда даже нужно, делать без этого. Зачастую описывая классы мы даже не задумываемся над тем, а будут ли объекты данного класса persistent настолько, что следует вводить понятие состояния объекта, и тем самым усложнять его реализацию. Какая-то группа данных, т.е. структура, действительно может иметь состояния если того пожелать, но тогда каждая точка входа в эту структуру, т.е. методы объекта, должны прежде сверяться с состоянием и затем только выполнять какие-то действия. Мы просто-напросто усложняем наши программы вводя состояния и сверки там, где в этом не было острой необходимости. Пол Грэм в одной из своих статей писал, что если возможно реализовать что-то в виде обычной функции с самыми обычными параметрами, то следует делать именно так, поскольку функция изолируется и следовательно отлаживается гораздо лучше метода объекта. Далее, один из коньков ООП, который вероятно создает максимальное чувство комфорта у любого программиста, это якобы относительное постоянство интерфейса и наоборот, изменчивость реализации. Описав однажды интерфейс и передав его соседу, вы можете спокойно и не спеша заняться реализацией, совершенно позабыв о бедном соседе. Например, вы можете определить абстрактный интерфейс list и реализовывать его как вам вздумается - хоть связным списком, хоть простым индексным массивом, хоть ассоциатвиным. Были и такие примеры, помните? Сегодня мы уже знаем, что все не так просто. Это правда, что вы можете безболезненно делать коррективы в реализации, в том числе исправлять свои ошибки, но это неправда, что вы всегда можете радикально менять реализацию. Не знаю как вас, но меня практика программирования научила тому, что интерфейс и реализация, при всех наших стараниях уводить их подальше друг от друга, все же слишком тесно связаны. Передать соседу list и не скзать ему как он реализован - значит заставить его когда-нибудь все таки заглянуть в ваш код, потому что этот несчастный сосед обязательно норовит использовать ваш лист самым неэффективным образом и поставить весь проект на грань провала. Он обязательно когда-нибудь заглянет в ваш код и тем самым нарушит одну из заповедей ООП, потому что с черными ящиками жить трудно. А если заглядывать запрещено, например, законами бизнеса, то... В результате в мире плодится огромное количество очень неэффективных приложений на Си++ и особенно Джава, в противовес которым можно найти или написать более компактные и быстрые аналоги на Си. Я не стреляю в Си++, потому что на нем можно писать хорошо, но это могут делать те программисты, которые пришли из мира Си. И наконец, когда апологеты ООП говорят об интерфейсах и о сокрытии реализации, то они вообще-то лукавят потому, что интерфейс в этом смысле есть библиотека, и вовсе необязательно класс. Т.е. ООП фактически присваивает себе то, что ему не принадлежит - понятие переиспользуемого библиотечного кода. *** Еще в 1998-ом году, когда я начал писать свою кустарную альтернативу STL, как вероятно когда-нибудь делали все Си++ программисты, я не смог бы изложить все свои претензии к ООП так пространно, но уже интуитивно осознавал где есть перебор с объектами. Позже моя кустарщина переросла в open-source проект под названием PTypes. Тогда, понимая, что не все должно иметь ОО интерфейсы, я решил экспериментально описать алгебраические интерфейсы там, где нет наследования и полиморфизма. Замечу, что внутри все по-прежнему реализовано в классах, поскольку требовалось, например, перегрузить опреатор + для строк, а вне класса в Си++ такое сделать невозможно. Строка - это атомарный (в смысле фундаментальный) тип, от которого вряд ли имеет смысл производить классы с перегруженными виртуальными методами, так же как и от простого int. Следовательно, интерфейс строки для пользователя может быть алгебраическим: такую простую вещь как length(s) никто не отменял и никто не сказал, что это обязательно должно записываться как s.length(). Точно так же я рассуждал и в отношении списков, и фактически удалось даже привести интерфейсы списков и строк в некотрое соответствие. Например length() был одинаково применим и ко всем типам списков. Делалось это для того, чтобы не загружать пользователя лишними понятиями. Length(), setlength() и некоторые другие методы универсальны насколько это возможно. Позже появился еще и variant, и тоже с алгебраическим интерфейсом. Но были и классы потоков, включая файлы, сокеты и пайпы, описанные в лучших традициях ООП: виртуальные методы, наследование и прочее. Им это очень подходило. Далее я даже умудрился описать шаблоны списков для произвольных базовых типов, сохраняя при этом алгебраические вызовы. Все бы ничего, только со временем я начал получать письма от пользователей с такими жалобами. Они в принципе не против самой идеи, но дело в том, что после усложнения библиотеки компиляторы стали путаться с большим количеством перегруженных функций (не методов!), описанных в шаблонах. Это были на самом-то деле баги в самих компиляторах (причем баги были разные, например у MSVC и GCC). Тут я понял, что такие фокусы с Си++ мало кто вытворяет, и поэтому ошибки в компиляторах остаются незамеченными. Я пытался давать баг-репорты создателям компиляторов, но ими никто не занимался наверное из-за слишком экзотического применения перегрузок обычных функций в купе с шаблонами. Словом, в версии 2.0 библиотеки я вынужденно переписал списки и сделал их чисто-объектными. Ничего страшного, но теперь к сожалению length() уже не применим к спискам, и первоначальная идея библиотеки малость пострадала. К счастью, она по-прежнему популярна, особенно среди новичков, потому что она, кроме всего прочего, позволяет очень быстро запрыгнуть на корабль сетевого и многопоточного программирования и писать очень простой и главное - ясный, читабельный код на Си++. *** Уверен, скоро это станет нормой - равноправное использование объектных и алгебраических вызовов. Должен произойти откат от религии ООП, поскольку программирование, в отличие от наук (а программирование это еще не наука), слишком коммерциализировано, и поэтому чувствительно к неэффективным решениям. Рынок решает все, и он сегодня все чаще жалуется как на ненадежность ПО, так и на слишком большие затраты на его разработку. И рынку наплевать на наши с вами религиозные пристрастия и теоретические баталии. Если вы способны сегодня в одиночку или очень малыми командами создавать сложные системы, в которых ООП будет применятся в сдержанных дозах, то вы начнете очень скоро выигрывать и соответственно хорошо зарабатывать. Или вам нужен не заработок и красивые, компактные решения, а вместо этого - блуждание в дебрях объектной теологии?
__________________
I never saw a wildthing sorring for itself. A small bird will drop frozen dead without ever felt sorry for itself. |
![]() |
Этот пользователь сказал Спасибо Pitty за это полезное сообщение: | бобс (26.04.2008) |
![]() |
#2 |
Местный
Регистрация: 26.04.2006
Адрес: Удомля, гдежещё
Сообщений: 1,986
Вы сказали Спасибо: 676
Поблагодарили 257 раз(а) в 167 сообщениях
|
![]()
Теология ООП (часть II)
19-Nov-2004 Продолжение статьи "Теология ООП" В этой статье я собираюсь немного попугать вас одной историей о том, как по вине стереотипного объектного мышления в мою программу вкралась весьма неприятная ошибка; далее продемонстрирую пример того, как целый класс можно запросто заменить одной-единственной функцией; и конечно же, как всегда, будет много брюзжания и критики. Но не сердитесь, мы с вами всего лишь пытаемся найти "срединный путь" в проектировании программ и библиотек. *** Подобно тому, как всякий естественный язык оттачивается и пополняется наиболее интеллектуальными его носителями - писателями и поэтами, язык математики формируется на кончике пера абстрактно-мыслящей элиты - математиков. И поскольку процесс формирования алгебраической нотации длится уже не один век, этот язык становится все более естественным в том же смысле, что и обычные языки. Естественность в некотором приближении есть эстетика плюс краткость. Но это так, вообще, о языке математики. Так чем в принципе запись length(s) оличается от s.length() ? Вероятно, принципиальных отличий тут нет за исключением того, что первый способ записи выглядит эстетичнее для тех, кто проходил в школе алгебру и учил ее хорошо. Второй же способ - это некая новая нотация, которая, будучи полностью изолированной (или очищенной - как хотите) от традиционной, приводит к таким странным вещам, как i.add(j). А ведь ОО парадигма поначалу вроде бы пыталась нас убедить в том, что все идет именно к этому: всё есть объекты и методы в них, даже числа и операции над ними. Но постойте, нотация i.add(j) не является ни эстетичной, ни краткой в сравнении с i+j. Конечно, программирование - это уже не математика (или еще не математика?), и оно имеет право вводить собственные абстракции. (Сами математики, к слову сказать, внеся некоторые дополнения и коррективы в уже имевшийся у них язык, получили Lisp, и весьма счастливы.) Вопрос лишь в том, чтобы вновь вводимые абстракции и нотации были достаточно кратки, выразительны, красивы, и что самое главное - заставляли бы думать человека таким же образом. Всегда можно распознать в толпе математика по лаконичности и меткости его речи, поскольку внутренний язык у хороших математиков - это по сути математика. Что же дает программисту ООП и какое мышление оно у него формирует? Затрудняюсь сказать, но скорее всего ничего интересного ООП у программиста не формирует. Может быть потому, что эта абстракция еще слишком молода и далека от состояния отточенности и естественности. Впрочем, перейдем от предположений к конерктным примерам. *** Существует множество объектных библиотек-оберток для базовых системных сервисов, в том числе и для интерфейсов многопоточного программирования. Обычно сами системные API навязывают нам объектный подход: они как правило создают дескриптор (handle), через который и предполагается манипуляция неким системным объектом, например файлом, графическим элементом или потоком исполнения. Все что остается сделать разработчику "обертки" - это просто инкапсулировать дескриптор внутри класса и затем переписать вызовы системного API в методы. Другими словами, буквально перевести все на язык ООП. И я тоже рассуждал приблизительно так же, когда описывал классы для многопоточного программирования в PTypes. Класс thread не был исключением, кроме разве что одной тонкости, связанной с запуском потока: дело в том, что системный вызов pthread_create() (или BeginThread() в Windows) не может быть в конструкторе класса, как это часто делается для дескрипторных интерфейсов. Вместо этого, создание дескпритора и запуск асинхронной функции был перемещен в отдельный метод thread::start(). Но дело, собственно, не в этом. Объекты этого класса имеют опцию autofree, которая указывает должен ли объект самоуничтожиться по окончании выполнения асинхронной функции thread::execute(). Так вот, на старых Linux-системах, базирующихся на LinuxThreads, PTypes мог вылетать со странными ошибками порчи памяти (memory corruption). Отладка показала, что LinuxThreads почему-то иногда записывает дескриптор потока в области памяти, в которых когда-то существовал мой объект типа thread, но его в этом месте уже нет. В чем же дело? Посмотрите на описание pthread_create(). Вы передаете этой функции ссылку на место в памяти, куда система должна записать новый дескриптор потока. По поводу запуска вашей асинхронной функции start_routine не дается никаких гарантий, но вы надеетесь, что она когда-нибудь будет вызвана. Моя ошибка состояла в том, что асинхронная функция могла быть вызвана и закончена еще до того, как LinuxThreads успевал записать дескриптор в соответствующее поле моего класса. И в случае autofree потока это приводило к порче памяти, поскольку выходило так, что объект удалялся из памяти еще до того, как основной поток вернется из pthread_create(). Интересно, что кроме старых версий Linux ни одна другая система не делала этого в таком порядке, вероятно предвидя потенциальные ошибки вроде моей. Но если сделать, как говорят киношники, отъезд и взять общий план, то моя ошибка на самом деле происходит от предположения о том, что pthread_create() является конструктором системного объекта. А он таки им не является, или скорее не вписывается в традиционное понятие конструктора, несмотря на внешнюю схожесть. Виной всему - мое стереотипное объектное мышление. Впрочем, какая-то доля вины тут должна пасть и на разработчиков стандарта POSIX Threads. Размышляя потом над всем этим я также понял, что на самом деле не только pthread_create() не является конструктором, но еще хуже: поток исполнения вообще не является объектом, и следовательно архитекторам POSIX нечего было водить нас за нос своими дескрипторами. Другие функции, использующие дескриптор потока - pthread_join() и pthread_detach() - избыточны и могли быть изъяты из интерфейса (синхронизацию с завершением потока можно реализовать в приложении при помощи семафоров). Если бы создатели PThreads были бы истинными минималистами, то они оставили бы только pthread_create() и уже без того параметра, принимающего дескриптор. В ядре Linux поддержка потоков долгое время ограничивалась одной-единственной функцией clone(), пока наконец начиная с версии 2.6 публике удалось убедить Линуса Торвальдса ввести полную поддержку POSIX-интерфейса в ядро. Я не в курсе какие при этом приводились доводы, но скорее всего не приводилось ничего путного кроме "POSIX это стандарт". Но стандартный интерфейс может быть таким же несовершенным, как и любой нестандартный, и наоборот. Впрочем, это отдельный разговор. Конечно же, на уровне приложения иногда полезно описывать классы-обертки для потоков исполнения, но суть вопроса не в этом. Надеюсь уже понятно в чем именно: следует быть осторожнее с ОО-мышлением. И подозреваю, что такая нетривиальная вещь, как потоки исполнения, на самом-то деле оказалась не по зубам объектной парадигме. *** Другой пример, на сей раз без ужасных ошибок с разрушением памяти: сокеты, и в частности дейтаграммные сокеты. Уже заранее могу вам сказать, что дейтаграммные сокеты не являются объектами, и они по сути притянуты к системному интерфейсу сокетов "за уши" ради единообразия, но из-за этого только вносят путаницу. В той же самой библиотеке PTypes по пожеланиям пользователей пришлось ввести интерфейс для дейтаграммных сокетов. Недолго раздумывая, я описал два класса ipmessage и ipmsgserver по образу и подобию их поточных эквивалентов ipstream и ipstmserver. (Возможно, для вас непривычен стиль именования в PTypes, но использование этого стиля обосновано: его можно комбинировать с любым другим в одной программе. Если бы PTypes был написан, например, в венгерской нотации, то как минимум один класс программистов - юниксоиды старой школы - были бы жутко недовольны.) В конце концов это всего лишь обертка для системного интерфейса сокетов, и я ничего не пытался менять в этой идеологии. Сам я никогда не использовал протокол UDP в своих программах, и поэтому развитие этой части библиотеки всегда опиралась на отзывы пользователей. И из этих же самых отзывов я понял, что такой интерфейс для дейтаграмм может легко путать программиста. Работа с дейтаграммными протоколами сводится к трем основным операциям: посылка пакета, привязка к порту и "слушание" его, получение пакета. Если взять только операцию посылки, то окажется, что для ее осуществления достаточно одной-единственной функции. По сути посылка, если можно так сказать, не имеет состояния, и поэтому нет необходимости описывать класс и размножать объекты; в нашем случае - это класс ipmessage, который таким образом оказывается избыточным. Что касается привязки и получения, то здесь ситуация иная: такой сокет имеет состояние, поскольку система параллельно с вашим приложением должна слушать определенный порт и буферизировать поступающие пакеты. Поэтому, такой класс как ipmsgserver на самом деле нужен, правда даже в нем в принципе есть кое-что лишнее: это методы посылки. Скорее всего в следующих релизах библиотеки я заменю класс ipmessage на обычную глобальную функцию для посылки дейтаграмм. Класс же ipmessage можно оставить для совместимости и еще для тех, кто со всем этим не разобрался. ***
__________________
I never saw a wildthing sorring for itself. A small bird will drop frozen dead without ever felt sorry for itself. |
![]() |
Этот пользователь сказал Спасибо Pitty за это полезное сообщение: | бобс (26.04.2008) |
![]() |
#3 |
Местный
Регистрация: 26.04.2006
Адрес: Удомля, гдежещё
Сообщений: 1,986
Вы сказали Спасибо: 676
Поблагодарили 257 раз(а) в 167 сообщениях
|
![]()
Многие программисты рассматривают ООП, кроме всего прочего, как средство описания небольших модулей в рамках приложения. Любой язык программирования, имеющий даже самое неполное ОО-расширение, позволяет хорошо изолировать миниатюрные интерфейсы в общем пространстве имен программы. Такой подход помогает нам мысленно изолировать небольшую функциональную единицу и заняться ее проектированием в отрыве от всего приложения.
И тут мы временами можем сталкиваться с трудностями, связанными с семантической принадлежностью того или иного метода данному классу. Например, принадлежность метода paint() некой абстрактной визуальной компоненте почти не вызывает сомнения. Но как быть с методом, который вставляет данную компоненту в список дочерних элементов родительской компоненты? Должен ли это быть метод add_child() в родительском интерфейсе, или set_parent() в дочернем (или может и то, и другое)? Я бы предпочел первый вариант, но в GUI-интерфейсах чаще встречается как раз таки второй, потому что дочерний объект должен еще и удалить самого себя из списка предыдущего "родителя". А что если попытаться разрешить проблему выставив этот метод за пределы класса? Представьте себе, вы имеете глобальную функцию set_affinity(parent, child), которая, возможно, вызывает защищенные виртуальные методы оповещения как в родительском классе, так и в дочернем. Не вижу в этом ничего страшного, и даже более того, мне будет легче запомнить тот факт, что set_affinity() никому не принадлежит, чем пытаться вспомнить в каком интерфейсе его следует искать: в родительском или дочернем. И все потому, что set_affinity() по сути никому не принадлежит и является "нейтральным" действием, в котором принимают участие больше одного объекта. Другой пример: обычно базовые классы, реализующие подсчет ссылок, объявляют по крайней мере два метода: acquire() и release() внутри этого абстракного интерфейса, которые увеличивают или, соответственно, уменьшают счетчик ссылок. Но возникает вопрос: насколько метод release() принадлежит данному объекту? Ведь в результате вызова этого метода объект может быть уничтожен, следовательно он является чем-то нейтральным скорее чем является "собственностью" объекта. В PTypes оба этих метода объявлены глобальными (правда в случае acquire() - только ради симметрии с release()). Примеров можно найти немало. Возьмите хотя бы преобразование строки в число и обратно: если в вашем языке строки и численные типы являются объектами, то каким именно классам должны принадлежать функции преобразования? На самом деле они по своей природе никому не принадлежат и являются самыми обычными глобальными функциями, а объявление их, к примеру, в классе string не дает никакого преимущества. Вы не выигрываете от этого ровным счетом ничего, или может даже проигрываете в естественности и интуитивности интерфейса. Кстати говоря, вовсе необязательно, чтобы такие нейтральные функции были объявлены буквально глобальными: вы вполне можете найти для них место в каком-то другом классе, лишь бы это выглядело логично. Например, тот же set_affinity() может оказаться в объекте окна (ведь визуальные компоненты так или иначе "живут" в окне), а пара acquire()/release() - в каком-нибудь class factory, которые нынче очень модны. Все это вам может и не понравиться, но лучше подумайте о том, что естественность интерфейса важнее привычек. А привычки ОО методологии настолько липучи, что, как видите, мы временами можем закрывать глаза на противоестественность в нашем коде. *** У меня в данный момент нет из всего этого готовых конкретных выводов в стиле "следует писать программы так-то, и не следует писать так-то", поскольку любой такой вывод был бы уязвим для критики. Но есть одна вещь, с которой вы не можете не согласиться: интерфейсы стоят того, чтобы им уделялось много времени на обдумывание. Реализация - это сосвем другая стихия, которая является сутью нашего с вами ремесла, но интерфейсы библиотек - это языки, на которых мы предлагаем думать и писать другим программистам. Я никогда не жалею своего времени, чтобы оптимизировать интерфейс, сделать его более компактным и естественным, если даже ради этого придется пойти наперекор собственным объектно-ориентированным привычкам. Попробую сделать еще один, уже более общий и менее очевидный вывод: следует проявлять консерватизм и минимализм в таких базовых вещах, как языки программирования, generic библиотеки и ядра операционных систем. В то же время, эти свойства становятся менее уместными по мере того, как вы поднимаетесь вверх в направлении к конечному пользователю.
__________________
I never saw a wildthing sorring for itself. A small bird will drop frozen dead without ever felt sorry for itself. |
![]() |
Этот пользователь сказал Спасибо Pitty за это полезное сообщение: | бобс (26.04.2008) |
![]() |
#4 |
Местный
Регистрация: 26.04.2006
Адрес: Удомля, гдежещё
Сообщений: 1,986
Вы сказали Спасибо: 676
Поблагодарили 257 раз(а) в 167 сообщениях
|
![]()
Ну, и как положено, ссылка на оригинал:
http://www.melikyan.com/dalshe/articles/oop2.html http://www.melikyan.com/dalshe/articles/oop.html Предлагаю высказать свои мнения... Особенно было бы интересно услышать мнение программистов, относящихся с идеологии *nix.
__________________
I never saw a wildthing sorring for itself. A small bird will drop frozen dead without ever felt sorry for itself. |
![]() |
![]() |
#5 | |
Местный
Регистрация: 20.09.2007
Сообщений: 4,226
Вы сказали Спасибо: 1
Поблагодарили 6,561 раз(а) в 1,660 сообщениях
|
![]() Цитата:
![]() |
|
![]() |
![]() |
#6 |
Местный
Регистрация: 26.04.2006
Адрес: Удомля, гдежещё
Сообщений: 1,986
Вы сказали Спасибо: 676
Поблагодарили 257 раз(а) в 167 сообщениях
|
![]()
Есть Главный Технический Императив Программирование - управление сложностью.... Чтобы объять это необъятное, надо его упростить... абстрагироваться от деталей на самом высоком уровне... и потом разбивать на подуровни, подсистемы, которые тоже абстрагировать, но уже более детально. В) Таким образом мы управляем сложностью, а главная задача управления сложностью - это ее минимизация. Принцип KISS.
__________________
I never saw a wildthing sorring for itself. A small bird will drop frozen dead without ever felt sorry for itself. |
![]() |
![]() |
#7 | |
Местный
Регистрация: 20.09.2007
Сообщений: 4,226
Вы сказали Спасибо: 1
Поблагодарили 6,561 раз(а) в 1,660 сообщениях
|
![]() Цитата:
![]() |
|
![]() |
![]() |
#8 | |
Местный
Регистрация: 28.09.2007
Сообщений: 4,510
Вы сказали Спасибо: 418
Поблагодарили 1,097 раз(а) в 680 сообщениях
|
![]() Цитата:
__________________
Каждому, каждому в лучшее верится... Падает, падает ядерный фугас (с) |
|
![]() |
![]() |
#9 | |
Местный
Регистрация: 26.04.2006
Адрес: Удомля, гдежещё
Сообщений: 1,986
Вы сказали Спасибо: 676
Поблагодарили 257 раз(а) в 167 сообщениях
|
![]() Цитата:
На полугодичных курсах по программированию максимум чему могут научить - синтаксису какого-то конкретного языка программирования, может быть основам ООП, но никак не научить программировать, или точнее, конструировать ПО.
__________________
I never saw a wildthing sorring for itself. A small bird will drop frozen dead without ever felt sorry for itself. |
|
![]() |
![]() |
#10 | |
Местный
Регистрация: 28.09.2007
Сообщений: 4,510
Вы сказали Спасибо: 418
Поблагодарили 1,097 раз(а) в 680 сообщениях
|
![]() Цитата:
![]() ![]() ![]()
__________________
Каждому, каждому в лучшее верится... Падает, падает ядерный фугас (с) |
|
![]() |
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1) | |
|
|