Pull to refresh

Ваш язык программирования — отстой

Reading time 54 min
Views 139K
Original author: Сообщество Theory.org
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 Библиотека
• 16.5 Обсуждение
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-синтаксис "<<<END" не может быть использован в инициации значений по умолчанию атрибутов метода на 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}");

• Имеются два способа начать комментарий в конце строки: // и #.

• Код всегда должен находиться между тегами <?php и ?>, даже если это не 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, где вместо изучения работы логического потока, вы просто вводите отступ, чтобы показать связь.

• Обязательным является аргумент self в методах, хотя это означает, что можно использовать методы и функции взаимозаменяемо.

• Однако даже при этом бывают случаи, когда 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 <condition>", что заставляет использовать модели вроде "while not <condition>:"

• Синтаксис для условного выражения в 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 (прежняя не различается слова с нижним подчёркиванием, тогда как последняя делает это).

Исправлено в Python 3

!= может быть записано также как <> (см. php).

• Неполная встроенная поддержка комплексных чисел: как (-1)**(0.5), так и pow(-1, 0.5) выдают ошибку вместо возврата 0+1j.

• Возможность смешивать пробелы и табуляторы вызывает путаницу в том, является ли один пробел или один табулятор увеличенным отступом.

Почему Ruby отстой


String#downcase? Кто называет это «downcase»? Правильное название — «lower case,» и метод должен называться «lowercase» или «lower». А String#upcase должен называться «uppercase» или «upper». Это, на самом деле, не делает Ruby плохим — дело только личного вкуса. Всё же лучше, чем история с tw… PHP.

• Поддержка Unicode должна была быть встроена, начиная с версии 1.0, но не была добавлена и после множества выражений недовольства в 1.9/2.0 в 2007 году.

• Нет поддержки ретроспективной/опережающей проверки в регулярных выражениях в версии 1.8.

• Регулярные выражения всегда находятся в многострочном режиме.

• (Начиная с Ruby 2.0, не действует!) Нет реальной поддержки произвольных именованных аргументов (пары ключ=значение в определениях функций являются позиционными аргументами со значениями по умолчанию).

• Использование @ и @@ для получения доступа к элементам экземпляра и класса может быть не совсем понятным сначала.

• Нет продуманных и тщательно планируемых изменений, проводимых так, чтобы они не могли нарушить совместимость; даже небольшое редактирование может привести к её нарушению: см. «Проблемы совместимости» и «файловые утилиты». Это ведёт к нескольким рекомендованным стабильным версиям: как 1.8.7, так и 1.9.1 для Windows. Какую из них использовать?

• Экспериментальные и (как известно) содержащие множество ошибок свойства добавлены в рабочие и «стабильные» версии: см. «прохождение блока в Proc».

• Документация не проверена: она содержит недоступные ссылки, как, например, «Что должен знать любой начинающий».

• Имеется несколько небольших «глюков». nil.to_i превращает «nil» в 0, но 0 не расценивается как «nil». nil.to_i.nil? #=> false.

String#to_i просто игнорирует замыкающие символы, а это означает: "x".to_i == 0.

• Ruby позволяет пользователям модифицировать встроенные классы, что может быть полезным, однако ограниченное пространство имён означает, что расширения могут конфликтовать. Опытные программисты знают, что можно добавить функциональные возможности через модули, а не заниматься партизанским латанием встроенных классов, но всё же склонны к злоупотреблению. Эта проблема была решена в Ruby 2.0.

• Методы с назначенными псевдонимами в стандартной библиотеке делают чтение кода, написанного другими, более трудным, если читающий ещё не очень хорошо знаком с основным материалом. Например, Array#size/Array#length, Array#[]/Array#slice.

• Изменчивые типы, такие как множества, являются всё еще хэшируемыми. Это может привести к ситуации, когда хэш будет содержать один и тот же ключ дважды и вернёт некоторое случайное значение (первое?) при доступе к этому ключу.

• Опускание круглой скобки в вызовах функции позволяет реализовать/смоделировать установщика свойств, но может привести к недоразумениям.

• Небольшая неоднозначность возникает между блоками (замкнутыми выражениями) и синтаксисом хэша при использовании фигурных скобок для обоих.

• Суффиксные условия после целых блоков кода, например, begin ... rescue ... end if expr. Гарантированно будет потеряно if expr, если блок кода содержит несколько строк.

• Ключевое слово unless (действует как if not) делает, как правило, код труднее для понимания для некоторых людей.

• Различие между необусловленными вызовами метода и доступом локальных переменных не является очевидным. Это особенно плохо в языке, который не требует от вас объявления локальных переменных и где можно получить доступ к ним без сообщения об ошибке, прежде чем они будут первый раз назначены.

• «Утрачивающие значение» функции. Значением последнего выражения в какой-то функции, как предполагается, является возвращаемое значение. Необходимо явно прописать nil, если требуется сделать вашу функцию «void» («пустая») (процедура). Это похоже на ситуацию, когда вы, действительно, заботитесь о том возвращаемом значении.

• pre-1.9: нет способа получить stdout, stderr и код завершения (все сразу) какого-то подпроцесса.

`` синтаксис со строковой интерполяцией для текущих подпроцессов. Это делает лёгкой атаку с внедрением в оболочку.

• Регулярные выражения волшебным образом назначают переменные: $1, $2,…

• Стандартные контейнеры (Array, Hash) имеют очень большие интерфейсы, что затрудняет их эмулирование. Поэтому эмулировать не следует — надо взамен наследовать. class ThingLikeArray < Array; end.

• Разрешены как символы, так и строки, и они часто используются как ключи в хэшах, но "foo" != :foo, что ведёт к нововведениям, как, например, HashWithIndifferentAccess.

• Ошибки анализатора могли бы быть выражены более ясно. «синтаксическая ошибка, неожиданный kEND, возможный $end» фактически означает «синтаксическая ошибка, неожиданный 'конец' ключевого слова, возможный конец ввода».

• Символы неодинаковые, если их закодированные байты не равны, даже если они представляют одни и те же строки.

Почему Flex/ActionScript отстой


• Класс String определён как завершённый, поэтому, если требуется добавить какую-то функцию к незавершённому классу String, как, например, startsWith или hashCode, то необходимо выполнить pass thrus для всех методов String.

• Переменные метода действительны для всего метода, а не только для текущего блока кода. (Следует отметить, что это является недостатком также и в JavaScript).

• Нет абстрактного ключевого слова и нет поддержки защищённых конструкторов, что делает невозможными проверки времени компиляции абстракций.

• Публично видимые внутренние классы не поддерживаются, хотя частные внутренние классы могут быть взломаны.

Почему скриптовые языки отстой


• Они являются слишком специфичными для домена, поэтому, когда требуется уйти из своего домена, то необходимо использовать какой-либо другой скриптовый язык, а если требуется расширить язык сам по себе или делать что-либо реально продвинутое, то необходимо использовать язык операционной системы. Это ведёт к большой мешанине различных языков программирования.

Почему С отстой


• Ручное управление памятью может быть утомительным.

• Обращение со строками является частью ручного управления памятью. См. выше.

• Поддержка параллельного программирования довольно слабая.

