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

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

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

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

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

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, Meld и т.п.:


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

Дополнение. Такой стиль ещё более спорный, настолько, что вообще никем не применяется в C, C++, MQL. Считайте это объяснением того, почему в некоторых современных языках вас "заставляют" использовать такой подход. При работе в группе необходимо чётко согласовывать оформление кода и поэтому такой вариант с модификаторами больше не использую даже я сам.

Модификаторы доступа. Порядок

С одной стороны, хочется, чтобы публичные методы были как можно выше, поэтому желательна сортировка типа public-protected-private. С другой стороны, неудобно располагать члены класса (часто приватные) в конце класса и рассеивать их по телу класса.

Есть простой выход из этой ситуации:
  1. Члены класса (или структуры) всегда идут перед методами.
  2. Члены и методы сортируются в порядке public-protected-private.
Получаем такой порядок:
  1. public члены 
  2. protected члены
  3. private члены
  4. public методы
  5. protected методы
  6. private методы
Публичные методы часто разделяются ещё на несколько типов:
  • конструкторы и деструкторы
  • геттеры и сеттеры
  • операторы
Их тоже можно группировать и сортировать в некотором порядке для удобства чтения кода.

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

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
{
 
private:
 
    // Свойство 1
    int prop1;

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


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

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

См. также

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

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