Полезные техники Akka

Я использую Akka в течение 3 лет в различных проектах и теперь, я с трудом представляю себе взаимодействие с некоторыми из частей моей работы без нее. Конечно, Scala предоставляет другие мощные парадигмы для работы с параллелизмом, но я нахожу, Акторы, одной из самых элегантных концепций, когда дело доходит до рассуждений о ней.

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

Часы с кукушкой

Я использую эту технику в основном в проектах с фреймворком Play. Первая версия фреймворка имела механизм для многократного выполнения задач, который был удален в версии 2 с рекомендацией использовать планировщики Akka.

Основная идея этой техники заключается в инкапсуляции логики задачи, которая должна быть выполнена многократно или в определенное время, в одном акторе, и пробуждении актора отправкой ему сообщения:

CockooClock.scalaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ScheduledOrderSynchronizer extends Actor {
private val SYNC_ALL_ORDERS = "SYNC_ALL_ORDERS"
private var scheduler: Cancellable = _
override def preStart(): Unit = {
import scala.concurrent.duration._
scheduler = context.system.scheduler.schedule(
initialDelay = 10 seconds,
interval = 5 minutes,
receiver = self,
message = SYNC_ALL_ORDERS
)
}
override def postStop(): Unit = {
scheduler.cancel()
}
def receive = {
case SYNC_ALL_ORDERS =>
try {
// synchronize all the orders
} catch {
case t: Throwable =>
// report errors
}
}
}

Давайте посмотрим на то, как работает этот механизм в деталях:

  • прежде всего, мы должны инициализировать наш планировщик. Для этого, я считаю, полезно использовать методы жизненного цикла актора preStart и postStop . Можно было бы целиком объявить планировщикагде-нибудь еще и отправлять сообщения актору, однако я считаю, что инкапсуляция логики сильно облегчает техническое обслуживание, особенно, когда есть несколько подобных механизмов в одном и том же приложении.

  • мы создаем планировщик используя метод ActorSystem scheduler.schedule, передав в него начальную задержку (после которой в первый раз будет отправлено сообщение), затем интервал, с которым сообщение должно быть отправлено, получателя (в нашем случае, мы хотим, чтобы оно было отправлено самому актору, поэтому мы используем self), и сообщение для отправки.

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

  • как для любого актора, реализация того, что должно быть выполнено повторно происходит в методе receive - в нашем случае, мы только обрабатываем сообщения вида SYNC_ALL_ORDERS

  • одинственно важной вещью, в данный момент, является обработка ошибки: я бы рекомендовал выполнять любой код внутри блока try-catch, как показано выше - в данном случае было бы излишним использовать стратегию обработки ошибок Akka, и мы не хотим разрушать наш планировщик, если одна из синхронизаций пойдет не так.

Теперь, единственной вещью, которую мы должны сделать, это привести механизм в движение. Для приложения Play, хорошим местом для этого был бы имеющий дурную репутацию Global:

CockooClockInit.scalaview raw
1
2
3
4
5
object Global extends GlobalSettings {
override def onStart(app: Application) {
Akka.system.actorOf(Props(new ScheduledOrderSynchronizer), name = "orderSynchronizer")
}
}

Вот и все! Наши часы с кукушкой готовы бить каждые 5 минут.

Примечание: API планировщика хорошо подходит для задач, которые подразумевают повторение, и менее для задач, которые должны выполниться в определенное время или дату. Однако, этого можно добиться с API путем расчета времени между инициализацией планировщика и запланированным временем. Для этих случаев, я использую библиотеку nscala-time, которая является оберткой поверх отличной библиотеки Joda Time.

Пакетный процессор

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

Один наблюдатель и его армия рабочих

Многоступенчатая структура

В большинстве случаев эта разновидность шаблона включает производителя (база данных, такая как MongoDB, MySQL, BaseX, Amazon SimpleDB; гигабайтный XML файл, и т.д.), из которого элементы должны быть собраны и отправлены для обработки.

Давайте посмотрим на примере простого наблюдателя - цепочки рабочих. Мы сделаем несколько предположений для этого примера:

  • время для получения нового пакета данных несущественно, так что мы не заинтересованы в оптимизации процесса получения новых данных раньше времени

  • наша JVM имеет приличный объем доступной памяти, так что рабочие способны ставить в очередь все элементы своего почтового ящика

