Shared rss feeds:

Servare Mentem - blog

Мой предыдущий блог

вторник, 20 мая 2008 г.

PHP vs C#

Когда со мной начали спорить что PHP - вообще никакой язык, я молчать не смог.
Утверждалось, что в PHP вообще нет никаких преимуществ перед великим и могучим C#. Ну как тут было не поспорить? Лично я считаю, что PHP в любом случае более гибкий язык (в силу того что он интерпретируем) и всё что можно реализовать на C# - можно несложно реализовать и на PHP, а вот обратное во множестве случаев практически невозможно!
Мне говорили "но ведь PHP - не объектно-ориентированный язык! в C# - есть полноценная объектная модель, наследование, интерфейсы, абстрактные классы, виртуальные методы, свойства, и большая коллекция паттернов!", "Позвольте, позвольте, но ведь всё это так же есть и в PHP!" - отвечал я. В PHP5 есть нормальная объектная модель со всеми этими необходимыми фичами.
"А есть там делегаты?" - спросили меня. "Они там попросту не нужны" - отвечал я. - "Язык скриптовый, динамический и нестрого типизированный, поэтому нечего говорить про generics, delegates и прочие приблуды - зачем они здесь? Я больше чем уверен, что если мне потребуется делегат, я смогу его реализовать на PHP в 2 счёта!". Вообще, я быстро устаю от подобных нападок. Они, как правило, абсолютно беспочвенны. Ну почему мало кто воспринимает скриптовые языки всерьёз? Один лишь Ruby как-то выделяется на фоне остальных. Sun даже собирается включить его в следующую версию JDK. А PHP, мол, какашка - вчерашний день, не для серьёзных проектов... Ну что за бред, извините меня? Так считают люди которые с ним либо плохо знакомы, либо вообще не имеют представления о том, что такое PHP. Спросите у Oracle и IBM - почему они уделяют столько внимания именно PHP.
У Ruby очень грамотная реклама и хороший подход к обучению - "как писать правильно". И пожалуйста - Ruby On Rails лучший фреймворк в Интернете. Может быть, так оно и есть, я спорить не буду. Мне нравится идеология Rails, но то что Ruby - намного продвинутее PHP как язык, я сказать не могу. Думаю, что они скорее сравнимы. Просто почему-то никто не учится писать на PHP правильно.
Почему-то люди чаще начинают на нём писать плохо, копируют дурацкие примеры, не обращают внимания на абстракцию и декомпозицию... В итоге - в Интернете очень много плохого PHP кода. На PHP гораздо больше фреймворков чем на Ruby, но далеко не все они сравнимы с ROR по удобству. Думаю, что по той же причине, которую я обозначил выше. PHP, видимо, располагает к тому чтобы писать на нём плохо. Он расслабляет? Позволяет не обращать внимания на мелочи? Я в замешательстве.
Что же до сравнения PHP и C# давайте пройдём по наиболее существенным различиям, сравнивая лишь сам язык. Я ни в коей мере не собираюсь сравнивать скажем .NET и PHP - говорю только за язык. Итак:
- ООП? Что сказать - необходимый минимум в PHP есть. Интерфейсы, абстрактные классы, наследование, паттерны - это всё легко реализуемо. Ну да, нет в нём парциальных и вложенных классов. Но, по-моему, это не так существенно. Вот то что нет пространств имён - уже минус, здесь спорить не буду. Хотя, при грамотном подходе можно обойтись и без них. В конце концов их можно реализовать самостоятельно так же как они реализуются в JavaScript(есть прекрасные примеры - ZendFramework, PRADO... etc). Ну а кого это не устраивает - можно подождать выхода 5.3, где namespaces уже будут включены в язык.
- Строгая типизация? Такой же плюс как и минус, объяснять, надеюсь, смысла нет.
- Generics? В PHP от них никакого толка поскольку опять же типизация нестрогая...
- Delegates? - Ну не нужны они здесь! В крайнем случае я могу вызвать create_function(). Это похоже на создание анонимного делегата в C#. Например, сортировка массива:
// PHP 5.x code:
usort($arr, create_function('$a,$b','return ($a==$b)?0:($a<$b)? 1 : -1;'));
- Closures? - Вот это уже интересно. В PHP их нет. Думаю, что это довольно существенный недостаток. Однако, нет ничего невозможного. Можно реализовать и такую приблуду, если захотеть. Один из вариантов реализации - в SMPHPToolkit. Для сравнения:
C# 3.0 code:
var ary = { 1, 2, 3 };
var x = 2;
ary.Select(elem => elem * x;);