• Ужасные названия стандартных функций: isalnum, fprintf, fscanf и т.д.

• Препроцессор.

• Не было сильных ощущений сегодня? Выполните аварийное завершение программы.

• Нехватка достоверной информации при аварийном завершении программы… «Стандартный» набор инструментальных средств GNU не информирует вас о случаях аварийного завершения программы во время стандартного прогона.

• Если программа унаследованная, то через пару дней у вас будет совсем не геройский вид.

• Стандартная библиотека охватывает только основные манипуляции со строками, ввод-вывод файлов, а также распределение памяти. И qsort() по какой-то причине. Вы не получаете ничего такого, что является по-настоящему переносимым.

• Разработка своих собственных контейнеров (вероятно, более низкого качества по сравнению со стандартной, хорошо оптимизированной версией) является тем, что вам лучше привыкнуть делать для каждого нового приложения; или следует использовать чрезвычайно сложные существующие конструкции, управляющие вашим приложением (nspr, apr, glib...).

• Также отсутствует стандарт для сетевого программирования. Большинство платформ использует интерфейс прикладного программирования (API) сокетов Berkeley, но он сильно зависит от поставщика. В частности, API от Microsoft имеет много необычных особенностей. Хотя можно пожелать удачи в поиске переносимого O (1) API выбора асинхронного сокета; нет ни одного (select() и poll() имеют характеристику O(n)).

• Автоматическая инициализация переменных в нуль не происходит, хотя они являются статическими, поскольку «это более эффективно».

• Есть желание использовать С99 в переносимом виде? Конечно; но если вам потребуется использовать MSVC, более старый чем 2015 (вполне вероятно у некоторых разработчиков), то ничего не получится.

• Есть желание использовать С11 в переносимом виде? Конечно, но в значительной степени никто, кроме GCC и случайных троллей, не поддерживает этого. Извините, пользователи MSVC.

• Массивы переменной длины, как правило, помещаются в стек, а это означает, что передача при слишком большом размере небезопасна: может произойти переполнение стека в тот момент, когда вы используете его. Нет никакого способа узнать, что ваше назначение является слишком большим, и это усугубляет опасность.

• malloc() берёт один параметр размера (размер в байтах), но calloc() — два (количество элементов и размер элемента в байтах).

• «Undefined behaviour» («Неопределённое поведение») — основной лейтмотив в С. Под «неопредёлённым» разработчики здесь понимают «Добро пожаловать делать всё, что желает поставщик, без необходимости учитывать, чего желаете вы». Самое плохое в этом то, что невозможно надёжно найти все случаи «неопределённого поведения» в какой-нибудь большой программе.

int a = 0; f(a, ++a, ++a); не гарантировано, что будет f(0, 1, 2). Порядок вычисления не определён.

• Еще один сюрприз, который «впечатляет»: i[++a] = j[a];, поскольку не определено, какая сторона рассчитывается сначала.

• Знаковое переполнение является технически неопределённым поведением. Это происходит на большинстве платформ.

• Сдвиг на более чем N битов на типе intN_t является неопределённым поведением.

• Преобразование типа int * во float * с последующим разыменованием является неопределённым поведением. Необходимо использовать memcpy().

• Разыменование указателя NULL является неопределённым поведением. Здесь нет подвоха — можно получить действительную память из этого!

• Преобразование типов между указателями функции и указателями данных является технически неопределённым поведением; на некоторых платформах они имеют разные размеры (почему это должно заботить программиста в C, оставим как упражнение для читателя).

• Преобразование типа void (*)() в int (*)(int, const char *), действительно, должно быть неопределённым? Нет! Все указатели функции могут быть преобразованы друг в друга. Хотя фактически вызов функции после преобразования, если тип неправильный, является неопределённым поведением (как можно было бы с полным правом и ожидать этого).

• Преобразование чего-нибудь вроде, скажем, FILE * в double * и снова обратно не является неопределённым, поскольку в промежутке не производится разыменование. Все указатели данных являются эквивалентными.

• Отладка оптимизированного кода может иногда не иметь смысла из-за агрессивной оптимизации компилятора.

• Безопасность данных в многопоточных программах, в действительности, не гарантируется языком; то, что, как вы полагаете, является элементарным, не обязательно является таковым без использования (C11, но см. выше) элементов. Получить нарушения при чтении и записи чрезвычайно просто, и никакие инструменты никогда не предупредят вас, что это происходит.

• Указатели на объект, тип которого неизвестен, создают проблемы; нет никакого способа определить, что на самом деле стоит за ними без какой-то определённой пользователем схемы маркировки, которая по определению не может содержать все типы. Решением является, несомненно, — «будьте внимательными».

• Система типов в общем случае является бесполезной, поскольку можно преобразовать (почти) всё во всё.

Почему С++ отстой


Некоторые позиции данного раздела обсуждались на соответствующей странице.

• Он имеет обратную совместимость с С.

• Однако имеются небольшие различия, из-за которых некоторые C-программы не удаётся скомпилировать компилятором C++.

• Функциональные возможности стандартных библиотек значительно уступают по рабочим циклам и конструкциям других языков.

• C++ не обеспечивает единой парадигмы. Не применяются ни процедурные, ни объектно-ориентированные парадигмы, что приводит к ненужному усложнению. [Некоторые рассматривают это как преимущество.]

• Процессы введения в действие и изучения очень трудные: описание превышает 1 000 страниц.

Не подходит для разработки систем низкого уровня и быстро превращается в беспорядочную мешанину при применении для приложений на уровне пользователя.

• Стандарт не имеет реализации для обработки исключений и декорирования имени. Это делает объектный код кросс-компилятора несовместимым.

• Широко используемая операционная система не поддерживает C++ ABI для системных вызовов.

• Что такое 's' — функция или переменная?

std::string s();

• Ответ: это на самом деле функция; чтобы быть переменной, здесь должны отсутствовать скобки; но это сбивает с толку, так как круглые скобки обычно используются для передачи аргументов в конструктор.

• Инициализированная по значению переменная 's' должна бы быть следующей:

std::string s = s(); /* or */ std::string s{};

• Есть try, но не finally.

• Рассмотрена «особенность», потому что RAII, как предполагается, является главным средством распоряжения ресурсами в C++. Исключением являются многие библиотеки, которые не используют RAII.

• Чрезвычайно плохая поддержка Unicode.

•Операторы могут быть перегружены, только если есть, по крайней мере, один параметр класса.

• Это также делает невозможными строки массива сцепленных символов, что иногда заставляет программистов использовать ужасные функции C, такие как strcat.


catch (...) не позволяет узнать тип исключения.

throw в сигнатурах функций совершенно бесполезен.

• Система исключений не связана с платформой: разыменование указателя NULL не породит исключение C++. [Некоторые люди рассматривают это как преимущество.]

• Спецификатор mutable трудно использовать целесообразно, и, поскольку это ухудшает спецификатор константного объекта const и, тем самым, потоковую безопасность, то могут легко возникнуть небольшие ошибки взаимосовместимости.

• Замкнутые выражения должны быть представлены в явном виде в лямбда-выражениях (никогда не слышал о чём-либо подобном ни в одном функциональном языке).