Примечание: Код намеренно оставлен ​​абстрактным, так как мы не заинтересованы в том, как данные будут получены из источника или обработаны, но в том, что произойдет с ними.

BatchProcessor.scalaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import akka.actor.{ Props, Actor }
import akka.event.Logging
import akka.routing.RoundRobinRouter
abstract class BatchProcessor(dataSetId: Long) extends Actor {
val log = Logging(context.system, "application")
val workers = context.actorOf(Props[ItemProcessingWorker].withRouter(RoundRobinRouter(100)))
var totalItemCount = -1
var currentBatchSize: Int = 0
var currentProcessedItemsCount: Int = 0
var currentProcessingErrors: List[ItemProcessingError] = List.empty
var allProcessedItemsCount = 0
var allProcessingErrors: List[ItemProcessingError] = List.empty
def receive = {
case ProcessBatch =>
if (totalItemCount == -1) {
totalItemCount = totalItems
log.info(s"Starting to process set with ID $dataSetId, we have $totalItemCount items to go through")
}
val batch = fetchBatch
processBatch(batch)
case ProcessedOneItem =>
currentProcessedItemsCount = currentProcessedItemsCount + 1
continueProcessing()
case error @ ItemProcessingError(_, _, _) =>
currentProcessingErrors = error :: currentProcessingErrors
continueProcessing()
}
def processBatch(batch: List[BatchItem]) = {
if (batch.isEmpty) {
log.info(s"Done migrating all items for data set $dataSetId. $totalItems processed items, we had ${allProcessingErrors.size} errors in total")
} else {
// reset processing state for the current batch
currentBatchSize = batch.size
allProcessedItemsCount = currentProcessedItemsCount + allProcessedItemsCount
currentProcessedItemsCount = 0
allProcessingErrors = currentProcessingErrors ::: allProcessingErrors
currentProcessingErrors = List.empty
// distribute the work
batch foreach { item =>
workers ! item
}
}
}
def continueProcessing() = {
val itemsProcessed = currentProcessedItemsCount + currentProcessingErrors.size
if (itemsProcessed > 0 && itemsProcessed % 100 == 0) {
log.info(s"Processed $itemsProcessed out of $currentBatchSize with ${currentProcessingErrors.size} errors")
}
if (itemsProcessed == currentBatchSize) {
self ! ProcessBatch
}
}
def totalItems: Int
def fetchBatch: List[BatchItem]
}
abstract class ItemProcessingWorker extends Actor {
def receive = {
case ProcessItem(item) =>
try {
process(item) match {
case None => sender ! ProcessedOneItem
case Some(error) => sender ! error
}
} catch {
case t: Throwable =>
sender ! ItemProcessingError(item.id, "Unhandled error", Some(t))
}
}
def process(item: BatchItem): Option[ItemProcessingError]
}
case object ProcessBatch
trait BatchItem {
val id: Int
}
case class ProcessItem(item: BatchItem)
case object ProcessedOneItem
case class ItemProcessingError(itemId: Int, message: String, error: Option[Throwable])