То же самое на PHP:
$arr = arr( 1, 2, 3 );
$x = 2;
$arr->select(eval(${new closure('$el')}->{'$el*=$x'}));
В принципе получилось не сильно больше кода. А по сути - почти то же самое. То что это можно реализовать средствами самого языка а не встроенной поддержкой - уже говорит о большей гибкости языка.
Возможно, это не лучшая эмуляция closure на PHP. Очень надеюсь что полноценные замыкания войдут в 6-ю версию.
Но, признаться, я не считаю это таким уж существенным преимуществом. В конце концов в Java тоже нет нормальных замыканий, но самые лучшие фреймворки и паттерны написаны как раз на нём.
Всегда можно воспользоваться классическими интерфейсами.
А теперь давайте рассмотрим преимущества PHP, которых уж точно нет в C# и которые так просто не реализовать.
- Буферизованный вывод. Считаю что это существенное преимущество PHP перед конкурентами. Например, для создания простейшего темплейтного движка:
 function apply_template($filename, $context)
 {
  ob_start();
  extract($context);
  require($filename);
  return ob_get_clean();
 }

 $content = apply_template("template.tpl.php", array('title'=>'Мой заголовок'));
Что делает эта фунцкия? Элементарно - включает файл шаблона в текст текущей страницы и при этом извлекает переменные из массива $context в текущий контекст. Таким образом, в шаблоне будет доступна переменная $title со значением 'Мой заголовок'. А весь текст шаблона (с любым PHP/HTML кодом) будет интерпретирован с текущим контекстом и результат будет возвращён функцией в виде строки.
- Eval. Элементарно - я могу в любой строке написать php/html код и сделать eval($code). Этот код будет интерпретирован с текущим контекстом. Таким образом, можно сделать так чтобы код генерировал код. Очень удобная возможность, отсутствующая в C#.
- Autoload. В PHP есть прекрасный механизм - можно подключать файлы с классами лишь тогда, когда они запрашиваются в первый раз. Например:
function __autoload($class_name) {
    require_once $class_name . '.inc.php';
}
$obj  = new MyClass1();
В данном примере при создании объекта класса MyClass1 будет подключён файл MyClass1.inc.php.
- "Magic methods". Суть "магических" методов в том, что можно реализовать динамически изменяемые классы меняющие своё поведение при обращении к определённым свойствам, методам или при преобразовании их к строке. С помощью __get и __set можно реализовать добавление новых свойств и методов для объекта при первом обращении к ним:
class Setter {
  private $_props = array();
  public function __get($prop) {
    if (isset($this->_props[$prop]))
   return $this->_props[$prop]
    return null;
  }
  function __set($prop, $val) {
      $this->_props[$prop] = $val;
  }
}
$foo = new Setter();
$foo->MyProperty1 = 1;
$foo->MyProperty2 = new MyClass();
Такое поведение похоже на поведение объекта в Ruby, где каждый класс является объектом с изменяемой в процессе работы сигнатурой.
- ${generate_id()}. Под этим я имею ввиду возможность обращаться к переменным или методам с именем, которое является просто строкой. Мне необязательно писать $var. Я могу написать ${'var'} или ${gen_var()}. Это всё будет обращением к переменной $var. На мой взгляд очень полезная возможность.

