Pull to refresh
0

Как перейти от Java к Scala в вашем проекте

Reading time 7 min
Views 31K
Всем привет.
Периодически от Java-разработчиков, которые узнали о существовании Scala, звучат вопросы «Как начать использовать Scala в существующем Java-проекте? Сложно ли перейти от одного к другому? Много ли времени это займет? Как убедить начальство?» В нашем проекте именно такой переход и происходил, на сегодняшний день практически весь проект уже на Scala, поэтому решил поделиться рецептами и впечатлениями.

С чего все началось, оно же «А зачем оно мне вообще надо?»:
  1. хотелось изучить что-то новое и полезное в производстве;
  2. надоело писать много букв на Java, но и радикально переписывать все, скажем на Python, совсем не хотелось;

С учетом таких желаний выбор пал на обзор альтернативных JVM-based языков.
После обзора остановились на Scala. Понравились компактный синтаксис, strong typing, возможность писать в ОО-стиле и заявленное хорошее взаимодейтствие с Java-кодом в обе стороны. Тот факт, что Scala уже активно используют такие крупные компании, как Twitter, LinkedIn, Foursquare и так далее, внушил определенную уверенность в будущем языка.
У нас уже имелся проект на Maven с юнит-тестами на JUnit, поэтому важно было легко включить Scala без существенных затрат на адаптацию инфраструктуры.
Итак, по порядку.

Много ли времени это займет?


Общее впечатление для тех, кто сомневается, стоит ли начинать сей процесс — переходить на Scala и изучать ее постепенно абсолютно реально. До первого практического применения времени пройдет мало, я бы сказал день или два, ведь поначалу это может быть даже просто «Java с val и без точек с запятой». Пройдет время, и постепенно вы вдруг обнаружите у строк кучу непонятно откуда взявшихся бонусных методов (hello implicit), начнете использовать функции, замыкания, case-классы, паттерн-матчинг, коллекции ну и так далее… Вот честно, прям постепенно, без отрыва от будничной работы.

Как начать использовать Scala в существующем Java-проекте?


Для начала добавляем поддержку Scala в родительском pom.xml:
<project>
  ...
  <properties>
    <version-scala>2.10.3</version-scala>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>${version-scala}</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>net.alchim31.maven</groupId>
          <artifactId>scala-maven-plugin</artifactId>
          <version>3.1.6</version>

          <configuration>
            <scalaVersion>${version-scala}</scalaVersion>
            <args>
              <arg>-deprecation</arg>
              <arg>-explaintypes</arg>
              <arg>-feature</arg>
              <arg>-optimise</arg>
              <arg>-unchecked</arg>
            </args>
          </configuration>

          <executions>
            <execution>
              <id>scala-compile</id>
              <phase>process-resources</phase>
              <goals>
                <goal>add-source</goal>
                <goal>compile</goal>
              </goals>
            </execution>
            <execution>
              <id>scala-compile-tests</id>
              <phase>process-test-resources</phase>
              <goals>
                <goal>testCompile</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </pluginManagement>

    <plugins>
      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Далее… а хотя нет, собственно это всё.
Теперь mvn compile будет собирать все .scala-исходники, расположенные в src/main/scala (впрочем расположенные в src/main/java тоже соберутся). Также можно писать JUnit-тесты на Scala и выполнять mvn test. Важное замечание к этому плагину: mvn test будет собирать и выполнять только те .scala-тесты, которые лежат именно в src/test/scala, в противном случае они вообще будут проигнорированы. Поэтому сразу кладите ваши .scala в src/(main|test)/scala/. Что касается Gradle, то, насколько мне известно, проблем с поддержкой Scala там тоже нет.

Вообще говоря в последнее время стандартом де-факто для Scala-проектов становится SBT (Simple Build Tool, по сути как Gradle, только на Scala). Переход на SBT на мой взгляд будет иметь смысл, когда вам понадобятся например ScalaTest, ScalaCheck, Specs и так далее, так как SBT нативно интегрируется с ними, однако никто вам их не навязывает. Мы на SBT так и не перешли, его преимущества пока не перевесили необходимость перетрясти структуру проектов и их конфигурацию.

Что касается IDE, мы работаем в IntelliJ IDEA, и в ней достаточно включить поддержку Scala в настройках (File → Settings → Plugins → чекаем Scala). После перезагрузки для всех модулей добавится настроенный из pom Scala-фасет, и можно будет билдить и самой IDEA без Maven'а. Плюс, если вы хотите переписать какой-то класс с Java на Scala, то у IDEA есть возможность конвертации, пункт «Convert to Scala» в меню для .java-файлов, или просто открыв .java и нажав Ctrl-Shift-G (.java-исходник останется на месте, что полезно как референс для сравнения и допиливания сконвертированного кода, потом его нужно удалить). Что касается Eclipse, то есть соответствующий плагин.

Сложно ли перейти от одного к другому?


