ФП надо. Просто хотя бы понимание основ. Оно для ООП как раз очень полезно.
ООП и ФП как в ортогональных измерениях. Можно и то и другое параллельно использовать. И понимание, как строятся ФП компиляторы, дает понимание, как лучше писать ООП программы.
Ну вот, пара секретов, которые я применяю в ООП:
1. Никогда не дублировать данные и ссылки на данные в полях. По возможности. У ФП нет состояний. В ООП сводим состояния к минимуму, «приближая» к ФП. Любое дублирование данных приводит к возможности их рассинхронизации. (логика не относится к передаче в методы, там можно моделировать имутабельность).
2. К полям обращаться только через методы. В ООП принято к делать паблик сеттеры или геттеры, но я усиливаю условие — даже к внутренним. Т.е. у каждого внутреннего поля один сеттер и геттер и они тоже условно закрыты.
Что это дает. Состояние как бы нигде не дублируется. Над ними строятся система методов. Которые на ходу перевычисляют всё что надо. Промежуточные результаты вычислений нигде не хранятся.
Когда такая система построена, то багов там минимум. Еще, понятно, фокусы, чтобы в рекурсию случайно не попали.
Такая система легко поддается изменениям. Нет издержек на синхронизацию дублированных данных. Минимизированы сайд-эффекты. Далее, если такая система не очень производительна, то ее очень легко оптимизировать. Оптимизация дается почти даром — механически. Все тормоза обычно как раз в лишних перевычислениях. Поэтому просто в нужных методах по результатам замеров, делается кеширование. Приходят одинаковые аргументы, например, а метод «почти» чистый, можно завести словарь, и вычислять один раз результаты.
Конечно, с этим нужно быть осторожным. Но обычно всё довольно прозрачно. Гораздо лучше, чем если люди на свой лад без никаких правил себе сооружают класс и что хотят, то и творят в нем.
У меня как раз «переворот сознания» произошел после работы на функциональном языке. Когда вернулся в шарп, заметил, что думаю совсем по другому. До этого казалось, что можно на шарпе писать как угодно. Что это только там нудят педантичные люди, как надо писать. Что задачу можно писать сотнями спопобов и они почти одинаковы. Лишь бы работало. А после работы на функциональном языке начал видеть кругом «потенциальные баги». И писать стал намного осторожнее.
а я и сказал, что сразу не надо учить работать с динамической памятью на С.
Там можно вполне не работать, а работать со стеком. Но поработать с ней надо. Тем более, что это не так и сложно. В эпоху .Net и C# как раз надо понимать, что такое ссылка и что такое куча. Чтобы хотя бы понимать, почему при передаче объекта в метод, изменение его состояния внутри метода, отразится на состоянии переданного объекта (что это не две копии). И нет ничего лучше, чем поработать с сырой памятью, понять, как это работает и заодно понять, как это плохо так программировать.
Потом, как раз о том статья, что программисты должны обладать некоторым кругозором и базовыми знаниями. Не потому, что они в работе каждый день нужны, а потому, чтобы легче и без заблуждений изучать новые технологии. ВУЗы отличаются от техникумов тем, что дают побольше знаний, чем просто узконаправленный специалист.
Нет, возможно, я ошибаюсь в последовательности обучения. Возможно, нужно мышление сразу правильным ставить и начинать с направлений более приоритетных. Но если уже начинают с императивного программирования (не важно, динамический скриптовый язык), то почему не с С.
И не вижу никаких оснований считать С высшей лигой. А может и к лучшему, если студенты не могут это сразу понять. Отсевать надо. А то потом работай с ними )) Если они того не могли понять, то что об остальном говорить.
Вам просто, возможно, плохо объясняли или плохие книги попались, что вы отнеслись к языку так, что у него высокий порог вхождения. В результате преподавания не требуется научить студента его знать досконально и не требуется научить его прямо стилю работы на этом языке.
А вот как раз начать с С#, я бы не стал. Шарп — это в первую очередь дотнет с огромной библиотекой. И там по стилю и по смыслу чем меньше люди пишут алгоритмы — тем лучше. Типы уже есть. Всё что надо есть. Сортировки есть. Списки, хеш-таблицы — есть.
Его или подобный язык уместно в курсе ООП изучать. Но это ж не сразу. Потом, ООП в шарпе (Джава, плюсы) — это такой себе синтаксический сахар над императивным программированием с маленькими вкраплениями функционального. ООП не стоит изучать сразу. Оно не является элементарным. Не зная алгоритмов чистыми классами (без методов) ничего не напишешь.
Так что шарп и джава не подходят для обучения на начальном этапе. Прививать желание писать алгоритмы на шарпе — не хорошо.
возможно мои взгляды устарели. Возможно надо начинать с SICP, со Scheme.
Но все же зная нашу реальность, я пока думаю, что С подойдет. Да и вообще говорю про то, как с нуля надо изучать. В вузах всё равно будут изучать циклы и ветвления, будут обязательно повторять.
Да и в школах хотя всё больше и больше выделяется на «информатику», на само программирование всё меньше и меньше.
Потом, я сам не знаю другого пути, потому что такой приблизительно прошел. Начинал изучать на древнем бейсике. Когда еще строки нумеровались. Потом паскаль. Потом С, потом плюсы, потом шарп, потом функциональный, потом снова шарп.
В этой цепочке явно лишним был бейсик и паскаль. Можно было сократить время, начиная с С.
А с другой стороны работаю с людьми, которые чуть ли не помешаны на оптимизациях. Откровенную чушь городят, делая наивные предположения как работают компиляторы и СУБД. И всё стремятся оптимизировать зараннее. Думая, что они умнее, смогут обмануть, сделать лучше свой велосипед. Мечтают в памяти работать, с многопоточностью и т.д. и т.п. причем делая это совершенно не обоснованно, делая необоснованные предварительные предположения, что оптимально, что нет.
В результате жесткий говнокод получается. И суть в том, что у людей не было опыта в написании оптимальных программ. Нет понимания, как может оптимизировать компилятор. Нет понимания, как этот процесс проводить. И из-за этого странная страсть это делать. И фанатичное убеждение, что говнокод — это код, который работает медленно (при первом его создании).
Как с этим бороться? Наверное, надо сразу дать на своей шкуре прочувствовать и научить выбирать нужные инструменты.
Я почему-то думаю, что всё же лучше обучать сразу на С. Хотя в принципе не важно, на каком языке, но второй этап легче будет даваться.
Т.е. на первом этапе нужно изучить базовые алгоритмы, без работы с динамической памятью. Циклы, ветвления, побегать по массивам. Если на этом этапе не грузить студента всеми прелестями (т.е. сложностями) С, а ограничить работу только со стеком и несколькими типами, и настаивать, что это занятия не по С, а только по базовым вещам, то язык в принципе не так и отличаться будет от паскаля.
Вторым этапом уже гоняют по алгоритмам, по работе с памятью. А если еще потом плюсы приплесть, так у студента должно сложиться отвращение к такой работе. Вот тут под это дело уже можно давать языки со сборкой мусора и функциональные языки. И учить студентов уже мыслить правильно, не писать говнокод, не стремиться к оптимизации, как к самоцели. Если они это не попробуют, не напишут пару задач или курсовых через ж, то так и будут потом писать.
Такой путь обучения, конечно, спорный. Как бы изучается лишнее. А некоторые могут застрять на начальных этапах и не понять, что хорошо, а что плохо. Но я не вижу выхода. И база нужна, и понимание, как работает память, и понимание, как работают компиляторы, СУБД, чтобы потом уметь ими пользоваться, писать простой код и не брать на себя лишнюю ответственность в плане написаний велосипедов.
Да просто растет количество знаний, а значит увеличивается «специализация». Кому-то сайтики клепать. Кому-то еще алгоритмы нужны.
Количество информации растет, по-моему, также из-за укоренившегося подхода к программированию. Императивный подход. Слишком много усилий тратится на вылизывание кода. Даже на оптимизации. И всегда вероятность багов высокая. Из-за чего жалко выбрасывать код. Отсюда следует, что невыброшенный код собирают в библиотеки. Библиотеки обрастают новыми именами (классами, методами, функциями). И далее приходится, чтобы снова не писать велосипеды, новому поколению программистов всё это учить больше и дольше.
Другие подходы, возможно, не решат кардинально проблему, но скорость образования снежного информационного кома могла бы быть ниже.
Ну и:
Вот вам сайт, он работает, картинки красивые, что еще нужно? Архитектура, говорите, плохая? Зато я его сделал на суперсовременном движке! И всего за полчаса!
Полчаса — принято. Если это экономически выгодно. Действительно, если сайт — законченный продукт, сделан за полчаса и работает (не важно как), то пусть работает. У хороших программистов и зарплата хорошая. Так пусть и хорошими делами занимаются, а такие сайты пишут те, кто умеют их за полчаса клепать задешево.
Каждый час нашей работы — это мера нашей эффективности. Идеальных продуктов не должно быть. Только соотношение — качество/цена.
Когда вы приводите к типу указатель на войд, то потом только в рантейме вы можете узнать реальный тип. Это и есть динамическая типизация.
Вообще, приведение к предку — нормально. Если будут использоваться только у предка методы. А вот приведение назад к потомку — самая настоящая динамическая типизация.
Компилятор уже не следит за этим. Он не может узнать во время компиляции, что вы там передаете.
Кстати, я как-то на С писал транслятор одного динамически типизированного языка. Интерпретатор. Так по сути это оно и есть. Я указатели на войд кругом передавал. А сам интерпретатор уже разруливал в рантайме что и как. Если бы С такого не позволял, то даже представить тяжело, как бы на нем можно было написать интерпретатор динамически типизированного языка.
То, что компилятор следит, чтобы передавали именно это — указатель на войд и т.д. — он отслеживает. В этом смысле тут статическая типизация. Но этого не достаточно. Т.к. реальный тип неизвестен. И кастуют его в рантайме.
Да вообще, просто если вы в рантайме делаете такие манипуляции, узнавая тип — это по определению динамическая типизация. А если вы разработали такую систему типов в программе, когда такое не требуется — вы хорошо продумали типы, чтобы использовать на всю статическую типизацию языка. И последний вариант в большинстве случаев лучший.
По моей логике С является статически типизированным языком. Это значит, что он поддерживает статическую типизацию. Но это не значит, что на нем нельзя работать с динамической типизацией.
Просто по определению — динамическая типизация, это когда тип узнают во время выполнения.
Вы правы, но там играет роль не только поле. Чтобы транзистор «переключался», важна еще скорость рассасывания электронов. Они должны на переходах накапливаться. В этом и основной смысл уменьшения транзисторов — меньше их надо, быстрее скорость.
Перестает. То, что язык статически типизирован, значит, что он в определенных пределах может проверить при компиляции. Но есть и много средств эту типизацию нарушить или обойти.
Если вы передаете в метод параметр типа int, то в методе можете смело надеяться, что туда передали целый тип и пользоваться им. Это статическая типизация. Если же вы в метод передаете тип object, но знаете, что там будет int и далее либо надеетесь на это, либо проверяете, бросаете эксепшин, либо еще что-то делаете — это уже не статическая типизация. Суть в том, что если вы ошиблись в программе и туда таки не передается int, то во время компиляции вы об этом не узнаете. Узнаете когда выполнение программы зайдет в этот метод.
Это я так написал, как псевдокод. Пишу на шарпе, там уместнее был бы модификатор abstract. Но виртуал более понятный другим (например, С++).
Это базовый класс Печь. От него могут наследоваться конкретные печи — ЭлектрическаяПечь, ГазоваяПечь и т.д.
Виртуальный — значит можно переопределять в потомках. Если нужна определенная последовательность действий с каждой едой, то самый простой способ — у печи сделать метод ПриготовитьЕду. Т.е. из примера выше. А каждая печь будет за это отвечать. Т.е. код переопределенного метода.
Должна ли печь за это отвечать или только методы предоставить — включить, зажечь газ и т.д. — это зависит от задачи. Моделировать же один к одному реальный мир нет смысла.
Если по задаче отдельно методы можно дергать и отвечать за готовку будет не печь, а повар, скажем, то можно применить другие, более сложные способы. Паттерн Стратегия, например. В зависимости от того, какую печь будет использовать повар, у него меняется взаимодействие с печью.
Потеряли. Я говорю о статической типизации. Конечно, всякие там метаданные или инстансы позволяют определить. Ветвление понадобится, бросить ексепшин. Или изменить способ обработки. Но это уже в рантайме, это не статическая типизация.
Да, это говорит не очень хорошее о дизайне. Но иногда без этого не обойтись.
В общем, такая возможность налажать существует на уровне языка. И я привел пример, как ее можно создать. Пример аналогичен примеру ковариантности массивов. Я специально привел, чтобы показать, что проблемы приведения с ковариантностью не являются чем-то новым и опаснее других способов сделать аналогичные ошибки
Если всё рассматривать в совокупности. Вы рассматриваете похоже только случай, если вы точно угадали, что будет дальше.
Так, во-первых, далеко не всегда бывает. А во-вторых методология написания ПО с помощью угадывания и такая как я описал отличается. Отличается в ту сторону, что ПО, которое пишут для здесь и сейчас — гораздо легче поддается изменениям. И затраты на изменения небольшие. Не грозят катасрофой. Потому в ТДД рефакторят всё время. И никто рефакторинга не боится.
И еще, так писать ПО — более научных подход. Также как в физике опираются на объективную информацию, полученную из эксперимента, так и в таком написании ПО опираются на объективную информацию, полученную от заказчика. А угадывание того, что будет дальше — это шаманство — удел гуманитарных наук или искусств. Там не заботятся об объективности. Там могут и сами придумывать как должно быть. Картины рисовать, стихи писать.
Вот и у нас в программировании сейчас такая холиварная война происходит, считать себя создателями, творцами и пророками или всё же инженерами, адептами точных наук и матана ))
так в случае мемсетов, нужны с одной стороны юнит тесты. Потом, если в функциях, которые делают что-то другое, а строки там вспомогательные и работает прямое выделение памяти на строку, так оно либо в одном месте один раз было, либо рефакторинг уже давно вынес в отдельное место работу с памятью для строк.
В С, например, можно не использовать прямо тип, а макрос препроцессора использовать. Захотели поменять, подменили в макросе. В других языках, если что, создаете свой тип, класс. Если же не предусмотрели (и возможно правильно, не было требований), то можно сейчас создать новый тип и подменить текстовой автоматической подменой.
А юнит-тесты покажут что не так. В общем, я бы из этого проблемы не делал. Потому что это простой автоматический рефакторинг. Да, может оказаться сложным. Но вообще-то, проблема с аски и юникодом довольно известная. И если язык не поддерживает тип юникодов, то можно что-то придумать сразу — написать класс-обертку над стандартным аски-типом. А потом ее подменить.
гибкость в каком-либо направлении означает негибкость в других направлениях. Поддержка преждевременной гибкости несет больше расходов.
1. Код сейчас принято покрывать юнит-тестами. Это один из самых серьезных методов сокращения багов и какой-то гарантии, что код делает то что надо. Лишняя гибкость — лишний функционал. Лишний функционал тоже покрывается юнит-тестами. При изменении чего-то (часто бывает), тесты являются определенным иногда тормозом. Потому что их тоже нужно менять. Но при этом, тесты дают саму возможность изменений, потому что без них вообще страшно что-то менять в уже работающем коде. Никакой без них гарантии, что всё идет как надо. В итоге изменения с тестами экономят время и деньги из этапа сопровождения.
т.е. издержки на поддержание лишних тестов. Могут быть довольно серьезными.
2. Если код простой и соответствует требованиям только на «здесь и сейчас», а также покрыт юнит-тестами, то любые изменения в коде делаются значительно легче делаются изменения. Т.е. это не 100500 долларов на его изменения.
3. Если вы не угадали направление, кроме всего прочего, далее возможно придется удалять код. Потери.
4. Если вы угадали направление, то времени и денег вы не так и много сэкономили, если сэкономили вообще. То, что делалось наперед, съело время раньше. А так съест время сегодня. Раньше могли бы заниматься более важными задачами. Обычно их хватает всегда.
Так что выгода от угадывания не очевидна. А даже наоборот. Человек, который пытается угадывать, имеет и специфическое мышление. Он боится рефакторинга. Потому что тогда придется удалять ненужный код. Если он пишет код, угадывая наперед, то и старый ненужный код ему жалко удалять. У него же кода на авось хватает.
В результате у таких программистов даже время от времени возникает желание всё переписать с нуля.
а в чем проблема в проекте на 1 000 000 строк заменить ASCII на UTF8? В зависимости от языка конечно. Даже простая автоматическая текстовая подмена типов вполне работает.
Главное — не забывать — юнит-тесты писать, чтобы потом узнать, сломалось ли что-то.
Дзен — это писать код в здесь и сейчас. Без никаких малейших предположений на счет будущего. И также с безжалостным рефакторингом остатков прошлого.
Дзен, это когда вам говорят:
— Почему ты не предусмотрел возможность того, что печь может быть и открытым костром. Это очевидно и завтра мы это будем внедрять.
— Завтра — будет завтра. Зачем нам думать о завтра? Завтра не существует, вчера не существует. Существует только сегодня.
Будет задача, будем рефакторить. Куда нам спешить? Нас не как пророков нанимали, а как программистов.
жестко )
ни эксепшины, ни пустой метод — не выход. Это значит, что неверно смоделировали.
на то ООП и нужно, чтобы его не хакать ))
class Oven
{
public virtual Food Cook(List<FoodProduct> products) ....
}
Нет?
В крайнем случае есть паттерны разные, вроде стратегий. Если не хочется заставлять саму печь готовить, а только пользоваться ею, нажимая на кнопки по разному.
Хорошая статья. Но я бы еще усилил ее тем, что для выбора лучшего решения вырабатываются определенные правила у программиста и он не выбирает интуитивно или следуя неясной красоте.
Конечно, всегда давят много факторов для выбора того или иного решения. Так, на вскидку, по приоритетам, какие у меня правила:
1. Код описывает задачу или реализацию? Декларативное описание самой задачи лучше хитрых алгоритмов.
2. Код является самым простым решением?
3. Код требует комментариев? Если да — код плохой. Нужно подумать, как средствами языка выразить яснее мысль.
4. Количество кода. Чем меньше, тем лучше.
ну и далее, примерные вопросы рефакторинга:
5. Есть ли в коде повторения?
6. Есть ли в данных повторения?
7. Есть ли магические числа?
И т.д.
Кое-какие пункты могут противоречить друг другу. Нужны приоритеты для выбора.
На счет ковариантности. Имхо, вещь оправданная. Не знаю, с каких соображений точно майкрософт ее ввел. Но я как-то делал свою ORM и реализацию MVP паттерна. Грешил на досуге. Пытался сделать общее хорошее решение. И мне надо было передавать на ГУИ либо сущности, либо списки. Без ковариантости список нельзя привести с списку базового и пришлось бы передавать object. А потом надеяться, что там будет список сущностей. Плохо.
А проблема типизации и без ковариантности существует. Вот пример:
string s = "1";
object o = s;
int a = (int)o;
Так устроено на шарпе ООП. Да и вообще это глобальная проблема. Статическая типизация — это то, что ограничивает и делает код негибким. Негибкость — специальная и хорошая вещь, которая сама проверяет, чтобы баги не возникали. Это рельсы для поезда. Если они есть, он не ездит там где не положено. А способы увеличения гибкости (полиморфизм на виртуальных методах) — это вещь противоположная. Оно нарушает хоть немного статическую типизацию.
Привели к базовому — потеряли информацию, что это за тип. Захотели привести к наследнику, ошиблись, привели не к тому.
Создали ссылку в классе на делегат. Код стал гибким. В рантайме можно подключить любой обработчик. Но не установили ссылку — приложение в рантайме падает. И статической типизацией никак не помочь.
Я бы использовал хранимки. Они не страшнее. Суть в том, что тригеры могут неявно переопределить поведение операции. Вы что-то инсертите в таблицу, а инсерт не все поля вставляет. И еще много чего может делать.
Тригеры использую, если код легаси и если уже так повелось, что при меньших затратах, можно на них что-то сделать. Логирование или кеширование.
ООП и ФП как в ортогональных измерениях. Можно и то и другое параллельно использовать. И понимание, как строятся ФП компиляторы, дает понимание, как лучше писать ООП программы.
Ну вот, пара секретов, которые я применяю в ООП:
1. Никогда не дублировать данные и ссылки на данные в полях. По возможности. У ФП нет состояний. В ООП сводим состояния к минимуму, «приближая» к ФП. Любое дублирование данных приводит к возможности их рассинхронизации. (логика не относится к передаче в методы, там можно моделировать имутабельность).
2. К полям обращаться только через методы. В ООП принято к делать паблик сеттеры или геттеры, но я усиливаю условие — даже к внутренним. Т.е. у каждого внутреннего поля один сеттер и геттер и они тоже условно закрыты.
Что это дает. Состояние как бы нигде не дублируется. Над ними строятся система методов. Которые на ходу перевычисляют всё что надо. Промежуточные результаты вычислений нигде не хранятся.
Когда такая система построена, то багов там минимум. Еще, понятно, фокусы, чтобы в рекурсию случайно не попали.
Такая система легко поддается изменениям. Нет издержек на синхронизацию дублированных данных. Минимизированы сайд-эффекты. Далее, если такая система не очень производительна, то ее очень легко оптимизировать. Оптимизация дается почти даром — механически. Все тормоза обычно как раз в лишних перевычислениях. Поэтому просто в нужных методах по результатам замеров, делается кеширование. Приходят одинаковые аргументы, например, а метод «почти» чистый, можно завести словарь, и вычислять один раз результаты.
Конечно, с этим нужно быть осторожным. Но обычно всё довольно прозрачно. Гораздо лучше, чем если люди на свой лад без никаких правил себе сооружают класс и что хотят, то и творят в нем.
У меня как раз «переворот сознания» произошел после работы на функциональном языке. Когда вернулся в шарп, заметил, что думаю совсем по другому. До этого казалось, что можно на шарпе писать как угодно. Что это только там нудят педантичные люди, как надо писать. Что задачу можно писать сотнями спопобов и они почти одинаковы. Лишь бы работало. А после работы на функциональном языке начал видеть кругом «потенциальные баги». И писать стал намного осторожнее.
Там можно вполне не работать, а работать со стеком. Но поработать с ней надо. Тем более, что это не так и сложно. В эпоху .Net и C# как раз надо понимать, что такое ссылка и что такое куча. Чтобы хотя бы понимать, почему при передаче объекта в метод, изменение его состояния внутри метода, отразится на состоянии переданного объекта (что это не две копии). И нет ничего лучше, чем поработать с сырой памятью, понять, как это работает и заодно понять, как это плохо так программировать.
Потом, как раз о том статья, что программисты должны обладать некоторым кругозором и базовыми знаниями. Не потому, что они в работе каждый день нужны, а потому, чтобы легче и без заблуждений изучать новые технологии. ВУЗы отличаются от техникумов тем, что дают побольше знаний, чем просто узконаправленный специалист.
Нет, возможно, я ошибаюсь в последовательности обучения. Возможно, нужно мышление сразу правильным ставить и начинать с направлений более приоритетных. Но если уже начинают с императивного программирования (не важно, динамический скриптовый язык), то почему не с С.
И не вижу никаких оснований считать С высшей лигой. А может и к лучшему, если студенты не могут это сразу понять. Отсевать надо. А то потом работай с ними )) Если они того не могли понять, то что об остальном говорить.
Вам просто, возможно, плохо объясняли или плохие книги попались, что вы отнеслись к языку так, что у него высокий порог вхождения. В результате преподавания не требуется научить студента его знать досконально и не требуется научить его прямо стилю работы на этом языке.
А вот как раз начать с С#, я бы не стал. Шарп — это в первую очередь дотнет с огромной библиотекой. И там по стилю и по смыслу чем меньше люди пишут алгоритмы — тем лучше. Типы уже есть. Всё что надо есть. Сортировки есть. Списки, хеш-таблицы — есть.
Его или подобный язык уместно в курсе ООП изучать. Но это ж не сразу. Потом, ООП в шарпе (Джава, плюсы) — это такой себе синтаксический сахар над императивным программированием с маленькими вкраплениями функционального. ООП не стоит изучать сразу. Оно не является элементарным. Не зная алгоритмов чистыми классами (без методов) ничего не напишешь.
Так что шарп и джава не подходят для обучения на начальном этапе. Прививать желание писать алгоритмы на шарпе — не хорошо.
Но все же зная нашу реальность, я пока думаю, что С подойдет. Да и вообще говорю про то, как с нуля надо изучать. В вузах всё равно будут изучать циклы и ветвления, будут обязательно повторять.
Да и в школах хотя всё больше и больше выделяется на «информатику», на само программирование всё меньше и меньше.
Потом, я сам не знаю другого пути, потому что такой приблизительно прошел. Начинал изучать на древнем бейсике. Когда еще строки нумеровались. Потом паскаль. Потом С, потом плюсы, потом шарп, потом функциональный, потом снова шарп.
В этой цепочке явно лишним был бейсик и паскаль. Можно было сократить время, начиная с С.
А с другой стороны работаю с людьми, которые чуть ли не помешаны на оптимизациях. Откровенную чушь городят, делая наивные предположения как работают компиляторы и СУБД. И всё стремятся оптимизировать зараннее. Думая, что они умнее, смогут обмануть, сделать лучше свой велосипед. Мечтают в памяти работать, с многопоточностью и т.д. и т.п. причем делая это совершенно не обоснованно, делая необоснованные предварительные предположения, что оптимально, что нет.
В результате жесткий говнокод получается. И суть в том, что у людей не было опыта в написании оптимальных программ. Нет понимания, как может оптимизировать компилятор. Нет понимания, как этот процесс проводить. И из-за этого странная страсть это делать. И фанатичное убеждение, что говнокод — это код, который работает медленно (при первом его создании).
Как с этим бороться? Наверное, надо сразу дать на своей шкуре прочувствовать и научить выбирать нужные инструменты.
Т.е. на первом этапе нужно изучить базовые алгоритмы, без работы с динамической памятью. Циклы, ветвления, побегать по массивам. Если на этом этапе не грузить студента всеми прелестями (т.е. сложностями) С, а ограничить работу только со стеком и несколькими типами, и настаивать, что это занятия не по С, а только по базовым вещам, то язык в принципе не так и отличаться будет от паскаля.
Вторым этапом уже гоняют по алгоритмам, по работе с памятью. А если еще потом плюсы приплесть, так у студента должно сложиться отвращение к такой работе. Вот тут под это дело уже можно давать языки со сборкой мусора и функциональные языки. И учить студентов уже мыслить правильно, не писать говнокод, не стремиться к оптимизации, как к самоцели. Если они это не попробуют, не напишут пару задач или курсовых через ж, то так и будут потом писать.
Такой путь обучения, конечно, спорный. Как бы изучается лишнее. А некоторые могут застрять на начальных этапах и не понять, что хорошо, а что плохо. Но я не вижу выхода. И база нужна, и понимание, как работает память, и понимание, как работают компиляторы, СУБД, чтобы потом уметь ими пользоваться, писать простой код и не брать на себя лишнюю ответственность в плане написаний велосипедов.
Количество информации растет, по-моему, также из-за укоренившегося подхода к программированию. Императивный подход. Слишком много усилий тратится на вылизывание кода. Даже на оптимизации. И всегда вероятность багов высокая. Из-за чего жалко выбрасывать код. Отсюда следует, что невыброшенный код собирают в библиотеки. Библиотеки обрастают новыми именами (классами, методами, функциями). И далее приходится, чтобы снова не писать велосипеды, новому поколению программистов всё это учить больше и дольше.
Другие подходы, возможно, не решат кардинально проблему, но скорость образования снежного информационного кома могла бы быть ниже.
Ну и:
Полчаса — принято. Если это экономически выгодно. Действительно, если сайт — законченный продукт, сделан за полчаса и работает (не важно как), то пусть работает. У хороших программистов и зарплата хорошая. Так пусть и хорошими делами занимаются, а такие сайты пишут те, кто умеют их за полчаса клепать задешево.
Каждый час нашей работы — это мера нашей эффективности. Идеальных продуктов не должно быть. Только соотношение — качество/цена.
Вообще, приведение к предку — нормально. Если будут использоваться только у предка методы. А вот приведение назад к потомку — самая настоящая динамическая типизация.
Компилятор уже не следит за этим. Он не может узнать во время компиляции, что вы там передаете.
Кстати, я как-то на С писал транслятор одного динамически типизированного языка. Интерпретатор. Так по сути это оно и есть. Я указатели на войд кругом передавал. А сам интерпретатор уже разруливал в рантайме что и как. Если бы С такого не позволял, то даже представить тяжело, как бы на нем можно было написать интерпретатор динамически типизированного языка.
То, что компилятор следит, чтобы передавали именно это — указатель на войд и т.д. — он отслеживает. В этом смысле тут статическая типизация. Но этого не достаточно. Т.к. реальный тип неизвестен. И кастуют его в рантайме.
Да вообще, просто если вы в рантайме делаете такие манипуляции, узнавая тип — это по определению динамическая типизация. А если вы разработали такую систему типов в программе, когда такое не требуется — вы хорошо продумали типы, чтобы использовать на всю статическую типизацию языка. И последний вариант в большинстве случаев лучший.
По моей логике С является статически типизированным языком. Это значит, что он поддерживает статическую типизацию. Но это не значит, что на нем нельзя работать с динамической типизацией.
Просто по определению — динамическая типизация, это когда тип узнают во время выполнения.
Если вы передаете в метод параметр типа int, то в методе можете смело надеяться, что туда передали целый тип и пользоваться им. Это статическая типизация. Если же вы в метод передаете тип object, но знаете, что там будет int и далее либо надеетесь на это, либо проверяете, бросаете эксепшин, либо еще что-то делаете — это уже не статическая типизация. Суть в том, что если вы ошиблись в программе и туда таки не передается int, то во время компиляции вы об этом не узнаете. Узнаете когда выполнение программы зайдет в этот метод.
Это базовый класс Печь. От него могут наследоваться конкретные печи — ЭлектрическаяПечь, ГазоваяПечь и т.д.
Виртуальный — значит можно переопределять в потомках. Если нужна определенная последовательность действий с каждой едой, то самый простой способ — у печи сделать метод ПриготовитьЕду. Т.е. из примера выше. А каждая печь будет за это отвечать. Т.е. код переопределенного метода.
Должна ли печь за это отвечать или только методы предоставить — включить, зажечь газ и т.д. — это зависит от задачи. Моделировать же один к одному реальный мир нет смысла.
Если по задаче отдельно методы можно дергать и отвечать за готовку будет не печь, а повар, скажем, то можно применить другие, более сложные способы. Паттерн Стратегия, например. В зависимости от того, какую печь будет использовать повар, у него меняется взаимодействие с печью.
Да, это говорит не очень хорошее о дизайне. Но иногда без этого не обойтись.
В общем, такая возможность налажать существует на уровне языка. И я привел пример, как ее можно создать. Пример аналогичен примеру ковариантности массивов. Я специально привел, чтобы показать, что проблемы приведения с ковариантностью не являются чем-то новым и опаснее других способов сделать аналогичные ошибки
Если всё рассматривать в совокупности. Вы рассматриваете похоже только случай, если вы точно угадали, что будет дальше.
Так, во-первых, далеко не всегда бывает. А во-вторых методология написания ПО с помощью угадывания и такая как я описал отличается. Отличается в ту сторону, что ПО, которое пишут для здесь и сейчас — гораздо легче поддается изменениям. И затраты на изменения небольшие. Не грозят катасрофой. Потому в ТДД рефакторят всё время. И никто рефакторинга не боится.
И еще, так писать ПО — более научных подход. Также как в физике опираются на объективную информацию, полученную из эксперимента, так и в таком написании ПО опираются на объективную информацию, полученную от заказчика. А угадывание того, что будет дальше — это шаманство — удел гуманитарных наук или искусств. Там не заботятся об объективности. Там могут и сами придумывать как должно быть. Картины рисовать, стихи писать.
Вот и у нас в программировании сейчас такая холиварная война происходит, считать себя создателями, творцами и пророками или всё же инженерами, адептами точных наук и матана ))
В С, например, можно не использовать прямо тип, а макрос препроцессора использовать. Захотели поменять, подменили в макросе. В других языках, если что, создаете свой тип, класс. Если же не предусмотрели (и возможно правильно, не было требований), то можно сейчас создать новый тип и подменить текстовой автоматической подменой.
А юнит-тесты покажут что не так. В общем, я бы из этого проблемы не делал. Потому что это простой автоматический рефакторинг. Да, может оказаться сложным. Но вообще-то, проблема с аски и юникодом довольно известная. И если язык не поддерживает тип юникодов, то можно что-то придумать сразу — написать класс-обертку над стандартным аски-типом. А потом ее подменить.
1. Код сейчас принято покрывать юнит-тестами. Это один из самых серьезных методов сокращения багов и какой-то гарантии, что код делает то что надо. Лишняя гибкость — лишний функционал. Лишний функционал тоже покрывается юнит-тестами. При изменении чего-то (часто бывает), тесты являются определенным иногда тормозом. Потому что их тоже нужно менять. Но при этом, тесты дают саму возможность изменений, потому что без них вообще страшно что-то менять в уже работающем коде. Никакой без них гарантии, что всё идет как надо. В итоге изменения с тестами экономят время и деньги из этапа сопровождения.
т.е. издержки на поддержание лишних тестов. Могут быть довольно серьезными.
2. Если код простой и соответствует требованиям только на «здесь и сейчас», а также покрыт юнит-тестами, то любые изменения в коде делаются значительно легче делаются изменения. Т.е. это не 100500 долларов на его изменения.
3. Если вы не угадали направление, кроме всего прочего, далее возможно придется удалять код. Потери.
4. Если вы угадали направление, то времени и денег вы не так и много сэкономили, если сэкономили вообще. То, что делалось наперед, съело время раньше. А так съест время сегодня. Раньше могли бы заниматься более важными задачами. Обычно их хватает всегда.
Так что выгода от угадывания не очевидна. А даже наоборот. Человек, который пытается угадывать, имеет и специфическое мышление. Он боится рефакторинга. Потому что тогда придется удалять ненужный код. Если он пишет код, угадывая наперед, то и старый ненужный код ему жалко удалять. У него же кода на авось хватает.
В результате у таких программистов даже время от времени возникает желание всё переписать с нуля.
Главное — не забывать — юнит-тесты писать, чтобы потом узнать, сломалось ли что-то.
Дзен — это писать код в здесь и сейчас. Без никаких малейших предположений на счет будущего. И также с безжалостным рефакторингом остатков прошлого.
Дзен, это когда вам говорят:
— Почему ты не предусмотрел возможность того, что печь может быть и открытым костром. Это очевидно и завтра мы это будем внедрять.
— Завтра — будет завтра. Зачем нам думать о завтра? Завтра не существует, вчера не существует. Существует только сегодня.
Будет задача, будем рефакторить. Куда нам спешить? Нас не как пророков нанимали, а как программистов.
ни эксепшины, ни пустой метод — не выход. Это значит, что неверно смоделировали.
на то ООП и нужно, чтобы его не хакать ))
Нет?
В крайнем случае есть паттерны разные, вроде стратегий. Если не хочется заставлять саму печь готовить, а только пользоваться ею, нажимая на кнопки по разному.
Конечно, всегда давят много факторов для выбора того или иного решения. Так, на вскидку, по приоритетам, какие у меня правила:
1. Код описывает задачу или реализацию? Декларативное описание самой задачи лучше хитрых алгоритмов.
2. Код является самым простым решением?
3. Код требует комментариев? Если да — код плохой. Нужно подумать, как средствами языка выразить яснее мысль.
4. Количество кода. Чем меньше, тем лучше.
ну и далее, примерные вопросы рефакторинга:
5. Есть ли в коде повторения?
6. Есть ли в данных повторения?
7. Есть ли магические числа?
И т.д.
Кое-какие пункты могут противоречить друг другу. Нужны приоритеты для выбора.
На счет ковариантности. Имхо, вещь оправданная. Не знаю, с каких соображений точно майкрософт ее ввел. Но я как-то делал свою ORM и реализацию MVP паттерна. Грешил на досуге. Пытался сделать общее хорошее решение. И мне надо было передавать на ГУИ либо сущности, либо списки. Без ковариантости список нельзя привести с списку базового и пришлось бы передавать object. А потом надеяться, что там будет список сущностей. Плохо.
А проблема типизации и без ковариантности существует. Вот пример:
Так устроено на шарпе ООП. Да и вообще это глобальная проблема. Статическая типизация — это то, что ограничивает и делает код негибким. Негибкость — специальная и хорошая вещь, которая сама проверяет, чтобы баги не возникали. Это рельсы для поезда. Если они есть, он не ездит там где не положено. А способы увеличения гибкости (полиморфизм на виртуальных методах) — это вещь противоположная. Оно нарушает хоть немного статическую типизацию.
Привели к базовому — потеряли информацию, что это за тип. Захотели привести к наследнику, ошиблись, привели не к тому.
Создали ссылку в классе на делегат. Код стал гибким. В рантайме можно подключить любой обработчик. Но не установили ссылку — приложение в рантайме падает. И статической типизацией никак не помочь.
Так что ковариантность — нормально.
Тригеры использую, если код легаси и если уже так повелось, что при меньших затратах, можно на них что-то сделать. Логирование или кеширование.