Conclusion Я перечислил далеко не всё, что, по-моему, легко реализуется в PHP, а в C# почти нереализуемо. Но и этих примеров должно хватить, чтобы понять что PHP всё-таки динамический и гибкий язык и не стоит к нему обращаться как к вчерашнему дню. У PHP свои преимущества и недостатки. Он не хуже Ruby или C#, но и не лучше их. Каждый язык предназначен для своей цели. PHP изначально затачивался под WEB. И он является в настоящее время лидирующей технологией, которая используется в Интернете. Не думаю, что это простая случайность и недоразумение. Да, в нём нет того язящества которое есть в Ruby. Да, его не сравнить по мощи с .NET или Java. Он делает лишь то, для чего создан и справляется с этим на ура. Чтобы найти на нём хороший фреймворк для веб-разработки, долго искать не нужно - их сотни, что так же касается и CMS. Примеров крупномасштабных проектов так же предостаточно. Это заблуждение, что PHP не для них, а только для персональных сайтов. Так было когда-то давно, когда он только появился. Сейчас же всё совсем по-другому.
Так что всё зависит от подхода и от программиста. То, что PHP расслабляет при программировании вовсе не значит, что нужно отказаться от написания хорошего кода и писать ad hoc. Нужно просто выбрать тот framework, который не позволяет этого делать. Вот Ruby On Rails во многом ограничивает. И это правильный подход, поскольку свобода всегда оставляет возможность для написания хардкода. А такой возможности программиста нужно безжалостно лишать.

Ссылки по теме:

Ярлыки: , , , ,

среда, 20 февраля 2008 г.

Java vs C#

Начну свой новый блог, пожалуй, с очень интересной и актуальной темы: сравнение возможностей Java и C#. Нашёл очень хорошую статью, где всё разобрано подробно и разложено по полочкам. Для сравнения на некоторых сайтах попадались совершенно безграмотные обзоры. Очевидно, что люди совершенно не знают о возможностях Java и судят о них с потолка. Например, видел где-то упоминание о том, что в Java нет очень полезного и удобного оператора foreach или аналогичного ему (который, впрочем, MS спёрли у Perl), тогда как в java есть полный аналог foreach: for({type} {identifier} : {array})
Вообще, решил заняться этим вопросом, когда мне начали говорить, что C# несравнимо более богатый и продвинутый язык, нежели Java. Меня это несколько удивило - ведь я всегда считал, что C# - это просто фактическая копия явы с чуть изменённым синтаксисом. А утверждается, что в C# в отличие от Java есть: нормальные Generic, delegates, partial classes и closures.

closures & delegates

