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



Изначально я человек первой культуры и очень долгое время считал вторую несерьёзной. Пару-тройку лет назад я окончательно понял, что ошибался. Многие «старички» ошибаются в ту же сторону, а в последние годы ещё большее число людей ошибаются в обратную. Знакомство с соседней культурой и понимание, почему дела в ней делаются так, как там принято, превратит вас в лучшего разработчика.

Размер кодовой базы


Культура 1: важны большие проекты
Культура 2: важны короткие фрагменты кода, насыщенные содержанием

Возможно, именно из этого различия следуют все остальные. Молодые разработчики не всегда понимают, насколько монструозны проекты первой культуры. Например, современная компьютерная игра AAA-класса, если писать её на C-подобном языке, — это несколько миллионов строк кода, уже больше, чем вы когда-либо сможете даже просто прочитать. Linux — штука более серьёзная, в нём больше 15 миллионов строк; Windows и macOS ещё в несколько раз больше. В интернете пишут, что автопроизводители каким-то образом переплюнули даже эти цифры и в «Мерседесе», помимо собственно автомобиля, едет 100 миллионов строк кода. Я не знаю, стоит ли такому верить, и думаю, даже если стоит, эти строчки кода довольно бессмысленные. В любом случае и 100 миллионов — ничто по сравнению с кодовой базой компании FAANG-класса, в которой могут содержаться буквально миллиарды строк.



Даже довольно «тупой» код типа бизнес-логики оказывается вовсе не тупым, если его приходится писать и читать в подобных количествах. Наверное, больше всего это похоже на написание романа: каждое следующее слово просто берёшь и пишешь, вроде бы. Но на больших масштабах слова или складываются в непротиворечивый работающий сюжет с интересными персонажами, или нет. И если просто графоманить, не придумав себе некоторые высокоуровневые принципы и инварианты, то точно не сложится. И тот факт, что вы уже можете написать короткий рассказ, ещё не означает, что вы научились писать книги.

Чтобы ориентироваться в большой кодовой базе, нужны средства навигации и рефакторинга. И то и другое гораздо проще в одних языках, нежели в других.

  • Возьмём, например, простейшую фичу go to definition: в месте вызова функции/метода посмотреть, где её код. В языках первой культуры сделать это тривиально: определение функции имеет чёткий синтаксис; их конечное число; омонимы, даже если есть, в конкретном месте различимы при помощи формальной процедуры проверки типов и областей видимости. VS Code и прочие IDE, конечно, притворяются, что и в Python или в JS тоже можно сделать go to definition, и иногда им это даже удаётся. Но поскольку функции — first class objects, «имя функции» и «тело функции», вообще говоря, относятся друг к другу как «многие к многим».
  • Или возьмём разделение доступов к членам класса — private/public. Эта фича на самом деле не имеет никакого отношения к безопасности в смысле security, как могло бы показаться со стороны, а важна она именно для выживания в больших проектах. Она позволяет очертить границы, «внутри» которых можно на обозримом пространстве определить API общения с данным куском кода, поддерживающее все нужные инварианты, а «снаружи» дёргать только это API, не имея физической возможности случайно, по ошибке, их нарушить. Если такого разделения нет, то в проекте на миллионы строк любой код может случайно нарушить работу любого другого и выживание, конечно, будет серьёзно затруднено.

Опять-таки для человека из первой культуры вторая начинает казаться игрушечной. Это совершенно неверно.

В проектах огромных размеров совершенно не важны разовые накладные расходы, поэтому создатели языков первой культуры их и не экономили. В частности, совершенно неважно, сколько занимает программа, выводящая «Hello, World!». Плюсовик начнёт её с заклинания #include <iostream>, дальше напишет ещё несколько строк и не видит в этом ничего особенного; джавист должен для начала объектно-ориентированно определить специальный класс. Для питониста всё это дико, почему нельзя просто написать print("Hello, World!"), как у нормальных людей?



«Hello, World!» программист на каждом новом для себя языке пишет ровно однажды, но это же относится ко всем случаям, где мы хотели бы, чтобы в несколько строк происходило нечто достаточно содержательное. REPL и первая культура совместимы очень плохо; скажем, Jupyter-ноутбуки, несмотря на букву J, на Java были бы невозможны (теоретически, наверное, можно изобрести способы, но у людей первой культуры их даже просто придумать не получилось бы).

То же упомянутое выше разделение доступов очень плохо сочетается с концепцией, в которой объекты — просто коробочки с разнородными данными, способные возникать откуда угодно. Например, добавляться уже в момент исполнения, через eval. Это делает систему удивительно управляемой и конфигурируемой в рантайме: вы можете прямо в процессе дебага исправить код, мгновенно заменить его на лету и продолжить исполнение. Или вы можете иметь конфиг системы на том же языке, что и вся система.

Для меня в своё время было шоком узнать, что реализация трансформерных нейросетей (великих и ужасных) занимает порядка 100 питоновских строк. Естественно, это очень высокоуровневый код, и понятно, что такие строчки невозможны без torch, NumPy, CUDA и так далее; тем не менее аналогичная компактность кода в первой культуре непредставима.

Ясно, что при подобной выразительности скорость разработки в подходящих ситуациях драматически растёт.

Скорость


Культура 1: важна скорость работы программы
Культура 2: важна скорость работы программиста

Для программиста на любом, наверное, языке программирования, кроме C/C++, крайне странно слышать, что поведение написанной на них программы может оказаться «неопределённым» (undefined), «неуточнённым» (unspecified), «определяемым реализацией» (implementation-defined) — и это три разные вещи. «Неопределённое» поведение (худшее из трёх) означает, что программист ошибся, но не означает, что программа выдаст ошибку; стандарт официально разрешает ей в этот момент сделать буквально что угодно. Зачем такое вообще могло понадобиться?!

Многие из вас уже знают ответ: это позволяет компилятору делать всякие интересные оптимизации на низком уровне. Например, увидев, что раскрытие макроса или шаблона привело к выражению (x+1 > x), компилятор может смело заменить его на true. Но что, если x == INT_MAX? Поскольку переполнение int'а — undefined behavior, компилятор имеет право проигнорировать этот экзотический случай. На простом примере мы заметили нечто жуткое: при исполнении программы, вообще говоря, не существует момента, в который «произошло» неопределённое поведение; его нельзя «детектировать», так как оно осталось в параллельной вселенной (но испортило нашу).

Если вам не приходится программировать на С/C++, то, скорее всего, вы в ужасе: люди пишут программы вот так?! Верно, пишут уже 50 лет и всё это время регулярно стреляют себе в ногу. Удобство для компилятора важнее удобства для программиста.

Примеров абсолютно противоположного подхода очень много в Python. Начнём с банального:

>>> x = 2**32
>>> x*x
18446744073709551616
>>> x*x*x
79228162514264337593543950336

Никакого переполнения и никаких проблем с ним! Голова программиста немного освободилась: целое число — это просто целое число, думать об INT_MAX больше не нужно. Естественно, у этого есть своя цена: bigint-арифметика намного медленней встроенной.

Вот чуть менее известный пример:

>>> 15 % 10
5
>>> -15 % 10
5

Целочисленное деление в Python подразумевает, что остаток от деления на N всегда будет числом от 0 до N-1, в том числе и для отрицательных чисел. В C/C++ это не так: остаток от деления -15 на 10 равен -5. И опять один подход экономит время и когнитивную нагрузку программиста: как узнать время суток, если у вас есть timestamp? Должны ли вы в этот момент думать о том, может ли текущий timestamp оказаться старше 1970 года? Это не случайность, сам Гвидо ван Россум подтверждает, что выбрал такую семантику именно из подобных соображений. Ну а второй подход лучше ложится на конкретное hardware и потому на сколько-то пикосекунд быстрее.

Чтобы оценить, насколько подобные вопросы волновали создателя Python и насколько тщательно он всё продумывал, последний пример: как думаете, что вы увидите, запустив такое?

>>> round(1.5)
>>> round(2.5)
>>> round(3.5)
>>> round(4.5)

Ответ
>>> round(1.5)
2
>>> round(2.5)
2
>>> round(3.5)
4
>>> round(4.5)
4

Проверьте. Это не баг, так и было задумано. Попробуйте догадаться (или найти в интернете), почему с точки зрения второй культуры правильно округлять именно так, хотя это и добавляет необходимость в дополнительных проверках внутри реализации функции round.