Давайте пройдемся по примеру шаг за шагом:

  • у нас есть наблюдатель (BatchProcessor), определение рабочего (ItemProcessingWorker) и набор сообщений (ProcessBatch, ProcessItem, ProcessedOneItem, и т.д.)

  • конструктор BatchProcessor принимает в качестве параметра ID набора данных, который мы хотим обработать (при условии, например, что данные живут в базе данных и имеют идентификатор). Новый BatchProcessor должен быть создан для каждого набора, который мы хотим обработать

  • BatchProcessor создает несколько экземпляров дочерних рабочих с помощью RoundRobinRouter

  • наблюдатель имеет несколько членов, которые следят за состоянием обработки. Он также отслеживает все имеющие место ItemProcessingError

    затем механизм работает следующим образом:

    • чтобы начать, BatchProcessor должен получить от клиента сообщение ProcessBatch

    • затем он извлекает первую группу из источника и передает его в метод processBatch. Если это первый раз, когда он запускается он будет так же показывать, как много элементов есть в общем

    • processBatch инициализирует состояние для этой группы сбрасывая счетчики и устанавливая размер пакета, и затем переходит к распределению работы рабочим с помощью batch foreach { item => workers ! item }.

    • когда элемент обработан, актор-рабочий отвечает сообщением ProcessedOneItem или ItemProcessingError в случае неудачи. Мы используем эту информацию, чтобы отслеживать прогресс обработки.

    • метод continueProcessing сообщает прогресс для каждых 100 обработанных элементов (на практике, это должно быть адаптированно к размеру имеющихся данных). когда текущий пакет будет полностью обработан (сумма успешных и провальных элементов равна размеру текущего пакета), он отправляет наблюдателя в другой метод ProcessBatch. Нереализованный метод fetchBatch может использовать счетчики для того, чтобы знать, какое смещение использовать для получения следующей группы элементов.

    • наконец, когда не осталось данных для обработки, метод processBatch сообщает нам об этом и обработка завершается.

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

  • обработку ошибок использующую стратегии наблюдателя Akka. Прямо сейчас, наш рабочий очень прост, и имеет выражение try - catch вокруг метода process. Но мы не по-настоящему восстанавливаемся после ошибок, мы просто позволяем им упасть и сообщить количество имеющихся у нас ошибок. Это может работать хорошо, если эти шибки являются фатальными, но на практике они могут стоить попытки обработать некоторые элементы из элементов.

  • информирование клиента, что наша работа выполнена. Это может быть простым, как сохранение ссылки на актор , при получении первого сообщения ProcessBatch, и отправка сообщения WorkDone, когда все элементы обработаны (не пытайтесь отправлять это сообщение по ссылке sender потому что, к тому времени когда вся работа выполнится, скорее всего, отправителем сообщения будет один из дочерних акторов)

  • наличие более хороших показателей относительно обработки, такие как скорость обработки. Я часто использую библиотеку метрик Кода Хейла для этой цели.

Обратное давление бедняка

Если вы являетесь членом команды Akka, пропустите этот раздел. Для остальной части человечества, эта тема имеет непосредственное отношение к одному предположению, что мы сделали в предыдущем примере: “наша JVM имеет приличный объем доступной памяти, так что рабочие могут ставить в очередь все элементы своего почтового ящика”. На самом деле, это не всегда так. Тем не менее, если вы будете продолжать получать данные от производителя и передавать их рабочим, вы в конечном итоге исчерпаете всю память: по-умолчанию, Akka использует UnboundedMailbox, который будет продолжать заполняться.

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

В такой ситуации, нам нужны средства, чтобы снизить скорость наблюдателя. Эта концепция также известна как обратное давление, и я приглашаю вас прочитать, как правильно ее реализовать. Иногда, однако, вы можете не быть заинтересованы в оптимальном решении проблемы обратного давления, и в этом случае, может быть нормальным нарушить основную директиву Akka: не допускайте блокировок внутри актора.

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

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

PoorManBackPreassure.scalaview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import akka.actor.{ Props, Actor }
import akka.event.Logging
import akka.routing.RoundRobinRouter
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.Await
import scala.concurrent.duration._
abstract class Processor(dataSetId: Long) extends Actor {
val log = Logging(context.system, "application")
val workers = context.actorOf(Props[ItemProcessingWorker].withRouter(RoundRobinRouter(100)))
val producer = context.actorOf(Props[DataProducer], name = "dataProducer")
var totalItemCount = -1
var currentItemCount = 0
var allProcessedItemsCount = 0
var allProcessingErrors: List[ItemProcessingError] = List.empty
val MAX_LOAD = 50
def receive = {
case Process =>
if (totalItemCount == -1) {
totalItemCount = totalItems
log.info(s"Starting to process set with ID $dataSetId, we have $totalItemCount items to go through")
}
val index = allProcessedItemsCount + allProcessingErrors.size
if (currentItemCount < MAX_LOAD) {
producer ! FetchData(index)
}
case Data(items) =>
processBatch(items)
case GetLoad =>
sender ! currentItemCount
case ProcessedOneItem =>
allProcessedItemsCount = allProcessedItemsCount + 1
currentItemCount = currentItemCount - 1
continueProcessing()
case error @ ItemProcessingError(_, _, _) =>
allProcessingErrors = error :: allProcessingErrors
currentItemCount = currentItemCount - 1
continueProcessing()
}
def processBatch(batch: List[Item]) = {
if (batch.isEmpty) {
log.info(s"Done migrating all items for data set $dataSetId. $totalItems processed items, we had ${allProcessingErrors.size} errors in total")
} else {
// distribute the work
batch foreach { item =>
workers ! item
currentItemCount = currentItemCount + 1
}
}
}
def continueProcessing() = {
val itemsProcessed = allProcessedItemsCount + allProcessingErrors.size
if (itemsProcessed > 0 && itemsProcessed % 100 == 0) {
log.info(s"Processed $itemsProcessed out of $totalItems with ${allProcessingErrors.size} errors")
}
self ! Process
}
def totalItems: Int
}
abstract class DataProducer extends Actor {
private val MAX_LOAD = 50
def receive = {
case FetchData(currentIndex) =>
throttleDown()
sender ! Data(fetchBatch(currentIndex))
}
def throttleDown(): Unit = {
implicit val timeout = Timeout(5.seconds)
val eventuallyLoad = context.parent ? GetLoad
try {
val load = Await.result(eventuallyLoad, 5.seconds)
if (load.asInstanceOf[Int] > MAX_LOAD) {
Thread.sleep(5000)
throttleDown()
}
} catch {
case t: Throwable =>
// we most likely have timed out - wait a bit longer
throttleDown()
}
}
def fetchBatch(currentIndex: Int): List[Item]
}
abstract class ItemProcessingWorker extends Actor {
def receive = {
case ProcessItem(item) =>
try {
process(item) match {
case None => sender ! ProcessedOneItem
case Some(error) => sender ! error
}
} catch {
case t: Throwable =>
sender ! ItemProcessingError(item.id, "Unhandled error", Some(t))
}
}
def process(item: Item): Option[ItemProcessingError]
}
case object Process
trait Item {
val id: Int
}
case class FetchData(currentIndex: Int)
case class Data(items: List[Item])
case object GetLoad
case class ProcessItem(item: Item)
case object ProcessedOneItem
case class ItemProcessingError(itemId: Int, message: String, error: Option[Throwable])

