Функция println и строковые шаблоны

Начнём с примера — функции, решающей квадратное уравнение и демонстрирующей решение пользователю.

fun solveQuadraticEquation(a: Double, b: Double, c: Double) /* no result */ {
    val sd = sqrt(discriminant(a, b, c))
    val x1 = (-b + sd) / (2 * a)
    val x2 = (-b - sd) / (2 * a)
    // Вывод на экран значений x1 и x2
    println(x1)
    println(x2)
    // Вывод на экран строки вида x1 = 3.0 x2 = 2.0
    println("x1 = $x1 x2 = $x2")
    // Вывод на экран произведения корней
    println("x1 * x2 = ${x1 * x2}")
}

Здесь мы подходим к такой важной части программирования, как взаимодействие с пользователем и вообще с внешним для программы миром. Обратите внимание — в этот момент используемые нами функции начинают отличаться от чисто математических, так как у них появляются побочные эффекты (side effects). Функция в программировании в общем случае не сводится только к зависимости между параметрами и результатом.

Функция println(p) определена в стандартной библиотеке языка Котлин и не требует подключения каких-либо пакетов. Её параметр p может иметь любой тип — так, вызов println(x1) выведет на отдельную строку консолизначение переменной x1. Чаще всего, однако, p является строкой, например, "x1 = $x1 x2 = $x2". В данной строке присутствуют строковые шаблоны $x1 и $x2, состоящие из символа $ и имени переменной (параметра). Вместо них программа автоматически подставит значение соответствующих переменных. Строковый шаблон позволяет также подставить значение сложного выражения, как, например, здесь: "x1 * x2 = ${x1 * x2}". В этом случае выражение записывается в фигурных скобках, чтобы программа имела возможность отследить его начало и конец.

Обратите внимание, что тип результата функции solveQuadraticEquation не указан. Это означает, что функция не имеет результата (в математическом смысле). Такие функции встречаются довольно часто, один из примеров — сама функция println, и их реальный результат сводится к их побочным эффектам — например, выводу на консоль.

Осталось определить — что же такое консоль? В привычной нам операционной системе Windows консоль — это окно или же его часть, которую программа использует для вывода текстовой информации. В Intellij IDEA данное окно можно открыть последовательностью команд View → Tool windows → Run. При запуске программы из операционной системы она сама откроет так называемое «окно терминала», которое будет использоваться программой для вывода текстовой информации.

Главная функция

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

fun main(args: Array<String>) {
    // Решаем x^2 - 3*x + 2 = 0
    val x1x2 = quadraticRootProduct(1.0, -3.0, 2.0)
    println("Root product: $x1x2")
}

Единственный параметр args главной функции имеет тип Array<String>, то есть массив строк. О массивах и об использовании параметра args главной функции мы поговорим позже. Результата главная функция не имеет. По правилам Котлина (и Java) она всегда обязана называться main. Для быстрого ввода заголовка главной функции в Intellij IDEA можно ввести в редактор специальную строку psvm с последующим нажатием клавиши Enter.

Данная короткая программа использует функцию quadraticRootProduct, определённую выше, для вычисления произведения корней квадратного уравнения, после чего выводит это произведение на консоль. Для того, чтобы её запустить, в Intellij IDEA достаточно щёлкнуть мышью на зелёный треугольник слева от заголовка функции main. Поскольку корни данного уравнения равны 1.0 и 2.0, после запуска программы на консоли мы увидим строчку

Root product: 2.0

Тестовые функции

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

// Разрешение использовать короткое имя аннотации org.junit.jupiter.api.Test
import org.junit.jupiter.api.Test
// Разрешение использовать короткое имя для функции org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertEquals

// Класс Tests, наличие класса обязательно для библиотеки JUnit
class Tests {

    // ...

    // Тестовая функция
    @Test
    fun testSqr() {
        assertEquals(0, sqr(0))  // Проверить, что квадрат нуля это 0
        assertEquals(4, sqr(2))  // Проверить, что квадрат двух это 4
        assertEquals(9, sqr(-3)) // Проверить, что квадрат -3 это 9
    }
}

Написание тестовых функций требует подключения к программе одной из библиотек автоматического тестирования, например, библиотеки JUnit. Большинство классов этой библиотеки находятся в пакете org.junit для версии JUnit 4.x или в пакете org.junit.jupiter.api для версии JUnit 5.х.

@Test — это так называемая аннотация, то есть, пометка, используемая для придания функции testSqrдополнительного смысла. В данном случае, аннотация делает функцию testSqr тестовой. Функция assertEqualsпредназначена для сравнения результата вызова некоторой другой функции, например, sqr, с ожидаемым. В приведённом примере она вызывается трижды.

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

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

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

Наконец, что же такое class Tests? По правилам библиотеки JUnit, все тестовые функции обязаны находиться внутри какого-либо класса. О том, для чего нужны классы, мы поговорим позднее. В данном примере для этой цели был создан класс с именем Tests (имя может быть произвольным), и тестовая функция была записана в нём. Зелёный треугольник напротив имени класса позволяет одновременно запустить все тестовые функции в данном классе.

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

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

fun main(args: Array<String>) {
    println("sqr(0) = ${sqr(0)}")
    println("sqr(4) = ${sqr(4)}")
}

В нормальном случае мы должны увидеть на консоли строчки

sqr(0) = 0
sqr(4) = 16

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

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.