Наконец, интересно, что при глубоком понимании обеих культур вполне возможен удачный компромисс между скоростью работы программы и программиста. Тяжёлыми вычислениями занимается библиотека, написанная на С++ (а то и на Фортране), снабжённая биндингами для Python или Lua, а уже на последних из этих библиотечных функций, как из кубиков Лего, собираются сложные конструкции. Приведу пример: наверное, самые вычислительно нагруженные IT-проекты современности — обучение и инференс больших нейросетей — сейчас в основном развиваются в рамках второй культуры. «Числодробилка» находится где-то глубоко под капотом; знать о её особенностях всё ещё полезно, но уже не обязательно — даже для получения результатов мирового класса.

Тестирование


Культура 1: можно математически строго доказать, что код не содержит ошибок определённого типа
Культура 2: есть статистические свидетельства, что код практически всегда делает то, чего мы от него ожидаем

После раздела, противопоставлявшего скорость работы программы и программиста, может показаться, что в первой культуре вообще не принято заботиться о безопасности и удобстве. Это обобщение, конечно, неверно (как и вообще любое сверхобобщение). Программисты как раз очень любят, чтобы компилятор проверял за них всё-всё-всё; на идее «а что, если компилятор сможет проверять всё, что только можно» основан целый язык Rust, сейчас стремительно становящийся мейнстримом.

Вообще, в первой культуре от ошибок защищаются статически. К этой группе относятся проверки типов на этапе компиляции, проверки формальных спецификаций и инструменты анализа кода. Кстати, такие проверки можно производить не только над кодом, но и над данными на этапе их «компиляции» в представление, с которым потом работает рантайм-код.

Во второй культуре от ошибок защищаются статистически, с помощью юнит- и регрессионного тестирования, а также ассертов, функциональных тестов, ручного тестирования и некоторых более экзотических практик типа fuzzing.

Как уже можно заметить, строгой границы между культурами нет; некоторые плюсовики старой школы с удовольствием пишут юнит-тесты, а некоторые питонисты — анализаторы кода. И всё же каждая культура тяготеет к своему набору практик. Это замечание верно и для всех остальных отличий между ними.

Свойство, которое отличает эти две группы практик, — доказательность. Скажем, компилятор С++ или Java гарантирует нам, что объект «не того типа» не может быть передан в качестве параметра функции, а у объекта не может быть вызван несуществующий метод. В Python проверить то же самое можно с помощью юнит-тестов, регрессионных и функциональных тестов, а также прославленным в веках методом «несколько раз запустить и посмотреть, не упадёт ли». Хотя оба подхода на практике могут обеспечивать надёжность, которая всех устраивает, отличие между ними довольно строгое: никакие тесты не покроют множество возможных ситуаций целиком. Всегда остаётся крохотная возможность, что пользователь программы сделает нечто экзотическое и скрытый баг вылезет наружу.



Конечно, тут вам не математика, и даже «доказательства» при помощи компилятора не могут быть абсолютно надёжными. Например, в самом компиляторе теоретически может содержаться редкая ошибка. На практике, однако, такой возможностью можно смело пренебрегать. Причин масса:

  • В среднем по сравнению с обычными программами компиляторы гораздо надёжнее, и их поведение гораздо строже регламентировано.
  • Даже если компилятор «умолчал об ошибке», она вскроется при небольшом изменении в коде.
  • Мы говорим не об одном редком событии, а о стечении сразу двух маловероятных обстоятельств (компилятор не заметил ошибку программиста, которая при этом крайне редко проявляется)
  • И так далее.

Большой пример

Много лет в разных обсуждениях на эту тему я приводил такой вот пример. Давным-давно, когда я работал в геймдеве над игрой для консоли Nintendo DS в жанре RTS, тестировщики нашли рассинхронизацию в мультиплеере.

Это крайне неприятный тип ошибки. Многопользовательская игра у нас была организована по p2p-схеме: разные DS-ки передавали друг другу только команды пользователей и каждая просчитывает состояние всего мира. Это идеальная для RTS схема, позволяющая организовать богатую фичами игру на основе очень узкого канала передачи данных и минимального «языка общения». Основной её недостаток в том, что вся игровая логика должна быть абсолютно детерминированной, то есть, получая один и тот же набор команд, на выходе всегда иметь одинаковое состояние игрового мира. Нельзя допустить никакого рандома, никакой зависимости от системного таймера, никаких шалостей с битами округления, неинициализированных переменных и тому подобного.

Как только рассинхронизация произошла, она уже фатальна: расхождения, даже начинающиеся с одного бита, быстро накапливаются и скоро оба игрока видят совершенно разные картины происходящего (естественно, оба выигрывают с огромным перевесом). При помощи вычисления контрольных сумм факт рассинхронизации можно установить с точностью до такта просчёта игровой логики, а вот дальше уже нужно целое расследование. Если вы можете себе позволить сериализовать все игровые данные с какими-то аннотациями, а затем сравнить два дампа с разных устройств, расследование несколько упрощается. К сожалению, у нас и этой возможности не было: мы должны были работать на Nintendo DS, консоли с крохотным объёмом памяти.

Вот какое описание бага я получил от тестировщика: «Иногда при невыясненных обстоятельствах происходит рассинхронизация. Что именно к ней приводит, непонятно. Reproduce: создать игру на четырёх человек и играть до посинения, активно пользуясь героями и их возможностями. Если игра закончилась и всё хорошо — повторить. Рано или поздно, сейчас или через день, ошибка случится».

Неприятно. В попытках повторить баг я провёл пару часов, но лишь убедился, что да, оно случается. Что делать?

Хорошо, а почему вообще игра может рассинхронизироваться? Насчёт неинициализированных переменных, к счастью, можно было не волноваться: мы написали собственное управление памятью, геймплейные данные всегда лежали в отдельном месте, изначально заполненном нулями, так что их состояния в отсутствие рассинхронизации совпадали бы до бита, даже если бы неинициализированные переменные встречались. Значит, кто-то совершил святотатство — позвал из игровой логики функцию, которая не обязана работать синхронно, например обратился к интерфейсу. Строго говоря, сделать это не так-то просто: нужно как минимум написать внутри игровой логики #include <../../interface/blah-blah.h> и ни о чём в этот момент не задуматься. Простой поиск регулярного выражения в коде показал, что такую глупость никто не совершал.

Тут я осознал, что передо мной задача проверки типизации. Не очень, правда, распространённая: меня интересовали не типы в смысле языка, а «логические типы» функций. Какой-нибудь Haskell не видит особенных различий между этими понятиями, и правильно делает, но у нас-то C++.

Итак, все наши функции, методы классов и данные должны делиться на синхронные и асинхронные. При этом синхронные функции могут спокойно звать асинхронные (ради side-эффектов), но не могут пользоваться возвращаемыми ими значениями. Например, геймплейная функция может сказать: «Интерфейс, покажи пользователю сообщение», — но не может спросить: «Интерфейс, какой у пользователя viewport?» И наоборот, асинхронные функции не могут вызывать синхронные с side-эффектами, но могут свободно пользоваться возвращаемыми ими значениями («Геймплей, какой процент здоровья у такого-то юнита?»).

Что здесь важно:

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

Отлично, оставалось поработать заместителем компилятора и явным образом переименовать все фунции doSmth на границе между игровым миром, интерфейсом и графикой либо в doSmthSync, либо в doSmthAsync, параллельно отслеживая, кто кого вызывает. Не прошло и часа, как все нарушения «типизации» были ликвидированы, а я смог восстановить цепочку событий, приводящих к рассинхронизации.

Ошибка оказалась в том месте, где интерпретировалась команда сбрасывания наковальни (как известно, это такой мультяшный аналог cупероружия). Чтобы проверить, сброшена она куда-то в пустую точку игрового поля или же наведена на конкретного юнита, по ошибке использовалась конструкция isVisible(getCurrentPlayer()), а не isVisible(игрок, который сбрасывает наковальню).