Так как же это работает? Давайте взглянем поближе:

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

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

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

  • теперь к самому интересному: DataProducer не будет получать новые данные сразу же, вместо этого, он проверит вместе с наблюдателем, какова текущая нагрузка. Если она выше, чем максимально допустимая (50 элементов в нашем примере, потому что мы, например, имеем дело с изображениями в высоким разрешении или чем-то подобным, что занимает много памяти) он будет ждать в течение 5 секунд и проверит снова, пока нагрузка снова не станет приемлемой. Только после этого он отправит новые данные в Processor , который продолжит делать свою работу, пока не останется никаких данных.

Некоторые комментарии об этой технике:

  • как говорится, нужно иметь хорошее ощущение того, каким должен быть максимум. Этот механизм в отличии от всех остальных, динамичный и не будет работать, если элементы весьма гетерогенны (в том смысле, что их обработка занимает очень разное количество времени)

  • она лучше подходит, чтобы ждать кучу элементов, которые будут обработаны, прежде чем запросить новую партию, в зависимости от случая

  • обработка ошибок в приведенном выше примере должна быть улучшена для использования в реальной жизни

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

Подводные камни

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

Создание слишком многих корневых акторов

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

Заключение future

Это ошибка, которую легко допустить, подробно описанная в этой статье. Давайте рассмотрим следующий пример:

ClosingOverFuture.scalaview raw
1
2
3
4
5
6
7
8
9
def receive = {
case ComputeResult(itemId: Long) =>
computeResult(itemId).map { result =>
// gotcha!
sender ! result
}
}
def computeResult(itemId: Long): Future[Int] = ???

Так в чем же проблема? computeResult производит Future , при этом переключается на другой контекст выполнения и освобождает основной поток. Это означает, что другое сообщение ComputeResult может прийти внутрь, и заменить ссылку sender. Тогда, когда первый future завершится, мы можем ответить не тому отправителю.

Легко исправить это, сохранив отправителя в val вот так:

ClosingOverFutureFix.scalaview raw
1
2
3
4
5
6
7
def receive = {
case ComputeResult(itemId: Long) =>
val originalSender = sender
computeResult(itemId).map { result =>
originalSender ! result
}
}

Неверное понимание смысла Future.andThen

Это в основном связано скорее с futures, чем с акторами, но я столкнулся с этим при смешивании обоих парадигм. Future API имеет метод andThen”, используемый исключительно для целей побочного действия”. Другими словами, он не будет трансформировать future и свяжет себя с ним, но скорее будет выполнен независимо.

Я ошибался полагая, что andThen вернет новый, преобразованный, Future, поэтому я попытался выполнить следующие действия:

FutureAndThen.scalaview raw
1
2
3
4
5
6
7
upload.andThen {
case Success(s) => {
FileUploadSuccess(item, s)
}
case Failure(t) =>
FileUploadFailure(item, Some(t))
} pipeTo originalSender

pipeTo является довольно мощным инструментом в инструментарий Akka, который позволяет отправлять результат future актору (под капотом он создает анонимный актор, который ждет завершения future).

Правильной реализацией будет:

Заключение

В заключении я хотел бы повториться, что Akka реально полезный инструмент, чтобы иметь его вашем наборе, и я думаю, что он собирается остаться таким гораздо дольше, поэтому я могу только посоветовать любому, кто этого еще не сделал, пойти и попробовать его (даже если вы работаете с Java, для вас есть Java API). Как мы и говорили, еще одной приятной парадигмой, которая станет стандартизированной, являются реактивные потоки, которые имеют дело с обратным давлением как с составной частью концепции.

Оригинал

Pimp My Git

Данный пост не претендует на статус руководства, а является лишь сборником usecases и workflow используемых автором при ежедневной работе с git, и будет пополняться и обновляться с течением времени.

.gitconfig

Любая работа с git начинается с настройки. Но не все разработчики задаются целью потратить больше времени и немного подкрутив git, сократить себе в дальнейшем время при разработке.
Так уж исторически сложилось, что я не являюсь сторонником использования какого-либо GUI, потому, что большинство времени провожу на удаленном сервере, и считаю, что git вполне позволяет реализовать все недостающие возможности стандартными средствами.
Чтож, приступим к прокачке, для начала улучшив стандартный и скучный git log добавив к нему графическое отображение состояния веток, и пропишем alias’ы для основных комманд git.
Данную конфигурацию желательно поместить в ~/.gitconfig, чтобы в дальнейшем использовать данные настройки в любом репозитории git:

.gitignoreview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[color]
ui = auto
branch = never
[color "branch"]
current = yellow reverse
local = yellow
remote = green
[color "diff"]
meta = yellow bold
frag = magenta bold
old = red bold
new = green bold
[color "status"]
added = yellow
changed = green
untracked = cyan
[alias]
st = status
ci = commit
br = branch
co = checkout
df = diff --color
rs = reset
rb = rebase
sh = stash
shp = stash pop
lg = log --pretty=format:'%h %Cblue%cn %Creset%cr %Cgreen%s'
plg = log --graph --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)— %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative
lg2 = log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)— %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative
alg = log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(bold white)— %an%C(reset)' --abbrev-commit

Теперь мы можем просмотреть древовидную историю текущей ветки просто выполнив в терминале git plg, а полную историю репозитория выполнив git alg.
Так же git df покажет нам более привычный вид изменений, как например в github’е, а git lg покажет улучшенную историю коммитов.
И как я уже говорил, вы получаете это в каждом репозитории живущем на вашей машине.

.bashrc

Не думаю что файл .bashrc, является безизвестным среди аудитории читающей данную статью. Он выполняет команды при инициализации сессии терминала.
Добавим некоторые улучшения по работе с git в терминал, просто добавив следующие строки в ~/.bashrc:
Про то, что , мы знаем не понаслышке.
Поэтому добавим alias для этой комплексной операции, добавив следующие строки в ~/.bashrc:

.bashrc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# отображение в терминале текущей ветки, если мы находимся в репозитории git
PS1='\[\033[01;36m\]`git branch 2>/dev/null | grep "^* " | sed "s/^* //" | sed "s/%->%//"` \[\033[01;37m\][\w]\[\033[00m\] \[\033[01;32m\]$\[\033[00m\] '
# чтобы всегда быть "на острие изменений" и ежедневно обновлять состояние ветки изменениями из master'a
git_update_current_branch() {
br=`git branch | grep "*" | sed -e "s/* //"`
echo " ==== Stashing changes"
git stash
echo " ==== Switching to master"
git checkout master
echo " ==== Updating master"
git pull
echo " ==== Switching back to $br"
git checkout $br
echo " ==== Merging master to $br"
git merge master
echo " ==== Unstashing changes"
git stash pop
echo " ==== Done!"
}
# очищаем репозиторий от неиспользуемых веток
git_clear_branches() {
git checkout master
echo " ==== Switching to master"
git branch | grep -v master | xargs git branch -D
echo " ==== Delete all branches"
}
# алиасы
alias cbr='git_clear_branches'
alias mrg='git_update_current_branch'

