понедельник, 7 октября 2013 г.

Несколько простых идей по улучшения кода на Scala

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

Самое очевидное, но и самое часто забытое - for. Альтернативой ему обычно выступают цепочки из map, filter, collect, flatMap, foreach. for имеет смысл использовать почти всегда когда нужна манипуляция с составом коллекции. Во-первых он существенно легче читается при росте длины. Цепочка из 3-4 преобразующих методов находится уже на грани понимания, при записи в for-нотации эта грань ещё далеко. Во-вторых for позволяет существенно читабельнее выписывать типы связанных переменных, а компилятор частенько от нас этого требует. Например сравните:
sessions.foreach((s: Session) => mngr.cancel(s.id))
и
for (s: Session <- sessions) mngr.cancel(s.id)

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

Однако есть и ситуация когда прямое использование методов коллекций оправдано - если у нас уже есть функция фильтрации или преобразования и у неё хорошее говорящее имя. Например names.filter(wellFormed). В таком случае скорее формируется мини-DSL вокруг коллекции в целом чем набор манипуляций с отдельными элементами.

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

def hostedOn(host: Host)(node: Node) = host.addresses.contains(node.address)

def handleFailure(failed: Host) = {
    val affectedNodes = allNodes filter hostedOn(failed)
    ...
}

Удачно сочетая методы стандартного API с именами и сигнатурами своих функций можно строить очень читабельные и производительные DSL просто на ровном месте. Там где в джаве обычно получаются циклы с телами на 200 строк.

Третья недоиспользованная возможность языка - case classes. Во-первых они позволяют с разумной перегрузкой по синтаксису создавать новые типы данных. Тип данных из 3 полей это не 50 строк кода, а одна! Их можно и нужно создавать всякий раз когда они нужны, каждый модуль на 100 строк может позволить себе иметь 10 внутренних типов с собственными именами и именами их полей и при этом быть читабельным. Во-вторых они могут быть использованы в паттерн-матчинге без дополнительных усилий.

Особенно всё описанное выше подходит для исключений, не забываем что catch - это по сути паттерн. Если наше исключение является case-классом - можно делать интересные вещи в обработке исключенией. Например вкладывать в них какие-то полезные (но не меняющие семантику данные) данные. Сравните

try {
    ...
} catch {
    case NoDataReceivedException =>
        channel.close()
        showError("No data received.")
    case PartialDataReceivedException =>
        if (confirmDataLoss(session.received()))
            channel.close()
}
и
try {
    ...
} catch {
    case TimeoutException(received) if received == 0 =>
        channel.close()
        showErrorDialog(received)
    case TimeoutException(received) =>
        if (confirmDataLossDialog(received))
            channel.close()
}

В какую сторону изменится выбрасывающий исключения код предлагаю додумать самостоятельно.
В завершение хочу поделиться вот этой вот замечательной книжкой: Scala By Example Раньше она висела на главной документации по языку, но сейчас почему-то исчезла оттуда. Если вы начали изучать Scala с какого-то мудрёного доклада на конференции или вводной на какой-то user group - скорее всего забыть всё услышанное и начать с этой книжки будет отличной идеей. Она является действительно кратким и очень целостным введением в язык, при этом просто полна идеями как с помощью самых простых средств писать очень выразительные программы на Scala.

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