Вот как воспроизводился баг: одному игроку нужно было построить Скаута, сделать его невидимым и пойти на вражескую базу. Второму нужно было построить Снайпера и использовать способность «сбросить наковальню» на точку, в которой стоит (или через которую проходит) невидимый Скаут — вернее, на верхнюю половину его туловища. Эта команда на одной DS-ке означала «сбросить наковальню в точку за торсом Скаута», а на другой — «сбросить наковальню на Скаута» (в точку у него под ногами). Мало того, важно было не подходить череcчур близко, чтобы не «заметить» Скаута и не вывести его из состояния невидимости.

Я не представляю себе юнит-, да и какой угодно ещё тест, способный отловить подобное фантастическое стечение обстоятельств. Чтобы в игре не было подобных ошибок, необходимы живые самоотверженные тестировщики и крайне желательна математическая строгость кода.



Коротко об остальном


Культура 1: проверка типов на этапе компиляции
Культура 2: duck typing

Почему так, уже должно быть примерно понятно по предыдущим разделам. Элементы и того, и другого, применённые к соседней культуре, точечно могут оказаться крайне полезными, если понимать, что «так можно было».

Культура 1: «Я прочитал код всех библиотек, от которых зависит мой проект, и уверен в них»
Культура 2: npm install left-pad

Новый код, который делает что-то новое, — и ценность, и нагрузка. Возьмём гигантский проект, где каждый десяток тысяч строк реализовывает собственную чётко определённую спецификациями функцию (иначе воцарится хаос и всё умрёт). В нём каждая новая строчка кода, особенно написанная не вами, — это боль и потенциальная мина замедленного действия. С другой стороны, иногда вам нужно просто загрузить картинку в png и определить, нарисован ли на ней хот-дог. Вы либо можете сделать это в четыре строчки вот прямо сейчас, не приходя в сознание, либо нет, и люди второй культуры гораздо чаще могут.

Культура 1: статическая сборка
Культура 2: динамическая сборка, включая подтягивание библиотек из неведомых глубин интернета

Есть два вопроса:

  • Могут ли незнакомые вам люди внезапно сломать ваш продукт без всяких действий с вашей стороны?
  • Могут ли незнакомые вам люди значимо улучшить ваш продукт без всяких действий с вашей стороны (например, закрыть дырку в безопасности, о которой вы и не подозревали)?

Как несложно догадаться, ответы на эти два вопроса максимально взаимосвязаны.

Культура 1: документация ведётся локально
Культура 2: документация находится в интернете (на сайте, Гитхабе, Read the Docs, Stack Overflow)

Казалось бы, мелочь. Какая разница, где хранится что-то, что по сути является текстом (ну хорошо, гипертекстом)? Но и тут различие довольно характерное. В одном случае документация лежит у меня на компьютере, она «моя», точно относится именно к той конкретной версии, которую я использую, не изменится без моего ведома и так далее. В другом случае я живу и развиваюсь вместе со всем миром, имея шанс узнать новое об этой технологии, ровно когда это стало известно хоть кому-то.

Культура 1: формальные языки, алгоритмы поиска, анализ конечных автоматов, хитрые структуры данных
Культура 2: deep learning

В каждой культуре есть свои герои и великие маги. Они делают что-то настолько крутое и наукоёмкое, что вы хотите быть похожими на них. В первой культуре это создатели компиляторов и стандартных библиотек. Как знает каждый, кто читал книжку с драконом, даже простой компилятор — штука на редкость сложная. Написать на С++ очередной контейнер для данных очень просто, но сделать такой, которым будут пользоваться, — уже искусство. Технологическое достижение похоже на доказательство математической теоремы: «Таким образом, мы установили, что существует структура данных с амортизированным временем поиска О(1) и добавления O(ln ln N)».

Во второй культуре герои — люди, которые заставляют всех удивиться тому, что теперь компьютеры умеют ещё и ТАКОЕ. Довольно часто инсайдерам это понятно заранее, что ничуть не умаляет достижение. Например, понятно, что кто-нибудь первым обучит diffusion для генерации видео без хаков, end-to-end. И, скорее всего, это произойдет до наступления Q2 2023, но результат всё равно будет поражать, и тот, кто это сделает, всё равно будет героем.



Средний возраст «продвинутой технологии» в первой культуре — 25 лет, обычно её сделал кто-нибудь из множества {Керниган; Томпсон; Вирт; Хоар; Дейкстра; Торвальдс}.

Средний возраст «продвинутой технологии» во второй культуре — год, а часть её создателей вполне могут оказаться, скажем, бакалаврами MIT. Вторая культура позволяет даже «складывающим кубики студентам» (и, естественно, не только им) сложить их в нечто новое и полезное, пока бородатые гуру разбираются с проездами по памяти. И это не случайность, а следствие набора фундаментальных и мудрых принципов. Например, такого: «Производительность программиста почти всегда важнее производительности программы». Или такого: «Вашу технологию не нужно долго изучать или производить с ней дополнительные ритуалы, чтобы решать базовые задачи». Подобные принципы эту культуру и составляют. И на них тоже можно учиться.