Конечно в данном варианте присутствует припрятывание текущих изменений используя stash, и оно не всегда валидно работает, например если нет изменений то stash pop выкатит какие-ннибудь давно припрятанные изменения.
Но это исправляется узъятием ненужных строк.

Have fun. Git make your life better!

Пока оно компилируется - Крис Маршалл

«Пока оно компилируется» - это новый цикл интервью с экспертами и практиками широкого спектра современных технологий, эксклюзивно для Skills Matter. Перевод подготовлен по материалам блога Skills Matter, где публикуются все интервью из цикла.

Сегодня, мы встретились с Крисом Маршаллом, одним из 22 лидеров рейтинга StackOverflow. Он занимается коммерческим программированием на протяжении 14 лет, немного на FORTRAN и Smalltalk, затем на Java (с 1999 года) и на Scala (с конца 2008 года) после того, как его терпение окончательно лопнуло. С более чем 12-летним опытом работы с финансовыми IT, у него была работа его мечты в GSA, маленьком технологически-ориентированном хедже начиная с 2006, которой предшествовало 6 лет работы в JPMorgan созданном для букмекерского бизнеса.

В свободное время он разгадывает “Суть шаблона Итератор”, с которым он сталкивался 500 раз.

1. Что привлекает Вас в функциональных языках в первую очередь?

Изначально я был привлечен Scala, как “улучшенной Java” (перед добавлением лямбд, когда включение замыканий в Java, казалось недостижимым). Имея большое количество библиотек на Java, Scala выработала ряд ключевых требований, а именно безшовную интеграцию с Java (которая, насколько я понимаю, вылилась в палку с двумя концами, и соглашаюсь с мнением Брайна МакКенны, что Java должна быть скрыта за внешними функциональными интерфейсами, но что-то я увлекся). Она выглядела “знакомой” (т.е. она не была похожа на Clojure), и имела свою “родословную” (Мартин и EPFL).
Начало использования Scala поставило меня на путь открытий типов данных и открыло для меня сообщество, которое показало мне новый (я бы сказал, лучший) способ ведений дел.

2. Над чем вы сейчас работаете?

В данное время, я не могу уделять много времени разработке, мы имеем большую команду разработчиков - но моим главным проектом в этом году будет битемпоральное API представляющее “бизнес объекты” в рамках GSA (торговые книги, бизнес-единицы, фонды и т.д.). Мы обнаружили, за последние несколько лет, что не иметь его это очень больно. Помимо этого, есть много усовершенствований для нашей существующей кодовой базы, которые исчисляются тысячами.

3. Вы работаете только с функциональными языками, или же в вашем проекте имеется некоторый ООП/Процедурный код? Если да, то как они уживаются вместе?

Нет - у нас есть много систем на Java, как унаследованных так и нет. Я до сих пор осторожно предоставляю Scala API для внешних команд (потому, что множество наших API нацелены на не специалистов, которые работают в таких языках как Matlab, поэтому ошибки с бинарной совместимостью могут привести к недовольству), поэтому, если бы мы нуждались в чем-то из этого, я бы, наверное предпочел Java.
Я тоже пишу чисто функциональный код - множество наших систем работают в режиме реального времени, или 24x5, так что, я могу написать маленькие участки функционального кода, которые в конечном итоге будут запущены на не-функциональных системах (например, на основе акторов).

4. Какой бы совет вы могли дать новым программистам?

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

5. О чем бы вы хотели спросить сообщество?

Может кто-то пожалуйста продемонстрировать код функциональной системы на Scala работающей в “реальном времени”? Под “реальным временем” я подразумеваю постоянно запущенную и обрабатывающую данные, в отличие от единственного вычисления с определенным концом и результатом.

Оригинал

Java и Scala - Бывшие конкуренты вскоре могут стать лучшими друзьями

В прошлом месяце язык программирования Scala отпраздновал свою 10-ю годовщину, в то время как Java 8 готовится к своему релизу в марте этого года. Как ни странно, два этих языка, которые соревновались на протяжении стольких лет становятся все более похожими.

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