Я начал с последнего, залез в справку и увидел, что closures в Java в прямом понимании действительно нет (их введут только с JDK1.7). Но вот что интересно - так ли эти closures важны - то есть неужели их наличие играет огромную роль? Сам я, как человек, имеющий достаточно небольшой опыт работы и с Java и с C#, их никогда не использовал. Давайте посмотрим. Что такое по сути closure? Первый раз при программировании я столкнулся с этим понятием когда писал класс для рендеринга на C++ Builder и хотел спрятать таймер, по которому происходила бы отрисовка сцены внутрь него. Соответственно мне требовался callback, который задавался бы этому таймеру и я хотел задать в качестве оного - метод этого класса. Но с удивлением тогда для себя увидел, что компилятор этого мне этого сделать не позволяет. Почитав справку, я узнал что у него на это есть две причины – во-первых, чисто формальная. По правилам языка, указатель на метод класса имеет тип не указателя на функцию, а указателя на метод. И его нельзя приводить к указателю на функцию. Из-за второй причины – когда мы вызываем какой-то метод, то мы его вызываем не сам по себе, а для какого-то объекта. И указатель на этот объект передается неявно как указатель this, таким образом этот метод знает поля какого именно объекта надо модифицировать. Поэтому при использовании callback'а - метода класса, нужно как-то передавать ещё и указатель на объект. Для Borland эта проблема решилась изменением стандарта C++ и добавлением модификтора __closure в свой компилятор. Closure – это специальный вид XE(расширения стандарта) указателя на функцию, используемый в большинстве функций в библиотеках Windows. В отличие от обычных указателей на функции, эти содержат не только адрес вызываемой функции (четырехбайтный указатель), но так же и указатель на объект , для которого вызывается событие (указатель this). Использование выражения __closure некоторым образом ограничивает возможности системы, так как при нем возможно использовать лишь ограниченное число объектов одного класса. Считается, что использование указателей closure – весьма важная концепция, осуществленная в CBuilder. Хотя, лично я считаю, что любая концепция, которая там осуществлена и которая при этом отдаляет их среду от стандарта - есть зло. Для того, чтобы использовать closure и при этом не нарушать стандарт можно легко обойтись маленькой библиотчкой, которую вполне можно написать самому. Подобная же вещь реализована в библиотеке boost без всяких дополнительных модификаторов. Итак, а что понимается под closure в C# и почему же это так важно? В шарпе под closure понимается функция, которая захватывает значения своего лексического окружения, когда она создается во время исполнения. Лексическое окружение функции есть множество локальных переменных, полей и других членов классов, видимых из этой функции. Определение замыкания подразумевает понятие создания функции во время исполнения. Рассмотрим пример использования замыканий на C#:
int[] ary = { 1, 2, 3 };
int x = 2;
Array.ConvertAll(ary, delegate(int elem) { return elem * x; }); // { 2, 4, 6 }
Таким образом, C# использует анонимные методы, способные замыкаться на контекст в котором они определяются. Вещь, конечно, удобная и полезная - спору нет. Но ведь в Java тоже есть замыкания! Они реализуются с помощью анонимных классов. Анонимный класс имеет доступ к полям класса, в лексическом контексте которого он определен, а так же к переменным с модификатором final в лексическом контексте метода. Например:
public final calculateInSeparateThread(final URI uri) {
 // The expression "new Runnable() { ... }" is an anonymous class.
 Runnable runner = new Runnable() {
         void run() {
             // It can access final local variables:
             calculate(uri);
             // It can access private fields of the enclosing class:
             btnSave.setEnabled(true);
         }
     };
 new Thread(runner).start();
}
Согласен, в подобном синтаксисе есть некоторая громоздкость, но не говорите мне что в Java нет замыканий. Что же касается делегатов, то вполне можно обойтись без них. В C# delegate - это по сути указатель на метод какого-то объекта. Допустим, один объект представляет собой часть какой-то работы и не хочет завязываться на input/output, но нужно реализовать лог этой работы. Всё что нужно знать объекту - сигнатуру метода, который будет вести лог (например, выводить информацию о ходе процесса на консоль). То есть, объект ничего не будет знать о реализации данного метода, но будет его вызывать. Лично на мой взгляд подобная ситуация легко заменяется имплементацией объектом, предоставляющим вывод на консоль какого-либо интерфейса ILogger. Таким образом, классу выполняющему работу нужно будет знать только о существовании этого интерфейса. А чем это собственно, хуже? По-моему, это менее запутанная тропа чем какие-то сомнительные делегаты...

Generics