Комментарии (92)


  1. baldr
    11.01.2023 11:07
    +27

    Ну вот опять начнем холивары на тему "статическая или динамическая типизация", "C или Python", "компилируемые vs интерпретируемые"...

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

    В смысле "несколько лет"? Даже Фортран появился уже лет 70 назад и тенденция пошла уже оттуда.

    Руководитель разработки международных проектов

    Простите, но по-моему только студенты и junior-разработчики еще по-серьезному могут сравнивать разработку на Python и на C или C++. Вы не будете красить стену фломастером - вы возьмете краску и валик - разные области применения же!


    1. yafinder Автор
      11.01.2023 11:41
      +16

      Вы не будете красить стену фломастером - вы возьмете краску и валик - разные области применения же!

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

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


      1. shpaker
        11.01.2023 11:48
        +7

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

        Вот под таким углом для меня статья новыми красками заиграла. Но я как-то не уловил этого по тексту.


        1. baldr
          11.01.2023 11:57
          +18

          А в статье этого и нет, практически. Может автор и не хотел этого, но большинство приведенных примеров довольно однобоки и очень спорны.

          Культура 1: статическая сборка

          Культура 2: динамическая сборка, включая подтягивание библиотек из неведомых глубин интернета

          Ну вот что это такое, а? Что вы там при статической сборке - просматриваете все исходники библиотек всех версий что ли? Вы точно так же из репозитория качаете какой-нибудь libgstreamer, причем, скорее всего, вообще бинарник. А если не бинарник - то он для своей сборки требует столько сторонних библиотек, что leftpad я бы, на вашем месте, постеснялся приводить в пример.

          Этот манипулятивный пример из серии:

          выберите наиболее безопасный маневр на дороге:

          - повернуть направо

          - повернуть налево с вероятностью сбить старушку на пешеходном переходе


          1. yafinder Автор
            11.01.2023 12:15
            +6

            Что вы там при статической сборке - просматриваете все исходники библиотек всех версий что ли?

            Есть опять же два подхода.

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

            2) Предпочитаем последнюю версию всех библиотек, фризим версию, только если на это есть серьезные причины. Предпочитаем внешние библиотеки "велосипедам", пишем что-то свое, только если ничего готового, решающего ту же задачу, не нашли. В этом случае, действительно, нужно "все исходники всех версий" читать, и никто этого не осилит делать, конечно.

            Ничего даже близко похожего на историю с leftpad в первом подходе произойти, мне кажется, не может. Зато второй защищен от уже исправленных известных уязвимостей старых версий и "велосипедов", потому безопасней в adversarial-смысле (по крайней мере, я много раз слышал такой аргумент)


            1. baldr
              11.01.2023 12:39
              +23

              Точно так же в серьезных компаниях никто ничего не тащит из интернета даже при разработке на Python или NodeJS. Ставятся внутренние репозитории с заранее одобренными пакетами и все собирается только оттуда.

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

              Вы в самом начале постулируете существование "только" 2 разных культур. Причем явно сразу классифицируете их как разработчиков на C/C++ и Python. И дальше по тексту, соответственно получается так что у вас первая "культура" (C/C++?) всегда использует более правильный подход, а вторая что-то там говнокодит.

              Это как из анекдота: "Есть 2 категории людей - те кто делит людей на категории и остальные".

              Так вот - нет явного разделения на такие "культуры", тем более на две. Основной посыл статьи - сразу неверный, хотя мысли вы, в целом, выражаете иногда верно.


              1. yafinder Автор
                11.01.2023 13:01
                +4

                Ставятся внутренние репозитории с заранее одобренными пакетами и все собирается только оттуда.

                "Ну вот что это такое, а? Что вы там <...> - просматриваете все исходники библиотек всех версий что ли?" (с)

                Выбор же все равно будет между тем, предпочитать контролируемые фиксированные устаревающие версии и "велосипеды" или постоянные апдейты до свежих и добавление новых зависимостей в ущерб наблюдаемости. Да, так получается, что какие-то программисты склоняются к первому, а какие-то ко второму. По моим наблюдениям, с используемым языком программирования это очень даже коррелирует.

                дальше по тексту, соответственно получается так что у вас первая "культура" (C/C++?) всегда использует более правильный подход, а вторая что-то там говнокодит

                Это не входило в мои намерения, и жаль, что это так читается. Я в последнее время пишу в основном на Питоне (хотя и не продакшн-код), очень одобряю подход "производительность программиста превыше всего" и то, до каких пределов можно её довести при таком подходе, меня как раз впечатляет. Скорость развития современного AI, мне кажется, невозможна была бы в "первой культуре".


                1. baldr
                  11.01.2023 13:20
                  +6

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

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


                  1. yafinder Автор
                    11.01.2023 14:21
                    +3

                    peace!


                1. Tresimeno
                  11.01.2023 15:36
                  +7

                  очень одобряю подход "производительность программиста превыше всего" 

                  Вам не кажется что этот подход имеет до боли много общего с "х-к,х-к и в продакшн + И так сойдет", которое аукается в виде изобилия одноразового вторичного продукта на рынке, причем не только в ИТ?


                1. PrinceKorwin
                  11.01.2023 18:44
                  +2

                  "Ну вот что это такое, а? Что вы там <...> - просматриваете все исходники библиотек всех версий что ли?" (с)

                  Извините. Не удержался влезть в ваш диалог.

                  Таки да. У нас и свои репы и код библиотек просматривается. Особенно пристально стали это делать после того как после недавних событий зловредный код для РФ в первых появился.

                  И статические анализаторы проверяются и анализаторы на зловреды. И часть глазами проверяется.


                1. WebConn
                  11.01.2023 19:56
                  +3

                  Насколько я видел (и представляю себе) применение тех же нейронок, которые были натренированы в Jupyter, после фазы research в больших проектах наступает фаза внедрения, когда получившуюся обученную сеть вводят в инфраструктуру, откуда она улетает в production.

                  А эта инфраструктура обычно больше похожа на проект "культуры 1" (даже когда написана на Python), потому что там надо учитывать кучу тонкостей вроде миграции с версии на версию, разворачивания на разных типах железа и так далее.

                  Мне тоже кажется, что предложенное вами разделение на "культуры" больше похоже на две фазы разработки продуктов, где сначала происходит r&d (где на качество кода в целом можно закрывать глаза), а потом внедрение (где разработка происходит скучно и медленно, потому что надо не поломать прод)


                1. morijndael
                  12.01.2023 07:36
                  +3

                  очень одобряю подход "производительность программиста превыше всего"

                  Это вы со стороны программиста одобряете :D
                  Со стороны пользователя вы скорее всего проклянёте разработчиков до девятого колена /half-joking

                  btw, по поводу питона, и его производительности для программиста — вопрос очень спорный. Там, где мощная система типов отловила бы всё на уровне линтера в IDE, в питоне просто вылетит исключение в самый неожиданный момент. Быстрее пишешь — дольше дебажишь. inb4: тайпхинты решают от силы половину таких случаев, и только если они используются. Они не спасут от рантаймовой магии с **kwargs

                  P.S. Естественно, речь про сложные системы. Для скриптов, где всё можно удержать в голове питон достаточно хорош


                  1. vkni
                    12.01.2023 19:28

                    тайпхинты решают от силы половину таких случаев, и только если они используются

                    И превращают язык в недо-С++, органично совмещая недостатки обоих подходов. Казалось бы, надо использовать Хиндли-Милнера...

                    Вообще, народ массово не понимает, что разные языки должны использоваться для задач разного масштаба. (См. пойнт статьи).


            1. DEADMC
              11.01.2023 18:58
              +5

              Предпочитаем внешние библиотеки "велосипедам"

              Это точно про Яндекс? :D


          1. AlexBonel
            12.01.2023 10:19
            +2

            А в статье этого и нет, практически.

            То есть названия поста и самых первых предложений:

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

            недостаточно?


      1. vkni
        11.01.2023 23:32
        +2

        Ровно наоборот, основной тезис - в обеих культурах можно подсмотреть полезные принципы и подходы, так что не игнорируйте их.

        Сказали вы, и в статье напрочь проигнорировали языки функционального программирования со статической типизацией и proof assistant's, растущие из них: OCaml/SML/Miranda/Clean/Haskell/Idris/Agda/Coq...

        ;-)

        Они как раз растут из идей Lisp, который и ввёл вот эту совершенную безбашенность кода (см. макросы и недавно мною переведённую статью про quasiquotation) и REPL. При этом, языки из семейства ML совмещают гибкость Lisp и строжайшую статическую типизацию.

        Зато вы упомянули Rust, который является наследником Clean, OCaml и C++! Это хорошо! ;-)

        А вы, оказывается, всё некинокритикуете. :-) Спасибо за статью!


    1. johnfound
      11.01.2023 19:17

      Вы не будете красить стену фломастером — вы возьмете краску и валик — разные области применения же!

      А художники регулярно именно так и делают. В самом широком смысле (не всегда фломастером и не всегда стену), если понятно о чём я. ;)


      1. QDeathNick
        12.01.2023 17:55

        Если у меня есть много фломастеров, то что мешает соорудить из них большую кисть и быстро покрасить стену :)


        1. johnfound
          12.01.2023 19:36

          Ну, это не ко мне. Я сам крашу фломастером – пишу все на ассемблере. И мне такое нравится.


    1. vkni
      11.01.2023 23:26
      +2

      В смысле "несколько лет"? Даже Фортран появился уже лет 70 назад и тенденция пошла уже оттуда.

      Да, типа Lisp vs FORTRAN.


  1. shpaker
    11.01.2023 11:36
    +29

    Какая-то графомания на давно известные холиварные темы. Только этот пост тот случай когда срачик даже разводить не хочется. Не знаю даже зачем я это прочитал. Да и на мой взгляд, в современном мире, культуры явно не две - бывают еще фронтовики со своими оттенками бегающих в головах тараканов, несколько сортов датасатанистов, программисты только кастующие одни джейсонки в другие и тд, и тп.


    1. PavelMSTU
      11.01.2023 15:24
      +2

      программисты только кастующие одни джейсонки в другие

      я их называю JSON-жонглёрами... :)


      1. 0xd34df00d
        11.01.2023 18:23
        +14

        Можно сократить до JSONглёров.


  1. speshuric
    11.01.2023 11:53
    +19

    Большинство программистов находятся в состоянии сложной невырожденной суперпозиции культур 1 и 2. Причём "ширина" диапазона этой суперпозиции, если бы её можно было измерить, нехило так коррелирует с "крутостью" программиста(если бы её тоже можно было измерить - хаха). А у тимлидов, менеджеров, экспертов и архитекторов есть еще разные дополнительные измерения в этой же суперпозиции.


  1. MinimumLaw
    11.01.2023 12:14
    +4

    Я полностью согласен с тезисами в данной статье. Но при всем при этом, не могу не заметить некоторых ограничений. В моем мире (мир встраиваемых систем) второй путь - это прототипирование. Очень нужная, важная и полезная штука. Но один из самых страшных грехов руководителя (за который потом приходится расплачиваться и инженерам по технике, и организации по имиджевым потерям) - это запуск прототипа в производство.

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


  1. screwer
    11.01.2023 12:31
    +9

    Linux — штука более серьёзная, в нём больше 15 миллионов строк

    Это только ядро. Далеко не самая объемная часть. Сравните его собранный размер (обычно менее 30мб) с размером дистрибутива. Прикиньте, из скольки миллионов (миллиардов) строк он собран.

    Из этих строк (навскидку) 2/3 это поддержка железа: различные драйвера и архитектуры, в т.ч.настолько специфичные, что вы их не увидите никогда.

    в «Мерседесе», помимо собственно автомобиля, едет 100 миллионов строк кода. Я не знаю, стоит ли такому верить, и думаю, даже если стоит, эти строчки кода довольно бессмысленные.

    Размер прошивки GSM модема больше линуксового ядра где-то на порядок (уже ~50млн строк) и они совсем не бессмысленные.

    Похоже вы сами совершаете свою же ошибку

    Молодые разработчики не всегда понимают, насколько монструозны проекты «первой» культуры

    и просто не осознаёте сложность современных систем.

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


    1. up40k
      11.01.2023 16:45
      +5

      Размер прошивки GSM модема больше линуксового ядра где-то на порядок (уже ~50млн строк) и они совсем не бессмысленные.

      Можете объяснить, в чём сакральный смысл аж три раза оставлять слепок окружения оболочки разработчика в прошивке? Чтобы что?

      Чтобы пользователь тянул из сети десятки и сотни мегабайт при каждом обновлении?
      ┌──(up4k㉿UP4K-X1C)-[/mnt/c/Windows/Firmware/FwSwitchbin]
      └─$ du -h m2_7360_nand.flz
      140M    m2_7360_nand.flz
      
      ┌──(up4k㉿UP4K-X1C)-[/mnt/c/Windows/Firmware/FwSwitchbin]
      └─$ du -h FirmwareData/18500.5001.00.05.27.30_5021.11_Secureboot.fls
      29M     FirmwareData/18500.5001.00.05.27.30_5021.11_Secureboot.fls
      
      ┌──(up4k㉿UP4K-X1C)-[/mnt/c/Windows/Firmware/FwSwitchbin]
      └─$ strings FirmwareData/18500.5001.00.05.27.30_5021.11_Secureboot.fls | grep SSH_CONNECTION
      SSH_CONNECTION=10.20.45.59 57271 192.168.20.222 22
      SSH_CONNECTION=10.20.45.59 57271 192.168.20.222 22
      SSH_CONNECTION=10.20.45.59 57271 192.168.20.222 22
      


      1. screwer
        11.01.2023 18:16
        -1

        Во-первых, с какого перепугу вы требуете от меня что-то обьяснять по хз каким файлам ?

        Во-вторых, налицо непонимание, что такое "строки". Нет, это не выхлоп утилиты "strings". Речь о строках исходного кода.

        В-третьих, я понятия не имею даже на каком языке написаны бинарники, по которым вы хотите получить от меня информацию. Я не их автор, и никаким образом с ними не связан. Давайте предположим что это С или С++.

        The compiler is allowed, but not required, to combine storage for equal or overlapping string literals. That means that identical string literals may or may not compare equal when compared by pointer.

        Т.е. компилятор может зареюзать одинаковые литералы. Но не обязан. А также строки могут быть неконстантными, и представлять собой массив символов в BSS. В таком случае компилятор вообще бессилен сделать реюз (для простоты понимания - представьте что указатель на эту строку отдаётся куда-то наружу, и внешний код может поменять данные по его адресу. Изначально строка-массив просто чем-то заполнена дефолтным, и это значение может (и должно) меняться в процессе работы.

        Также неясно что такое "fls" вообще. Это может быть контейнер, из которого достаются отдельные файлы. И три экземпляра - это три различные прошивки, из которых выбирается к загрузке нужная. Это может быть единственная прошивка, а три экземпляра - это три разных функциональных блока, которые могут менять свой набор данных независимо. По крайней мере с т.з. компилятора.

        Ну и напоследок. С чего вы, чёрт возьми, взяли, что файл должен обновляться весь целиком ? Уже десятки лет сущетствуют инкрементальные обновления. Которые затронут только изменившиеся части при апдейте. Может здесь применяться подобная технология ? На мой взгляд - запросто.

        Наезд на 29 мегабайт - ойвей, 2023 год на дворе, сейчас полное обновление для мобильного телефона(!!!) занимает по 2-4гб.

        Вообщем моё мнение - и комментарий не по адресу, и явное отсуствие попытки разобраться в деталях самостоятельно.


        1. up40k
          11.01.2023 21:41
          +4

          Во-первых, с какого перепугу вы требуете от меня что-то обьяснять по хз каким файлам ?

          Бросьте, мой вопрос не в требовательной форме.

          Во-вторых, налицо непонимание, что такое "строки". Нет, это не выхлоп утилиты "strings". Речь о строках исходного кода.

          Которые в конечном итоге компилируются в бинарь, выполняющийся у пользователя на устройстве, который доставляется по каналам обновления. И да, те 50кк строк исходников, про которые вы говорите, прекрасно могут в течение жизненного цикла отдельного релиза (корявый термин, я подразумеваю последовательность действий от коммита разработчика в прод до доставки пользователю исполняемого кода) ужаться на порядки.

          В-третьих

          С этим согласен. Думал, из имен файлов будет понятно, о чем речь. Ниже поясню.

          Также неясно что такое "fls" вообще. Это может быть контейнер, из которого достаются отдельные файлы. И три экземпляра - это три различные прошивки, из которых выбирается к загрузке нужная. Это может быть единственная прошивка, а три экземпляра - это три разных функциональных блока, которые могут менять свой набор данных независимо. По крайней мере с т.з. компилятора.

          FLS - это образ файловой системы для NAND на устройствах от Intel, я не разбирался подробно, но, как я понимаю, он содержит саму ФС, чексуммы и подписи разработчика для безопасной загрузки на конкретном устройстве. В данном случае - модем Fibocom L850-GL на платформе XMM7360.

          Конкретно этот файл на 29Мб - это базовый образ для конкретной версии HW и оператора (который целиком прошивается на устройство). Помимо него в пакете распространяются другие файлы, как раз то, о чем вы сказали - оверлейные образы, динамически подгружающиеся (прямо в NAND) в зависимости от оператора сети и (возможно) других условий через ПО в ОС:

          Листинг директории всех образов в одном пакете прошивки.
          ┌──(up4k㉿UP4K-X1C)-[/mnt/c/Windows/Firmware/FwSwitchbin]
          └─$ du -ah FirmwareData
          29M     FirmwareData/18500.5001.00.05.27.12_5021.11_Secureboot.fls
          29M     FirmwareData/18500.5001.00.05.27.16_5021.11_Secureboot.fls
          29M     FirmwareData/18500.5001.00.05.27.30_5021.11_Secureboot.fls
          368K    FirmwareData/ATT_cust.201.046_signed.fls
          372K    FirmwareData/DoCoMo_cust.216.044_signed.fls
          368K    FirmwareData/DT_cust.119.042_signed.fls
          364K    FirmwareData/Generic_cust.001.035_signed.fls
          376K    FirmwareData/KDDI_cust.115.046_signed.fls
          368K    FirmwareData/Optus_AU_cust.226.036_signed.fls
          8.0K    FirmwareData/package_info.xml
          364K    FirmwareData/Saudi_cust.234.018_signed.fls
          372K    FirmwareData/SBM_cust.218.047_signed.fls
          368K    FirmwareData/Sprint_cust.102.034_signed.fls
          368K    FirmwareData/Swisscom_cust.210.037_signed.fls
          368K    FirmwareData/Telefonica_cust.205.036_signed.fls
          368K    FirmwareData/Telstra_cust.107.044_signed.fls
          368K    FirmwareData/TMO_cust.203.013_signed.fls
          364K    FirmwareData/Turkey_cust.232.010_signed.fls
          368K    FirmwareData/Turkey_VDF_cust.233.010_signed.fls
          372K    FirmwareData/Verizon_cust.101.043_signed.fls
          368K    FirmwareData/Vodafone_Germany_cust.202.038_signed.fls
          92M     FirmwareData
          

          Ну и напоследок. С чего вы чёрт возьми взяли что файл должен обновляться весь целиком ? Уже десятки лет сущетствуют инкрементальные обновления. Которые затронут только изменившиеся части при апдейте. Может здесь применяться подобная технология ? На мой взгляд - запросто.

          Самый первый du в моем первом комментарии - это как раз то, что прилетает в виде апдейта от производителя (через Windows update или fwupd) - ZIP-файл со всеми вариантами прошивки. 140 Мегабайт. И в каждом из вариантов, и во всех оверлеях (!!!) есть те самые слепки окружения шелла со всеми переменными. И не по одному разу.

          Наезд на 29 мегабайт - ойвей, 2023 год на дворе, сейчас полное обновление для мобильного телефона(!!!) занимает по 2-4гб.

          Да, вот только это не мобильный телефон, а модем. Довольно значительную часть работы которого обеспечивает ОС компьютера, к которому он подключен. Да, там в апдейте помимо образов есть исполняемые файлы для этой ОС, которые следят за тем, какой из образов динамически подгрузить. Это особенности реализации, необходимое зло. Моё недоумение не к этому.

          Вообщем моё мнение - и комментарий не по адресу, и явное отсуствие попытки разобраться в деталях самостоятельно.

          Чтобы разобраться в деталях того, каким чудесным образом в продакшн-образе ФС прошивки оказались динамические переменные окружения шелла разработчика, нафига они там нужны, а главное - почему они там не по одному разу (натурально, полные слепки переменных во время сборки, включая флаги gmake и вообще всё остальное; я для краткости грепнул переменные SSH, как самые курьёзные/ненужные), нужно либо курить то же, что разработчик (читайте - разбираться в тонкостях и тенденциях прошивкостроительства), либо потратить часы и дни на реверс прошивки, чего мне совсем не хочется. Вы заявили, что раздувание кода прошивки модема имеет смысл - я подумал, что вы в теме, и сможете объяснить, почему китайские...

          Китайские же?
          ┌──(up4k㉿UP4K-X1C)-[/mnt/c/Windows/Firmware/FwSwitchbin]
          └─$ strings FirmwareData/* | grep USER=| sort -u
          USER=fujike
          USER=guantao
          USER=lihongwei
          USER=zhaixianni

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

          Предвижу ваше возражение - вы говорили про исходники. Но позвольте пару риторических вопросов: если у них такое в клозетах, то что у них в головах? И если спагетти-код называют индусским, то как называть такие вот лазанья-бинарники?

          В разрезе темы статьи. Автор предлагает совмещать две "культуры" программирования. Я не программист, вижу всё это под иным углом. Для меня люди, которые разрабатывают ПО - это разработчики. Кодеры, архитекторы ПО (software engineers) - всё одно. Люди, которые переводят ТЗ в его реализацию. К процессу перевода можно подходить по-разному. В конечном счете это влияет на business value работодателя (заказчика, коим может быть сам разработчик) и на самооценку разработчика. Не важно, как ты это сделаешь, главное, чтобы все причастные были довольны (включая тебя самого). И к опенсорсу в тч. это относится в полной мере, да. И к 1C. И к PHP.

          А если у разраба низкая социальная ответственность самооценка, которая не зависит от производимого им кода - получаем неадекватное ПО. Неадекватное требованиям заказчика, неадекватное потребностям конечного пользователя, неадекватное фазе луны и политическому курсу текущего режима (зачеркните лишнее).


    1. AndreyAf
      12.01.2023 08:02

      Размер прошивки GSM модема больше линуксового ядра где-то на порядок (уже ~50млн строк) и они совсем не бессмысленные.

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


      1. screwer
        12.01.2023 11:18

        Hе я предложил строками исходника меняться, а автор. Я наоборот подчеркнул, что в конкретном линуксовом ядре бОльшая часть строк не будет задействована.


  1. Pastoral
    11.01.2023 12:36
    +2

    «Плюсовик начнёт её с заклинания #include » - А я то по наивности всегда считал, что плюсовик начнёт с «import std;».

    А когда я слышу слово «культура», я хватаюсь за С.


    1. klirichek
      11.01.2023 18:02
      +2

      Плюсовик, умеющий в import - это уже шаг в сторону 2-й категории.

      А в энтерпрайзе, где "лишний раз не трогай, как бы чего не сломалось" в сторону С++20 с его импортами начнут смотреть только когда протухнут все LTS-версии целевых дистрибутивов (вот ubuntu xenial протухла в прошлом году - и стало можно перейти на С++17).

      Вот Go с его возможностью сделать import "github.com/company/package" прямо из текста, при этом с типизацией и прочим - вызывает сильное неравнодушие "да что ты, блин, такое?!!"


      1. Aldrog
        11.01.2023 18:35
        +4

        import std; это даже не C++20, а ещё не утверждённый C++23.


        1. klirichek
          11.01.2023 20:56
          -1

          я смотрел доклад Саттера на CppConf, где он изобрёл Cpp2.
          Очень-очень круто, минус много-много аргументов у адептов ржавчины (rust), но это вообще не самоцель, а просто самоочевидно, что без холиваров можно плюсы сделать сильно современнее. Но, говоря как пессимист - это то, о чём будут рассказывать уже наши дети... своим внукам...


        1. klirichek
          11.01.2023 21:00

          Вот я совсем вдумчиво и детально не смотрю, но возникло ощущение, что он таки утверждён. (если я неправ - ссылочку бы, что оно не так. Потому что далее кажется только детали реализации, особенно stdlib. Потому что C++17, несмотря на "зрелость", тоже пока что не весь/везде реализован)


  1. gandjustas
    11.01.2023 12:44
    +5

    Очередная статья про C++ vs Python, вместо Python можно подставить любой современный динамический типизированный язык. Аксиома Эскобара говорит о том, что обе альтернативы так себе.

    Надо найти вариант, который содержит лучшее из обоих миров и заимствует как можно меньше плохих качеств.

    Как минимум необходимо:

    • Богатая библиотека пакетов с готовыми решениями задач

    • Лаконичный синтаксис без `public static void main overhead` и c переопределением операторов

    • Статическая типизация со структурной в определенных пределах (duck typing)

    • IDE с богатыми возможностями проверки корректности при наборе, подсказками, сниппетами и навигацией по коду

    Я знаю несколько таких языков, которые более-менее соответствуют

    1. C# (с натяжкой и только последней версии)

    2. Rust

    3. TypeScript

    Для всех них есть прекрасная IDE - VSCode


    1. anone18236
      11.01.2023 13:14

      Чем не угодил Kotlin?

      По всем 4 пунткам подходит.


      1. gandjustas
        11.01.2023 13:18

        Я его совсем не знаю, видимо тоже подойдет под указанные критерии


    1. Layan
      11.01.2023 15:02
      +9

      есть прекрасная IDE - VSCode

      Он конечно хорош, но далек от IDE.


      1. whoisking
        11.01.2023 17:37

        Смотря для чего, для флаттера, например, возможности просто космос


        1. Layan
          11.01.2023 17:58
          +4

          Прям из коробки без траты кучи времени на подбор плагинов, их настройку? Иначе так и vim можно IDE называть. Но от этого оно им не станет.


          1. whoisking
            11.01.2023 18:21
            +1

            Без траты, только плагин флаттера установить, может пути какие-то указать, но телодвижений точно минимум и не больше чем в альтернативе - андроид студии


            1. Layan
              11.01.2023 19:38

              Без траты, только плагин флаттера установить

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


              1. whoisking
                11.01.2023 20:19
                +3

                Немного согласен с вашим определением, для меня идэе это когда фич, касающихся разработки там полно и это не просто редактор текста, а рефакторинг, работа с зависимостями, анализ кода, отладка, анализ перформанса и всё это с кнопками в UI. Вимы и другие редакторы с прикрученным LSP действительно полноценной IDE сложно назвать, это что-то посередине, но вот некоторые плагины под vscode действительно делают его сносной штукой под некоторые стеки


      1. vabka
        11.01.2023 23:17

        Ну например Rust в нём работает прекрасно, благодаря rust-analyzer.


      1. gandjustas
        11.01.2023 23:19

        Почему это?

        IDE это:

        1. Текстовый редактор, который "понимает" код, обеспечивает навигацию по элементам программы - классам и функциям

        2. Навигация по элементам проекта - в большинстве случаев это навигация по файлам и папкам

        3. Интерактивная отладка

        4. Графические дизайнеры для элементов UI

        VSCode не умеет первые три пункта из четырех. Поэтому не подходит для WinForms например, но вполне подходит для веб-разработки.


    1. 0xd34df00d
      11.01.2023 18:25
      +6

      Хаскель ещё. Особенно синтаксис — лаконичнее, пожалуй, только лиспы.


      1. Alexey2005
        11.01.2023 19:32
        +5

        Это write-only код. Разбираться в чужом коде, написанном в функциональном стиле - это ад, потому что там вместо наследования композиция. То есть программа фактически "собирается" в рантайме, её точная структура напрямую из кода выводится с изрядным трудом.

        И не существует способа быстро проверить, при каких условиях выполняется вот именно данный участок кода (и выполняется ли он вообще). Потому что эта функция не вызывается напрямую, а передаётся в качестве параметра (одного из возможных параметров) в другую функцию, а оттуда в третью в составе какого-нибудь списка, который обрабатывается внутри лямбды, где и происходит собственно вызов. При этом ничто в синтаксисе не говорит о том, что вот именно в этой строчке произойдёт вызов искомой функции.


        1. 0xd34df00d
          11.01.2023 20:18
          +7

          Разбираться в чужом коде, написанном в функциональном стиле — это ад, потому что там вместо наследования композиция. То есть программа фактически "собирается" в рантайме, её точная структура напрямую из кода выводится с изрядным трудом.

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


          Поэтому стандартный ООП-код — это нечитаемое месиво, иногда присыпанное попытками использовать паттерны, которые очень редко угадывают, когда и куда код будет расширяться, что и превращает его в это самое месиво со временем.


          В то же время я ещё не встречал проекта на функциональном языке (будь то хаскель, идрис, агда…), с которым у меня были бы сложности с именно самой структурой проекта. Непосредственная сложность предметной области — да, но от неё никуда не деться.


          И не существует способа быстро проверить, при каких условиях выполняется вот именно данный участок кода (и выполняется ли он вообще).

          Зачем вам это знать, если код не имеет эффектов?


          А если код имеет эффекты — это есть в типах.


      1. gandjustas
        11.01.2023 23:12

        У хаскела разве есть IDE с подсказками, навигацией и сниппетами?


        1. 0xd34df00d
          11.01.2023 23:40
          +1

          Да, любой LSP-клиент + HLS (который LSP-сервер для хаскеля).


      1. masai
        13.01.2023 02:53

        Haskell и лиспы слишком разные, чтоб их сравнивать. А то так APL победит в лаконичности.


    1. pharo
      11.01.2023 23:08
      +1

      А, ещё «есть» FreePascal, FreeBasic…
      Давайте чтобы расцветали все цветы. :)

      P.S. Для FreePascal Инструкция по насторойке VSCode

      Учебник по языку FreeBasic.pdf за авторством Стаса Будинова.


      1. gandjustas
        11.01.2023 23:20
        -2

        Паскаль это образец страшного синтаксического оверхеда и по сути ничем не лучше C#. Все что есть в современных версиях паскаля есть и в C#.


    1. vabka
      11.01.2023 23:17
      -1

      • Богатая библиотека пакетов с готовыми решениями задач

      • Лаконичный синтаксис без `public static void main overhead` и c переопределением операторов

      • Статическая типизация со структурной в определенных пределах (duck typing)

      • IDE с богатыми возможностями проверки корректности при наборе, подсказками, сниппетами и навигацией по коду

      Кажется, что все критерии выбраны специально под Go, которого почему-то нет в примерах.
      > C#
      Так у не как раз и есть public static void. Хоть его и убрали из Main - в остальных местах он остался.
      Да и VSCode не особо то с ним работает.


      1. gandjustas
        12.01.2023 00:35

        Уже год как C# живет без `public static void Main`, а с недавнего времени еще и шаблоны проектов без него. в VSCode работает прекрасно, проверял буквально вчера.


      1. gandjustas
        12.01.2023 00:56
        +2

        Что касается Go, то я с ним сталкивался последний раз больше года назад и он оставил впечатление языка значительно более низкого уровня, чем C#. Для решения тех же задач что на C#\.NET кода на Go было примерно в 3 раза больше.

        Возможно потому что библиотеки такие, возможно потому что язык слаб - генериков нет, анонимных типов нет, лямбды ограниченные, про PM и record types не слышали.

        В итоге Go очень многословен. Но не в синтаксисе, а в количестве кода, который надо написать для решения задачи.


  1. mbait
    11.01.2023 12:58
    +18

    Всё это ерунда по сравнению с другой, смежной проблемой:

    1. Я знаю Python, буду писать на нём.

    2. Кажется, интерпретатор Python слишком медленный для управления роботом в реальном времени.

    3. man cython, apt install python-dev

    4. О, напишу статью на Хабр, как переписать весь код на Си, но в виде питоньих модулей. Там обязательно будут сырые указатели и доступ к разделяемой памяти. Обязательно назову её "как мы ускорили код на Python в 100500 раз".

    5. "Дорогие коллеги, это был замечательный проект, на котором я узнал много нового, но настала пора двигаться дальше. Поэтому я принял прежложение в компании X. Она занимается разработкой носимого устройства под управлением ATtiny25, там я буду заниматься разработкой ядра встроенной ОС на Haskell."


    1. MyWave
      11.01.2023 15:33
      +3

      На каком из этих 5 этапов возникает "проблема" и у кого?


      1. TimsTims
        11.01.2023 22:44
        -1

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


    1. pharo
      11.01.2023 23:15

      там я буду заниматься разработкой ядра встроенной ОС на Haskell.

      Разработка ОС на FreeBasic Little-OS :)


  1. nzinov
    11.01.2023 13:16
    +3

    По-моему, тут смешаны как минимум две разных оси. Различие в подходах "быстрее запрограммировать" и "быстрее работает", о котором речь в статье, и постепенное появление новых и новых уровней абстракции. Программисты на КПДВ совсем не обязательно из разных культур, они просто работают на разных уровней абстракции. Программист, который пишет на плюсах совсем не обязательно полностью разбирается в том, что происходит: например, он наверняка не разбирался в микрокоде процессора и ассемблере, на котором написан компилятор плюсов. В каждый момент времени какие-то стандартные вещи принимаются как черный ящик и над ними строится что-то новое — и это не зависит от описанных двух культур. Точно так же в пассаже про Дейкстру и бакалавров речь скорее не про две культуры, а про эпохи накручивания новых слоев абстракции. Возможно полезнее искать различия двух культур в одном слое абстракции: например в ML часть исследователей клепает эксперименты, объединяя готовые архитектуры, а часть доказывает теоремы о том, почему работает ML.


  1. AirLight
    11.01.2023 13:45

    Небольшое уточнение по поводу REPL - его завезли в C#, например.


    1. vabka
      11.01.2023 23:21
      +2

      REPL на самом деле много где есть.
      Даже в rust (evcxr)


      1. masai
        13.01.2023 02:57

        И в C++ (Cling). :)


  1. antarx
    11.01.2023 13:51
    +3

    Кажется в дихотомии 2 культур самое ценное — найти, где эти культуры пересекаются. Где с одной стороны есть читаемость и удобство второй культуры, и гарантии=сходу понятное для программиста поведение первой. Я пристрастен, но Go как раз об этом — сделан «людьми из множества {Керниган; Томпсон; Вирт; Хоар; Дейкстра; Торвальдс}», и для людей второй культуры, с максимально низким порогом входа. Особенно это характерно для документации, которая хранится в коде, и автоматически подтягивается в онлайн документацию из гитхаба.

    Отдельно, обе культуры объединяются в мире микросервисов, где гарантии вызова между сервисами заведомо слабые, а строгая e2e проверка чаще всего невозможна.


  1. holodoz
    11.01.2023 13:52
    +7

    Статья в основном - графомания, но я узнал, что такое банкирское округление, так что время потрачено не зря


    1. vvzvlad
      12.01.2023 01:45
      +5

      Ваш комментарий в основном попытка самоутвердиться, но я узнал, что такое банкирское округление, так что время потрачено не зря


  1. avshkol
    11.01.2023 13:58
    +5

    Спасибо, сделал себе памятку, чтобы периодически перечитывать


    1. FoxisII
      12.01.2023 17:51

      согласно пункта 2
      Культура 2 ===> Овна Кусок --- Быстро накодить тормозящий кусок овна, а потом еще выторговать себе сроки и бюджеты на оптимизацию .... ну такое .... ну на любителя.


  1. Gryphon88
    11.01.2023 15:10
    -1

    Спасибо за статью. У меня такой вопрос: как на этапе собеседования распознать культуру разработки, принятую в команде? Я ближе к категории 1, и поэтому начинаю дергать глазиком на словах «молодая развивающаяся команда, agile, гибкие методологии», но возможно есть более четкие критерии?


    1. cross_join
      11.01.2023 16:06

      Конечно есть: технологический стек, круг задач, организация их реализации


  1. PavelMSTU
    11.01.2023 15:32
    +1

    Да нет никакой второй культуры... Просто программирование стало массовым явлением. По этой причине потребовались "люди попроще". Вот вам и "вторая культура".

    При этом есть серьезные спецы, которые немножко недоумевают. Часть из них становится чуток токсичными и они "защищаются" от всех этих вайтишников и инфоцыган созданием своей "культуры 1".

    Я не принадлежу ни к одной из них. Люблю вырви мозг алгоритмы и чистый Си, многое пишу на Python. Меньше токсичности, больше дела.

    Культура 1: формальные языки, алгоритмы поиска, анализ конечных автоматов, хитрые структуры данных
    Культура 2: deep learning

    Вы серьезно? :) Не путайте реальный Data Science, Machine Learning, Deep Learning и "я установил scipy, torch...

    Как практикующий DS-ник с большим опытом и работы руками, и руководства, и R&D с патентами... могу сказать следующее: очень много хайпа среди "культуры 2". Сейчас в моде диплёрнинг, через десять лет будет другое... Это просто хайп.

    Поэтому правильно писать так:

    Культура 1: формальные языки, алгоритмы поиска, анализ конечных автоматов, хитрые структуры данных
    Культура 2: хайп (сейчас это deep learning)


  1. cross_join
    11.01.2023 15:59

    Не совсем понятна новизна ситуации. С 1960-х годов, когда были изобретены (но далеко не все внедрены) 80% используемый сейчас технологий, появилось разделение на системных и прикладных программистов. Для работы первых важна надежность и производительность программ. Прикладникам всегда важнее быстрее реализовать запрашиваемую функциональность. Тем более прикладникам "домашней" (in-house) разработки. Та, что раньше велась в отделах АСУ предприятий.

    Поэтому всегда были рядом Фортран и Лисп, потом Си и Бейсик (Си++ и Вижал Бейсик), Ява и Ява-скрипт и т.д. и т.п. И было еще множество инструментов "посередине", таких как Паскаль-Дельфи. И появлялись культуры использования операционных систем, вместо их написания. Потом СУБД.

    Питон - это прекрасный заменитель старого Бейсика с теми же целями: побыстрее реализовать небольшую по масштабам прикладную задачку. Побыстрее вставить свой кирпич в общую стену-плотину, затыкая потоки прорывающейся воды пользовательских историй.

    Речь не о культуре, а о специализации.


    1. Layan
      11.01.2023 18:01

      Для работы первых важна надежность и производительность программ. Прикладникам всегда важнее быстрее реализовать запрашиваемую функциональность.

      На самом деле нет. Вопрос в решаемых задачах. А вопросы к надежности - это вопрос исключительно квалификации исполнителей и требований заказчика.

      Можно и во ebmedded делать то, что будет зависать регулярно и ни о какой стабильности даже речи не будет. А можно прикладные задачи решать с максимальной стабильностью и производительностью, лишь бы бизнес на это деньги выделял (и в определенных сферах типа финансов это делается).


      1. cross_join
        11.01.2023 18:40

        На самом деле нет. Вопрос не в том, что можно делать, а в том, что нужно. Несмотря на.

        Все системные продукты (ОС, СУБД, виртуализация, сервера приложений...), компиляторы, базовые библиотеки, фреймворки по умолчанию имеют повышенные требования к надежности, производительности, развертыванию, зависимостям и прочая. Для прикладных систем нужно дополнительное обоснование.


        1. Layan
          11.01.2023 19:35

          Все системные продукты (ОС, СУБД, виртуализация, сервера приложений...), компиляторы, базовые библиотеки, фреймворки по умолчанию имеют повышенные требования к надежности, производительности, развертыванию, зависимостям и прочая.

          Прям все? Кучу продуктов, которые имеют такие названия и работают не стабильно и не надежно вы пропускаете, берете во внимание только те, которые действительно имеют такие требования? У меня вот был давний опыт использования одной БД, которая без утечек памяти не могла проработать неделю и требовалось по крону ее перезапускать. Это другое?


          1. cross_join
            11.01.2023 19:44

            Вы "опровергаете" повышенные требования к СУБД тем, что такие требования есть у вас, как пользователя, а неполное им соответствие вызывает у вас же проблемы эксплуатации?

            Выглядит как подтверждение сказанного выше, спасибо.


  1. Aquahawk
    11.01.2023 17:50

    очень холиварно, когда начинал читать подумал что согласен, по мере чтения и применения на себя и коллег, понял что всё очень относительно. Проще хочется сказать: Профессионализм и непрофессионализм. И профессионал может задать вопрос, а что на самом деле нужно бизнесу и где нужно бороться за 2% а где нет. А где составить план что делаем без борьбы, какие метрики оценить и когда включаться в борьбу, попутно ещё и поправку на скорость найма, но это уже даже сильно каждый техлид команды может.


  1. JI0C0Cb
    11.01.2023 21:04

    Увы, но зачастую подходы, якобы облегчающие работу программиста, по факту не очень-то её облегчают.


  1. event1
    11.01.2023 21:08
    +1

    откуда такая дихотомия? на каком основании те или иные подходы отнесены к первому или второму типу? Перефразируя старый анекдот: программисты делятся на плохих и хороших. А уже плохие делятся на культуру 1 и культуру 2. Или (простите за автоцитату): "Программистам, не постигшим Дао, никогда не хватает места и времени для своих программ. Программистам, постигшим Дао, всегда хватает места и времени для достижения своих целей. Разве может быть иначе?"


    1. slonopotamus
      11.01.2023 21:20

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

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

      И мало того. В зависимости от контекста (что за софт я пишу, для какой аудитории/окружения, на каком языке), приоритеты могут быть разными.


  1. onyxmaster
    11.01.2023 21:57
    +2

    Про round написано что-то странное.
    "Банковское" округление (к ближайшему целому за исключением дробной части 0.5, округляемой к ближайшему чётному) не требует никакой особой логики на большинстве современных компьютеров, потому что является режимом округления по умолчанию в стандарте IEEE-754. То что в Python его реализация сложнее чем просто вызов (на x86/x64) frndint (при условии что текущий режим округления -- стандартный) -- судя по коду в основном результат того, что round там поддерживает округление до произвольного знака, а не просто до целого.


  1. lyadnov
    12.01.2023 00:21
    +3

    Культура 1 прочно занимает свою нишу потому, что производительность железа упёрлась в фундаментальные ограничения физического мира. Но есть ощущение, что вся движуха в Культуре 2.


    1. vassabi
      12.01.2023 01:31
      +1

      так ведь пока культура1 фиксит баги предыдущего проекта Культуры2, Культура2 уже продала три новых проекта


  1. Andruh
    12.01.2023 09:43

    Интересно, хотя не согласен с некоторыми деталями. Например, приходится читать, конечно, код используемых библиотек, но я предпочитаю работать с подсистемами через контракты, т.е. через формальное API, ну и бенчмаркинг. Разбиратся как печатает число ryu - интересно, но не продуктивно, к примеру.

    Вообще, хотелось бы подняться чуть выше и определить главное мировоззренческое отличие, разделяющее две культуры. В первом случае программирование - цель, во втором - средство. Это примерно как work-life баланс. Не стоит впадать ни в одну крайность (иначе будешь читать весь код Linux или станешь совсем бестолковым раздолбаем, методом тыка комбинирующим высокоуровневые кирпичики), но стоит уметь соединять обе культуры в оптимальных пропорциях в каждой конкретной части работы.


  1. Karl_Marx
    13.01.2023 15:00
    -2

    Поздравляю, вы заметили чем отличаются интровертная и экстравертная логика. Еще три таких наблюдения, и вы заново изобретете колесо, которое Юнг изобрел еще 100 лет назад.


    1. Karl_Marx
      13.01.2023 22:24
      -1

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


  1. AndreyMust19
    13.01.2023 19:56

    Вот как раз из-за чуваков 2-ой культуры у нас на компьютерах все и тормозит.


    1. Tresimeno
      13.01.2023 22:32

      Они просто не знают пока латинской мудрости - Fastenum lentum :)))