Хотя есть еще много причин считать, что Java 8 избавит от большей части причин, по которым люди перешли на Scala, недавно я встретился с Мартином Одерски, создателем языка Scala и председателем Typesafe, чтобы узнать его мнение о том, что стоит между двумя языками, которых объединяет любовь к виртуальной машине Java.

Java похожая на Scala - это хорошо для Scala

Автор: Кажется, что Java 8 воспользовалась некоторыми основными, для оригинального дизайна Scala, концепциями. Является ли это защитой от Scala? Другими словами не боитесь ли вы, что усовершенствования в Java 8 могут увести разработчиков из Scala-сообщества?

Мартин: Java 8 представляет одно из самых значимых изменений для языка в его истории, вместе с новыми конструкциями, оно изменяет традиционные подходы Java-разработчиков к решению проблем.

Люди часто спрашивают меня, что если возможности, появляющиеся в Java, замедлят адоптацию Scala. Как раз наоборот! На самом деле, я чувствую, что процесс принятия этих возможностей сообществом Java проверяет наши инновации в дизайне языка на JVM. Когда разработчики осознают, что это за возможности и как они работают, они так же оценят широкую поддержку функционального программирования, которую предоставляет Scala.

Так же будет повышенный интерес к изучению “Реактивных инструментов”, таких как Akka и фреймворк Play, даже посредством Java API.

Влияние Java 8 на Scala

Автор: Каким будет основное воздействие Java 8 на Scala?

Мартин: Список возможностей включенных в Java 8 на самом деле довольно большой, но некоторые из них особенно интересны для меня. Java 8 безусловно сделает ближе сообщества Java и Scala разработчиков.

Я так же верю, что это сделает поддержку множества языков на JVM проще, и в то же время даст разработчикам Java почувствовать, развитие языков для поддержки высокой степени конкурентности и параллелизма.

Java учит новые трюки с лямбдами: Последуют ли разработчики?

Автор: Какие возможности Java 8, являются выделяющимися для Вас?

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

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

Реализация лямбд в Java 8 вносит новые методы в тип java.util.stream.Stream , который звучит доволно знакомо для Scala разработчиков. По моему опыту, эти методы сделают написание высокоуровневого кода для колекций намного проще и будут действовать, как шлюз для изменений в сторону функционального стиля. Большинство пользователей этих абстракций в дополнение оценят читаемость, предоставляемую Scala, вместе с её for-выражениями.

Иметь эти реализации вероятно будет так же полезно для таких инструментов, как Akka и фреймворк Play, для поддержании более связанного Java API, которое позволит им создавать функциональные интерфейсы, основанные на Java, какие они не могли себе позволить ранее. Это означает, что API будут очень похожи, независимо от того, вызываете вы их из Java или Scala, увеличивая удобство для разработчиков на обоих языках.

Пишите байт-код, не надо войны

Автор: Какие другие возможности выделяются в Java 8?

Мартин: Scala разработчики так же будут рады включению реализаций по-умолчанию для методов Java интерфейсов Разработчики компилятора Scala уже давно прыгали через обручи для того, чтобы создавать байт-код для трейтов Scala, который был бы приемлемым для JVM, например создавали интерфейс для трейта и класс для реализации методов, который бы расширялся классами реализующими данный трейт.

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

Java: все больше похожа на Scala с каждым днем

Автор: Это звучит почти как вы находитесь в режиме “объятия” с сообществом Java. Я вспоминаю Рода Джонсона, создателя Sprint, с противоречивым докладом на ScalaDays прошлым летом в Нью Йорке. Джонсон перечислил примеры, где Scala прозрачна и тяжела, что препятствует ее широкому распространению. Он призвал Scala быть более похожей на Java для того, чтобы быть более дружелюбной и полезной для основного потока разработчиков.

Мартин: На самом деле Род, является хорошим другом Scala! Он находится в совете Typesafe вместе со мной. И Typesafe фокусируется на предоставлении первоклассной поддержки для Java разработчиков по работе с инструментарем для конкурентных систем Akka и фреймворком Play.

На самом деле, Typesafe считает, что добавление данных возможностей в Java 8 поможет нам предоставить более мощные и подвижные API для разработчиков Java, которые точно отражают большую часть нашего пути в Scala. Это хорошо тем, что Java будет иметь конструкции со своими аналогами в Scala. Введение этих и других возможностей под влиянием Scala, таких как не блокирующие CompletableFuture и параллельные коллекции, подчеркивает успешное влияние Scala на будущее Java.

Оригинал