Документация
ОС Аврора 5.0.1

Исходный код на QML

В данной статье описываются соглашения о коде QML, которые соблюдаются в документации и примерах Qt и рекомендуются остальным разработчикам.

Объявления объектов QML

В документации и примерах атрибуты объектов QML всегда структурированы в следующем порядке:

  1. Идентификатор id. id в первой строке облегчает поиск объекта.
  2. Свойство objectName.
  3. Объявления и определения новых свойств.
  4. Объявления сигналов.
  5. Объявления и определения методов.
  6. Связывания свойств:
    • В первую очередь указываются наиболее важные для определения объекта свойства.
    • Рекомендуется группировать свойства по назначению: положение, цвет, настройки шрифта и т.п.
    • Присоединённые свойства указываются в последнюю очередь.
  7. Элементы Binding.
  8. Обработчики сигналов. Исключение: обработчик Component.onCompleted.
  9. Вложенные объекты.
    1. Элементы Connections.
    2. Невизуальные элементы.
    3. Визуальные элементы.
  10. Состояния.
  11. Переходы.
  12. Обработчик сигнала Component.onCompleted.

Для удобства чтения части разделяются пустой строкой. Рекомендуется добавлять её после id, семантических групп и между компонентами.

Например, гипотетический объект QML Photo выглядит следующим образом:

Rectangle {
    id: photo
    // id в первой строке позволяет легко найти объект

    property bool thumbnail: false                        // объявление свойств
    property alias image: photoImage.source

    signal clicked                                        // объявление сигнала

    function doSomething(x)                               // JavaScript-функция
    {
        return x + photoImage.width
    }

    x: 20                                                 // семантически связанные свойства группируются вместе
    y: 20

    height: 150                                           // семантически связанные свойства группируются вместе
    width: {                                              // большие связывания
        if (photoImage.width > 200) {
            photoImage.width;
        } else {
            200;
        }
    }

    color: "gray"                                         // остальные свойства объекта

    onColorChanged: console.debug("New color is ", color)

    Rectangle {                                           // дочерние объекты
        id: border
        
        anchors.centerIn: parent
        color: "white"

        Image {
            id: photoImage
            anchors.centerIn: parent
        }
    }

    states: State {                                       // состояния
        name: "selected"
        PropertyChanges { target: border; color: "red" }
    }

    transitions: Transition {                             // переходы
        from: ""
        to: "selected"
        ColorAnimation { target: border; duration: 200 }
    }

    Component.onComplete: console.debug("Photo is ready.")
}

Сгруппированные свойства

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

// Не рекомендуется
Rectangle {
    anchors.left: parent.left; anchors.top: parent.top; anchors.right: parent.right; anchors.leftMargin: 20
}

Text {
    text: "hello"
    font.bold: true; font.italic: true; font.pixelSize: 20; font.capitalization: Font.AllUppercase
}
// Рекомендуется
Rectangle {
    anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: 20 }
}

Text {
    text: "hello"
    font { bold: true; italic: true; pixelSize: 20; capitalization: Font.AllUppercase }
}

Длина строк кода

Код может быть разбит на отдельные строки или оформлен в одну строку по усмотрению разработчика, в зависимости от того, какой вариант будет удобнее для чтения. В этом случае рекомендуется ориентироваться на максимальную длину строки (рекомендуется 120 символов).

Списки

Если список содержит только один элемент, квадратные скобки обычно опускаются. Например, очень часто компонент имеет только одно состояние.

// Не рекомендуется
states: [
    State {
        name: "open"
        PropertyChanges { target: container; width: 200 }
    }
]
// Рекомендуется
states: State {
    name: "open"
    PropertyChanges { target: container; width: 200 }
}

Код JavaScript

Если скрипт представляет собой одно выражение, рекомендуется записать его в одну строку:

// Рекомендуется
Rectangle {
	width: parent.width / 3
	color: "blue"
}

Если скрипт состоит всего из пары строк, лучше использовать блок:

// Рекомендуется
Rectangle {
    color: "blue"
    width: {
        var w = parent.width / 3
        console.debug(w)
        return w
    }
}

Если скрипт длиннее пары строк или может использоваться разными объектами, рекомендуется создать функцию и вызвать её следующим образом:

// Рекомендуется
function calculateWidth(object)
{
    var w = object.width / 3
    // ...
    // more javascript code
    // ...
    console.debug(w)
    return w
}

Rectangle { color: "blue"; width: calculateWidth(parent) }

Длинные скрипты помещаются в собственный файл JavaScript, который импортируется следующим образом:

import "myscript.js" as Script

Rectangle { color: "blue"; width: Script.calculateWidth(parent) }

Использование ";" после JavaScript-строк

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

