Давно хотел написать подобный текст, но все никак не доходили руки. А вот после завершившегося летнего заседания комитета по стандартизации C++ и поднявшегося воя о том, что сложность языка еще больше увеличилась, пришлось таки изыскать время и зафиксировать собственные мысли на этот счет.
Текста будет много, поэтому тех, кому не жаль своего времени, приглашаю заглянуть под кат.
Язык программирования — это технологичный продукт, но не все так просто
Некоторое время назад довелось прочесть интересную книгу «Дилемма инноватора». Там на примерах технологичных продуктов показывается, как возникают новые продукты, которые сперва проигрывают доминирующим сейчас на рынке решениям, а затем кардинальным образом меняют состояние рынка.
Один из самых ярких примеров: цифровая фотография, которая в 1990-х была вообще никакой, но спустя всего 20 лет привела к краху такого монстра XX-го века, как Kodak (который, кстати говоря, первым и сделал прототип цифровой камеры).
Другой пример, близкий к разработчикам: эволюция жестких дисков. От здоровенных монстров времен мейнфреймов и мини-компьютеров, до небольших 3.5" и 2.5" дюймовых моделей, а затем до SSD формата M.2 и распаянных прямо на плате eMMC.
Тоже самое и с дискетами, которые сперва дошли до 3.5", а потом исчезли как класс под давлением USB-шных флешек.
Еще один пример из нашей профессиональной области — это развитие персональных компьютеров. Которые изначально были не в состоянии соперничать с «настоящими» компьютерами середины-конца 1970-х годов. А потом просто уничтожили мини-компьютеры как класс. Потом привели к появлению современных серверов, чем-то напоминающих тогдашние мини-компьютеры. И теперь ПК сами исчезают из массового потребления под влиянием ноутбуков, планшетов и смартфонов.
Авторы книги декларируют, что события во всех подобных случаях развиваются по одному и тому же сценарию:
- сперва появляется потребность в каком-то продукте, решающем ту или иную задачу. Допустим, гибкий магнитный диск;
- поскольку на начальном этапе все конкурирующие решения (коих, в принципе, немного) строятся на одних и тех же технологиях, то рынок оказывается поделен между более-менее похожими продуктами;
- производители успешных продуктов начинают конкурировать между собой и в этой конкурентной борьбе доводят свои продукты чуть ли не до максимально выгодного соотношения цена-качество. И, главное, при этом существующие продукты максимально соответствуют взглядам на то, как должны решаться задачи, для которых эти продукты предназначены. Грубо говоря, все знают, что для переноса информации с одного мини-компьютера на другой посредством гибких магнитных дисков отлично подходят 8" дискеты. И другого способа никто в мейнстриме себе не представляет;
- где-то в стороне, базируясь на каких-то новых открытиях/материалах/технологиях, появляется новый продукт, существенно уступающий уже имеющимся продуктам практически по всем параметрам. Как правило, дороже. Как правило, не сопоставимый по возможностям/мощностям. Но обладающий очень важным качеством: он может применяться там, где невозможно или затруднительно применять мейнстримовые решения. Например, в персональный компьютер не воткнешь 8" дисковод. А владельцам ПК не интересно таскать с собой 8" дискеты. Это просто не практично. Тогда как 5.25" дискета — вполне себе. И плевать, что по стоимости килобайта 5.25" дискета изначально была дороже 8". Выбора-то все равно не было;
- вокруг нового продукта формировался новый рынок, который изначально был совсем не интересен производителям мейнстримовых продуктов. Но, поскольку это был новый рынок, то на него устремялись новые игроки, которые создавали серьезную конкуренцию друг другу. И эта конкуренция быстро переводила маргинальную и неэффективную технологию в чрезвычайно эффективную. Которая вскоре переставала быть маргинальной, ибо как только она превосходила по совокупности своих качеств старые мейнстримовые продукты, она стремительно заменяла их в их собственной нише. Грубо говоря, если вокруг есть сотни тысяч 5.25" дисководов и миллионы 5.25" дискет, то какой смысл держаться за гораздо более маргинальные на данный момент 8" устройства?
Языки программирования как технологические продукты
На первый взгляд похожие аналогии можно увидеть и в том, как языки программирования приходили в мейнстрим. И, пожалуй, наиболее ярким примерами здесь являются языки Java и Go.
Язык Java появился как результат попытки создать «правильный C++». Но получившийся тогда язык вряд ли был бы кому-нибудь нужен, т.к. он был весьма убогий и, что еще важнее, медленный и прожорливый. На тогдашнем десктопе, даже учитывая бурный рост мощностей ПК в то время, у Java не было никаких шансов.
Однако, Java зашла на «рынок» совсем с другой стороны, через нишу, в которой никто тогда толком работать не мог: через Интернет и апплеты для браузера (все было несколько сложнее и был еще рынок JavaCard и STK-апплетов, но не будем уходить в дебри). Интернет был очень горячей темой, а делать динамичные сайты/страницы тогда было просто не на чем (JavaScript появился уже после анонса Java). И, поскольку кроме Java применить было нечего, Java начали применять. Ну а потом, по мере ее развития и преодоления детских болезней, Java вышла в другие сферы, вытеснив оттуда другие технологии. Хотя в том же десктопе существенного куска пирога она себе откусить не сумела.
Язык Go вряд ли смог бы кого-то заинтересовать в традиционных нишах, где уже жили C, C++, Java, C#, Python, Ruby и т.д. Но развитие продуктов для Интернета породило еще одну нишу — RESTful-сервисы. Для разработки которых Java была оверкиллом, C++ слишком сложным и опасным, Python/Ruby и прочая динамика — слишком тормозными. И вот один из самых убогих в выразительном плане языков, разработанных в XXI-ом веке, становится чуть ли не серебряной пулей для этой прикладной ниши. Откуда, возможно, со временем распространится еще куда-нибудь (чему лично я не удивлюсь с учетом общего уровня квалификации молодого поколения разработчиков).
Так что складывается ощущение, что с языками программирование должно происходить тоже самое, что и с другими технологическими продуктами: появление новых языков ведет либо к практически полному исчезновению, либо к вытеснению в отдельные маргинальные ниши предыдущих языков. При этом старые языки, в процессе своего развития в рамках конкурентной среды, становятся все более мощными и выразительными для более дешевого решения типичных для них задач. Как следствие, старые языки становятся все более и более объемными и сложными. И менее привлекательными для использования вне тех ниш, которые они уже успели занять.
Поэтому кажется, что жизненный цикл технологического продукта, описанный в «Дилемма инноватора», должен распространяться и на языки программирования.
Но не все так просто с языками программирования
В описанной в «Дилемме инноватора» схеме проникновения новых технологических продуктов на рынок одна из важнейших причин перехода пользователей со старых мейнтримовых продуктов на новые — это либо просто существенное снижение стоимости владения, либо получение ранее доступных возможностей + снижение стоимости владения.
Скажем, развитие ПК и рост их мощности делает владение парком ПК дешевле, чем владение одним или несколькими мини-компьютерами. Трехдюймовые дискеты в итоге оказываются дешевле на единицу емкости, чем 5.25" (с учетом большей надежности и пр. факторов). Цифровая фотография в итоге оказывается дешевле на кадр, чем пленочная. И т.д.
Но с переходом от одного продукта к другому связаны еще два важных показателя — это стоимость/сложность непосредственно перехода, а так же скорость, с которой можно перейти на новый продукт. Эти показатели вполне можно оценить в деньгах. И если выгода от перехода на новый продукт присутствует, то переход осуществляется. Возможно не быстро, но осуществляется.
И вот тут-то выясняется, что стоимость перехода с одного языка программирования на другой гораздо выше, чем в случае перехода от мини ЭВМ к ПК, от 8" дискет к 5.25" или от HDD к SSD. Поскольку смена языка программирования — это, обычно, полное переписывание программного продукта. Зачастую с нуля.
А что значит переписывание? Оплата труда новой команды программистов, которой потребуется повторить уже имеющийся в продукте функционал. И пусть даже новый ЯП позволяет в два раза сократить размер команды, все равно это будет означать существенные затраты. Ибо если на разработку старой версии продукта было потрачено $10M, то переписывание потребует, минимум, $5M.
Но, что еще более важно, это то, что переписанный продукт не появится вот так сразу, одномоментно. Нужно время. Много времени. Опять же, если предположить, что новый ЯП позволяет писать работающий код в два раза быстрее, то переписывание продукта, разработка старой версии которого заняла 5 лет, потребует всего-навсего 2.5 года.
Получается, что прямо сегодня нужно начать вкладывать кучу денег, чтобы в какой-то неблизкой перспективе получить копию того, что давно работает и приносит деньги уже сейчас.
И нужно упомянуть еще одну сторону этой медали: если программный продукт эксплуатируется в меняющихся условиях, то продукт неизбежно дорабатывается или перерабатывается под современные нужды. При этом у бизнеса нет возможности подождать год-полтора, пока появится новая версия продукта на новом ЯП, новая функциональность, как правило, нужна в обозримое время. Зачастую, еще вчера.
Поэтому при переписывании расходы увеличиваются: нужно одновременно и писать новую версию, и развивать старую.
На мой взгляд, именно это и объясняет, почему новые языки «выстреливают», в первую очередь, в разработке новых приложений для новой прикладной ниши. А вот вытесняют старые языки из ранее занятых областей уже гораздо медленнее. Самыми яркими примерами, наверное, могут считаться Fortran и Cobol. Мало того, что написанный на них софт все еще эксплуатируется, так и новый код на этих языках продолжают писать. И сами эти языки эволюционируют.
И мне кажется, что один из самых страшных снов владельцев программных продуктов на Cobol, — это переписывание продукта на Java или C# ;)
И еще один важный фактор: развитие самого ИТ
Еще один момент, на который я хотел бы обратить внимание, — это факт того, что ИТ существует не так давно и история эволюции языков высокого уровня еще короче. Первая часть этой истории вряд ли даст нам твердую опору для рассуждений о том, как одни языки заменяют другие. 1950-е и 1960-е — это были годы экспериментов. Да еще годы, когда сам рынок компьютеров был сегментирован и значительная часть ПО писалась под конкретные компьютеры и ОС, без особых требований к переносимости. Количество действующих разработчиков ПО, а так же спектр областей, в которых широко применялись компьютеры, не идет ни в какое сравнение с текущим положением дел.
ИМХО, принципиально вещи поменялись в 1970-х годах, а с 1980-х мы наблюдаем появление ЯП, которые уже опираются как на практический опыт прошлых лет, так и на результаты теоритических исследований. Как по мне, так именно 1980-е (и, может быть, поздние 1970-е) являются началом эпохи языков программирования, нацеленных на производство ПО в промышленных масштабах. Ибо именно тут мы видим Modula-2, SmallTalk, Ada, C++, Eiffel, объектные расширения Паскаля, Objective-C, Perl.
Поэтому далее я буду отталкиваться именно от того, что появилось уже в эту эпоху промышленных ЯП.
Кстати говоря
Перечисляя языки, появившиеся в 1980-х, я вспомнил про ЯП разработанные Никлаусом Виртом: сперва Pascal, затем Modula/Modula-2, затем Oberon.
На примере этих языков можно увидеть, как опыт их автора приводит к появлению инструментов, учитывающих недостатки предыдущих попыток, а так же отвечающих новым требованиям своего времени.
Но эти же языки показывают и то, насколько важно пользователям ЯП оставаться в рамках однажды выбранного языка. Переход с Pascal на Modula-2 был. Но отнюдь не массовый. И, не смотря на то, что Modula-2 более-менее активно использовался, настолько же популярным, как наследники Pascal, в особенности Delphi, так и не стал. А уж какого-то заметного перехода на Oberon так и вообще не наблюдалось, насколько я помню.
Востребованный язык программирования нельзя просто так заменить. И что из того?
Итак, основной посыл в моих предшествующих рассуждениях в том, что если язык нашел более-менее широкое применение и с его помощью было создано множество разнообразных, находящихся в повседневном использовании программных продуктов, то этот язык не может быть просто так полностью заменен другими языками программирования. Тем более за короткое время.
Успешные языки программирования обречены на то, чтобы продолжать использоваться годами. И, скорее всего, эволюционировать.
А эволюция языка программирования подразумевает расширение языка новыми возможностями. Что неизбежно, т.к. прогресс не стоит на месте. Люди придумывают более удобные способы решения известных задач. Сталкиваются с новыми задачами, для которых нужны дополнительные выразительные возможности языков программирования. И, поскольку язык программирования всего лишь инструмент, то люди идут по пути совершенствования своего инструмента.
Что означает, что широко востребованные языки программирования просто обречены на то, чтобы становиться все объемнее и сложнее, по ходу своего развития обрастая возможностями, о которых изначально даже не было и речи.
В принципе, об этом давным давно говорил Бьярн Страуструп. И даже то, что я сам наблюдаю в течении почти что тридцати лет, подтверждает слова Страуструпа. Скажем, современная Java уже очень сильно отличается от Java 1.0 образца 1995-го года. Язык C# демонстрирует еще более впечатляющую эволюцию от удачного клона первой Java до, пожалуй, самого выразительного мейнстримового языка, пригодного для использования индусопрограммистами (вне зависимости от их национальности).
Но самый яркий пример для меня — это все-таки язык Go. Который уже в XXI-ом веке начали делать сознательно повыбрасывав кучу вещей, отлично зарекомендовавших себя на протяжении десятилетий широкого использования в разных ЯП. И который благодаря, в том числе и этому, стал популярным. Но, тем не менее, жизнь берет свое и в Go вынуждены добавлять то, от чего авторы изначально намеренно отказывались — средства для обобщенного программирования (aka шаблоны/генерики).
Так что востребованные языки программирования эволюционируют в сторону расширения своих возможностей. А значит и усложения. Поскольку новые возможности нужно добавить так, чтобы не поломать всерьез уже написанный код. Ибо эпическая история с Python второй и третьей версий стала наглядным примером, повторить который отважатся немногие.
А так ли это плохо?
Негативная сторона постоянно увеличивающейся сложности языков программирования (в особенности такого языка, как C++), вроде бы, очевидна: слишком высокий порог входа. Слишком много времени нужно потратить при изучении языка для того, чтобы начать выдавать код приемлемого качества в приемлемые сроки. Что делает разработку на сложном языке программирования и дорогой, и рискованной. Что будет, если из проекта уйдет один или несколько квалифицированных разработчиков? Как быстро и просто будет найти замену? Непростые вопросы.
С другой стороны, поскольку язык программирования — это такой же инструмент записи намерений конкретного человека, как и, например, математические выражения, то уместно провести аналогию с математикой.
В школе мы начинаем изучать математику начиная с простейших арифметических операций. Потом переходим к более сложным вещам: дробям, степеням и корням. Потом идем еще дальше, в сторону логарифмов. Потом немного захватываем интегральное исчисление. Аналогично и с геометрией.
В результате выпускник нормальной средней школы обладает неким математическим аппаратом, который вполне может быть избыточным для отдельно взятого человека. Не ошибусь, если предположу, что многим после школы вообще никогда не требовалось вычислять что-либо с помощью логарифмов или брать интегралы.
Тем не менее, математический аппарат, который осваивается в средней школе, не идет ни в какое сравнение с тем, что студентам ВУЗов затем будут давать на курсах по высшей математике. Особенно если это студент математического или физического факультета (да и не только, серьезно загружают высшей математикой на многих специальностях).
Но ведь никому же не приходит в голову ругать математику за то, что чем глубже в нее погружаешься, тем она сложнее. Поскольку если человек сталкивается с областью деятельности, где ему требуется ТФКП, то ничего не поделаешь, ТФКП придется изучать. Как бы это ни было сложно. Ну и да, нормально то, что не у всех получается.
Собственно, тоже самое и с языками программирования.
Если вам нужно решать относительно простые задачи, то у вас есть выбор: либо вы используете более простой язык программирования, либо вы используете ограниченное подмножество более сложного языка. Но если вы столкнулись со сложной задачей (или специфическими условиями для ее решения), то у вас может не быть такого выбора вообще: трудоемкость решения этой задачи на «простом» языке может быть слишком большой.
Кстати о задачах
Время от времени доводится встречать утверждение, что практически все сложные задачи уже решены (т.е. ОС разработаны, СУБД множество, ПО промежуточного слоя на любой вкус и цвет, и т.д., и т.п.), поэтому в большинстве своем остается только несложная и однообразная рутина, для борьбы с которой сложные инструменты и не нужны.
Мне сложно согласиться с такой точкой зрения. Но и уверенно опровергнуть я ее не могу, т.к. вот просто физически не имею возможности обозреть все то, что происходит за пределами свой узкой профессиональной ниши. Сам я, вроде бы, занимаюсь не самыми простыми задачами. Но, при этом, цель моих усилий состоит в том, чтобы другим людям было проще выполнять свою работу. Так что, кажется, я сам стремлюсь к тому, чтобы становилось больше несложной и однообразной рутины.
Кроме того, судя по распространенности таких языков, как Go, Python, Ruby и PHP, можно предположить, что в процентном соотношении количество задач, в которых действительно востребованы сложные инструменты, постепенно сокращается. При этом, благодаря тому, что в абсолютных цифрах программируют все больше и больше, может оказаться, что сложных задач с годами все равно становится все больше. Пусть даже над их решением работает все меньший процент разработчиков ПО.
Однако, нужно принять в рассмотрение еще и такой фактор, постоянно усиливающийся с течением времени, как увеличение объема работы, приходящейся на одного программиста. Грубо говоря, если 25 лет назад один разработчик мог делать средней сложности форму для GUI приложения за один рабочий день, то сейчас такую же форму нужно склепать за два часа. А все остальное время потратить на другие задачи, которые бы 25 лет назад заняли бы еще неделю.
Поэтому вполне может быть так, что вроде как вокруг тебя и несложная с виду рутина, но ее так много, что ее объем сам по себе представляет сложность. И поэтому сложный инструмент, который позволит автоматизировать рутину и/или снизит количество ошибок при ее реализации, так же может оказаться предпочтительнее более примитивного инструмента, пусть и более простого в освоении.
Так что, с одной стороны, действительно, можно предполагать увеличение количества задач небольшой или средней сложности, для которых нет смысла использовать сложные языки, уровня C++, Scala или Haskell. Но, с другой стороны, сложные задачи все равно никуда не исчезают. Да и простая, но объемная, задача так же может потребовать для себя инструмента уровнем повыше Go или чистого C.
При этом в нашем распоряжении есть языки программирования на любой вкус и цвет. Можно выбрать любой. Поэтому, как уже говорилось выше, у разработчиков есть выбор: можно взять простой инструмент для решения простой задачи, можно взять инструмент посложнее, но ограничиться только частью его возможностей. Поэтому, если кого-то смущает сложность современного C++, то это вообще не проблема. Т.к., во-первых, нет смысла применять C++ всегда и везде. И, во-вторых, никто не заставляет использовать сразу все фичи C++ в конкретном проекте, достаточно ограничиться только тем, что реально нужно.
Итого
Итак, что же можно сказать в итоге? Несколько вещей:
- во-первых, времена изменились. Ситуация, когда язык программирования появился, начал широко использоваться, а затем исчез «с радаров», каковая имела место быть в 1960-1970-х годах, стала совсем иной. Появился спектр широкоиспользуемых аппаратных платформ, которые живут долго и будут жить долго. Время доказало всем важность такой штуки, как переносимость кода. Сами программы стали гораздо объемнее и сложнее (особенно с учетом всех их зависимостей). Поэтому если какая-то программа вчера была написана на языке X, сегодня она приносит пользу и завтра надобность в этой программе внезапно не пропадет, то язык X никуда не денется. Собственно, убедиться в этом можно посмотрев на язык C. Говно редкое, да и древнее как копролиты мамонта. Но многие продолжают жрать кактус. Причем, немалое их количество — добровольно и с удовольствием. Так что у мейнстримовых языков сейчас шансов остаться широковостребованными гораздо больше;
- во-вторых, поскольку языки программирования не будут сменяться один другим так быстро, как это происходило лет 40 назад, то каждый востребованный язык будет неизбежно эволюционировать. Как по мне, так очевидно, что в сторону усложнения. Ну и увеличения объема, т.к. в языке будет сохраняться то, что в нем было изначально, так и будет накапливаться новое;
- в-третьих, есть приличный выбор инструментов для различных задач. Где-то можно обойтись Ruby/Python, где-то подойдет Go, где-то дешевле всего будет остановиться на Java. Ну а где-то придется воспользоваться Rust-ом, C++, Scala или Haskell-ем. Так что вовсе не обязательно связываться со сложными языками программирования, если перед вами нет реально сложных задач. А если сложный инструмент все-таки потребовался, то вовсе не обязательно использовать его «на полную катушку», можно ограничиться лишь более простым и понятным для вас подмножеством сложного языка. И это нормально.
Но главное, пожалуй, все-таки другое: специфика работы программиста заключается в том, что в этой профессии учиться чему-то новому и переучиваться приходится всегда.
Автор: Евгений Охотников