Статьи

Насколько строг ваш динамический язык?

Учитывая «большую четверку» динамических, процедурных языков; Perl, Python, PHP и Ruby, в некоторой степени, они очень разные, предлагая только небольшие вариации на одну и ту же тему (игнорируя отсутствие поддержки PHP для программирования в функциональном стиле). Но иногда мелочи имеют большое значение, и, возможно, больше всего, когда вашему коду дается ввод для обработки, для которой он не предназначен. Подбрал простой пример, чтобы сравнить их в этой области …

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

Perl

Сначала мы поговорим о Perl, вот рабочий пример …

#!/usr/bin/perl -w use strict; my $names = [ {'first'=>'Bob','given'=>'Smith'}, {'given'=>'Lukas'}, {'first'=>'Mary','given'=>'Doe'}, ]; sub printName { my $name = shift; print "Name is ".$name->{'first'}." ".$name->{'given'}."n" } foreach my $name ( @{$names} ) { printName($name); } 

Обратите внимание на список имен — во второй «строке» отсутствует «первая» клавиша. Выполнение этого вывода;

 Зовут боб смит
 Использование неинициализированного значения в конкатенации (.) Или в строке names.pl 12.
 Зовут Лукас
 Зовут Мэри Доу

Обратите внимание на вторую строку в выводе — Perl жалуется на попытку отменить ссылку на переменную, которая не существует. Но, после жалоб, исполнение продолжается

PHP

То же самое снова в PHP …

#!/usr/bin/php <?php $names = array( array('first'=>'Bob','given'=>'Smith'), array('given'=>'Lukas'), array('first'=>'Mary','given'=>'Doe'), ); function printName($name) { print "Name is ".$name['first']." ".$name['given']."n"; } foreach ($names as $name ) { printName($name); }
#!/usr/bin/php <?php $names = array( array('first'=>'Bob','given'=>'Smith'), array('given'=>'Lukas'), array('first'=>'Mary','given'=>'Doe'), ); function printName($name) { print "Name is ".$name['first']." ".$name['given']."n"; } foreach ($names as $name ) { printName($name); } 

Выход …

 Зовут боб смит

 Примечание: неопределенный индекс: сначала в /home/harryf/php/names.php в строке 9
 Зовут Лукас
 Зовут Мэри Доу

PHP делает почти то же самое, что и Perl — жалуется, но продолжает выполнение. Конечно, ошибка возникает как УВЕДОМЛЕНИЕ — во многих установках PHP по умолчанию этот уровень ошибки отключен, что означает, что вы не получите сообщение об ошибке. Еще один интересный момент — PHP жалуется на отсутствие индекса массива, а не на то, что значение каким-то образом невозможно использовать.

Рубин

Здесь мы идем снова, на этот раз в Ruby …

#!/usr/bin/ruby names = [ {'first'=>'Bob','given'=>'Smith'}, {'given'=>'Lukas'}, {'first'=>'Mary','given'=>'Doe'}, ]; def printName(name) puts("Name is "+name['first']+" "+name['given']+"n") end names.each { |name| printName(name) }
#!/usr/bin/ruby names = [ {'first'=>'Bob','given'=>'Smith'}, {'given'=>'Lukas'}, {'first'=>'Mary','given'=>'Doe'}, ]; def printName(name) puts("Name is "+name['first']+" "+name['given']+"n") end names.each { |name| printName(name) } 

И выход …

 Зовут боб смит
 names.rb: 10: в `+ ': невозможно преобразовать nil в String (TypeError)
     from names.rb: 10: в `printName '
     from names.rb: 14
     from names.rb: 13

Ruby, напротив, останавливает выполнение (вызывает исключение, которое не было обработано) при обнаружении недостающего хеш-ключа. Интересная причина: Ruby говорит, что отмена ссылки на то, что не существует, создает значение nil, которое нельзя просто присоединить к строке, отсюда исключение — TypeError.

питон

Наконец-то Python …

#!/usr/bin/python names = [ {'first':'Bob','given':'Smith'}, {'given':'Lukas'}, {'first':'Mary','given':'Doe'}, ]; def printName(name): print("Name is "+name['first']+" "+name['given']+"n") for name in names: printName(name)
#!/usr/bin/python names = [ {'first':'Bob','given':'Smith'}, {'given':'Lukas'}, {'first':'Mary','given':'Doe'}, ]; def printName(name): print("Name is "+name['first']+" "+name['given']+"n") for name in names: printName(name) 

А как реагирует Python?

 Зовут боб смит

 Traceback (последний вызов был последним):
   Файл "names.py", строка 12, в?
     printName (имя)
   Файл "names.py", строка 9, в printName
     печать ("Имя есть" + имя ['первый'] + "" + имя ['дано'] + "n")
 KeyError: 'first'

Как и Ruby, Python останавливает выполнение при обнаружении проблемного хеша (dict), вызывая исключение. В отличие от Ruby, но больше похож на PHP, Python жалуется на отсутствующий хеш-ключ — он вызвал исключение KeyError.

Ну и что?

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

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

И да, вы всегда должны писать тесты, но когда дело касается грубозернистых API-интерфейсов, обрабатывающих сложные структуры данных, всегда есть что-то, для чего вам не хватает теста. Фактически, я бы сказал, что эта общая категория проблем является одним из самых больших источников ошибок в «динамическом коде» — языки поощряют быстрое создание прототипов, что означает, что эти дополнительные 48 строк кода делают его действительно надежным, что происходит намного позже, если в все. Так что поведение языка по умолчанию важно для меня.

Итак, одна разделительная черта здесь — должна ли возникать какая-то фатальная ошибка. Perl и PHP продолжают выполнение по умолчанию, пока Ruby и Python останавливаются, если вы явно не решите проблему. Что лучше?

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

Другая разделительная черта здесь — то, на что фактически жалуются языки. Perl и Ruby обеспокоены тем, что ценность в некотором роде плохая. Python и PHP гораздо более явные, они жалуются именно на отсутствующий хеш-ключ.

С точки зрения обнаружения проблемы в коде позже, основываясь исключительно на сообщении об ошибке в файле журнала, думаю, что путь Python / PHP лучше — он точно говорит вам , что пошло не так. Кроме того, для Ruby то, что он вызвал исключение, кажется отчасти удачей — я попытался встроить значение в строку, что сразу вызвало ошибку. Но что, если бы я сделал что-то приемлемое со значением nil — возможно, просто присвоил ему другое значение? Вероятно, исключение появилось бы где-то, но не сразу, в тот момент, когда я отменил ссылку на него — отладочная радость, которую я себе представляю (зажигаешь меня, если я ошибаюсь).

Во всяком случае, просто для удовольствия (читай: троллинг), вот субъективная оценка строгости …

  1. Питон (самый строгий)
  2. Рубин
  3. Perl / PHP

Это похоже на философию Python . Я поставил Perl и PHP на последнее место, потому что PHP позволяет вам отключить эти ошибки E_NOTICE — в противном случае PHP стал бы строже, чем Perl, потому что он обнаружил отсутствующий хеш-ключ.

Если кто-то захочет, было бы интересно сравнить пример с IO , Lua и Erlang , где это возможно (примечание: я понял — просто нажал на зуммер PHP для удовольствия) — восхищен тем, что Damien предлагает в CouchDB , особенно часть о «Распределенной, показывающей надежную, инкрементную репликацию с двунаправленным обнаружением и разрешением конфликтов».).