[Перевод] Ваш язык программирования отстой
1 Почему JavaScript отстой
• 1.1 Плохая конструкция
• 1.2 Система типов
• 1.3 Плохие функции
• 1.4 Отсутствующие функции
• 1.5 DOM
2 Почему Lua отстой
3 Почему PHP отстой
• 3.1 Исправлено в поддерживаемых в настоящее время версиях
4 Почему Perl 5 отстой
5 Почему Python отстой
• 5.1 Исправлено в Python 3
6 Почему Ruby отстой
7 Почему Flex/ActionScript отстой
8 Почему скриптовые языки отстой
9 Почему C отстой
10 Почему C++ отстой
11 Почему .NET отстой
12 Почему C# отстой
13 Почему VB.NET отстой
15 Почему Objective-C отстой
16 Почему Java отстой
• 16.1 Синтаксис
• 16.2 Исправлено в Java 7 (2011)
• 16.3 Библиотека
• 16.4 Обсуждение
17 Почему Backbase отстой
18 Почему XML отстой
19 Почему отстой XSLT/XPath
20 Почему CSS отстой
• 20.1 Исправлено в CSS3
21 Почему Scala отстой
22 Почему Haskell отстой
23 Почему Closure отстой
24 Почему Go отстой
• 24.1 Базовые средства программирования (базовый язык)
• 24.2 Взаимосовместимость
• 24.3 Стандартная библиотека
• 24.4 Набор инструментальных средств
• 24.5 Сообщество
25 Почему Rust отстой
• 25.1 Безопасность
• 25.2 Синтаксис
• 25.3 Конструкция API и система типов
• 25.4 Сообщество
• 25.5 Набор инструментальных средств
Почему JavaScript отстой
Учтите, что некоторые положения относятся не к самому JavaScript, а к программным интерфейсам веб-приложений (https://developer.mozilla.org/en/docs/Web/API). Плохая конструкция
• Каждый скрипт исполняется в едином глобальном пространство имён, доступ в которое возможен в браузерах с оконным объектом.
• Camel-регистр никуда не годится:
XMLHttpRequest
HTMLHRElement
• Автоматическое преобразование типа между строками и числами в сочетании с перегруженным »+» означает конкатенацию и сложение. Это порождает очень неожиданные явления, если вы непредумышленно преобразуете число в строку:
var i = 1;
// некоторый код
i = i + ""; // ой!
// ещё какой-то код
i + 1; // преобразует в строку "11"
i - 1; // преобразует в число 0
• Автоматическое преобразование типа для функции + также ведёт к наглядному явлению, что += 1 отличается от оператора ++. То же происходит при сортировке массива:
var j = "1";
j++; // j приобретает значение 2
var k = "1";
k += 1; // k приобретает значение "11"
[1,5,20,10].sort() // [1, 10, 20, 5]
• Оператор var использует область действия функции, а не область действия блока, что интуитивно совершенно непонятно. Вместо этого хочется использовать оператор let.Система типов
• JavaScript выстраивает мир в точную иерархию прототипов с Объектом наверху. На самом деле элементы не вписываются в точную иерархию.
• Невозможно что-то унаследовать от массива или других встроенных объектов. Синтаксис наследования через прототипы также представляется весьма загадочным и необычным. (Исправлено в ES6).
• Что не устраивает в наследовании через прототипы в JavaScript: функции, заданные в прототипе, не могут получить доступ к аргументам и локальным переменным в конструкторе, означая, что такие «открытые методы» не могут получить доступ к «приватным полям». Для какой-то функции оснований стать методом мало или нет вообще, если метод не может получить доступ к приватным полям. (Исправлено в ES6 через символы).
• JavaScript не поддерживает хэши или словари. Можно рассматривать такие объекты, однако Объекты наследуют свойства __proto__, что создаёт проблемы. (Используйте Object.create (null) в ES5 или Map в ES6).
• Аргумент не является массивом. Можно преобразовать его в таковой, используя срез (или Array.from в ES6):
var args = Array.prototype.slice.call(arguments);
(Аргументы в итоге будут устаревшими).
• Числовой тип имеет проблемы с точностью.
0.1 + 0.2 === 0.30000000000000004;
Проблема не в ожидаемом результате, а в выборе использования числа с плавающей точкой для представления чисел, и это является отложенным выбором разработчика языка. См. http://www.math.umd.edu/~jkolesar/mait613/floating_point_math.pdf.
• NaN не является обозначением числа, а само по себе является числом.
typeof NaN === "number"
// Чтобы сделать ситуацию ещё более трудной, NaN не равно самому себе
NaN != NaN
NaN !== NaN
// Проверяется, является ли "х" числом "NaN".
x !== x
// Это - правильный способ тестирования
isNaN(x)
Здесь показано, как должно быть согласно IEEE754. Снова проблема в непродуманном выборе IEEE754 со стороны разработчика или конструктора языка.
• Нуль (null) не является экземпляром Объекта, но typeof null === 'object'.
Плохие функции(Можно обойти многие из этих плохих функций, используя http://www.jslint.com/)
• JavaScript унаследовал многие плохие функции от C, в т.ч. переключаемый проход при невыполнении условия и позиционно-чувствительные операторы ++ и --. См. раздел «Почему сосет С» ниже.
• JavaScript унаследовал непонятный и проблемный синтаксис регулярных выражений у Perl.
• Ключевое слово «this» («это») является неоднозначным, сбивает с толку и вводит в заблуждение:
// "This" как локальная ссылка на объект в некотором методе
object.property = function foo() {
return this; // "This" является объектом, к которому присоединена функция (метод)
}
// "This" как глобальный объект
var functionVariable = function foo() {
return this; // "This" является окном
}
// "This" как новый объект
function ExampleObject() {
this.someNewProperty = bar; // "This" указывает на новый объект
this.confusing = true;
}
// "This" как локально изменяемая ссылка
function doSomething(somethingHandler, args) {
somethingHandler.apply(this, args); // Здесь "this" будет тем, что мы "обычно" ожидаем
this.foo = bar; // "This" было изменено вызовом "применить"
var that = this;
// Но это только начало, потому что смысл "this" может измениться три раза в одной функции
someVar.onEvent = function () {
that.confusing = true;
// Здесь "this" относилось бы к someVar
}
}
• Вставка точки с запятой
// "This" возвращается неопределённым
return
{
a: 5
};
• Объекты и операторы, а также метки имеют очень схожий синтаксис. Пример выше на самом деле возвращался неопределённым, затем формируя некоторый оператор. Этот пример в действительности вызывает ошибку синтаксиса.
// "This" возвращается неопределённым
return
{
'a': 5
};
• Подразумеваемые глобальные объекты:
function bar() {
// М-да, я не указал ключевое слово var, теперь у меня есть глобальная переменная
foo = 5;
}
(Это может быть исправлено при использовании директивы «use strict» («строгий режим») в ES5.)
• Оператор == разрешает приведение типов данных, что является полезным свойством, но не тем, которое хотелось бы иметь действующим по умолчанию. Оператор === устраняет эту проблему при неприведении типов данных, но вводит в заблуждение, будучи происходящим из других языков.
0 == ""
0 == "0"
0 == " \t\r\n "
"0" == false
null == undefined
"" != "0"
false != "false"
false != undefined
false != null
• Недостаток типизированных обёрток:
new Function("x", "y", "return x + y");
new Array(1, 2, 3, 4, 5);
new Object({"a": 5});
new Boolean(true);
• parseInt имеет действительно странное поведение по умолчанию, так что в общем случае необходимо добавлять его, если требуется, чтобы ваше основание логарифма было 10:
parseInt("72", 10);
Можно использовать Number ('72') для преобразования в число.
• Оператор «with» (не рекомендуемый) имеет тот недостаток, что он подвержен ошибкам.
with (obj) {
foo = 1;
bar = 2;
}
• Оператор «for in» участвует в цикле через элементы, унаследованные через цепь прототипов, поэтому его в общем случае необходимо включить в длинный вызов к object.hasOwnProperty (name) или использовать Object.keys (…).forEach (…).
for (var name in object) {
if (object.hasOwnProperty(name)) {
/* ... */
}
}
// Или
Object.keys(object).forEach(function() { ... });
• Там нет числовых массивов, имеются только объекты со свойствами, и эти свойства называются по текстовым строкам; как следствие петля «for in» проваливается при действиях на псевдочисловых массивах, поскольку итерационной переменной является фактически строка, а не число (это делает добавление целого числа трудным делом, т.к. необходимо вручную выполнять функцию parseInt с итерационной переменной при каждой итерации).
var n = 0;
for (var i in [3, 'hello world', false, 1.5]) {
i = parseInt(i); // выход является неправильным без этой громоздкой строки
alert(i + n);
}
// Или
[3, 'hello world', false, 1.5].map(Number).forEach(function() { alert(i + n) });
• Имеется также много устаревших (нерекомендуемых) функций (см. https://developer.mozilla.org/en/JavaScript/Reference/Deprecated_Features), таких как getYear и setYear на объектах Date. Отсутствующие функции
• Потребовалось ждать ES6, чтобы обеспечить неизменяемость. Этот оператор не работает для наиболее важных типов данных JavaScript — объектов, для которых приходится использовать Object.freeze (…).
// Это хорошо работает для чисел и строк
const pi = 3.14159265358;
const msg = "Hello World";
// Это не работает для объектов
const bar = {"a": 5, "b": 6};
const foo = [1, 2, 3, 4, 5];
// Также довольно трудно сделать ваши параметры постоянными
const func = function() {
const x = arguments[0], y = arguments[1];
return x + y;
};
• Должно быть более удобное средство написания функций, которое содержит неявное возвращение, особенно при использовании таких свойств функционального программирования как карта, фильтр и сворачивание. (ES6 исправил это).
ES6
x -> x * x
• Учитывая важность экспоненциальности в математике, Math.pow должен быть на самом деле инфиксным оператором, таким как **, а не функцией. (Исправлено в ES6 как **)
Math.pow(7, 2); // 49
• Стандартная библиотека не существует. Это приводит к тому, что браузеры загружают сотни килобайт кода при каждом обращении к веб-странице в мире только для того, чтобы быть в состоянии делать то, что мы обычно считаем само собой разумеющимся. DOM (объектная модель документов)
• Несовместимость браузеров Firefox, Internet Explorer, Opera, Google Chrome, Safari, Konqueror и т.д. делает работу с DOM чрезвычайно трудным делом.
• Если имеется обработчик событий, вызывающий alert (), то он всегда прекращает событие, независимо от того, желаете вы этого или нет.
// Данный обработчик событий даёт возможность событию распространяться
function doNothingWithEvent(event) {
return true;
}
// Данный обработчик событий прекращает распространение
function doNothingWithEvent(event) {
alert('screwing everything up');
return true;
}
Почему Lua отстой
• Объявление переменной является глобальным по умолчанию и выглядит точно так же, как назначение.
do
local realVar = "foo"
real_var = "bar" -- Oops
end
print(realVar, real_var) -- nil, "bar"
• Разыменование на несуществующем ключе возвращает ноль вместо ошибки. Это вместе с вышеупомянутым пунктом делает орфографические ошибки в Lua опасными и, в основном, скрытыми.
• Если vararg (аргумент переменной длины) находится в середине списка аргументов, то только первый аргумент будет учтён.
local function fn() return "bar", "baz" end
print("foo", fn()) -- foo bar baz
print("foo", fn(), "qux") -- foo bar qux
• Одновременно можно держать только один vararg (аргумент переменной длины) (в
...
).• Невозможно сохранить varargs (аргументы переменной длины) для дальнейшего.
• Невозможно выполнять перебор varargs (аргументов переменной длины).
• Невозможно видоизменять varargs (аргументы переменной длины) непосредственно.
• Можно собрать varargs в таблицах, чтобы сделать все эти действия, но тогда необходимо будет позаботиться об исключении нулевых значений, которые имеют силу в varargs, но являются сигналом конца таблиц, как, например, \0 в C-строках.
• Табличные индексы начинаются с единицы в литералах массива и в стандартной библиотеке. Можно использовать индексацию, основанную на 0, но тогда невозможно будет использовать ни одно из указанных действий.
• Выражения break
, do while
(while (something) do
, repeat something until something
) и goto
существуют, но нет continue
. Странно.
•Операторы отличаются от выражений, а выражения не могут существовать вне операторов:
>2+2
stdin:1: unexpected symbol near '2'
>return 2+2
4
• Строковая библиотека Lua по умолчанию обеспечивает только подмножество регулярных выражений, которое само несовместимо с обычными регулярными выражениями PCRE.
• Нет способа по умолчанию для копирования таблицы. Можно написать функцию для этого, которая будет работать, пока вы не пожелаете скопировать таблицу, используя __index-метаметод.
• Нет способа наложить ограничения на аргументы функции. «Безопасные» функции Lua представляют собой мешанину из кода проверки типа.
• Отсутствует модель объекта. Само по себе это не имеет большого значения, но приводит к некоторым противоречиям — строковый тип может быть обработан как объект, включая метатаблицу и строковые значения, вызванные этим методом. Это не действует для любого другого типа.
>("string"):upper()
STRING (СТРОКА)
>({1,2,3}):concat()
stdin:1: attempt to call method 'concat' (a nil value)
>(3.14):floor()
stdin:1: attempt to index a number value
Почему PHP отстой
•
'0'
, 0
и 0.0
являются неправильными, но '0.0'
— правильным.• Это и множество других проявлений плохой конструкции вызывают у меня грусть.
• Нет какой-то одной непротиворечивой идеи о том, что представляет собой выражение. Имеются, как минимум, три: нормальное плюс следующие исключения:
• здесь doc-синтаксис "<< не может быть использован в инициации значений по умолчанию атрибутов метода на PHP < 5.3.
• Документация не имеет версий. Имеется единственная версия документации, предлагаемая для использования с php4.x, php5, php5.1…
• Отсутствует общая концепция идентификатора. Некоторые идентификаторы (такие как имена переменных) чувствительны к регистру, другие — нет (как, например, вызовы функций):
$x = Array();
$y = array();
$x == $y; # is true
$x = 1;
$X = 1;
$x == $X; # is true
• Если неправильно ввести имя встроенной константы, то выдаётся предупреждение, и оно интерпретируется как строка «nonexistent_constant_name». Исполнение скрипта не останавливается.
• Если в вызов функции, задаваемый пользователь, введено слишком много аргументов, то ошибка не выдаётся; лишние аргументы будут проигнорированы.
• Это целенаправленное поведение для функций, которые могут принимать переменное количество аргументов (см. func_get_args()
).
• Если во встроенный вызов функции введено неправильное количество аргументов, то ошибка выдаётся; лишние аргументы не будут проигнорированы, как это имеет место при нормальном вызове функции.
• Array()
является хэш-массивом в одном типе данных.
• «Хэш-массив» сам по себе — нормально. Однако упорядоченный хэш-массив — это просто беда. Рассмотрим:
$arr[2] = 2;
$arr[1] = 1;
$arr[0] = 0;
foreach ($arr as $elem) { echo "$elem "; } // печатает "2 1 0"!!
• Нет использования динамических областей действия идентификаторов.
• Имеется автовосстановление идентификатора с неэквивалентом "use strict"
.
• В дополнение к реализации POSIX STRFTIME (3) собственный язык форматирования данных.
• Неважный строковый интерполирующий преобразователь:
error_log("Frobulating $net->ipv4->ip");
Frobulating Object id #5->ip
$foo = $net->ipv4;
error_log("Frobulating $foo->ip");
Frobulating 192.168.1.1
Однако здесь это будет работать как ожидалось:
error_log("Frobulating {$net->ipv4->ip}");
• Имеются два способа начать комментарий в конце строки: // и #.
• Код всегда должен находиться между тегами и
?>
, даже если это не HTML порождающий код, обсуждавшийся на соответствующей странице.
• Два имени для одного и того же типа с плавающей точкой: float
и double
.
• Имеются псевдотипы для определения параметров, которые принимают различные типы, но нет способа задать тип для специфического параметра, отличного от объектов, массивов или вызовов, начиная с PHP 5.4 (исправлено в PHP 7).
• Переполнение при целочисленной операции автоматически преобразует тип в плавающий (float)
.
• Имеются тысячи функций. При работе с массивами, строками, базами данных и т.п. приходится иметь дело с десятками функций, такими как array_diff
, array_reverse
и т.д. Операторы являются несовместимыми; например, массивы можно объединять лишь при помощи + (- не работает). Методы? Нет способа: $a.diff($b)
, $a.reverse()
не существует.
• PHP является языком на базе C и Perl, который не является, по своему существу, объектно-ориентированным. И если вы знаете внутренние переменные для объектов, то есть основания почувствовать себя счастливым.
• Имена функций неоднородные: оба имени —
array_reverse
и shuffle
— относятся к работе с массивами.• Некоторые функции являются функциями «needle, haystack», тогда как другие — «haystack, needle».
• Доступ к символам строки может быть как через обычные скобки, так и фигурные.
• Изменяемые переменные создают неоднозначности с массивами: $$a[1]
должно быть представлено как ${$a[1]}
или ${$a}[1]
, если требуется использовать $a[1]
или $aa
в качестве переменной для ссылки.
• Изменяемые переменные, в общем случае, являются великим злом. Если ответом являются изменяемые переменные, то, определённо, задан неправильный вопрос.
• Константы могут быть заданы только для скалярных значений: логические (булевские) значения, целые числа, ресурсные единицы, числа с плавающей точкой и строки (они могут держать массивы согласно PHP 7).
• Константы не используют префикс $
, как переменные, — что имеет смысл, так как они — константы, а не переменные.
• !
имеет больший приоритет, чем =
, но не в этом — if (!$a = foo())
— «специальном» случае!
• В 32- и 64-битных системах операторы сдвига (<< >> <<= >>=
) дают разные результаты для более чем 32 сдвигов.
• Встроенные классы (их экземпляры) можно сравнивать, но только если они не являются определяемыми пользователем.
• Массивы могут оказаться «несравнимыми».
•Операторы and
и or
совершают то же действие, что &&
и ||
, но только с разным приоритетом.
• Как фигурные скобки, так и :
с последующим endif;
, endwhile;
, endfor;
или endforeach
разделяют блоки для соответствующих операторов.
• Для преобразования в целые числа имеются (int)
и (integer)
, в логические значения — (bool)
и (boolean)
, в числа с плавающей точкой — (float)
, (double)
и (real)
.
• Преобразование числа с плавающей точкой в целое число может привести к неопределённым результатам, если исходное число находится за пределами целых чисел.
• Это вызывает фатальную ошибку на PHP5 — если вы не используете объект, реализующий функциональные возможности массива, давая вам то, что работает как объект И массив (и создаёт проблемы).
• Включаемый файл наследует область видимости переменной строки, в которой происходит включение, но функции и классы, определённые во включаемом файле, имеют глобальную область видимости.
• Определения класса и функции всегда имеют глобальную область видимости.
• Если некоторый включаемый файл должен вернуть какое-то значение, но этот файл не может быть включён, то оператор
include
возвращает FALSE
и только выдаёт предупреждение.• Если требуется какой-то файл, то необходимо использовать require()
.
• Функции и классы, определённые внутри других функций или классов, имеют глобальную область видимости: Они могут быть вызваны за пределами исходной области видимости.
• Значения по умолчанию в функциях должны быть справа от любых аргументов, используемых не по умолчанию, но можно задать их в любом месте, что может привести к неожиданностям:
function makeyogurt($type = "acidophilus", $flavour)
{
return "Making a bowl of $type $flavour.\n";
}
echo makeyogurt("raspberry"); // печатает "Изготовление вазы для малины". Будет только выдано предупреждение.
• Методы (PHP 4) могут быть вызваны как методы экземпляра класса (с заданной специальной переменной
$this
) и как методы класса (с self
).•Если класс объекта, который должен быть рассериализирован, не определён при вызове unserialize()
, то взамен будет создан экземпляр __PHP_Incomplete_Class
, теряющий любую ссылку на исходный класс.
• Оператор цикла перебора массивов foreach, применённый к объекту, выполняет итерацию через его переменные по умолчанию.
• Статические ссылки на текущий класс, такие как self: или __CLASS__, работают при использовании класса, в котором определена данная функция (может быть выполнено в PHP >= 5.3 с static::):
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test(); // печатает A, не B!
• Вложенные классы (класс, определённый внутри некоторого класса) не поддерживаются.
class a {
function nextFoo() {
class b {} // не поддерживается
}
}
• Глобальные объекты (параметры, переменные) не всегда «глобальные»:
$var1 = "Example variable";
$var2 = "";
function global_references($use_globals)
{
global $var1, $var2;
if (!$use_globals) {
$var2 =& $var1; // видимость обеспечена только внутри данной функции
} else {
$GLOBALS["var2"] =& $var1; // видимость обеспечена также в глобальном контексте
}
}
global_references(false);
echo "var2 is set to '$var2'\n"; // var2 установлена на ''
global_references(true);
echo "var2 is set to '$var2'\n"; // var2 установлена на ''Модельная переменная"
• Отсутствует концепция модуля/пакета: только вложенные файлы, «как в С».
• Значительная часть функциональных возможностей PHP обеспечена через скомпилированные модули, написанные в С.
• Нет способа сохранить 64-битные целые числа в собственном типе данных на 32-битовой машине, что ведёт к несообразностям (intval ('9999999999') возвращает 9999999999 на 64-битовой машине, но — 2147483647 на 32-битовой).
• Класс, представляющий файл, называется: SplFileObject
.
• SplFileObject
расширяет файловый метаобъект SplFileInfo
и одновременно является его свойством. Невозможно выбрать между наследованием и композицией? БУДЕМ ИСПОЛЬЗОВАТЬ ТО И ДРУГОЕ?!
• PHP в настоящее время почти единственный язык программирования, вообще, с файлом конфигурации. Такие штуки, как short_open_tags, которые являются единственным, что имело бы смысл задавать для каждого приложения, заданы администратором для всех приложений, которые он устанавливает!
• От этого у меня просто едет голова:
in_array("foobar", array(0)) === true
• Причина в том, что при выполнении нестрогих сравнений строка «foobar» принудительно вставляется в целое число, подлежащее сравнению с 0. Чтобы получить ожидаемый результат, необходимо установить для in_array флажок strict, который, очевидно, использует === вместо == для обеспечения подлинного тождества, а не просто какого-то типа равенства.
• php.ini может изменить всё поведение ваших скриптов, сделав их непереносимыми между различными машинами с различным файлом настройки (установочным файлом). Таким образом, он является единственным скриптовым языком с файлом настройки.
• null
, ""
, 0 и 0.0 — все они равны.
• Числа, начинающиеся с 0
, являются восьмеричными, поэтому 08
, 09
, 012345678
и подобные порождают ошибку. Вместо этого, любые цифры после первых 8 или 9 просто игнорируются: 08 == 0
, 08 != 8
, 0777 == 07778123456
(исправлено в PHP 7).
• Целочисленного деления нет, только с плавающей точкой, даже если оба операнда являются целыми числами; необходимо усекать результат, чтобы вернуться к целым числам (имеется функция intdiv()
как в PHP 7).
До PHP 5.5 действовали приведённые далее положения. Более старые версии не поддерживаются (как 04/19/2016):
• Фатальные ошибки не содержат обратную трассировку или трассировку стека (исправлено в PHP 5).
• Применение []
или {}
к переменной типа не массив или строка даёт обратно NULL
(исправлено в PHP 5).
• Имеются конструкторы, но нет деструкторов (исправлено в PHP 5).
• Имеются «методы класса», но нет (статических) переменных класса (исправлено в PHP 5).
•Ссылки в конструкторах не работают (исправлено в PHP 5):
class Foo {
function Foo($name) {
// создаёт ссылку внутри глобального массива $globalref
global $globalref;
$globalref[] = &$this;
$this->setName($name);
}
function echoName() {
echo "\n", $this->name;
}
function setName($name) {
$this->name = $name;
}
}
$bar1 = new Foo('set in constructor');
$bar1->setName('set from outside');
$bar1->echoName(); // печатает "установить извне"
$globalref[0]->echoName(); // печатает "установить в конструкторе"
// Необходимо снова сослаться на возвращённое значение, чтобы получить назад ссылку на тот же объект:
$bar2 =& new Foo('set in constructor');
$bar2->setName('set from outside');
$bar2->echoName(); // печатает "установить извне"
$globalref[1]->echoName(); // печатает "установить извне"
• Метод, определённый в базе, может «волшебным образом» стать конструктором для другого класса, если его имя соответствует классу первого (исправлено в PHP 5):
class A
{
function A()
{
echo "Constructor of A\n";
}
function B()
{
echo "Regular function for class A, but constructor for B";
}
}
class B extends A
{
}
$b = new B; // вызов B() как конструктора
• Исключения в функции __autoload не могут быть перехвачены, что приводит к фатальной ошибке (исправлено в PHP 5.3).
• Нет поддержки закрытия; create_function не учитывается, так как она принимает строку в качестве аргумента и не ссылается на область, в которой она была создана (исправлено в PHP 5.3).
• Если функция возвращает массив, то вы просто не можете писать (исправлено в PHP 5.4).
$first_element = function_returns_array()[0]; // Синтаксическая ОШИБКА!!
$first_element = ( function_returns_array() )[0]; // Это ни то, ни другое!!
// Взамен надо написать:
$a = function_returns_array();
$first_element = $a[0];
Почему Perl 5 отстой
• Perl хуже, чем Python, потому что люди хотели, чтобы он был хуже. Ларри Вол, 14 окт. 1998.
• «use strict» («строгий режим») — на самом деле, должно быть «use unstrict» («нестрогий режим») или «use slop» («нечёткий режим») (как в бильярде/пуле), что изменяет ситуацию и что само по себе никогда не должно использоваться. В любой ситуации. Кем бы то ни было. «Строгий» режим должен быть по умолчанию.
use strict;
use warnings;
• Вариантность символов чрезвычайно раздражает.
my @list = ("a", "b", "c");
print $list[0];
• Нет списков параметров (если вы не используете Perl6:: Subs).
sub foo {
my ($a, $b) = @_;
}
• Точечное представление для методов, свойств и т.д. является хорошим делом, особенно когда множество языков С-стиля делают это, а Perl оказывается случайно одним из тех языков, который этого не делает.
• Система типов должна быть модернизирована. Имеется поддержка только для типов скаляр, массив, хэш и т.д. и отсутствует поддержка для типов целое число, строка, логическое значение и т.д.
• Очень тяжело задать тип скаляр, например, нет лёгкого способа определить, является ли строкой некоторая переменная.
• Попытайтесь разъяснить некоторую ссылку С-программисту. Он скажет: «О, это указатель!», — хотя это будет не так. Ну не совсем. Это похоже на указатель, или я просто так слышу. Будучи Perl-программистом я знаю, что это не совсем указатель, но я не знаю точно, что такое указатель (в то время, как я знаю, что такое ссылка, но я не могу объяснить это — это не смог бы и Ларри Уолл: он назвал это — «штучка»).
• Синтаксис регулярных выражений ужасен.
• Редко есть хорошее место, чтобы поместить точку с запятой после here-doc.
my $doc = <<"HERE";
But why would you?
HERE
print $doc;
• Обычно требуется десять лет, чтобы понять, как вызывать функции или методы внутри строк, заключённых в двойные кавычки, как любая другая переменная. Хотя, если вы читаете это, вы добъётесь успеха: «Вы сделаете это @{[sub{'like'}]}. Легко».
• Так же, как Ruby, он имеет избыточное ключевое слово «unless» («пока не»). Можно выполнить операцию «if not», по крайней мере, тремя способами, что может привести к путанице и ухудшенной читаемости кода:
1. if (! выражение)
2. if (нет выражения)
3. unless (выражение)
• Я не могу использовать if($a==$b) $c=$d ;
; вместо этого я должен употребить:
1. $c=$d if ($a==$b); или
2. if ($a==$b) { $c=$d; }
• Как обстоит дело со всеми этими $,@,%,& перед переменными? Требуются изрядные усилия, чтобы печатать все эти штучки каждый раз … С и большинство других языков позволяют задать тип один раз, а затем можно использовать его, не задумываясь, что это такое. В любом случае Perl позволяет изменять ход выполнения в зависимости от того, что вы желаете делать, поэтому ещё более глупо использовать, скажем, @ и $ перед одной и той же переменной.
• Вы не поймёте свою программу, когда снова выйдете на неё через 6 месяцев.
Почему Python отстой
• Проблема отступов — обычно её называют «проблемой пробелов»: относительная величина отступа у оператора определяет, на какой уровень цикла / условия / и т.д. он действует. Ссылаясь на приведённый ниже фрагмент псевдо-C, можно сказать, что цель была, по-видимому, в том, чтобы обойти своего рода явную глупость, которая не должна появляться на первом месте.
if(something)
if(something_else)
do_this();
else
do_that();
Очевидно в С, что оператор «else», в действительности, относится ко второму оператору if (), несмотря на вводящий в заблуждение отступ; это не так в Python, где вместо изучения работы логического потока, вы просто вводите отступ, чтобы показать связь.
• Обязательным является свой аргумент в методах, хотя это означает, что можно использовать методы и функции взаимозаменяемо.
• Однако даже при этом бывают случаи, когда a.b () необязательно вызовет «b» как метод для «a»; другими словами, бывают случаи, когда a.b () не отправит «a» как «свой» аргумент; например:
class NoExplicit:
def __init__(self):
self.selfless = lambda: "nocomplain"
def withself(): return "croak" #will throw if calld on an _instance_ of NoExplicit
a = NoExplicit ()
print(a.selfless()) #won't complain
print(a.withself()) #will croak
Это значит, что даже если выражение в форме a.b () выглядит как вызов метода, это не всегда так; возможно, это противоречит линии «всё должно быть чётко и предсказуемо, насколько это возможно», которую Python упорно пытается держать.
• Многие библиотеки возвращают кортежи из вызовов функций, и приходится перерывать гору документации, чтобы выяснить, что же означают эти поля кортежей; если бы они взамен вернули dicts как в JavaScript, то имена полей часто были бы очевидны без документации.
• Синтаксис кортежей, х,
довольно «деликатный». При добавлении запятой в какое-то выражение оно превращается в кортеж. Это ведёт к ошибкам, которые трудно обнаружить:
foo = 1.0 + 2 # Foo is now 3.0
foo = 1,0 + 2 # Foo is now a tuple: (1,2)
foo = 3 # Foo is now 3
foo = 3, # Foo is now a tuple: (3,)
• Поскольку кортежи представлены с круглыми скобками для ясности и разрешения неоднозначности, то распространённым заблуждением является то, что круглые скобки необходимы и являются частью синтаксиса кортежей. Это приводит к путанице, поскольку кортежи концептуально похожи на списки и множества, но их синтаксис отличается. Легко подумать, что круглые скобки делают выражение кортежем, тогда как, на самом деле, это делает запятая. И если ситуация представляется ещё недостаточно запутанной, то примите: пустой кортеж не имеет запятой — только круглые скобки!
(1) == 1 # (1) is just 1
[1] != 1 # [1] is a list
1, == (1,) # 1, is a tuple
(1) + (2) != (1,2) # (1) + (2) is 1 + 2 = 3
[1] + [2] == [1,2] # [1] + [2] is a two-element list
isinstance((), tuple) == True # () is the empty tuple
• Значения по умолчанию для необязательных именованных аргументов оцениваются во время анализа, а не во время вызова. Примечание: можно использовать декораторы для имитационного моделирования динамических аргументов по умолчанию.
• Прерывание или продолжение по метке отсутствуют.
• Телом лямбда-функций может быть только выражение — не оператор; это означает, что невозможно сделать назначения внутри лямбда-функций, что делает их довольно бесполезными.
• Нет оператора выбора — приходится использовать кучу неприятных тестов if/elif/elif или неприглядных диспетчерских словарей (которые имеют низкую производительность).
• Отсутствует оператор типа »do ... until
», что заставляет использовать модели вроде »while not
:»
• Синтаксис для условного выражения в Python является неудобным (x if cond else y) Сравните с С-подобными языками: (cond? x: y).
• Нет оптимизации хвостового вызова функции, что значительно снижает эффективность некоторых функциональных моделей программирования.
• Нет констант.
• Нет интерфейсов, хотя абстрактные базовые классы являются шагом в этом направлении.
• Имеется, по крайней мере, 5 различных типов (несовместимых) списков [1].
• Непоследовательность в использовании методов/функций — некоторые функциональные возможности требуют использования методов (например, list.index ()), а другие — функций (например, len (list)).
• Нет синтаксиса для многострочных комментариев, идиоматический Python злоупотребляет многострочным синтаксисом строк вместо
"""
• Сосуществование Python2.x и Python3.x на системе Linux создаёт большие проблемы.
• Имена функций с двойным символом подчёркивания спереди и сзади представляют собой неприглядное явление:
__main__
• Основная конструкция функции выглядит очень неважно и является, по-видимому, худшим, что я когда-либо видел:
if __name__ == "__main__":
• Типы символов строки перед строкой — ужасное зрелище:
f.write(u'blah blah blah\n')
• Python 3 позволяет аннотировать функции и их аргументы, но они ничего не делают по умолчанию. Поскольку не существует никакого стандартного значения для них, то использование варьируется в зависимости от набора инструментов или библиотеки, и невозможно использовать это для двух разных вещей одновременно. Python 3.5 попытался исправить это, добавив дополнительные подсказки типа в стандартную библиотеку, но, так как основная часть Python не аннотирована и аннотировать можно только функции, то такая доработка, в основном, бесполезна.
• Генераторы определяются использованием ключевого слова «yield» в теле функции. Если Python встречает одинокое слов «yield» в вашей функции, то эта функция превращается в генератор, и любой оператор, который возвращает что-либо, становится синтаксической ошибкой.
• Python 3.5 вводит ключевые слова «async» и «await» для задания сопрограмм. Python претендовал на то, чтобы получить сопрограммы через генераторы с ключевыми словами «yield» и «yield from», но вместо исправления ситуации с генераторами была добавлена другая проблема. Теперь имеются асинхронные функции, а также нормальные функции и генераторы, а правила, определяющие ключевые слова, используемые внутри них, стали ещё более сложными [2]. В отличие от генераторов, где всё, содержащее ключевое слово «yield», становится генератором, всё, что использует сопрограммы, должно начинаться с префикса «async», в т.ч. «def», «with» и «for».
• Вызов super () является особым случаем компилятора, который работает по-другому, если он переименован [3].
• Стандартная библиотека использует несовместимые схемы назначения имён, например: os.path.expanduser
и os.path.supports_unicode_filenames
(прежняя не различается слова с нижним подчёркиванием, тогда как последняя делает это).
•
!=
может быть записано также как <>
(см. php).• Неполная встроенная поддержка комплексных чисел: как (-1)**(0.5)
, так и pow(-1, 0.5)
выдают ошибку вместо возврата 0+1j
.
• Возможность смешивать пробелы и табуляторы вызывает путаницу в том, является ли один пробел или один табулятор увеличенным отступом.
Почему Ruby отстой
•
String#downcase
? Кто называет это