вторник, 28 сентября 2010 г.

Маленькие вещи, которые в Delphi сделаны верно

Это перевод Little things Delphi gets right. Автор: Mason Wheeler.

Для тех, кто ещё этого не видел: разработчики StackOverflow создали по популярным просьбам новый сайт, называемый programmers.stackexchange.com - сайт для субъективных программистских вопросов, которым не место на StackOverflow. Некто недавно опубликовал опрос: какой ваш любимый язык. Вы, вероятно, можете угадать, каков был мой ответ:
Delphi
Это язык со стандартной, императивной, объектно-ориентированной парадигмой, с которой знакомы большинство программистов. Но в нём сделаны правильно некоторые небольшие вещи, которые в большинстве языков семейства C делаются неверно. Плюс, последние версии языка получили поддержку некоторых концепций функционального программирования без применения уродливых инверсий абстрации, которые обычно сопровождают функциональные языки.
Мне написали множество интересных комментариев. Кто-то спросил: “чтобы оправдать наезд на C (чтобы они вас не избили!), и удовлетворить моё любопытство: о каких "маленьких деталях" идёт речь?”

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

Давайте начнём со слона в комнате.

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

Не существует хорошей причины, чтобы человек когда-либо при любых обстоятельствах писал сетевую программу с требованиями по безопасности (типа операционной системы или web-браузера) на C, C++ или Objective-C. Мы знаем, что прошло уже 22 года, с тех пор как Роберт Моррис выпустил своего Интернет-червя, который использовал переполнение буфера в ОС Unix для парализации около 10% Интернета, что привело к ущербу в десятки миллионов долларов (в то время это было очень маленький Интернет. Сегодня ущерб измеряется в сотнях миллиардов долларов, а может и больше - если вы просуммируете ущерб от ошибок переполнения буфера с другими, специфичными для C уязвимостями, эксплуатируемыми в последние два десятилетия).

Тут должен "прозвенеть звонок". Вы не можете писать программы с надёжной безопасностью в языке, который был спроектирован без единой мысли о ней! По крайней мере, не с любой степенью согласованности. Просто посмотрите, как много патчей выпускается сегодня для Windows, Linux, OSX, iOS и различных Интернет служб и Web-браузеров - большая часть из которых происходит из-за переполнения буфера. И это становится всё хуже. Люди говорят о компьютеризации энергосистемы, что имеет смысл в теории, но на практике, вероятнее всего, просто откроет несколько сотен миллионов новых уязвимостей для террористов (кто не смотрел Крепкий орешек 4.0 (Live Free Or Die Hard)? Кто-нибудь хочет жить в их мире, только без Брюса Уиллиса и этого парнишки “I’m a Mac”, которые так удобно спасают мир?).

Одна и та же старая проблема повторяется снова, и снова, и снова. Она продолжает существовать, потому что она просто не может быть решена в C без разрушения обратной совместимости. В любом разумном мире, язык C вымер бы к 1989 (червь Морриса показал, что он просто не подходит для своей цели: постройки операционной системы), и наши компьютеры стали бы безопаснее. Нельзя сказать, что тогда не было никаких альтернатив. К этому времени Apple уже несколько лет делала самую продвинутую ОС того времени - на Паскале. Продолжить писать Internet-программы на C (или C++ или Objective-C) - должно рассматриваться как акт преступной халатности.

В Delphi, с другой стороны, у нас есть настоящий строковый тип - самый продуманный из всех, что я видел в других языках. У него есть счётчик ссылок и он меняет размер строки сам по мере необходимости, что освобождает программиста от мук с размером строк и памяти для строк. Он проверяет на допустимость индекса и он не живёт на стеке, поэтому не существует способа использовать переполнение буфера строки для эксплоита типа stack-smashing. Аналогично и для других типов данных: у Delphi есть настоящий динамический массив, который также проверяем на допустимость индекса. Фактически, у нас есть два типа массивов (индексы проверяемы у обоих), и тот, размер которого не фиксирован (самый опасный из двух), также не живёт на стеке. У нас также есть подпрограмма форматирования строк, которая не использует varargs (переменное число аргументов), а код вывода строк не предполагает, что его данные являются форматируемой строкой, что делает Delphi программы нечувствительными к атакам на строки форматирования.

