Учитывая «большую четверку» динамических, процедурных языков; 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 — возможно, просто присвоил ему другое значение? Вероятно, исключение появилось бы где-то, но не сразу, в тот момент, когда я отменил ссылку на него — отладочная радость, которую я себе представляю (зажигаешь меня, если я ошибаюсь).
Во всяком случае, просто для удовольствия (читай: троллинг), вот субъективная оценка строгости …
- Питон (самый строгий)
- Рубин
- Perl / PHP
Это похоже на философию Python . Я поставил Perl и PHP на последнее место, потому что PHP позволяет вам отключить эти ошибки E_NOTICE — в противном случае PHP стал бы строже, чем Perl, потому что он обнаружил отсутствующий хеш-ключ.
Если кто-то захочет, было бы интересно сравнить пример с IO , Lua и Erlang , где это возможно (примечание: я понял — просто нажал на зуммер PHP для удовольствия) — восхищен тем, что Damien предлагает в CouchDB , особенно часть о «Распределенной, показывающей надежную, инкрементную репликацию с двунаправленным обнаружением и разрешением конфликтов».).