• Можно использовать [=] и поместить всё, но это увеличивает словесное наполнение.

• Природа C++ заставила разработчиков писать код, зависящий от компилятора, что порождает несовместимость между различными компиляторами и даже разными версиями одного и того же компилятора.

• Вызов std::string::c_str() требуется для преобразования std::string в char*. Из самого мощного языка мы всегда имели бы полностью принимаемый перегруженный operator const char* () const.

• Разработчикам, возможно, придётся побеспокоиться о вопросах оптимизации, таких как, например, объявлять функцию inline (встраиваемой) или нет; но после принятия такого решения оно является только предложением: компилятор может решить, что предложение неправильное, и не принять его. В чём смысл? Должны ли разработчики беспокоиться о вопросах оптимизации?

• Чтобы исправить эту ситуацию, многие компиляторы применяют __forceinline или аналогичное.

• Шаблоны являются полными по Тьюрингу, поэтому компиляторы должны решить проблему останова (неразрешимую), чтобы выяснить, можно ли, вообще, скомпилировать код.

• Неиспользуемые глобальные символы не создают какие-либо предупреждения или сообщения об ошибке; они просто компилируются, увеличивая размер создаваемого объектного файла.

• Изменения, которые могли бы на самом деле стандартизировать ключевую функциональность или существенно улучшить полезность языка, часто уходят в никуда, не попадая в техническое описание. Технические описания представляются местом, куда хорошие идеи идут умирать, — часто по политическим причинам или потому, что они не устраивают какую-то страну.

• Тот факт, что expor никогда не поддерживался, означает, что необходимо включать всю полноту шаблона в заголовок вашей библиотеки. Традиционный C (и большинство других кодов C++) разделяет реализацию и прототипы.

Почему .NET отстой


• SortedList использует пары «ключ — значение». Нет стандартной .NET-совокупности для списка, которая сохраняла бы элементы в отсортированном порядке.

• Изменение какой-то совокупности (коллекции) делает недействительными все операции итерации в каждом потоке, даже если текущий элемент не влияет в данный момент. Это значит, что каждый оператор цикла перебора массивов «foreach» может сгенерировать и сгенерирует исключение, когда вы меньше всего этого ожидаете. Так будет, если вы не используете блокировки, которые, однако, в свою очередь, могут привести к взаимоблокировкам. Может быть преодолено с помощью формальных параллельных или неизменяемых совокупностей (коллекций) или при итерации путём использования «for» вместо «foreach».

• MSDN-документация (гипотетического) GetFrobnicationInterval должна была бы разъяснить, что тот возвращает значение интервала, на котором объект бесцельно манипулирует (например, клавиатурой или мышью). Она должна была бы также указать, что этот метод выдаст InvalidOperationException, если указанный интервал не может быть найден. Вы также найдёте два «комментария сообщества», один из которых будет наполнен бессмысленным набором символов, а в другом будет запрос на ломаном английском о том, как происходит бесцельное манипулирование клавиатурой при комнатной температуре.

Почему С# отстой


• Стандарты ECMA и ISO для C# были обновлены, начиная с C# 2.0; с тех пор существует спецификация только от Microsoft.

• i++.ToString работает, но ++i.ToString — нет. (Необходимо использовать круглые скобки.)

• Параметры не поддерживаются для большинства свойств, только индексаторы, даже если это возможно в Visual Basic .NET.

• Соглашения являются трудными для анализа кода и отличаются от соглашений других распространённых языков (схожего стиля).

• Почти всё выполнено в варианте Pascal (SomeClass, SomeConstantVariable, SomeProperty).

• Невозможно различить «extends» и «implements» без использования венгерской нотации (IInterface).

• Продвигает устаревшее (типы вариантов и LINQ, что существенно добавляет беспорядка).

•«out»-параметры (с синтаксической обёрткой).