Для нас ответом стало «нет», но куда ж совсем без сложностей:
  • в виду отсутствия for(;;) и break приходилось основательно перерабатывать некоторые сложные циклы. Впрочем это лишь пошло на пользу, так как код в стал чище и понятней. break можно временно компенсировать использованием scala.util.control.Breaks;
  • иногда все же сложно было использовать Scala из Java в виду более богатой type-system в Scala, или отсутствия в Java аналогов Scala traits с имплементацией, или отсутствия checked exceptions (в Scala-коде компилятор будет молчать как партизан, а скажем чтобы заставить Java код увидеть checked, в Scala есть аннотация @throws[SomeException], которая указывают компилятору сгенерить байт-код с throws);
  • не совсем сложность, отсутствует try with resources (для тех, у кого Java 7), который впрочем легко заменяется loan pattern'ом, то есть функцией вида…

// допустим весь ваш код находится в пакетах ru.blablabla…, тогда можно получить «халявный импорт» функции во всем пакете ru
package object ru {
  // по сути это прямая интерпретация http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.20.3.1
  // можно вместо [A <: AutoCloseable] написать [A <: { def close() }], тогда это охватит всех у кого есть метод close, но будет использоваться рефлексия
  def using[A <: AutoCloseable, R](resource: A)(block: A => R): R = {
    var primary: Throwable = null
    try {
      block(resource)
    } catch {
      case ex: Throwable =>
        primary = ex // to allow suppresing
        throw ex
    } finally if (resource ne null) {
      if (primary eq null) {
        resource.close()
      } else try {
        resource.close()
      } catch {
        case ex: Throwable => primary.addSuppressed(ex)
      }
    }
  }
}

после чего можно использовать его в коде
package ru.habrahabr.hello
...
  val maybeSomeResult = using (Files.newBufferedReader(…)) { reader =>
    // use reader and maybe return some result
  }
...

Как убедить начальство?


Воспевать Scala я не собираюсь, просто опишу реальные дивиденты, которые мы получили:
  • стало значительно меньше букв. Конкретно в нашем проекте, даже простое конвертирование исходников с небольшими быстрыми доработками давало уменьшение количества строк в среднем на 25%, а в общем пропало порядка 9 тысяч строк из 37 тысяч. Это именно строки, самих букв стало еще меньше. Очевидно, что меньше букв значит быстрее решаем задачи и легче находим ошибки. Возможность писать классы в одну строку вообще сносит крышу поначалу;
  • богатейшая библиотека коллекций с кучей методов избавила от огромного количества рутинной работы с циклами. Все эти map filter find forall exists min par и т.д. и т.п. просто убивают намеки на возвращение к Java (один только par чего стоит). Все это богатство дополняют immutable-коллекции, которых так не хватает в JDK (в котором у Iterable нету size, у Collection уже есть add, Collections.unmodifiable… лишь добавляют защиту от дурака но не защищают от неверного использования). На мой взгляд, даже только ради этой библиотеки стоит изучить Scala (впрочем у JDK есть богатый выбор производительных concurrent-коллекций);
  • возросла экспрессивность кода, особенно часто употребляем Option[T] который в самом простом случае решает проблему проверок на null (читай, багов с этим связанных);
  • higher order functions и прочая функциональная составляющая Scala опять же позволяет писать более короткий, экспрессивный и переиспользуемый код (в Java 8 небольшим утешением конечно будут лямбды, но их еще дождаться надо);
  • implicit conversions в умеренных количествах позволили избежать замусоривания кода всякими оберточными классами;
  • == в Scala это как Objects.equals(o1, o2) в Java, прощай случайное сравнивание ссылок (впрочем если нужно именно это, есть eq и neq);
  • хорошее взаимодействие с Java лежит в основах дизайна Scala, поэтому интеграция всяких Java-фреймворков (Spring, Hibernate и тд) проходит довольно безболезненно. Например есть куча аннотаций типа @BeanProperty, которая говорит компилятору сгенерить для поля геттеры и сеттеры в стиле JavaBeans.

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

Итого, если вкратце описать результат, то «меньше букв — меньше багов — больше продуктивности».
С момента перехода на Scala новый код на Java писать более не приходилось, это была точка невозврата.
Хочется еще раз повториться, вовсе необязательно выделять на обучение сразу много времени (как вариант для начальства, «необязательно выделять много денег»).

PS: есть мнение, что Scala сложна и не подходит большинству Java-разработчиков (см перевод на Хабре тут habrahabr.ru/post/134897), но на мой взгляд входной порог у Scala небольшой, язык не заставляет с головой кидаться в омут и основательно изучать его перед практическим применением, а вот пользу может принести достаточно быстро.

Полезные ресурсы:
Tags:
Hubs:
+34
Comments 56
Comments Comments 56

Articles

Information

Website
www.acronis.com
Registered
Founded
Employees
1,001–5,000 employees
Location
Сингапур