суббота, 11 мая 2024 г.

[prog.c++] Из непонятого: как же лучше погружать в современный C++ новых людей?

У меня недавно закончился небольшой контракт на проведение для молодых программистов чего-то вроде "курса молодого бойца" по C++. Что заставило в очередной раз задуматься на тему "а не стал ли C++ настольно монстрообразным, что обучить новичка практически невозможно?"

Но это сильно глобальный вопрос, я даже не знаю как к нему подойти. Радует лишь то, что сам я C++ изучаю года с 1991-го, когда это был, мягко говоря, совсем другой язык. Гораздо более простой, последовательный и логичный. И мне повезло, что на эту небольшую базу затем инкрементально ложились новые возможности: пространства имен, исключения, шаблоны, STL и т.д., и т.п. Т.е. сам я учил C++ по мере того, как в него добавлялись новые фичи. И поскольку добавлялись они раньше не в таком количестве и не с такой скоростью, то поспевать за ростом сложности C++ где-то до 17-го стандарта еще удавалось. Однако, на C++20 это уже сломалось даже для меня :(

Впрочем, это опять таки уход в сторону от того, о чем хотелось сегодня поговорить.

Попробую сконцентрироваться на более простом вопросе, ответ на который для меня самого совсем неочевиден.

С одной стороны, я противник подхода, при котором разработчика сперва учат языку Си, а затем начинают давать ему C++ по частям. Типа сперва чистый Си, затем немного "Си с классами", затем чуть-чуть шаблонов, затем чуть-чуть STL, затем еще чуть-чуть шаблонов и т.д.

Как по мне, Си и C++ уже очень и очень давно совершенно разные языки. Да, в C++ есть изрядное подмножество чистого Си, но все-таки идеология C++ другая и к таким вещам, как ссылки, классы, деструкторы, нужно привыкать сразу. Чем меньше приемов из чистого Си, тем лучше.

Поэтому вроде как есть смысл новичков сразу учить использовать то хорошее, что есть в C++. Например, std::string для строк вместо char*, std::string_view вместо const char*, std::vector и std::array вместо Си-шных массивов, std::find_if вместо голых циклов и вот это вот все.

Но тут есть серьезная засада, особенно в случаях, когда у новичков за плечами уже есть опыт работы с языками со сборкой мусора (будь то Python или Java, не суть важно). По моим впечатлениям у тех, кто изучает C++ сейчас, есть проблемы с пониманием разницы между values и references. Грубо говоря, люди пишут:

void f(std::string param) {...}

даже не задумываясь о том, что здесь происходит передача по значению, а не по ссылке.

И это, как по мне, проблема, т.к. требуется время и регулярное битье по рукам, чтобы выработать у молодого C++ника навык автоматически отслеживать передачу по значению или передачу по ссылке.

Как следствие, та же move-семантка, без которой современный C++ был бы не нужен в принципе, не очень хорошо заходит. Научить избегать копирования -- это уже задача. А научиться избегать копирования за счет грамотного и регулярного использования move-семантики оказывается задачкой со звездочкой.

Рефлексируя на эту тему я пока придерживаюсь мнения, что виной тому как раз использование готовых "высокоуровневых" возможностей STL. Те же самые std::string и std::vector освобождают программиста от изрядного геморроя, но не дают этому самому программисту понимания того, как же это все работает "под капотом". А без такого понимания сложно программировать на C++ эффективно.

Что заставляет задуматься о том, чтобы учить людей C++у без использования готовых классов и шаблонов из STL.

Грубо говоря, если начинать обучение с того, чтобы молодой C++ник сам написал аналог string-а, vector-а и hash_map-а, сам бы почувствовал где и когда вызываются конструкторы, деструкторы, операторы копирования, где требуется выделить память, где освободить, где скопировать содержимое и столкнуться с проблемой exception safety... Вот если начинать обучение с этого, то может быть у человека будет гораздо больше понимания разницы между передачей по значению или по ссылке, между копированием и перемещением и т.д., и т.п.

Такой подход, на первый взгляд, производит впечатление более перспективного. Мол, мы сперва даем людям базу. А потом на эту базу кладем знания об уже готовых классах/алгоритмах из STL, чтобы можно было ощутить насколько жизнь может быть проще если пользоваться тем, что уже написано.

Но и здесь все не так радужно. Во-первых, такое обучение потребует гораздо больше времени. Ведь если мы научим молодого C++ника делать свой vector, то он все равно еще будет далек от того, чтобы уметь писать нормальный прикладной код.

Во-вторых, по моему субъективному мнению, написание библиотек классов (пусть даже в такой библиотеке будет всего лишь string, vector и hash_map) требует несколько иных навыков, нежели написание прикладного кода. Да, возможно это тараканы в моей голове, но я убежден, что библиотеко-писатели и прикладники -- это разные специалисты. Бывают, конечно, и те, кто одинаково хорош и в том, и в другом, но это такое же частое явление, как и хорошие программисты, вырастающие в хороших менеджеров. В общем, начиная с реализации собственных string и vector можно не получить должного эффекта, если обучаемый больше склонен к решению прикладных проблем, чем к написанию библиотек.

В-третьих, этот подход как раз таки очень близок к подходу, когда мы сперва учим людей чистому Си, а уже затем переходим к использованию высокоуровневых возможностей C++. Близок, не не тождественен. Тем не менее, если молодой разработчик привыкает писать относительно низкоуровневый код с ручными new/delete, alignof/alignas и пр. заморочками, то есть опасность, что он и продолжит в том же духе. Тут людей сразу пытаешься учить пользоваться std::unique_ptr и std::make_unique, а они все равно норовят использовать голые владеющие указатели с new/delete... Что уж говорить о тех, кого специально с самого начала ограждают от std::unique_ptr, std::optional, std::variant, std::vector и т.д.

В общем, я пока для себя ответа не нашел. Не знаю по какому пути следует двигаться, особенно если на серьезное погружение в С++ есть не месяц, а хотя бы полгода.

В завершение еще раз поделюсь своими тягостными впечатлениями о том, что C++ на мой взгляд, стал слишком уж большим и сложным для того, чтобы его мог изучить и освоить человек "со стороны". Речь не о старпёрах, вроде меня, кто за 30 лет в программизме имел возможность осваивать фичи "современного C++" по мере их появления. А о тех, кто про C++ ничего не знал, но должен изучить за относительно небольшое время. Как таким новичкам преподавать тот же С++20? Вот это вопрос.

Хотя, может быть я излишне пессимистичен. Может это лично мне тяжело осваивать C++20, т.к. я уже старый, мозги уже плесневеть начали, много там лишних и устаревших знаний скопилось, да еще и не так уж много возможностей применять C++20 на практике. Отсюда и мое собственное заблуждение о том, что изучать C++20 сложно. А когда человеку 20 лет, все вокруг неизвестно и удивительно, то может быть C++20 залетает в пустую юную голову просто "на ура"... Ведь может же быть и такое.