Чтобы быть честным, я, конечно же, должен сказать, что, как и в любом языке, использующем указатели, в Delphi возможно написать небезопасный код. Но для этого вам надо сильно извернуться; это не нормальное состояние для языка, как это имеет место быть для C! (полное удаление указателей из языка также привносит больше проблем, чем решает. Вот почему и в Java и в C# есть явные небезопасные возможности: потому что они необходимы для выполнения важных задач).

Синтаксис и семантика

OK, хватит о безопасности. Давайте перейдём и к другим вещам. Много проблем с безопасностью в C решены в управляемых языках типа C# и Java. Но что они не могут сделать - так это исправить синтаксис, по крайней мере, не разорвав свои общие корни с C, которые являются мощным инструментом маркетинга.

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

Недавно я услышал действительно ужасную игру слов: Торт может быть ложью, но Пи всегда истина (потому что Пи не равно нулю, а в C любое значение, отличное от нуля, считается истинным логическим значением). Когда у вас что угодно является логическим выражением, включая операцию присваивания, вы не можете безопасно написать if x = 5 - и не важно, как разумно и интуитивно это выглядит. Также, когда у вас всё является логическим типом, включая числа, если вы попробуете выполнить операцию and между двумя выражениями, то компилятор не знает, имели ли вы в виду логическую или побитовую операцию - поэтому вам нужно два варианта каждого логического оператора. А если вы ошибётесь, то или ваш код может работать или у вас будет ошибка, которую-хрен-найдёшь.

Java, JavaScript и C# всё ещё имеют две версии операторов. И я знаю, что проблема с if x = 5 всё ещё существует в JavaScript (мне сказали, что компилятор не пропустит такое в C#. Не уверен, как тут обстоят дела в Java).

В Delphi логический тип - это логический тип, а число (или строка или объект) - это не логический тип. Это значит, что у нас есть одно and, одно or, одно xor и одно not, а компилятор знает, какую операцию нужно применить (логическую или побитовую), просто смотря на типы операндов. А если вы попробуете сделать что-то странное, типа and между логическим значением и числом, то компилятор просто не пропустит это, вместо молчаливой генерации не имеющего смысла кода.

И пока мы не отошли от операторов, кто из вас может сказать мне, что делают * или & в C? “Ну, это зависит от того, ...” Ох, да. Фундаментальные синтаксические элементы, чьё значение определяется по контексту. Как мило. В Delphi, “a * b” означает умножение и ничего иного. А для взятия адреса и разыменования у нас есть операторы @ и ^ соответственно, которые имеют мнемонический смысл.

А потом - ООП. Модель объектов в C++ - это большой беспорядок. Нет базового объектного типа. Что означает, что вы не можете написать подпрограмму, принимающую любой объект. Это также означает, что нет стандартного способа получить информацию об объекте в run-time (в Delphi это называется RTTI). А объекты являются value-типами - т.е. хранящимися (по-умолчанию) на стеке (или внедрёнными в другой объект), а передаются по коду по значению (по-умолчанию). Это сводит на нет наследование и полиморфизм.

Например, что выведет эта программа? Если вы измените сигнатуру функции Foo, чтобы она передавала объект по ссылке - изменит ли это вывод программы?
#include <iostream>

class Parent
{
public:
        int a;
        int b;
        int c;

        Parent(int ia, int ib, int ic) {
                a = ia; b = ib; c = ic;
        };

        virtual void doSomething(void) {
                std::cout << "Parent doSomething" << std::endl;
        }
};

class Child : public Parent {
public:
        int d;
        int e;

        Child(int id, int ie) : Parent(1,2,3) {
                d = id; e = ie;
        };
        virtual void doSomething(void) {
                std::cout << "Child doSomething : D = " << d << std::endl;
        }
};

void foo(Parent a) {
        a.doSomething();
}

int main(void)
{
        Child c(4, 5);
        foo(c);
        return 0;
}
Если вам пришлось остановиться и поразмышлять об этом коде - это показательный признак. Я попросил нашего эксперта C++ посмотреть на код и сказать, что он делает. Он изучал его несколько минут, пришёл к логично-звучащему выводу о том, что он должен делать. После чего он прогнал код через компилятор, чтобы быть уверенным (и это человек, который профессионально писал на C++, когда я ещё учился в университете. И он очень хорош в нём. Но даже со всем своим опытом, у него не было полной уверенности без пробного прогона).

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

Внешний код

Для всего вышеперечисленного хотя бы есть технические причины. Но вот кое что действительно эксцентричное, чему я пока не слышал хорошего объяснения. Как-то мне надо было отладить DLL, написанную на C, которую вызывала моя Delphi программа. Я открыл эту DLL в Visual Studio и попытался скомпилировать. Хотя всё было синтаксически верным и компилировалось отлично, но сборка проваливалась на фазе линковки, потому что линкёр не мог найти .lib-файл для второй DLL, которую требовала эта DLL.

.lib-файл? Что, во имя Тьюринга, есть .lib-файл?!? Ну, оказывается, что это файл, который описывает …что-то… о другой DLL, так что линкёр может правильно прицепить это …что-то…. Я не представляю, почему это является необходимым. Раньше мне не приходилось с ними работать. В Delphi, если тебе надо прицепить другую DLL, ты объявляешь в коде заголовок функции, указываешь, что это внешняя функция, и предоставляешь имя DLL, в которой находится функция - вот и всё.

У кода C есть вся та же информация: .h-файл, содержащий заголовок функции и … ооохх, постойте-ка. Теперь я вижу, что происходит! Ведь это фактически тот же самый .h-файл, который используется в DLL, с которой я связываюсь. Поэтому он не указывает, ни что эти функции являются внешними, ни где их искать. Эта информация, хотя и является важной частью кода, должна быть предоставлена отдельно - в .lib-файле, двоичном контейнере, сгенерированном компилятором по той самой внешней DLL, который к тому же не читабелен человеком и плохо переносит версионный контроль (а если ваша внешняя DLL не была написана на языке семейства C, то вас ожидает ещё больше веселья в попытках сделать .lib-файл для неё).

Всё это сводится к тому, что, по какой-то странной причине, Delphi делает работу со ссылками на библиотеку C более беспроблемной, чем это делает сам C. Это же не имеет никакого смысла, но так и есть. Delphi может говорить с внешним кодом C легче, чем C!

Я могу и продолжать (я даже не упомянул шаблоны!), но этот пост и так уже большой. Я думаю, что факты говорят сами за себя. Уделяя внимание таким мелочам, типа упомянутых выше, и продумыванием последствий, проектировщикам языка Delphi удалось построить язык, с которым проще работать и с которым проще писать корректный код. Я надеюсь, что этот пост проясняет, что я имел в виду, когда писал свой ответ на вопрос.

Примечание переводчика:
Вот ещё два примера вещей, которые получили бешеную популярность, несмотря на техническое превосходство конкурентов: архитектура x86, Windows. В частности, в связи с упоминанием выше переполнения буфера, хочу особо ткнуть сюда (это одновременно и ещё один пример, как более технически совершенная платформа ia64 проиграла платформе Amd64).

16 комментариев:

  1. Ну, IA64 проиграла по объективным причинам. Во первых высокая цена обусловила дорогое вхождение. Intel хотели сделать платформу для Ъ-энтерпрайз решений, они получили её. Вот только оказалось, что эти решения неплохо стали жить и на более дешёвых платформах, а бизнес умеет считать деньги.
    Во вторых, насколько я помню, долгое время не могли сделать нормальный компилятор. Помню, менеджеры Intel долго хвалились, что для их супер-процессора потребуется компилятор с элементами искусственного интеллекта(sic!), ну так пока с искусственным интеллектом тоже плохо. :)

    ОтветитьУдалить
  2. На С надо писать аккуратно. В этом ничего нового нету. Все описываемые ужасы С - от неаккуратности. Неаккуратно можно написатьна любом языке, на Delphi очень много забавного можно написать с использованием типа Variant, например :)

    ОтветитьУдалить
  3. Вот Плюсы трогать было ни к чему.
    "А потом - ООП. Модель объектов в C++ - это большой беспорядок. Нет базового объектного типа." На самом деле это громадное преимущество.
    "Что означает, что вы не можете написать подпрограмму, принимающую любой объект."Могу. Да, безопасно.
    Итд по тексту, не холивар, всё-таки. Я б посоветовал для начала ознакомиться с предметом. Когда-то я обожал ObjectPascal, потом примерно одинаково любил Delphi и C++. Сейчас я терпеть не могу того Колосса на подпорках, в который Борланд превратила Дельфи. Искренне жаль.

    ОтветитьУдалить
  4. Какова актуальность этой статьи?
    ибо по пунктам:
    Безопасность!!!
    В С уже давным-давно есть безопасные функции snprintf, strncpy и т.д. Так что не понятно, что хотел сказать автор. Может быть он просто некомпетентен. (Хотя даже обычный студент знает про эти функции)

    Синтаксис и семантика

    Я сам переходил на С/С++ с паскаля/делфи, и не вижу никаких проблем с отсутствием типа boolean. Вообще все понятно даже зеленому новичку: 0 - это ложь, все остальное истина.
    C
    if (a) b=c;
    Pascal
    if a=true then b:=c;

    Здесь все хорошо видно: С более лаконичен, плюс добавляется некоторая гибкость - в скобках не обязательно может быть boolean выражение.

    "Когда у вас что угодно является логическим выражением, включая операцию присваивания, вы не можете безопасно написать if x = 5 - и не важно, как разумно и интуитивно это выглядит." В С = это оператор присваивания, == - оератор сравнения. Ошибаются только зеленые новички, плюс компилятор в такой ситуации выдает warning. (Не вижу проблемы)

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

    Я тоже был когда-то начинающим С-ишником и ни разу (!) не ошибся, странно...

    "В Delphi, “a * b” означает умножение и ничего иного." Я не сталкивался с уже давно существующей перегрузкой операторов в Делфи, поэтому гадать не буду, но создавая функцию для умножения объектов комплексных чисел (к примеру) и называя ее Mul() (ну или еще как), мы можем заложить туда сложение вместо умножения, и никто нас не остановит. Вывод: пишите нормально.

    "А потом - ООП. Модель объектов в C++" дальше начинается полная бессмыслица, ибо автор вообще некомпетентен в этом вопросе.

    "Я попросил нашего эксперта C++" Что это за эксперт такой? Я и то понял, что делает код при беглом просмотре, хотя не считаю себя даже компетентным в С/С++.

    ОтветитьУдалить
  5. На базе этого.

    Для справки: на C++ я программировал лет десять. После этого я благополучно децл сменил область деятельности и перешёл на Delphi. Из этого всем должно быть очевидно, что оба языка я хорошо знаю и понимаю (кому не очевидно, тот сам виноват). Однако же пару дней назад пришлось-таки снова написать всякого на C++. Что вызвало нехилое раздражение.

    Я понял, что подход C++ к автомобилестроению примерно такой: человеку дают все инструменты, с помощью которых можно собрать совершенно любой автомобиль. Но ничего кроме простейших инструментов не дают. Типа, если можно собрать, то и собирай что хочешь. Стандартная библиотека C++ хотя и довольно гибка, но неуловимо ущербна. Например, в WinAPI нет функции копирования директории. Нет функции для получения списка файлов, вместо этого есть какой-то недоитератор. Складывается ощущение, что копирование директорий такая редкая операция, что и заморачиваться не стоит: кому надо, сам напишет.

    И так во всём. Чуть где нужно что-то очевидно стандартное – его нет. Зато есть что-то очень-очень гибкое, с помощью чего это стандартное следует собирать. Причём, началось это очень давно. В тот самый момент, когда разработчики С решили вдруг, что строки вполне можно встроить в виде char*. Да чего там – в тот момент, когда они поняли, что с помощью массивов вообще всё что угодно можно сгенерить. Они были правы, но их правота вылилась в мега-кучи ошибок с битой памятью. По опыту, половина ошибок при написании чего-то на C проистекает из-за бития памяти с помощью char*.

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

    ОтветитьУдалить
  6. По счастью, при разработке С++ часть этих недочётов была учтена и в STL всё-таки сделали и вменяемые строки и вменяемые контейнеры. Но С, друзья мои, не умер. Он живёт в обрывках кода, которые до сих пор приходится использовать. Так, например, в WinAPI не стали использовать STL, вместо этого ввели свои типы и очень весёлую форму записи. С указателями, а иногда даже с двойными указателями. Если надо получить набор значений, то изволь заводить буфер под них и сообщать его размер соответствующей функции. Так мега-экономия памяти обернулась её разбазариванием в буферах.

    Стоит ли говорить, что для превращения std::string в спецтип WinAPI и обратно, надо писать дополнительный код. Ну а если нужен не WinAPI, а MFC, то там тоже свои классы и тоже нужна конверсия. Казалось бы мелочь – не сделали встроенный тип «строка», однако вон оно как вышло.

    Кстати, строки иногда надо превращать в числа, а числа – в строки. Что в этих случаях делается в Delphi? Число в строку превращается FloatToStr, а обратно парсится встроенными же StrToFloat и им подобными. Операция-то очень распространённая, отчего бы её не встроить в С++? Говорят, нельзя. Говорят, каждый программист сам для себя напишет. Вот и пишут.

    Самое интересное: кто сходу скажет, что надо написать, чтобы вывести на экран double с той точностью, которая в данном случае есть у числа? Ну, то есть, если double x = 123123.5657356252, то на экран вывелось бы 123123.5657356252?

    cout << x; ?

    printf("%f", x); ?

    Фиг вам, дорогие программисты. Всё это выведет число вовсе не в том виде, в котором хотелось бы. Для одних чисел обрубится дробная часть, другие переведутся в экспоненциальную форму и так далее. Простая до тривиальности задача в С++ имеет совершенно дикие и неочевидные решения. И не каждый джигит сможет до них допетрить.

    Зато в printf можно очень гибко задавать формат вывода. Любой формат. Кроме самого простого и очевидного. Точнее, его тоже можно задать, но выглядит это так, что никто не догадается. В общем, чем так согрешила функция «перевести в строку»? Неужели же своей простотой? Я не знаю. Знаю только, что в Delphi нет никаких проблем с переводом чисел во все стороны. И, кстати, printf там тоже есть. Но им пользуются редко.

    ОтветитьУдалить
  7. Ах да, есть же функции парсинга в С! Как же они называются? fromString? Нет. StringToDouble? Для С++ слишком очевидные названия. Слишком длинные. Наверно str_to_dbl? Тоже нет. Функция называется atof. Такое вот простое и очевидное название. Любой сразу догадается, что функция должна называться именно так. Что интересно, если конверсия не удаётся (вместо «1.2» в строке содержится, например, «Fuck off»), то что будет? Выкинется исключение? Но, увы, это – функция из С, а в С нет исключений. Поэтому функция просто и незамысловато вернёт ноль. Что делает проверку валидности значений весьма нетривиальным занятием – по действиям функции ведь совершенно непонятно, действительно ли ноль был в строке или какое-то левое значение. И как, блин, проверить? Сравнить строку с "0"? А если там было "0.0" или "000.00000"? Фигня получается.

    Справедливости ради, надо отметить, что есть функция с более очевидным названием strtod. Но ни та, ни другая не работают с std::string, то есть, опять же нужна конверсия через c_str().

    Всё это напоминает мне ситуацию, при которой автомеханику даются средства, с помощью которых можно сделать руль очень замысловатой формы, но круглый руль делать так же тяжело, как звёздообразный. Наверно поклонников звёздообразных рулей это порадует, но рули обычно ведь круглые и их изготовление должно быть наиболее простым. В Delphi так, в С++ – нет.

    Проблема в том, что в С++ отсутствует последовательность. Он сделан на основе С, обладает с ним почти полной совместимостью, отсутствующее в С прикручено к нему изолентой, и, – что самое плохое, – он исповедует свободу во вред – не задаёт стандартов. Отсутствие стандартного подхода ко всему, полагание на «программист сам додумается» приводит к разнобою даже внутри самого языка, не говоря уже про сторонние библиотеки. С++ не задаёт даже стандартных способов именования. Поэтому в разных программах встречаются совершенно разные способы. У кого-то венгерская нотация, у кого-то всё идёт через подчёркивание, у кого-то вообще никакой. Свобода, блин. Программиста не притесняют. Но такая свобода хороша, когда программист находится в вакууме и всё его общение с внешним миром сводится к вбрасыванию туда экзешников.

    ОтветитьУдалить
  8. Увы, обычно это не так. Программист обычно пользуется чужими библиотеками и чужим же кодом. В результате Delphi-код и Delphi-библиотеки в 99% случаев имеют один и тот же стиль именования и даже begin/end там проставлены одинаково. Чужой Delphi-код не портит своего. А вот в случае С++ всё сразу идёт вразнос. В одной библиотеке функции называют в виде MyFunc, в другой – My_Super_Func, в третей – mymegafunc, ваш коллега любит венгерскую нотацию, а вы сами уважаете нэйминг из Delphi. При этом одни требуют указатели, другие – ссылки, а третьи – ссылки на указатели. В вашем же коде всё это превращается в

    int myValue = mymegafunc(**My_Super_Func(&MyFunc(*m_piValue));

    Смотришь на всё это и думаешь «да где тут что вообще?!!»

    Вот такая она – свобода. Людям её только дай... Стандарты, блин, руля́т.

    Вообще в С++ крайне тяжело назвать то, что там сделано лучше, чем в Delphi. До Delphi 2009 в С++ лучше было наличие шаблонов. Однако в Delphi 2009 появились дженерики и анонимные методы. Последний плюс уверенно ушёл в прошлое.

    Кстати, в С++ есть и ещё одна полезная штука – возможность передать константный объект и объявить константную функцию. Вот этого в Delphi пока не сделали. Жаль. Итого один плюс против разливанных морей минусов.

    Что меня реально вспарило при временном возвращении к С++, так это долбанные хэдеры. Тут я реально не понимаю, нафига их вообще ввели. Может, предполагалось, что сорсы к хэдерам можно будет подменять? Но, мой бог, зачем? Никто же этим никогда не пользуется. С чего бы тогда код не писать прямо там, где объявляются функции? Загадка, блин, природы. Точнее, загадка человеческой мысли.

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

    ОтветитьУдалить
  9. Хэдеры же, кроме необходимости объявлять в одном месте, а писать в другом, влекут за собой ещё одну болезнь С++ – кучи мусора. Выражающиеся в проверках того, что хэдер подключен ровно один раз. Ну да, те самые #ifndef и так далее. Стандартные шаманские строки, которые всех так сильно задолбали. Которые содержатся в каждом хэдере, поскольку далеко не все знают, что можно написать не менее шаманское #pragma once. Но даже с прагмой вопрос остаётся открытым: а какого хрена это одноразовое включение не встроено сразу в язык? Чего бы такого мы потеряли, если бы хэдер включался ровно один раз без нашего вмешательства? Ответа нет. Поскольку ничего бы мы не потеряли. Просто в С++ модно писать мусорный код. В С++ – это элемент стиля. Который ничего не даёт, но очень круто выглядит.

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

    Ещё неприятно поразила необходимость писать инклюды вручную. Это, конечно, особенность не языка, а оболочки, но ведь неспроста наверно в Delphi импорты включаются автоматически, а в оболочках С++ их надо вписывать руками, хотя и с подсказками компьютера. Печаль сего не в том, что тяжело эту строчку набрать, а в том, что тяжело вспомнить, в какой именно библиотеке лежит нужная функция. В результате приходится постоянно лезть в help.

    Кстати, в Delphi проблема хэлпа тоже была решена универсальным способом – хэлп содержится ровно там, где код. То есть, комментарии к коду являются и хелпом к нему. Некоторые оболочки С++ тоже показывают комментарии во всплывающих подсказках, но без стандарта это – полумера. Никто ведь не знает, в какой форме будет написан комментарий тем или иным программистом. Да и ключевых слов для комментариев не предусмотрено.

    ОтветитьУдалить
  10. В Delphi разработчики уверенно забороли практически все болезни. Во-первых, ввели стандарт на многие вещи. Во-вторых, документацию скрестили с кодом и слили объявления с реализациями. В-третьих, практически убрали долбанные указатели, заменив их более высокоуровневыми конструкциями. В-четвёртых, встроили автоматическое управление памятью. 90% проблем и ошибок исчезли сами собой. В-пятых, ввели стандарты именования. Пусть негласно, но ввели. В-шестых, встроили объект «строка» в язык и научили всех работать именно с ним. В-седьмых, трэды опять же встроили в язык и встроили в него же все механизмы работы с трэдами. Всё перечисленное офигенно удобно.

    В Delphi встроен GUI. Причём, не только в виде одной из его возможных реализаций, но и в виде стандартной реализации, которые поддерживают все остальные.

    Встроенные библиотеки реализуют большинство стандартных задач. А сторонние, хорошо известные и де-факто стандартные сторонние библиотеки реализуют решения практически всех стандартных и наиболее часто встречающихся задач.

    Ну и функции в этих библиотеках называются так, что по названию понятно, что они делают. И передаются в них ссылки, а не двойные указатели и переменная с длинной массива. Чуть не забыл: массив в Delphi знает, какая у него длина. И даже может сказать об этом программисту.

    В Delphi встроена сериализация объектов. Встроены регулярные выражения. Понятно, что всё это можно найти и для С++, но тут даже искать не надо. Более того, контейнеры уже сериализумы – на уровне языка, без дополнительных ухищрений. Во встроенных строках уже все функции работают с регулярными выражениями. Удобство всего этого тем, кто пишет на С++, неочевидно. Зато при возвращении с Delphi на С++ без этого как без рук. Там, где в Delphi всё решалось одной строкой, в С++ надо городить нехилый огород или искать в интернете готовые решения. Подчеркну, решения вовсе не узкоспециальных случаев, а стандартных и очень распространённых задач. Давно уже решённых задач, что характерно.

    ОтветитьУдалить
  11. Возвращаясь к автомобильной терминологии, при временном возврате у меня было ощущение, что я из автомобиля пересел на велосипед – проехать вроде можно в большее число мест, но езда на дальние расстояния адски, неоправдано тяжела.

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

    В общем, чтобы я ещё раз сел на этот велосипед, чтобы хоть ещё раз...

    ОтветитьУдалить
  12. Оттуда-же.

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

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

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

    И ещё:
    Не думай о секундах свысока

    ОтветитьУдалить
  13. GunSmoker - респект !

    хорошая статья !

    ОтветитьУдалить
  14. Это не моя статья, это перевод.

    ОтветитьУдалить
  15. только начал изучать с++, а тут такое прочитал=)) я в шоке....

    ОтветитьУдалить
  16. Жаль, Мозилле с их Rust'ом не дали почитать эту статью.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.