// Не рекомендуется
var x = a + b;
// Рекомендуется
var x = a + b

Обоснование: чем меньше кода, тем лучше.

Пробелы в арифметических операциях

Рекомендуется добавлять пробелы вокруг знаков выражений, поэтому вместо математического стиля

// Не рекомендуется
var y = 2*x

следует использовать

// Рекомендуется
var y = 2 * x

Пробелы между условными операторами, кодом и фигурными скобками

Рекомендуется ставить пробелы между условными операторами, кодом и фигурными скобками. Поэтому вместо

// Не рекомендуется
if(a){}

или

// Не рекомендуется
if ( a ) {}

следует использовать

// Рекомендуется
if (a) {}

Обоснование: критерии в основном эстетические. В целом добавление пробелов улучшает визуальную группировку (но только если не используется чрезмерно) и улучшает читаемость.

Однострочные короткие функции

Рекомендуется однострочные короткие функции размещать в одной строке.

// Не рекомендуется
onClicked: {
    if (a) {
        doSomething()
    }
}
// Рекомендуется
onClicked: if (a) doSomething()

Обоснование: во втором случае требуется меньше строк, хотя обе формы одинаково удобны для чтения.

Фигурные скобки в однострочных условных операторах

Первый подход. Рекомендуется всегда ставить фигурные скобки.

// Не рекомендуется
if (a)
    doSomething()
// Рекомендуется
if (a) {
    doSomething()
}

Обоснование: обычно подход считается более безопасным.

Второй подход. Фигурные скобки следует использовать только в том случае, если тело условного оператора содержит более одной строки. Это совпадает с правилом для C++-кода.

В компании «Открытая мобильная платформа» принят следующий подход. Не рекомендуется использовать скобки, когда оператор только один (или по одному оператору в обоих блоках if else). Но если в одном блоке if или else есть скобки, то и в другом они должны быть.

Присвоения свойств

Рекомендуется исключать лишние присвоения свойств.

// Не рекомендуется
property bool propertyName: false
property int propertyName: 0
// Рекомендуется
property bool propertyName
property int propertyName

Обоснование: во втором случае требуется меньше кода, разработчик QML уже должен знать значения по умолчанию.

Идентификатор элемента

Не нужно задавать идентификатор элемента, если идентификатор не используется или не понадобится в будущем.

// Не рекомендуется
Image {
    id: background
}
// Рекомендуется
Image {}

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

Отрицания

Следует избегать лишних отрицаний, более удобная для чтения конструкция следующая.

// Не рекомендуется
if (!a) .. else ..
// Рекомендуется
if (a) .. else ..

Обоснование: для расшифровки отрицаний требуется больше умственной нагрузки. Необходимо избегать двойного отрицания, например, ! NO_SOMETHING.

Позиционирование

Рекомендуется использовать якори вместо позиционирования вручную.

// Рекомендуется
Rectangle { id: rect1; ... }
Rectangle { id: rect2; anchors.left: rect1.right; ... }

Тернарный оператор

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

// Рекомендуется
var message = speed >= 120
              ? 'Too Fast'
              : speed >= 80
                ? 'Fast'
                : 'OK'

Прикреплённые свойства

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

// Не рекомендуется
TextField {
    text: dialog.title
    EnterKey.iconSource: "image://theme/icon-m-enter-close"
    EnterKey.onClicked: focus = false

    onTextChanged: dialog.title = text
}
// Рекомендуется
TextField {
    text: dialog.title
    EnterKey.iconSource: "image://theme/icon-m-enter-close"

    onTextChanged: dialog.title = text
    EnterKey.onClicked: focus = false
}

Пометка приватных API

Необходимо явно помечать приватные API, нельзя помешать разработчикам читать код, и если они не могут отличить публичные элементы от приватных, их код будет зависеть от того, что изменится в более поздних версиях. QML не поддерживает приватные атрибуты, однако есть несколько соглашений, полезных для предотвращения использования подобных элементов. Самый простой и самый быстрый — добавить знак подчёркивания:

// Рекомендуется
Item {
    property int iAmPublic
    property int _iAmPrivate

    Text {
        text: "Hello"
    }
}

Импорт файлов в QML

Чтобы импортировать такие элементы, как файлы или каталоги, применяется ключевое слово import, аналогично тому, как используется оператор import QtQuick 1.0.

import QtQuick 1.0
import "subdirectory"
import "script.js"

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

Например, если компонент QML находится в файле с именем Button.qml, пользователь может импортировать компонент, объявив Button {}. Следует обратить внимание, что такой прямой подход работает только в том случае, если файлы QML находятся в одном каталоге или каталог, содержащий файл компонента, подключён инструкцией import.

Комментирование кода

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