• Вы не можете переопределить виртуальное или абстрактное свойство, которое имеет метод чтения ИЛИ метод записи, на другое, которое имеет метод чтения И метод записи, что делает жизнь довольно трудной в части наследования свойств. (Хотя это не применимо к интерфейсам, см. http://stackoverflow.com/questions/82437/why-is-it-impossible-to-override-a-getter-only-property-and-add-a-setter.)

• Невозможно выполнить какие-либо операции (даже простейшие арифметические) с объектами внутри обобщённого метода (например, T plus<T>(T t1, T t2) { return t1+t2; }.

• Невозможно назначить новое значение внутри цикла «foreach» (например, foreach(int i in vec) { i = i+1; }.

Выполнение IDisposable правильно представляется очень сложным. (В частности, примитивная реализация не будет проведена, если финализатор вызван сборщиком мусора.)

Почему VB.NET отстой


• Опция Strict Off: она позволяет выполнить неявное преобразование, когда компилятор решает, что это уместно, например, lang="text">Dim a As Integer = TextBox1.Text ' (происходит преобразование строки в целое число при использовании Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger).

• Опция Explicit Off: автоматически объявляет локальные переменные типа Object везде, где используется необъявленная переменная. Обычно используется с опцией Strict Off.

On Error Goto и On Error Resume Next: это — процедурные способы скрыть ошибки или отреагировать на них. Чтобы действительно перехватить исключение, необходимо использовать Try-Catch-Block (блок попытка-перехват).

• Множество функций для совместимости вниз, таких как UBound(), MkDir(), Mid(),… Они могут быть скрыты при удалении импорта по умолчанию для пространства имён Microsoft.VisualBasic в настройках проекта.

• My-Namespace (Моё пространство имён) (исключение составляют My.Resources и My.Settings, которые могут быть очень полезными). Всё в этом пространстве имён является менее гибкой версией имеющегося элемента, например, My.Computer.FileSystem.WriteAllText vs System.IO.File.WriteAllText.

• Модули, потому что их элементы засоряют автодополнение ввода, т.к. модульные элементы видны, только когда виден сам модуль.

• Методы расширения могут быть заданы только в модулях.

• Методы расширения не могут быть применены к любым образом типизированному объекту, даже когда динамическое связывание (опция Strict Off) отключено. См. данную статью о StackOverflow.

• The Microsoft.VisualBasic.HideModuleNameAttribute. Это является необязательным из-за природы модулей.

• Экземпляры по умолчанию для форм. Действительным является написание

Form2.InstanceMethod()

Вместо
Dim Form2Instance As New Form2
Form2Instance.InstanceMethod()

поскольку в результате компиляции получается следующее:
MyProject.Forms.Form2.InstanceMethod()

• Объединение строк может быть проведено при помощи + и & вместо просто &. + путает новых программистов, особенно если у них опция Strict установлена на Off.

Почему VBA отстой


• Индексы массива начинаются с нуля, но индексы коллекции — с единицы.

• У классов нет конструкторов, которые могут принять аргументы.

• Невозможно перегрузить методы.

• Нет наследования.

• Поддерживает GoTo.

• `OnError Resume Next` — Yeah… Происходит именно то, что вы читаете. Обнаружена ошибка? Нет проблемы! Просто продолжайте упираться и продвигаться вперёд в следующей строке.

• Свойства по умолчанию.

Приведённые ниже две строки означают не одно и то же.
Dim myRange As Variant
myRange = Range("A1")
Set myRange = Range("A1")

Одна задаёт для `myRange` значение «A1», тогда как другая делает его текущим объектом Range.

Почему Objective-C отстой


• Нет какого-либо реального использования за пределами того программирования для OS X и iOS, которое не может быть сделано с помощью другого языка на базе C; это значит, что ваш набор навыков не удастся применить за рамками вполне ограниченного рынка.

• Использование Objective-C в переносимом варианте является сочетанием несочетаемого — оксюмороном; GNUStep является менеджером окон для Fringe на Linux, и нет ничего на самом деле там для Windows, вообще… Единственное, что работает, так это компилятор.

• Нет перегрузки оператора.

• Для перегрузки метода существует «обходной путь».

• Попытки втиснуть язык Smalltalk с динамической типизацией в язык C со статической типизацией.

• Нет объектов со стековой архитектурой.

• Синтаксис очень странный по сравнению с другими языками (я должен вставлять @ перед кавычкой, создавая строку?! Вызов методов происходит аналогично за исключением случаев, когда имеется единственный аргумент?!? [methodName args];)

http://fuckingblocksyntax.com

• Техническое описание, вообще, отсутствует. Никто (за исключением, может быть, некоторых разработчиков LLVM и Apple), в действительности, не знает, что происходит «под капотом».

• Может случайно рухнуть при возвращении SEL из метода [4].

• Ужасная система типов. В качестве типов здесь выступает то, что можно назвать, скорее, рекомендациями.

• Objective-C++ и сплошная жуть, связанная с ним.

• Классы Objective-C и C++ не могут наследовать друг друга.

• Пространство имён C++ не может, вообще, взаимодействовать с кодом Objective-C.

• Передача параметров по значению C++ не может быть применена к объектам Objective-C в функциях C++; необходимо использовать указатели.

• Анонимные функции C++ и блоки Objective-C различаются и не являются взаимозаменяемыми.

• Классы Objective-C не могут иметь членов, которые являются классом C++, не имеют конструктора по умолчанию или имеют один или несколько виртуальных методов… за исключением введённых через указатели и назначенных через new.

• Не имеет пространства имён и вместо этого рекомендует использовать префиксы (два символа, в основном) для имени каждого класса, чтобы предотвратить конфликт имён.

• Objective-C не имеет стандартной библиотеки. Apple управляет фактически одной и всеми с префиксом NS*.

• Сосредоточенность Apple на Swift (который сам по себе является ещё одним специфическим для платформы языком) означает, что Objective-C оставлен существовать без внимания к нему.

Почему Java отстой


Синтаксис

• Чрезмерная многословность.

• Java имеет неиспользуемые ключевые слова, такие как goto и const.

• Нет перегрузки оператора… за исключением строк. Поэтому для псевдочисловых классов, таких как BigInteger, необходимо делать такие операции как a.add(b.multiply(c)), что выглядит довольно неуклюже.

• Нет делегатов; каждый раз, когда нужен указатель на функцию, приходится реализовывать заводскую разработку.

• Массивы не работают с обобщёнными типами: невозможно создать массив с типом переменной new T[42], бокс массива требуется, чтобы сделать это:

class GenSet<E> { Object[] a; E get(int i){return a[i];}}

• Нет свойств. Простые определения класса имеют длину в 7-10 раз больше, чем требуется.

• Нет буквенных констант для карт или массивов. Массив и карта являются интерфейсами коллекций.

• Нет ключевого слова var для предполагаемых локальных типов (как в C#). Учтите, что здесь дан пример плохой конструкции и что имена классов ни в коем случае не должны быть такими длинными. Пример:

// В Java
ClassWithReallyLongNameAndTypeParameter<NamingContextExtPackage> foo = new ClassWithReallyLongNameAndTypeParameter<>();
// В C# | Могло бы легко быть просто:
var foo = new ClassWithReallyLongNameAndTypeParameter<NamingContextExtPackage>();
// В Java | То же самое происходит для вызовов функции:
SomeTypeIHaveToLookUpFirstButIActuallyDontReallyCareAboutIt result = getTransactionResult();

• Вроде бы, невозможно написать в Java без IDE с автозаполнением, генерацией кода, управлением импортом и рефакторинга.

• Нет пар или троек. Возврат двух значений из функции или помещение пары в набор обеспечивает новый класс в новом файле. Параметризованный класс Pair ведёт к «кудрявым» типам повсюду.

Исправлено в Java 7 (2011)

Операторы перехвата (перехватчики) могут содержать только одно исключение, заставляя программиста переписать один и тот же код N раз, если программист желает реагировать одинаково на N различных исключений.

Нет автоматической очистки ресурсов; взамен мы получаем пять строк «allocate; try {...} finally { cleanup; }».

Модель

• Нет функций и классов высшего порядка.

• Проверяемые исключения являются экспериментом, который провалился.

• Имеются типы int и объекты Integer, типы float и объекты Float. Поэтому можно иметь эффективные типы данных или объектно-ориентированные типы данных.

• Базовый класс чисел не определяет арифметические операции, поэтому невозможно написать полезные обобщённые типы для подклассов Number.

• До Java 8 использование только интерфейсов для реализации различных наследований не позволяло совместное использование общего кода для них.

Библиотека

• Функции в стандартной библиотеке не используют согласованное назначение имён, соглашения по сокращениям и заглавным буквам, что делает трудным запоминание названий элементов:

java.net имеет URLConnection и HttpURLConnection: почему не UrlConnection или HTTPURLConnection или HttpUrlConnection?

java.util имеет ZipOutputBuffer и GZIPOutputBuffer: почему не ZIPOutputBuffer или GnuZipOutputBuffer или GzipOutputBuffer или GZipOutputBuffer?

• Это, на самом деле, часть стандарта; необходимо писать всё прописными буквами, если имеется 3 или менее букв, или делать прописной только первую букву, если букв больше, т.е. RPGGame, а не RpgGame, и TLSConnection, но следует использовать Starttls.

• Конструкция за Cloneable и clone просто сломана.

• Массивы являются объектами, но неправильно используют .toString() (если попытаться распечатать массив, то получите просто тарабарщину из хэш-кода) или .equals() (массивы с одинаковым содержанием не считаются равными, что создаёт проблемы при попытке ввести массив в коллекции).

• До Java 8 полезные методы, такие как, например, сортировка, двоичный поиск и т.п., не были частью классов Collection (Коллекция), а были частью «вспомогательных классов», таких как Collections (Коллекции) и Arrays (Массивы).

• Почему Stack — класс, а Queue — интерфейс?

Stack принадлежит старой коллекции API, и его больше не следует использовать. Взамен используйте Deque (интерфейс) и ArrayDeque (реализация).

• Код загромождён преобразованиями типа. Массивы в списки, списки в массивы, java.util.Date в java.sql.Date и т.д.

• Программный интерфейс данных (Date API) считается устаревшим, но до сих пор повсеместно используется. Плана замены нет.

• До Java 8 не было функции объединения строк.

• Программный интерфейс Reflection API требует несколько строк кода для простейших операций.

• Регулярное выражение (a|[^d]) преобразует StackOverflowException в длинные строки.

• Отсутствуют беззнаковые числовые типы.

Обсуждение

Некоторые позиции данного раздела обсуждались на соответствующей странице.

• Почти всё связано с объектами, и для многого требуется буферизация, даже для тех позиций, которые, вроде бы, не должны быть объектами или буферизированы (примеры?).

• Некоторые интерфейсы, такие как Serializable и RandomAccess, используются почти так же как комментарии: они пустые, а если заполнены, то единственной их целью является отразить какую-то семантику.

• Блоки инициализации (как статичный, так и нестатичный) не могут выдать проверяемые исключения.

• Массивы являются небезопасными по типу: Object[] foo = new String[1]; foo[0] = new Integer(42); компилируется прекрасно, но вызывает аварийное завершение.

• Управляющие символы Unicode могут иметь неожиданные результаты, поскольку они добавляются до того, как код подвергается синтаксическому анализу, и поэтому они могут повредить ваш код, например: (1) если комментарий конца строки содержит \u000A (возврат строки), то остаток комментария не будет на той же строке и не будет в комментарии вообще; (2) если строковый литерал содержит \u0022 (двойная кавычка), то строковый литерал будет завершён, а остаток строки войдёт в текущий код; (3) если в каком-то комментарии появляется \u и эта комбинация не является действительно управляющей (как, например, «c:\unix\home»), то будет выдана ошибка анализа, хотя имеем дело с комментарием.

• Вспомогательные функции должны быть перегружены для каждого фундаментального типа (например, max(float,float), max(double,double), max(long,long)).

Почему Backbase отстой


• О, эта тема может занять целую новую википедию сама по себе.

Почему XML отстой


• Атрибуты, как правило, ограничены неструктурированными данными.

• Слишком много слов, слишком трудно выполнять синтаксический анализ программ и слишком трудно для чтения людьми.

• Многословие библиотеки стандартных текстов плохо согласуется с системами управления версиями, такими как Git. Записи одного типа запускаются и заканчиваются одинаково, даже если их содержание полностью различается. Это сбивает с толку программы автослияния (которые часто не понимают, как работают теги), заставляя принимать одну запись за другую, и нередко требует проведения слияния вручную, чтобы избежать искажения.

• Имеет место перепутывание метаданных и контента.

• Используется для обмена данными, когда это, на самом деле, просто формат кодирования данных.

• Нет способа по умолчанию для кодирования в двоичном представлении.

Почему отстой XSLT/XPath


• Нумерация начинается с 1. В отличие от *каждого отдельного другого* основного языка программирования, используемого в настоящее время. В отличие от XML DOM.

• XPath имеет функции обработки даты, позволяющие получить секунду, минуту, час, день, месяц и год из даты-времени. Но нет функции для получения дня недели, поэтому полезность нулевая.

• Нет способа сделать модульными или абстрактными какие-либо выражения XPath, что ведёт к большому количеству скопированного и вставленного кода.

• Условные переходы в атрибутах test="" элементов <xsl:if> и <xsl:when>.

• Условия сортировки в <xsl:sort>.

• Если вашим контекстом является контент какого-то узлового набора, то функции key(), определённые на целом входе XML, не работают. И что ещё глупее — не выдаётся никакого сообщения об ошибке! Ваш select="key(...)" просто молча возвращает пустое множество. Должно было бы придти, по крайней мере, «key() не работает внутри узловых наборов» или, возможно, «нет такого ключа в контекстном узловом наборе».

• Атрибуты select="", value="" и match="" делают, по существу, то же самое. И их использование является произвольно исключительным; вы обязаны использовать правильный атрибут в правильном месте, а если вы что-то подставляете неправильно, то узел выпадает без какого-либо предупреждения. Эти три атрибута должны иметь одно и то же имя.

• Если вы импортируете какую-то функцию (например, str:replace ()), но импорт проходит неправильно или неполностью (например, теряется пространство имён), а вы затем вызываете эту функцию, то какое-либо сообщение об ошибке не выдаётся. Функция просто проводит перерасчёт со своим последним аргументом. Как это могло когда-либо быть желательным поведением? Если я вызываю какую-то функцию, которая по каким-то причинам отсутствует, то, ясно, что это всегда ошибка программиста и должно появиться предупреждение.

• Нет способа создать пользовательский набор значений и затем выполнить итерации по нему во время выполнения программы, несмотря на то, что есть способ создать единственное пользовательское значение и затем работать с ним во время выполнения программы. Другими словами, этот язык не имеет типа list/array/tuple/dict/hash/set/iterable/collection.

• Он позволяет использовать '-' в идентификаторах, однако синтаксический анализатор недостаточно проработан для понимания, что вы имеете в виду 'minus' вместо — . Если вы собираетесь разрешить использовать '-' как символ идентификатора и как оператор, то, по крайней мере, следует сделать так, чтобы строка за символом идентификатора '-' соответствовала требованиям к стандартному шаблону идентификатора, [a-zA-Z_][a-zA-Z0-9_]*. Не делайте это использование пробела значащим в языке, где пробел, как правило, не имеет значения вокруг операторов. Никто никогда не пожелает для переменной имя вроде $foo-100, поскольку это очень похоже на $foo — 100.

$foo-bar справедливо интерпретируется как имя переменной.

$foo - 100 справедливо интерпретируется как вычитание.

$foo+100 и $foo + 100 справедливо интерпретируется как сложение.

$foo-100 неправильно интерпретируется как имя переменной.

• Концепция типов, вообще, отсутствует. Всё в основе своей является строкой. Это значит, что даже те элементы, которые по существу разделены на типы, обрабатываются принципиально как строки. Например, сортировка по номеру подчинённых узлов происходит по строковой последовательности, а не по числовой, несмотря на то, что счёт является по сути числовой операцией.

<xsl:sort select="count(*)"/>
<!-- sorts like this: 10, 11, 1, 23, 20, 2, 37, 33, 31, 3, 4, 5, 6, 78, 7, 9 -->

• Имеется слишком много уровней синтаксической и семантической интерпретации:

1. Анализ синтаксиса XML (обеспечивает, что все узлы закрыты и т.п.).
2. Анализ синтаксиса XSL (обеспечивает, что узлы, которые должны быть под другими узлами и/или должны содержать другие узлы, присутствуют, проверяет, что все имена узлов xsl:foo являются действительными и т.д.).
3. Анализ семантики XSL (находит правильные атрибуты для каждого типа узла и т.п.).
4. Анализ синтаксиса XPath (полностью содержащийся внутри значений атрибутов, анализ не может быть проведён ранее).
5. Анализ семантики XPath.

Почему CSS отстой


• Почему есть hsla(), но нет hsva()?

• text-align:justify;, на самом деле, означает «выравнивание по левому краю». Нет способа выровнять по правому краю или по центру.

• vertical-align:middle; не работает с элементами блока, хотя работает со строковыми и табличными элементами. Это ведёт к тому, что люди предлагают display:table; и display:table-cell;, а это значит, что необходимо придавать стиль также упаковщику. Ничего себе!

• Не предполагалось поддерживать горизонтальное выравнивание элементов блока и может быть сделано, в лучшем случае, лишь хакерским приёмом (margin: 0 auto;).

• Можно поместить элемент слева или справа. Но невозможно поместить элемент в центре.

• float: только по горизонтали; нет эквивалентной вертикальной операции. (ОК, это, на самом деле, операция потока, пойди разберись.)

• Аналогично нет вертикального эквивалента для clear:.

• Нет способа агрегировать или программно создавать цвета. Если надо, чтобы текст и рамки использовали один и тот же цвет, то необходимо вписать этот цвет дважды.

• Нет способа агрегировать или программно создавать длину. CSS3 вводит calc в значения CSS (CSS Values) и модуль единиц (Units Module), но невозможно задать что-нибудь вроде { width:50% — 2px; }.

• Спецификация CSS является противоречивой в отношении идентификаторов:

• Синтаксис утверждает, что идентификаторы не позволяют использовать символы верхнего регистра везде, кроме первого символа:


• ident {nmstart}{nmchar}*
• nmstart [a-zA-Z]|{nonascii}|{escape}
• nmchar [a-z0-9-]|{nonascii}|{escape}

• В разделе «4.1.3. Символы и регистр» сказано:

«Согласно CSS2 идентификаторы (включая имена элементов, классы и идентификаторы в селекторах) могут содержать только символы [A-Za-z0-9] и символы ISO 10646 161 и выше, а также дефис (-); они не могут начинаться с дефиса или цифры.»

Почему понадобилось отклониться от стандартного формата идентификатора, [a-zA-Z_][a-zA-Z0-9_]*, который использовался с 1970-х годов?

• У нас когда-либо будут альфа-маски? Webkit делает это, но…

• С поддержкой префиксов поставщика дело обстоит неважно. Претендующий на это — Webkit — является единственным префиксом поставщика, и с ним совсем плохо.

• Имеются SASS, LESS и Stylus. Возьмите каждую отдельную характеристику. Каждая является CSS wtf (хотя синтаксис, базирующийся на отступе, должен быть опциональным).

• Нет селекторов родительских элементов, даже в CSS3. Может быть, это будет, в конце концов, завершено в CSS4.

См. также «CSS выглядит неэлегантно» и «Неполный список ошибок, сделанных при разработке CSS».

Исправлено в CSS3

• Привет? Закруглённые углы? Mac OS имел их ещё в 1984. (CSS3 ввёл border-radius:)

• Может быть задано только одно фоновое изображение. (CSS3 поддерживает несколько фонов)

• Невозможно задать растягиваемые фоновые изображения. (CSS3 вводит background-size: и background-clip:)

• Невозможно подготовить вертикальный или наклонный текст. (CSS3 вводит rotate)

Почему Scala отстой


• Иерархия scala.collection слишком сложная.

• Отсутствуют общие функциональные структуры вроде Monad и Functor. Хотя Scalaz обеспечивает большинство требуемых классов.

• Инвариантность, называемая также глубокой неизменяемостью, не может быть проверена на соответствие типов.

• Чистота функций не может быть проверена на соответствие типов.

• Слишком много способов сделать всё.

Почему Haskell отстой


• Чрезмерная увлечённость короткими и произвольными именами (даже символы) для всего. Программисты на Ассемблере и С не имеют ничего против Haskell из-за этого, потому что при коротких именах программы больше походят на математические выкладки. Очевидно, это характеризует положительно соглашения о программировании, а не означает что-либо плохое о соглашениях по математическим выражениям.

• Отложенное вычисление делает утечки памяти особо забавным делом при отладке.

• Неприятная система типов.

• Есть на самом деле специальная поисковая система Haskell, чтобы искать что-либо связанное с Haskell, поскольку из-за необычного синтаксиса возникают проблемы с Google.

• Haskell позволяет очень легко написать блоки кода, которые будут семантически идентичными, но по вычислениям различающимися. Эта проблема остро стоит со сторонними библиотеками.

Почему Clojure отстой


• Синтаксис Lisp не позволяет видеть, что представляет собой функция и т.д. — Нет визуального различия.

• Длительный запуск делает почти невозможным написание достаточно производительной программы командной строки.

• Функция Conj действует несообразно в зависимости от предусмотренного типа (присоединяет к началу векторов и заголовку списков).

Почему Go отстой


Базовые средства программирования (базовый язык)

• Go поддерживает пустой («nil») указатель. Это похоже на void * в С — чудовищный источник ошибок. Поскольку «nil» («ноль») может представлять любой тип, то это полностью разрушает систему типов.

func randomNumber() *int {
  return nil
}

func main() {
  a := 1
  b := randomNumber()
  fmt.Printf("%d\n", a+*b) // Замешательство при выполнении из-за нулевого (пустого) указателя
}

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

• Несмотря на вышесказанное, Go имеет два конкурирующих типа для представления текста — string и []byte. Тип string намного превосходит тип []byte, так как он даёт символы Unicode при итерации, при нём можно проводить сравнение с использованием == > <, проводить слияние с использованием + и применять символы как условные обозначения. Однако важные стандартные интерфейсы, как, например, io.Writer, используют []byte.

• Аналогично функция len на строках возвращает количество байтов в строке, которое необязательно равно количеству символов. Чтобы получить истинную длину строки, необходимо использовать хорошо названную, но ужасно многословную библиотечную функцию utf8.RuneCountInString().

• Хотя Go не требует наличия break в конце каждого case, оператор break всё же выделяется из операторов switch. Та же логика всегда может быть реализована другими структурами управления, но необходимо использовать маркированное прерывание и продолжать выходить из цикла, находясь внутри switch.

• Удаление элемента «n» из последовательности не похоже на удаление: slice = append(slice[:n], slice[n+1:]...)

• Если вы импортируете библиотеку или объявите переменную, но не используете их, то ваша программа не будет скомпилирована, даже если всё остальное будет правильным.

• Составные функции, возвращающие несколько типов, вносят путаницу.

• Тип ошибки Go является просто интерфейсом для функции, возвращающей строку.

• В Go отсутствуют типы сопоставления с образцом и абстрактных данных.

• В Go отсутствуют неизменяемые переменные.

• В Go отсутствуют обобщённые типы.

• В Go отсутствуют исключения, а вместо этого везде используются коды ошибок, что приводит к повторяющемуся шаблону контроля ошибок:

if v, err := otherFunc(); err != nil {
  return nil, err
} else {
  return doSomethingWith(v), nil
}

• На самом деле, Go поддерживает один вид исключения, но называет это тревогой. Можно обнаружить исключение, но Go называет его восстановлением. Можно написать, «в конце концов», блоки, которые работают в зависимости от того, запущена функция под действием исключения или нормально, но Go называет их задержанными функциями, и они работают в порядке, обратном тому, как они написаны.

• Go-цикл range итерирует по коллекции, назначая свои ключи и/или значения некоторому количеству переменных, но количество разрешённых переменных и их значения зависят от типа коллекции, который может быть не прописан в исходном коде:

d := loadData()

for i := range d {
  // если "d" является каналом, то "i" является элементом, считываемым из канала
  // если "d" является картой, то "i" является условным обозначением
  // иначе "i" является индексом массива/совокупности/строки
}

for i, j := range d {
  // если "d" является каналом, то это неправильно!
  // несмотря на то, что при нормальном получении из канала, можно получить второе логическое значение,
  // показывающее, закрыт ли он)
  // если "d" является строкой, "i" - индексом, а "j" - руном (необязательно d[i])
  // иначе "i" является индексом массива/совокупности или условным обозначением, а "j" является d[i]
}

• Go позволяет дать имена возвращаемым значениям, благодаря чему можно неявным образом вернуть их. Он также позволяет переопределить переменные во внутренних областях, перекрывая определения во внешних областях. Это может способствовать нечитаемому коду само по себе, но взаимодействие между этими двумя языковыми особенностями просто причудливо:

func foo1(i int) (r int) {
  if i != 0 {
    r := 12
    fmt.Println(r)
  }
  return  // возвращает 0
}

func foo2(i int) (r int) {
  if i != 0 {
    r = 12
    fmt.Println(r)
    return  // возвращает 12
  }
  return  // возвращает 0
}

func foo3(i int) (r int) {
  if i != 0 {
    r := 12
    fmt.Println(r)
    return  // ОШИБКА: "r" перекрыто при возврате
  }
  return
}

• Задержанные функции могут выполнить запись в возвращаемые значения, имеющие имена функций, что может привести к неожиданностям:

func dogma() (i int) {
  defer func() {
    i++
  }()
  return 2 + 2
}

func main() {
  fmt.Println(dogma) // печатает 5
}

• Сравнение интерфейса с нулём проверяет, является ли *типом* интерфейса ноль, а не его значение. Таким образом формируется ловушка, в которую попадался каждый Go-программист:

func returnsError() error {
  var p *MyError = nil
  if bad() {
    p = ErrBad
  }
  return p // Будет всегда возвращена ошибка "не ноль".
}

Взаимосовместимость

• Если имеется несколько каналов для приёма или отправки, то оператор select выбирает case случайным образом, а это означает, что для обеспечения приоритета одного канала над другим необходимо написать:

select {
case <-chan1:
  doSomething()
default:
  select {
  case <-chan1:
    doSomething()
  case <-chan2:
    doSomethingElse()
  }
}

• Оператор select реализован в виде примерно 700 строк исполняемого кода. Можно даже ощутить снижение производительности при каждом его использовании.

•Утечки памяти в стандартных Go-программах, когда Go-программа теряет все свои пути коммуникации с другими, могут привести к потере всей «стековой» памяти, для которой не может быть автоматического управления освобождением динамической памяти.

Стандартная библиотека

• Строки формата даты/времени не используют тип мнемонических кодов, имеющийся в других языках, вроде «ddd» для сокращённого дня или "%H" для часа. Вместо этого библиотека Go использует систему, где «1» обозначает месяц, «5» — секунды, «15» — час, «6» — год и т.д. Документация объясняет это в терминах магического отсчета времени (пн 2 янв 2006 15:04:05 MST) и говорит: «Чтобы задать свой собственный формат, запишите, как будет выглядеть опорная точка отсчёта времени, отформатированная вашим способом». Но это на самом деле не разъясняет, как преобразовать время, чтобы удовлетворить ваши требования; здесь просто признаётся фиксированный, недокументированный набор кодов.

• Две реализации случайных чисел — math/rand и crypto/rand.

• Пакет flag, который реализует флажки командной строки, не является POSIX-совместимым и не поддерживает сокращённые флажки.

• Пакет errors занимает двадцать строк, т.к. тип ошибки Go является просто интерфейсом для функции, возвращающей строку.

Набор инструментальных средств

• Собственная пакетная система Go не поддерживает специфицирующих версий или фиксаций в информации о взаимозависимости. Взамен Go-сообщество рекомендует иметь для каждого главного релиза свой собственный отдельный repository; github.com/user/package/package-{v1,v2,v3}.

• Собственная система пакетов Go не поддерживает зависимости, вынуждая сообщество создать альтернативы.

• Версия 1.5 преобразует весь набор инструментальных средств из C в Go. Поскольку это выполнялось в основном автоматически, то производительность существенно снижалась как при компилировании, так и при работе.

• Компилятор имеет опцию "-u", задокументированную как «отвергнуть небезопасный код». Всё, что она делает, препятствует импорту unsafe пакета или любого пакета, который импортирует его рекурсивно, что включает в себя практически всю стандартную библиотеку; это делает любую программу, компилируемую с указанной опцией, неспособной к выводу и таким образом бесполезной.

• У компоновщика есть такая же опция, только она предотвращает компиляцию *любой* программа, поскольку рабочий цикл Go зависит от unsafe.

Сообщество

• Большинство выражений протеста, особенно по обработке ошибок, отметается как «вы недостаточно хорошо понимаете этот язык».

• Например, данная публикация была закрыта разработчиками немедленно как «WONTFIX» («Проблема есть, но решаться не будет»), несмотря на большое число пользователей, поддержавших её.

• Или предложение добавить псевдонимы, которое было проведено, несмотря на большое количество отрицательных отзывов от сообщества. И когда оно было отозвано, то это произошло не потому, что сообщество просило об этом, а потому, что были проблемы, которых они даже не ожидали.

• Почти ничто в этом списке не может быть исправлено из-за обещания совместимости Go 1. Это значит, надо ждать Go 2.0, но он может и не придти.

• Участники нарушили это обещание в версии 1.4, отвергнув двойные разыменовывания.

Почему Rust отстой


Безопасность

• borrowck отвергает безопасный код.

• «Нелексические займы» будут всё же отвергать безопасный код. В конце концов, «небезопасный» блок не говорит, что содержание блока вызовет неопределённое поведение; это означает, что содержание небезопасного блока было ошибочно отклонено компилятором.

• borrowck, всё же, имеется, даже когда вы находитесь внутри unsafe {} блока или функции; чтобы сотворить, действительно, волшебство, необходимо дать вызов, содержащий некоторые многословные имена вроде sliceable.unsafe_get(index). К тому времени, как вы закончите писать, вы получите своего рода текстовый эквивалент дымовой пожарной сигнализации, срабатывающей, когда ваш тост уже сгорел.

• Никто не знает, какие правила определяют опасность.

• Если вы думаете, что знаете их, то, может быть, присоединитесь к [команде, обсуждающей правила определения небезопасности]?

• Это означает, что содержимое небезопасного блока является трудным для чтения, что оно по определению является слишком сложным, чтобы быть очевидно правильным, что оно нечётко задано и должно быть написано на коде низшего уровня. Удачи — и топайте дальше, если что-то получите не так, как это было у С-программистов последние двадцать лет!

• У Rust есть исключения. Он называет их тревожными и редко использует их, но у него они есть, и ваш небезопасный код должен быть транзакционным в отношении безопасности памяти. До сих пор они имели примерно такой же успех, последовательно соблюдая это, какой имели разработчики в C++ (за исключением тех разработчиков в C++, которые запрещают исключения, как, например, Google).

• Оптимизатор LLVM рассматривает неопределённое поведение как лицензию на убийство. Очевидно, что это имеет значение только для небезопасного кода, но прокрутите текст назад, если вы думаете, что «да пошли вы все, кто использует небезопасный код,» является оправданием. И это при условии, что оптимизатор не имеет ошибок, что в действительности не так.

Очевидно, что из-за проблемы останова не представляется возможным исключить программы, которые делают определённые вещи, не исключая также программы, которые на самом деле не делают их. Однако интересно, что есть такое твёрдое правило об определённых видах ошибок, которые не являются даже худшими видами имеющихся ошибок. SEGV намного лучше, чем CRIME, и Rust не мог бы предсказать это. Я предлагаю прочитать статью Даниэля Дж. Бернштейна о том, почему оптимизирующие компиляторы для безопасных относительно памяти языков являются неправильным решением.

Таким образом, «безопасность» Rust позволяет делать существенно меньше ошибок в чрезвычайно простых кодах, но не помогает в сколько-нибудь сложных.

Синтаксис

• Точки с запятой и неприятный синтаксис :: получены в наследство от С++. Также унаследован ещё более безобразный шаблонный/универсальный синтаксис.

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

  #[allow(unrooted_must_root)]
  pub fn new(localName: Atom,
             prefix: Option<DOMString>,
             document: &Document) -> Root<HTMLIFrameElement> {
      let element = HTMLIFrameElement::new_inherited(localName, prefix, document);
      Node::reflect_node(Box::new(element), document, HTMLIFrameElementBinding::Wrap)
  }

  #[allow(unrooted_must_root)]
  pub fn new(localName: Atom,
             prefix: Option<DOMString>,
             document: &Document) {
      let element = HTMLIFrameElement::new_inherited(localName, prefix, document);
      Node::reflect_node(Box::new(element), document, HTMLIFrameElementBinding::Wrap);
  }

• Чрезмерно кратко поименованные типы и ключевые слова, которые не передают их назначение, как, например, impl и ().

mut означает исключительный, а не изменчивый (mutable). &Mutex означает изменчивый (mutable), а &mut str — неизменяемый (immutable).

Конструкция API и система типов

• Чрезмерно кратко поименованные типы и ключевые слова, которые не передают их назначение, как, например, Vec и Cell.

• Большинство думает, что Rust имеет два типа строк (и это непохоже на С++, где имеется один хороший тип и один тип, унаследованный от С; поддержка обеспечена для обоих — как для str, так и для String). Здесь их фактически шесть: str, String, CStr, CString, OsStr и OsString. Нет независимого от платформы способа преобразования друг в друга OsStr, OsString, CStr, CString (из-за Windows), но есть много слегка различающихся способов преобразования CStr, CString, OsStr, OsString в String и str, и есть способы преобразования в другую сторону.

• Повсеместное неявное преобразование (типа) означает, что справочные документы практически бесполезны. Можно взять Vec<T>, поскольку он неявным образом преобразует в &[T], например, и можно использовать для циклов с Vec<T> и &[T], но результат будет немного различающимся в зависимости от того, что используется.

• Не совсем повсеместное преобразование (типа) означает, что ваш код оказывается замусоренным тарабарщиной вроде &*some_var (который преобразует интеллектуальный указатель в ссылку) и &some_var[..] (это — та самая магическая формула, которая преобразует Vec<T> в &[T] или String в &str).

• Дублирующие элементы, такие как, например, структуры кортежа и модулеподобные структуры (учтите, что сам модуль, который на самом деле является () по некоторой причине, есть кортеж, а не модулеподобная структура).

• В системе типов имеется гражданин второго сорта в форме не-Sized типов. Примеры — str и [T]. В отличие от любой другой характеристики в этом языке, каждый обобщённый тип требует Sized, если вы не отказываетесь, выставляя требование ?Sized. Эти типы настолько второсортны, что народ на странице Reddit, вообще, не признаёт str типом. Скажите это моей Box<str>. И это вовсе не ограниченные безответственные посетители сайта Reddit; авторы Reddit, которые достаточно подготовлены, чтобы знать о взаимосвязи между проблемой останова и граничным умозаключением, неправильно понимают семантику не-Sized типов.

• Типы суммы — не enum. Хватит притворяться С.

Сообщество

Ссылка на эту страницу является, несомненно, нарушением Кодекса поведения. (Заархивированная копия) Делайте с этим, что пожелаете.

• Это просто сборище «няшек». Вам не позволено неконструктивно критиковать Rust (и, очевидно, что высказывание «концепция, лежащая в основе этого языка, настолько неправильная, что он никогда не может быть исправлен» здесь не пройдёт), но вам одновременно также запрещается называть другие языки за безнадёжно низкое качество. Я надеюсь, что вы способны к двоемыслию, потому что придётся думать, что Rust нуждается в веб-структурах и одновременно не конкурирует с Java!

Набор инструментальных средств

• rustc работает медленно.

Здесь приведены все целевые поддержки rustc. Нет PIC. Нет AVR. Нет m68k. Только ARM, x86, MIPS, PowerPC и NaCl. Все платформы, которые могут легко позволить себе сборщик мусора.

• Изменить файл? Потребуется перекомпилировать всю вашу библиотеку. Кстати — rustc работает медленно.

• Поскольку он статически связывает всё (спасибо, Go, за выпуск этой модной штучки), вы получите тысячи, вероятно, устаревших копий кольца на вашем компьютере.

• Действительно, он статически связывает почти всё. Он динамически связывает вашу программу с библиотекой, в результате чего ваши исполняемые файлы не являются, на самом деле, самодостаточными.

• Обновление какой-либо зависимости требует от вас перекомпилировать всё, что связано с нею. Это занимает много времени, поскольку rustc работает медленно.

• Нет пространства имён в поставке. Всё получает «креативные» имена, как, например, «ring» (набор криптопримитивов) или «serde» («serialization and deserialization» («сериализация и десериализация»), ничего?).

• Каждый исполняемый элемент содержит по умолчанию копию менеджера распределения памяти jemalloc, выдающего «hello world» [размер почти мегабайт].

• Поскольку в поставке нет никаких попыток кэширования артефактов компилятора, то вы компилируете каждую раздутую библиотеку каждый раз, когда создаёте новый проект. Я уже говорил, что rustc работает медленно?

• Обобщённые типы очень распространены, и они по существу копируются и вставляются для каждого конкретного типа, с которым они используются (спасибо тебе, С++, что сделал это популярным). Вы знаете то, что делает компиляцию по сути тем же самым кодом часто ещё более болезненной?

• Нет хорошего IDE. Если вы использовали что-либо вроде C# в VS или Java в Eclipse, то получите ощущение старой ржавой вещи из 90-х.

• Почему нельзя иметь IDE, который рекомпилирует проект постоянно, как это делает Eclipse? У меня есть одно предположение.

• Автозавершение опережающего ввода с клавиатуры? Это, действительно, может быть сделано, но возникает ощущение реактивного двигателя, смастерённого из набора «сделай сам». За тем исключением, что если бы этот реактивный двигатель выходил из строя так же часто, как это происходит с Racer, то он никогда не был бы разрешён к использованию Федеральным авиационным агентством.

• Сообщения об ошибках от глубоко вложенных макросов? Это именно то, что вы ожидаете.
Tags:
Hubs:
+20
Comments 353
Comments Comments 353

Articles