
Всем привет, меня зовут Иван, я Android-разработчик и сегодня мы с вами поговорим об контрактах. Предвосхищая вопрос попробую сразу дать все ответы:
Контракты - это давний эксперимент языка Kotlin, какой смысл обозревать их сейчас, когда уже столько написано?
Нормальной статьи на русском языке я не обнаружил;
Я решил выбрать эту тему, так как мы сталкиваемся с контрактами в нашей повседневности, при этом не осознавая, как именно работает та или иная вещь;
Это моя первая проба пера и мне хотелось начать с чего-нибудь достаточно легкого, но при этом интересного.
Вводная часть
Во-первых, разберемся с основными понятиями:
Контракт – это договор с компилятором, который мы используем чтобы обозначить определенное поведение на участке кода, он состоит из эффектов;
Эффект – это свойство функции, возникающее при ее вызове и выполнении определенных условий в ней. Эффект оказывает влияние на блок кода откуда он вызывается.
Примером эффекта из реальной жизни может служить наша повседневность. После долгого рабочего дня, при попытке встать со стула нас скорее всего ожидает ошибка.

Мы можем ее легко решить, разделив процесс.

Также и с кодом, вызывая определенные методы мы оказываем эффект на всю логику в месте вызова.
Как же контракты реализованы в Kotlin?
Стоит упомянуть что контракты оказывают эффект только на сборку, но не работают во время самого исполнения программы. Это оказывает определенное влияние на исполнение программы и может приводить к странным ситуациям. Контракты расположились в пакете kotlin.contracts и имеет набор эффектов и функций для их создания. Начнем с эффектов.
Эффекты
Взглянем на древо эффектов и расскажем об каждом по-отдельности.

Effect – это отправная точка для всех эффектов, верхний уровень абстракции. Она оповещает что функция имеет какой-то эффект, не вдаваясь в особые детали.
SimpleEffect – также как и Effect оповещает что функция влияет на участок кода, но уточняет, что он возникает по факту вызова самой функции. В текущей реализации контрактов делится на два подкласса:
Returns – возникает при обычном вызове и выполнении функции;
ReturnsNotNull – данный эффект произойдет только если функция возвращает не нулевое значение.
Что Returns, что ReturnsNotNull описывают ситуации, когда мы что-либо возвращаем. При этом стоит учесть, что речь не идет о том, что мы можем вернуть только эти значения.

ConditionalEffect – это эффект, который возникает при выполнении какого-то условия. Он используется в связке с Simple-эффектами и представляет собой ситуацию: если было возвращено X, то условие Y верно.

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

Отлично, значит попробуем написать собственный метод инициализации.

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

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

CallsInPlace позволяет отслеживать не только единоразовый вызов но и остальные виды (они представлены в enum-классе InvocationKind).
Необычное поведение
Благодаря этому эффекту мы также можем обманывать компилятор, из-за чего получаются такие курьезы

DSL язык
При написании примеров мы использовали методы contracts, callsInPlace и returns. Что это такое, какие еще есть и как ими правильно пользоваться?
contract – высокоуровневый метод, который позволяет нам объявить сам контракт. Он используется при создании абсолютно всех контрактов и является отправной точкой для работы с API.
returns, returnsNotNull, callsInPlace – строительные блоки для создания эффектов типа Returns, ReturnsNotNull, CallsInPlace соответственно. Могут быть вызваны только при создании контракта, так как работают с контекстом ContractBuilder, который предоставляется только методом contract. Стоит отметить что returns может принимать три вида значения (true/false/null), благодаря которым компилятор может отследить какой эффект оказывается на участок кода при возвращении разных значений;
implies – строительный блок для ConditionalEffect, работает только в связке с SimpleEffect и его наследниками. Принимает в качестве аргумента булево выражение, позволяющее отследить оказываемый эффект.

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

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

Мы бы могли продолжить расписывать подобным образом каждую конфигурацию отдельно, но можно только представить, каким по итогу захламленным и неудобным станет наш метод

Давайте вынесем наши условия в отдельный метод для проверки необходимой нам конфигурации машины

Отлично, теперь мы можем легко проверить какая конфигурация перед нами и выполнить поставленную перед нами задачу

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

К сожалению, в текущей реализации контрактов мы не можем проверять те параметры, которые зашиты в класс Car, как же тогда нам поступить?

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