пятница, 20 июля 2012 г.

Оформление кода MQL. Классы

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

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

Возьмем для примера простейший класс с двумя полями и методом:

class MyClass1
{
private:
    int prop1;
    double prop2;

public:
    void DoSomething(const CObject *object)
};

void MyClass1::DoSomething(const CObject *object)
{
    // do something
}

Это почти типичное оформление кода класса от MetaQuotes, встречающееся в документации и стандартной библиотеке, приведенное в соответствие с правилами предыдущих статей. Здесь получаем несколько проблем, которые попытаемся решить.

Модификаторы доступа выходят из структуры

В коде выше модификаторы public и private находятся на том же уровне, что и название класса, хотя они являются частью структуры (группировка), причем их верхним уровнем является как раз объявление класса. То есть эти модификаторы должны писаться с отступом. Некоторые современные стандарты оформления кода C++ уже учитывают эту ошибку и предлагают делать дополнительный отступ.

class MyClass1
{
    private:
        int prop1;
        double prop2;

    public:
        void DoSomething(const CObject *object);
};

void MyClass1::DoSomething(const CObject *object)
{
    // do something
}

Изменение модификатора и контроль версий

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

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

class MyClass1
{
    private: int prop1;
    private: double prop2;
    public: void DoSomething(const CObject *object);
};

void MyClass1::DoSomething(const CObject *object)
{
    // do something
}

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

Подобные удаления и вставки приводят к проблеме невидимости изменений в перемещаемом содержимом, когда вы видите только факт перемещения кода, но не видите, что в нем также были сделаны какие-то изменения. Поэтому групповых перемещений нужно избегать. Для того, чтобы изменение одной строки рассматривалось как отдельное изменение, необходимо отделить ее пустой строкой.

Изменение модификатора доступа для двух полей класса:


Довольно сложно заметить, что помимо изменения модификатора доступа сменилось еще и имя одной из переменных. При большом числе изменений такие мелкие изменения могут быть еще более трудноуловимыми. Если мы заранее будем использовать разделение переменных пустыми строками, изменение будет выглядеть уже так:


В этом случае проще обнаружить изменения в отдельной строке.

Вы можете использовать более продвинутые средства сравнения, например TortoiseMerge:


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

Объявление и реализация

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

Нам предлагается писать сначала объявление, а под объявлением класса - реализацию. Такой подход используется, например, в Object Pascal и Delphi, как обязательный. Привожу в пример этот язык, так как именно на его примере был получен большой негативный опыт работы с подобным синтаксисом. С введением в процесс разработки системы контроля версий становится понятно, что такой подход дает не только необходимость выполнения лишней работы, но и неудобство работы с версиями - каждое изменение объявления функции приходится теперь делать в двух местах, что усложняет визуализацию изменений в версии.


Такой же подход предлагается по умолчанию и в MQL5. Хорошо, что синтаксис языка позволяет писать реализацию метода прямо за объявлением, что решает проблему.

class MyClass1
{
    private: int prop1;
    private: double prop2;

    public: void DoSomething(const CObject *object)
    {
        // do something
    }
};

В итоге получили редкое для улучшения оформления явление - уменьшение числа строк. Следует отметить важную деталь - если бы в редакторе MetaEditor 5 не было бы навигации по функциям и определениям, то разделение объявления и реализации давало бы некоторое удобство, но навигация есть, и разделение уже не решает проблему ее отсутствия. Более того, разделение ее усложняет - постоянно приходится выбирать между объявлением и реализацией.

Пример излишне усложненной навигации (функция "Перейти к определению"):


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

Прикинем, куда и как можно здесь поставить комментарии.

// Мой класс
class MyClass1
{
    // Свойство 1
    private: int prop1;

    // Свойство 2
    private: double prop2;

    // Сделать что-нибудь
    public: void DoSomething(const CObject *object)
    {
        // do something
    }
};

Если бы мы сначала писали объявление, а потом реализацию метода класса, то комментарий пришлось бы продублировать, - лишняя работа.

См. также

Комментариев нет:

Отправить комментарий