Как и в JavaScript или C++, есть два способа комментирования кода QML:

  • однострочные комментарии начинаются с // и заканчиваются в конце строки;
  • многострочные комментарии начинаются с /* и заканчиваются */.

Интерфейс и логика

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

  • Декларативные языки хорошо подходят для определения пользовательских интерфейсов.
  • Код QML проще писать, поскольку он более краток, чем C++, и не имеет строгой типизации. Таким образом, это отличный язык для прототипирования, что крайне удобно, например, при сотрудничестве с дизайнерами.
  • JavaScript можно легко использовать в QML для реакции на события.
  • C++ — это строго типизированный язык, поэтому он лучше всего подходит для логики приложения. Обычно C++-код выполняет такие задачи, как сложные вычисления или обработка данных, которые в C++ выполняются быстрее, чем в QML.

Qt предлагает различные подходы для интеграции кода QML и C++ в приложение. Типичный вариант использования — отображение списка данных в пользовательском интерфейсе. Если набор данных является статическим, простым и/или небольшим, модели, написанной на QML, может быть достаточно.

Следующий фрагмент демонстрирует примеры моделей, написанных на QML:

model: ListModel {
    ListElement { name: "Item 1" }
    ListElement { name: "Item 2" }
    ListElement { name: "Item 3" }
}

model: [ "Item 1", "Item 2", "Item 3" ]

model: 10

Рекомендуется использовать C++ для больших или часто изменяемых динамических наборов данных.

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

При объявлении свойств в QML легко и удобно использовать тип var

// Не рекомендуется
property var name
property var size
property var optionsMenu

Однако у такого подхода есть несколько недостатков.

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

Рекомендуется использовать фактический тип там, где это возможно.

// Рекомендуется
property string name
property int size
property MyMenu optionsMenu

Декларативные привязки лучше императивных присвоений

В QML можно использовать императивный код JavaScript для выполнения таких задач, как реагирование на события, отправка данных по сети и т.д. Императивный код занимает важное место в QML, но также важно знать, когда его не следует использовать.

Пример императивного кода:

// Не рекомендуется
Rectangle {
    Component.onCompleted: color = "red"
}

Код имеет несколько недостатков:

  • Он медленный. Свойство color сначала будет сверяться со значением, созданным по умолчанию, а затем снова с "red".
  • Он откладывает ошибки, которые могут быть обнаружены во время сборки, до момента выполнения, замедляя процесс разработки.
  • Он перезаписывает любую декларативную привязку, определённую для color. В большинстве случаев это делается намеренно, но иногда может быть и непреднамеренно.
  • Он мешает работе инструментов. Qt Quick Designer, например, не поддерживает JavaScript.

Код следует переписать как декларативную привязку:

// Рекомендуется
Rectangle {
    color: "red"
}

Масштабируемый пользовательский интерфейс

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

Qt предлагает своё решение данной проблемы и рекомендует разработчикам приложений следовать советам:

  • Использовать якори или модуль Qt Quick Layouts для компоновки визуальных элементов.
  • Не указывать явно ширину и высоту для визуального элемента как числа. Для указания размеров и отступов рекомендуется использовать константы из компонента Theme.
  • Предоставить ресурсы пользовательского интерфейса, такие как изображения и иконки, для каждого разрешения экрана, которое поддерживается приложением.
  • Использовать изображения SVG для небольших значков. При этом большие SVG-файлы могут обрабатываться медленно. Векторные изображения избавляют от необходимости предоставлять несколько версий изображения, как это необходимо для растровых изображений.
  • Использовать иконки на основе шрифтов, например Font Awesome. Они масштабируются до любого разрешения экрана, а также могут быть раскрашены.
  • Для использования стиля ОС Аврора применять компонент Theme.

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

Соглашения о тестировании

Разработка приложений ОС Аврора следует соглашениям Qt, и автотестирование ничем не отличается.

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

Сравнение значений

Рекомендуется использовать compare(). вместо verify()

// Не рекомендуется
verify(value == 10)
// Рекомендуется
compare(value, 10)

Обоснование: сompare генерирует более подробные сообщения об ошибках и выполняет больше проверок типов.

Использование SignalSpy вместо wait()

Рекомендуется использовать SignalSpy вместо wait().

// Не рекомендуется
function test_case() {
    wait(100)
    compare(object.value, newValue)
}
// Рекомендуется
SignalSpy { id: valueSpy; target: object; signalName: "onValueChanged" }
function test_case() {
    signalSpy.clear()
    signalSpy.wait()
    compare(object.value, newValue)
}

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

Мы используем cookies для персонализации сайта и его более удобного использования. Вы можете запретить cookies в настройках браузера.

Пожалуйста ознакомьтесь с политикой использования cookies.