Generics в C# и Java реализованы действительно по-разному. Кто-то мне говорил что в Java генерики так сказать "формальные", то есть их там фактически нет. Я решил понять что он имеет ввиду. А выяснилось следующее. В Java они реализованы с использованием "затирания типа" (type erasure). Это значит, что представление о типа существует только на этапе компиляции, после которой компилятор заменяет тип во всех generic-объявлениях на Object. Затем компилятор добавляет приведения типов в нужные места. Причина реализации данного подхода состоит в требованиях совместимости кода, использующего Generics и старого кода, который их не поддерживает. Главная проблема с затиранием типа состоит в том, что информация о типе generic не доступна во время выполнения через Reflection или идентификацию типа во время выполнения. Другое последствие этого подхода в том, что generic типы структур данных должны всегда объявляться, используя объекты а не не примитивные типы. Таким образом нужно создавать Stack<Integer> вместо Stack<int> для работы с целыми числами.
В C# есть явная поддержка generics на уровне языка MSIL. Когда generic тип компилируется, произведенный MSIL содержит информацию об определенных типах. Во время выполнения, когда ссылка применена к generic типу (например, List<int>) система проверяет был ли осуществлён запрос типа или нет. Если тип уже запрашивался, то будет возвращен созданный предварительно определенный тип. В противном случае JIT компилятор создаст новый тип, заменив параметр generic типа в MSIL определенным типом (например заменит List<T> на List<int>). Нужно отметить, что, если требуемый тип - ссылочный, тогда параметр generic типа заменяется на Object. Однако, приведения типов во среде выполнения .NET при доступе к типам нет.
Ещё одним отличием generics в Java и в C# являются constraints(ограничения) на параметр типа. В Java всего одно ограничение - derivation constraint ограничение происхождения(можно указать что тип должен обязательно расширять какой-либо класс). В C# есть ещё 2 типа ограничений: можно указать, что generic тип должен обязательно иметь пустой конструктор(конструктор по-умолчанию), а так же наложить ограничение на то ссылочный ли это тип или простой тип.
Итак, в чём это принципиальное отличие которое делает C# языком с большой буквы в сравнении с Java? Лишь в том, что нельзя определить во время выполнения какой тип передан в generic в качестве параметра? Так ведь и с этим можно справиться более простыми методами. Например, можно опять же отнаследовать свой generic от класса, в котором определить метод:
protected Class<T> genericParameter;
//...
public Class<T> getGenericParameter() {
    return genericParameter;
}
Теперь для объекта MyGeneric<Integer> mygeneric я могу получить тип Integer вызвав: mygeneric.genericParameter(). Всё предельно просто и ясно.

partial classes

Угу. Есть такой момент - в C# можно тело класса раскидать на кучу файлов. В Java же всё строго - один класс - один файл. Да, не спорю, это немного стесняет свободу. Но что же делать - идеология Java нам советует дробить программу как только можно. Если класс разрастает до такой степени, что ему становится тесно в своём файле - значит это неправильный класс с точки зрения архитектуры, его следует разбить на подзадачи и вынести их в свои классы. А идеология Microsoft - это "давайте напишем такую штуку которая будет делать ВСЁ". И на тебе - всю функциональность давайте запихнём в один класс, вот будет прекрасно. Нет уж, ребята, по-моему, это лишнее и только затрудняет понимание архитектуры приложения, как, впрочем и все остальные вещи которые якобы выводят C# на несравнимо более высокий уровень.

conclusion

Ну что сказать. Разумеется, C# в отличие от Java предоставляет много "вкусностей". Но на мой взгляд они зачастую излишни и затрудняют понимание назначения кода, запутывают программиста. Конечно, я разобрал лишь малую толику отличий. В предлагаемой статье разобрано куда больше. Я не учёл такие моменты как: принудительный вызов GC (зачем?!); передача аргументов нессылочных типов по ссылке(излишне); различие между зубчатыми массивами и многомерными массивами(достаточно полезно, хотя и не столь принципиально); свойства(properties); препроцессорные директивы; перегрузка оператора []; недвусмысленная имплементация интерфейсов; дружественные пространства имён; и ещё многая прочая муть (подробнее - в статье).
В Java многое из этого не было реализовано именно из-за того, что эти вещи часто затрудняли читабельность кода в C++. C# больше похож на C++ чем Java - это факт. Но становится ли он от этого на ступень выше Java - вопрос очень спорный. Это тоже самое что сказать "Марс лучше Юпитера" или, скажем, "Интерференция на ступень выше дифракции" или "Берёза лучше Дуба"... Я не противник C#. Согласен, он предоставляет множество удобств, которых в Java нет. Но они не столь существенны. Оба языка дают широкие возможности для выражения собственных мыслей, и нет проблем, которые можно было бы решить на C# и нельзя на Java.

[to be continued...]

Ярлыки: , ,

-->