Этот пост, возможно, вызовет много споров в комментариях, поскольку затрагивает щепетильную тему — сравнение языков программирования. Скажу сразу: моя цель — не выявление абсолютного победителя, я просто делюсь собственным опытом. Поэтому, как говорится, статья отражает личное мнение автора, которое может отличаться от вашего.
Почему
Начну с небольшой предыстории, как вообще вышло, что я буду сравнивать эти языки. Я занимаюсь программированием уже более десяти лет. Причем большинство из них я работал исключительно с технологиями Microsoft. Естественно, «родным» для меня языком программирования все это время был C# и сопутствующий ему .NET Framework. Я писал коммерческий код в разных компаниях, больших и не очень, начиная с версии 2.0 и заканчивая 4.5.
Однако судьба решила, что пора бы изменить привычный ход вещей. И вот, начиная с сентября 2014 года, я кардинально поменял стек технологий и теперь пишу код на Perl в компании Booking.com. Это довольно радикальный шаг, потому что «Perl? Че, серьезно? Да это ж пипец...», ну и так далее :) С момента написания моей первой строки на perl прошло уже девять месяцев, и вот я наконец «родил» эту статью.
Примечание: здесь и далее речь будет идти в основном о Perl 5.
Идеология
При создании нового языка программирования, его авторы чаще всего имеют какую-то цель. Это либо исправление проблем какого-то другого языка (Swift), либо решение узкоспециализированных задач (Haskell), либо просто веселье (Hodor).
Изначально Perl разрабатывался как многоцелевой скриптовый язык для Unix (а было это в далеком 1987). Впоследствие он стал популярен в вебе, поскольку предоставлял простые и мощные возможности по работе со строками (привет регуляркам), списками, хешами, наряду с другими своими функциями.
В основе Perl лежит парадигма There's More Than One Way To Do It (TMTOWTDI). Второй слоган — все простое должно быть просто, все сложное должно быть возможно. Следствием этих двух утверждений является уникальная гибкость языка и связанные с ним шутки и стереотипы.
За C# тоже стоит идея. В те времена в энтерпрайз разработке была очень популярна Java. Оно и понятно — простой и знакомый синтаксис, кросс-платформенное исполнение, автоматизированное управление памятью, плюс огромная Sun за спиной. В Microsoft решили создать аналог, который был бы таким же удобным в использовании, подходил под многие задачи (настольные приложения, веб, консоль), поддерживал ООП, имел строгую типизацию и тому подобное (хотя с кросс-платформенностью не получилось). Таким образом в 2000 году родился .NET Framework и новый язык программирования — C#.
Синтаксис
Самое первое, с чего, стоит начать сравнение языков, это синтаксис. Perl был рожден в далеком 1987 году. И он получил C-подобный синтаксис с примесями shell-скриптов. Вот небольшой пример программы на перле, создающей массив с квадратами чисел от 0 до 9:
my @a = ();
for (my $i = 0; $i < 10; $i++) {
push @a, $i*$i;
}
C# родился гораздо позже, но он также унаследовал многое из C-семейства. Например:
int[] a = new int[9];
for (int i = 0; i < 10; i++)
{
a[i] = i*i;
}
Сишные корни обоих языков очень помогают, если вам (как и мне) вздумается переключиться с одного на другой. Однако уже в таком простом примере видна разница. Perl больше похож на PHP (точнее PHP похож на Perl), в то время как программу на C# легко спутать с таковой на C++. И чем дальше углубляться в дебри, тем больше различий между языками вы найдете.
Чьих будешь? (или типы данных)
Perl, в отличие от C#, является языком с динамическими типами данных. Это значит, что одна переменная может содержать в себе и строку, и число, и ссылку. C# же, наоборот, строго типизированный язык, и если вы вдруг станете присваивать число строке, то получите ошибку компиляции.
Основными типами данных в Perl являются:
- Скаляр — числа, строки или ссылки.
my $value = "This is a scalar variable";
- Список — упорядоченная коллекция скаляров
my @array = (1, 2, 3, 4, 5);
- Хеш — неупорядоченный набор пар ключ-значение
my %hash = ( key => 'value' );
Все. То есть действительно все. Нет никаких int, float, string и т.п. Нет object, и других типов. Все, что не список или хеш, то скаляр. Кладите туда все, что угодно, и работайте с этим на свой страх и риск.
У C# ситуация иная. Формально в языке тоже два основных типа данных — это Value Type и Reference Type. Однако это не столько типы, сколько группы. А поскольку у нас строгая типизация, то такой код в C# работать не будет:
int a = 5;
string b = '6';
a = b;
Хороша или плоха строгая типизация — вопрос крайне интерсный. Я считаю, что однозначного ответа на него не существует и все зависит от конкретной задачи и проекта. С одной стороны строгая типизация помогает выявлять (и даже не допускает) ошибки, которые могут возникнуть при сравнении числа и строки (привет '===' в JavaScript). С другой стороны она вносит дополнительные сложности с преобразованиями типов (особенно явным), что усложняет код и делает его менее читабельным.
Динамическая типизация, увы, дает вам больше свободы выстрелить себе в ногу. За ваш код ответственность несете только вы, и компилятор не поможет выловить ошибки сравнения числа и строки. Но с другой стороны вы имеете больше гибкости, ваш код может быть лаконичнее и порой динамическая типизация просто более читабельна.
Know your operators
Именно под таким девизом прошел весь май в Booking.com. Дело в том, что многие начинающие разработчики и дизайнеры путают операторы, из-за чего на продакшене появляется множество предупреждений (а наша политика — относись к любому warning-у как к ошибке).
Причина постоянных ошибок — Perl и его «изобретательность» (назовем это так, чтобы не ругаться). В перле, в отличие от других динамических языков, тип операции определяется не типом первого операнда, а заранее. Поэтому для сравнения строк и всего остального есть два разных набора операторов. Иными словами, нельзя написать if («string1» == «string2»), поскольку это всегда будет правдой (а также вызовет предупреждение в рантайме). Вместо == надо использовать eq. Аналогично существуют операторы ne, gt, lt, ge, le (подробнее про операторы тут).
Помимо тонкостей при сравнении строк, перл специфичен и с приоритетом операторов, который стоит помнить для избежания проблем. Например есть оператор &&, а есть оператор and. Оба они являются логическим И, однако у второго приоритет выполнения наименьший и будет выполнен последним. С одной стороны это удобно (не нужно писать лишних скобок), но читать программу, злоупотребляющую подобными нюансами, новичку будет тяжело.
Про операторы в C# рассказать не много. Они практически полностью перекочевали из C++, Java и подобным. Там все логично и понятно. Единственный нюанс, вытекающий из объектно-ссылочной сущности языка и платформы, что нельзя сравнивать два объекта напрямую операторов ==, ибо вы будете сравнивать ссылки, а не значения (только если вы не реализовали интерфейс IComparable). Осознав это, остальное уже несложно. Поэтому в данном разделе мои личные симпатии уходят C#, как более user friendly.
/"(?:[^"]++|.)+"/* (ах, эти регулярки)
Раз уж я упомянул странности и причуды перла, а также строки и user friendly, то самое время уделить немного главной фишке языка — регулярным выражениям.
Регулярки — основа основ языка, без которого Perl был бы уже не тем, чем он есть сейчас. Это очень мощный инструмент в умелых руках, помогающий найти, заменить или преобразовать (или все вместе) в строке какие-то данные, причем сделать это быстро и эффективно (но, зачастую, абсолютно непонятно). Именно про регулярки сложены основные легенды и мифы, а также различные извращения.
Давайте сравним синтаксис Perl и C# при работе с регулярками:
Perl:
my $str = 'Sent: 45 of 100 messages';
if ( $str =~ m/Sent: (\d+) of (\d+) messages/ ) {
my ($sent, $total) = ($1, $2);
print "Sent ratio is ". $sent/$total*100;
}
C#:
string input = "Sent: 45 of 100 messages";
Match match = Regex.Match(input, @"Sent: ([0-9]+) of ([0-9]+) messages$",
RegexOptions.IgnoreCase);
if (match.Success)
{
string sent = int.Parse(match.Groups[1].Value);
string total = int.Parse(match.Groups[2].Value);
Console.WriteLine("Sent ratio is {0}", sent/total*100);
}
Разница не так велика, однако код на перле лаконичнее, понятнее (особенно $1 и $2 для выделения групп) и не требует валидации, в то время как в C# я не стал проверять на ошибки парсинг строк в число и тому подобное.
В общем чего тут сравнивать — если ваши задачи требуют использования регулярок, то языка, лучше чем Perl, вы вряд ли отыщите. Например, мы в букинге используем их для парсинга user agent'ов (и не только), это очень упрощает жизнь при роутинге и дает полный набор всех полезных параметров посетителя.
Списки и хеши
Мы не задумываемся, но почти все программы так или иначе работают с данными в виде списков, массивов и хешей. Прочитали из БД — получили список строк, открыли файл — аналогично.
Поскольку список и хеш — встроенные типы данных в Perl, то в нем созданы все условия, чтобы упростить с ними работу. Давайте опять посмотрим на пример. Предположим, нам дан массив числе и нам надо найти в нем те, что встречаются больше 5 раз. Сравним код.
Perl:
my %input_hash;
$input_hash{$_}++ foreach @input;
my @filtered = grep $input_hash{$_} > 5, keys %input_hash;
C#:
var inputHash = new Dictionary<int, int>();
foreach (var elem in input)
{
if (inputHash.ContainsKey(elem))
inputHash[elem]++;
else
inputHash.Add(elem, 1);
}
var result = new List<int>();
foreach (var elem in inputHash.Keys)
{
if (inputHash[elem] > 5)
result.Add(elem);
}
Мне кажется, результат данного раунда очевиден. Многообразие map, grep, sort и других операторов/функций для манипуляций над списками позволяет творить чудеса и значительно экономит время программиста, сохраняя код читабельным и лаконичным. Для меня сочетание map+grep является killer feature языка, я влюбился в них с первого взгляда.
Что примечательно, многие из C#/Java программистов просто не помнят, что есть такие вещи, как хеш. Я провожу собеседования и вижу, как люди с подобным опытом работы не всегда вспоминают, в чем преимущества этих структур. Я и сам не часто задумывался над этим, пока в своем время не начал готовиться к собеседованию в Google (а затем в Booking.com).
ООП
Perl изначально не был создан как ООП язык, да и ООП тогда не сильно то был в моде. С течением времени мода на объекты возростала. И к счастью, гибкость перла не стала помехой. Несмотря на то, что объектов в классическом понимании этого слова в Perl нет, принципы работы с объектами существуют (как существуют и понятия состояния, наследования, инкапсуляции). Любой объект Perl — это хеш, которому было сказано, что он теперь объект. Примерно так:
my $obj = bless $class_name, $hash;
Теперь, помимо доступа к внутренним полям этого хеша, вы можете вызывать функции, объявленные в этом классе:
my $obj->do_some_magic();
Код класса объявляется в отдельном модуле, который затем подключается к основной программе. Модуль — это такой же обычный Perl-скрипт, без экстра магии.
ООП в C#, конечно же, классический. Тут есть все — инкапсуляция, полиморфизм, наследование. Всё есть объект, и объект есть всё. Любой тип, любая переменная может быть приведена к типу object. Интерфейсы, абстрактные классы, перегрузка функций — рай для архитекторов и любителей выделять абстракции из всего.
Что касается меня, то я раньше твердо считал, что ООП — это благо, которое помогает вам упорядочить код, легко его поддерживать и максимально переиспользовать. Когда работаешь с языком и платформой, которые кроме ООП ничего другого не позволяют, ты как-то не задумываешься об альтернативах. Однако после начала работы в букинге я поменял свою точку зрения.
Все дело в том, что на ООП уходит очень много времени. Необходимо продумать класс, понять, какие в нем выделить абстракции, что вынести в базовый класс, а что в наследников. Потом все эти IoC, сервис локаторы и прочие умные подходы. В букинге ничего этого нет. Мы пишем код очень линейно, вынося логику не в объекты, а в отдельные модули, код которых можно переиспользовать. Такой подход сложился в силу исторических причин (сайт был впервые написан в 1996 году) и в силу языковых особенностей. Когда вас не заставляют использовать объекты, вы вольны сами решать, что вам подходит больше всего.
Я вспоминаю, сколько времени уходило у меня на создание простого приложения ASP.NET MVC. Объявить классы контроллеров, добавить прослойку работы с БД, правильно её настроить, добавить IoC контейнер, интерфейсы к нему… Конечно, настроив все в начале потом становится проще, но этого «потом» может не быть (ибо становится лень).
Подводя итог разделу, я не хочу делать какой-то выбор. Все сильно зависит от решаемой задачи. Настольные приложения писать с помощью ООП гораздо сподручнее. Однако в вебе я вижу на практике, что можно обойтись без таких усложнений, ограничившись вынесением кода в отдельные модули, а сложную архитектуру сведя до минимума. Победителя нет, но приз зрительских симпатий уходит к Perl, потому что если вам нужны объекты, они тут есть, но их использование — дело сугубо добровольное.
IDE
В заключение стоит сказать про IDE. Для меня это, пожалуй, единственный раздел, где победителя выявить проще всего. Те из вас, кто хоть раз работал в Visual Studio, наверняка меня поддержат, если я скажу, что это лучшая IDE всех времен и народов. Подсветка синтаксиса, отладчик, билд-сервисы и встроенные автоматизированные тесты. Все это невероятно облегчает жизнь программиста. Увы, все это доступно только при работе с технологиями Microsoft — .NET, ASP.NET, C#.
Однако в последнее время VS все больше и больше становится пригодной и для других языков. Например, в последних релизах студии появилась отличная поддержка JavaScript, включая динамические подсказки IntelliSense. Есть встроенный Git с визуальными тулами (сравнение изменений, ветки и прочее). Есть даже расширение для работы с Python.
К сожалению, Perl в Visual Studio не поддерживается. Более того, в среде перл программистов считается постыдным работать на винде (ибо масдай, линукс, все дела). Поэтому я на своем рабочем маке пользуюсь Sublime Text 3. Очень хороший текстовый редактор, который имеет плагины для подстветки перлового синтаксиса, немного умеет автодополнения кода (даже динамического) и в целом работает стабильно.
При работе с перлом больше всего раздражает отладка. Я привык, что в C# в любом месте программы можно поставить брейкпоинт и Visual Studio любезно остановит выполнение программы ровно в этом месте, а заодно покажет весь call stack и значения локальных и глобальных переменных. Увы, но Perl, в силу своей природы, лишен подобных прелестей. Надо отладить код? Выводи все в консоль или логи, анализируй, меняй код, снова выводи в логи, и так далее. У нас в букинге инфраструктура построена так, что при изменении скрипта в Perl необходимо перезагружать сервер. Что-то вроде компиляции, несмотря на то, что язык динамический. А теперь представьте, какая это боль — отлаживать что-то в коде. Мало того, что тебе приходится ждать, пока случится рестарт (а это около секунд 20), так потом еще добраться до нужного места с ошибкой и понять, что забыл вывести в консоль нужные тебе переменные. Ко всему, конечно, привыкаешь, но этот процесс сильно тормозит разработку.
Поэтому я со всей уверенностью утверждаю, что в споре между C# и Perl по части IDE с огромным отрывом побеждает C# и Visual Studio.
Заключение
Если вы смогли дочитать до заключения и при этом не набросились на меня в комментариях — спасибо вам большое. Как вы могли понять, целью данной статьи было вовсе не разведение холивара или доказывания, что один язык лучше другого. Наоборот, по совокупности плюсов и минусов ни один язык не победил в явном виде. В чем-то C# лучше, а чем-то Perl, а в чем-то оба, в зависимости от ситуации.
Я вообще считаю, что споры о том, что лучше, они или для амбициозных молодых программистов, или для фанатиков (что, зачастую, совпадает). У любого языка есть свои сильные и слабые стороны. Выбор технологий в первую очередь должен быть обусловлен требованиями проекта, а не личными предпочтениями. Как показывает практика, даже в современном мире можно писать коммерчески успешные (очень успешные) продукты с огромной нагрузкой, используя при этом технологии из прошлого века (да, если вы еще не поняли, почти весь Booking.com написан на Perl). В то же время, если бы я запускал сейчас стартап, я бы не стал выбирать Perl просто хотя бы потому, что было бы трудно найти необходимых специалистов, а обучать новичков, как делает букинг, слишком накладно.
Мораль сей басни — нет лучшего языка, как и нет худшего. Да прибудет с вами сила :)
P.S.
Если вам интересно самому научиться Perl'у, а также поработать в крупнейшей конторе по продаже отелей, да еще и в центре европы, Амстердаме, то предлагаю ознакомиться с нашими вакансиями. Если решитесь попробовать, то можете прислать резюме мне, я его передам напрямую в HR, что будет гораздо быстрее.
Также я в свое время написал небольшие две статьи в своем блоге про то, как я сам собеседовался в Booking.com, возможно сочтете их интересными
На сим откланяюсь. Спасибо за внимание. Комментарии для срача — ниже :)