Scala код
Contents
Scala код#
Используйте View или withFilter, чтоб избежать создания временных коллекций#
View - это особый вид коллекции в Scala, который берет базовую коллекцию и лениво выполняет методы преобразования в этой коллекции. Мы можем преобразовать каждую коллекцию Scala в отложенное представление и обратно с помощью view метода. Вместо создания новой коллекции после каждой операции, они применяют сразу всю цепочку преобразований к каждому элементу оригинальной коллекции, благодаря чему цепочка преобразований выполняется за один проход. Преобразование виртуальной коллекции в реальную выполняется либо одним из преобразующих методов из серии to{Название коллекции}, либо методом force.
Пример:
// До
seq.map(f).flatMap(g).filter(p)
// После
seq.view.map(f).flatMap(g).filter(p).force
withFilter работает так же, как и view — создает временный объект, который ограничивает область последующих преобразований коллекции (так, что он реорганизует возможные побочные эффекты). Однако, нет нужды явно преобразовывать коллекцию к (или наоборот) от временного представления (вызвав view и force). Если filter используется перед map, flatMap или foreach, то для лучшей производительности вместо него должен использоваться withFilter.
Пример:
// До
Stm_OrderInDetApi()
.byParent(rop)
.filter(f => f.get(_.idStock).isNotNull)
.foreach { ropDet =>
if (Stk_StockApi().load(ropDet.get(_.idStock)).get(_.idBisObj).isDistinct(value))
Stm_OrderInDetApi().setidStock(ropDet, None.nl)
}
// После
Stm_OrderInDetApi()
.byParent(rop)
.withFilter(f => f.get(_.idStock).isNotNull)
.foreach { ropDet =>
if (Stk_StockApi().load(ropDet.get(_.idStock)).get(_.idBisObj).isDistinct(value))
Stm_OrderInDetApi().setidStock(ropDet, None.nl)
}
Избегайте использования filter несколько раз подряд#
Пример:
// До
seq.filter(p1).filter(p2)
// После
seq.filter(x => p1(x) && p2(x))
Используйте lazy val#
Компилятор не сразу вычисляет связанное выражение отложенного значения val. Он вычисляет переменную только при первом обращении к ней.
Пример:
// До
val idgForming = Btk_ClassStateApi().findByNameAndIdClass("Forming", idClass)
// После
lazy val idgForming = Btk_ClassStateApi().findByNameAndIdClass("Forming", idClass)
Используйте коллекции, имеющие оптимальную сложность поиска, взамен обладающих линейной при частом обращении за нахождением#
Пример:
//до:
def registerByParent(ropDoc: RopDoc, idap: CSeq[NLong]): Unit = {
val idavAlreadyRegistered = byParent(ropDoc).map(_.get(_.idGdsType)).toList
idap.foreach { idv =>
if (idv.isNotNull) {
if (!idavAlreadyRegistered.contains(idv)) {
val rop = // ...
}
}
}
}
//после:
def registerByParent(ropDoc: RopDoc, idap: CSeq[NLong]): Unit = {
val idavAlreadyRegistered = byParent(ropDoc).map(_.get(_.idGdsType)).toSet
idap.foreach { idv =>
if (idv.isNotNull) {
if (!idavAlreadyRegistered(idv)) {
val rop = // ...
}
}
}
}
На while и рекурсии добавляйте защиту по кол-ву итераций#
Пример:
// До
while (dotsNeedUpdate.nonEmpty) {
val newDotsNeedUpdate = scala.collection.mutable.HashSet[NNumber]()
???
dotsNeedUpdate = newDotsNeedUpdate.toSet
}
// После
var count = 0
while (dotsNeedUpdate.nonEmpty && count < 10000) {
val newDotsNeedUpdate = scala.collection.mutable.HashSet[NNumber]()
???
dotsNeedUpdate = newDotsNeedUpdate.toSet
count += 1
}
Не эмулируйте существующие методы работы с коллекциями#
Пример:
// До
seq.map(f).flatten
seq.toSet.toSeq
seq.reverse.iterator
seq.reverse.map(f)
seq.collect{case P => ???}.headOption
// После
seq.flatMap(f)
seq.distinct
seq.reverse.iterator
seq.reverseMap(f)
seq.collectFirst{case P => ???}
Используйте ArrayBuffer вместо ListBuffer#
ArrayBuffer выигрывает в производительности ListBuffer при операциях добавления и итерации, а это 90% всего использования. Также ArrayBuffer занимает меньший объем в оперативной памяти, потому что ListBuffer основан на связанном списке.
При проверке на существование - не прибегайте к фильтрации#
Пример:
// До
seq.filter(p).nonEmpty
seq.filter(p).isEmpty
// После
seq.exists(p)
!seq.exists(p)
При проверке на пустоту - не вычисляйте длину#
Пример:
// До
seq.length > 0
seq.length != 0
seq.length == 0
// После
seq.nonEmpty
seq.nonEmpty
seq.isEmpty
В checkWorkability (CWA) используйте getVar вместо обращений к БД (ASelect, ATSQL, OQuery, Load, byParent, byKey и т.д.)#
Так как CWA вызывается на большое количество событий, выполняемые в нем запросы к БД могут существенно замедлить работу интерфейса пользователя. Вычисления дополнительных полей для CWA можно осуществлять в onRefreshExt, selectStatement или на события при открытии карточки (onLoadMeta, beforeOpen, afterOpen и т.д.). Пример:
//До
override def checkWorkability(): Unit = {
super.checkWorkability()
if (thisApi.load(getVar("id").asNLong).get(_.idStateMC) >= 300.nn)
opers("delete").isEnabled = false
}
//После
override def checkWorkability(): Unit = {
super.checkWorkability()
if (getSelfVar("idStateMC").asNNumber >= 300.nn)
opers("delete").isEnabled = false
}
Избегайте использования copyAro, если берете меньше половины атрибутов из rop#
При использовании copyAro все данные из rop копируются, что увеличивает объем потребляемой оперативной памяти на сервере и может привести к ошибке потребляемой памяти в сессии. Пример:
// До
val rvWorkDoc = Mct_DocumentApi().loadByGid(rop.get(_.gidMctDocument)).copyAro()
val idvPrj = rvWorkDoc.idPrj
val idvPrjVer = rvWorkDoc.idPrjVer
// После
val ropWorkDoc = Mct_DocumentApi().loadByGid(rop.get(_.gidMctDocument))
val idvPrj = ropWorkDoc.get(_.idPrj)
val idvPrjVer = ropWorkDoc.get(_.idPrjVer)
В Avi пишите только бизнес логику для взаимодействия с пользователем, все остальное выносите в методы Api#
Пример:
// До
override protected def onInsertItem(): Unit = {
super.onInsertItem()
if (getSelfVar("idCandidat#").asNLong.isNotNull) thisApi().setidCandidate(thisRop(), getSelfVar("idCandidat#").asNLong)
if (getSelfVar("idVacancy#").asNLong.isNotNull) thisApi().setidVacancy(thisRop(), getSelfVar("idVacancy#").asNLong)
if (getSelfVar("nSalaryProbAcc#").asNNumber.isNotNull) thisApi().setnSalaryProb(thisRop(), getSelfVar("nSalaryProbAcc#").asNNumber)
if (getSelfVar("nProbationAcc#").asNNumber.isNotNull) thisApi().setnProbation(thisRop(), getSelfVar("nProbationAcc#").asNNumber)
if (getSelfVar("nSalaryAcc#").asNNumber.isNotNull) thisApi().setnSalary(thisRop(), getSelfVar("nSalaryAcc#").asNNumber)
if (getSelfVar("idCurAcc#").asNLong.isNotNull) thisApi().setidCur(thisRop(), getSelfVar("idCurAcc#").asNLong)
if (getSelfVar("idEmployment#").asNLong.isNotNull) thisApi().setidEmployment(thisRop(), getSelfVar("idEmployment#").asNLong)
if (getSelfVar("idWorkGraph#").asNLong.isNotNull) thisApi().setidWorkGraph(thisRop(), getSelfVar("idWorkGraph#").asNLong)
if (getSelfVar("idWorkMode#").asNLong.isNotNull) thisApi().setidWorkMode(thisRop(), getSelfVar("idWorkMode#").asNLong)
if (getSelfVar("idMentor#").asNLong.isNotNull) thisApi().setidCurator(thisRop(), getSelfVar("idMentor#").asNLong)
if (getSelfVar("idHREmpl#").asNLong.isNotNull) thisApi().setidHREmpl(thisRop(), getSelfVar("idHREmpl#").asNLong)
if (getSelfVar("dPlan#").asNDate.isNotNull) thisApi().setdPlan(thisRop(), getSelfVar("dPlan#").asNDate)
}
// После
override protected def onInsertItem(): Unit = {
super.onInsertItem()
if (getSelfVar("idOffer#").notNull())
thisApi().fillByOffer(thisRop(), getSelfVar("idOffer#").asNLong)
}
Не пишите длинные выражения и запросы в одну строчку#
Длинные выражения в одну строку затрудняют чтение кода и отнимает много времени на понимание Пример:
// До
Stk_InternalWarrantDetApi().byParent(rop).withFilter(f => (gidapDet.contains(f.gid) || gidapDet.isEmpty)).foreach { ropDet =>
???
}
// После
Stk_InternalWarrantDetApi().byParent(rop)
.withFilter(f => (gidapDet.contains(f.gid) || gidapDet.isEmpty))
.foreach { ropDet =>
???
}
Пример:
-- До
select gidObj, nPercent, nQty from Bs_CostDistr d where d.gidSrc = :gid
-- После
select d.gidObj
,d.nPercent
,d.nQty
from Bs_CostDistr d
where d.gidSrc = :gid
Создавайте общие trait без реализации для разных отображений с одинаковой функциональностью#
Чтобы избежать дублирование кода и упростить дальнейшую поддержку Пример:
trait List_gidDocGeneral extends Default with super.List {
???
}
trait List_gidDocTax extends List_gidDocGeneral {
override def idAccKind: NLong = Bs_AccKindApi().idTax
}
trait List_gidDocMng extends List_gidDocGeneral {
override def idAccKind: NLong = Bs_AccKindApi().idMng
}
trait List_gidDocAit extends List_gidDocGeneral {
override def idAccKind: NLong = Bs_AccKindApi().idAit
}
Создавайте библиотеки для использования общих методов в различных выборках#
Чтобы избежать дублирование кода и упростить дальнейшую поддержку Пример:
Pm_Lib
Btk_FileLib
Не используйте поля Tuple, а по возможности пользуйтесь экстрактором#
Пример:
val t = ("Vlad", 23, "M")
//До
setName(t._1)
setAge(t._2)
setSex(t._3)
//После
val (name, age, sex) = t
setName(name)
setAge(age)
setSex(sex)
Seq(t)
.foreach { case (name, age, sex) =>
setName(name)
setAge(age)
setSex(sex)
}
Используйте «.lastOption, .headOption, dropRight(1), .drop(1), .reduceOption» вместо «.last, .head, .init, .tail, .reduce»#
Использование «.last, .head, .init, .tail, .reduce» может приводит к исключению в случае если коллекция пустая Пример:
val unknownSizeList: List[_] = ???
//До
val sum = unknownSizeList.reduce(_ + _) // есть шанс ошибки
//После
val sum = unknownSizeList.reduceOption(_ + _).nn // мы получим NNumber(null) в случае если список пустой
Не складывайте строки через +, используйте строковую интерполяция#
Пример:
val hello = "hello"
val world = "world"
//До
val str = hello + " " + world
//После
val str = s"$hello $world"
При сборе сложной строки не создавайте много промежуточных результатов#
Пример:
val ropa: List[???] = ???
//До
var str = "Names: "
val result =
for (rop <- ropa) yield {
str += rop.sName + ";" // каждый раз создаётся новая строка
load(rop.idSmth)
}
str += " end"
//После
val stringBuilder =
new StringBuilder()
.append("Names: ")
val result =
for (rop <- ropa) yield {
stringBuilder
.append(rop.sName)
.append(";")
load(rop.idSmth)
}
val names = stringBuilder.append(" end").toString()
// или если есть возможность сформируйте список
val (result, names) = ropa.map { rop =>
(load(rop.idSmth), rop.sName)
}.unzip
val str = names.mkString("Names: ", ";", " end")
Используйте Option при работе с пустыми (null) ссылками#
Пример:
val rop = ???
val oldJavaLib = ???
//До
val ropSmth = load(rop.idSmth) // забыли обработать null
var result = _
val thing = oldJavaLib.getThing
if(thing != null) {
val smth = thing.getSmth
if(smth != null) {
try {
result = smth.doWork()
} catch {
case _: Exception => {}
}
}
}
if(result == null) throw AppException("Не получилось")
//После
val ropSmth = get(rop.idSmth)
.getOrElse(throw AppException("Чего то нету"))
val result = (for {
thing <- Option(oldJavaLib.getThing)
smth <- Option(thing.getSmth)
result <- Try(smth.doWork()).toOption
} yield result).getOrElse(throw AppException("Не получилось"))
Избегайте выполнения запросов внутри вложенных циклов#
Пример:
case class TaskDoc(/* атрибуты */, aDocDet: CSeq[TaskDocDet])
case class TaskDocDet(/* атрибуты */, aFile: CSeq[TaskDocDetFile])
case class TaskDocDetFile(/* атрибуты */)
//до:
def getTasksData(dpFrom: NDate, dpTo: NDate): CSeq[Task] = {
val avResult = mutable.ArrayBuffer.empty[TaskDoc]
for (rvx <- new ASelect {
// документы Task
}) {
// ...
for (rvxDet <- new ASelect {
// позиции Task
}) {
// ...
for (rvxDetFile <- new ASelect {
// файлы позиций Task
}) {
// ...
}
}
}
avResult
}
//после:
def getTasksData(dpFrom: NDate, dpTo: NDate): CSeq[Task] = {
val avResult = mutable.ArrayBuffer.empty[TaskDoc]
val avIdToTask = new ASelect {
val id = asNLong("id")
// остальные атрибуты
}.map(rvx => rvx.id() -> TaskDoc(/* ... */)).toMap
val avIdDocAndTaskDets = new ASelect {
SQL"""
select t.idDoc
,t.id
,t.nRow
-- ..
from Md_TaskDet t
where t.idDoc in (select unnest(${LongPgArray(avIdToTask.keys)}) )
"""
}.map { rvx =>
(rvx.idDoc(), rvx.id(), TaskDocDet(/* ... */))
}.toSeq
val gidavTaskDet = avIdDocAndTaskDets.map(vEntry => Md_TaskDetApi().getGid(vEntry._2))
val avIdTaskDetAndFiles = new ASelect {
SQL"""
--
where t.gidSrc in (select unnest(${GidPgArray(gidavTaskDet)}) )
"""
}.map { rvx =>
(rvx.id(), TaskDocDetFile(/* ... */))
}
// агрегация с циклом по Task, поиском TaskDet, подбором File для Det
avResult
}
Пример с поисковым ключом:
case class objRem(
idGds: NLong
, idStock: NLong
, gidSlitAccount: NGid
, gidSTKAcc: NGid
, gidMaster: NGid
, gidSuite: NGid
, idCons: NLong
, var nQtyBase: NNumber
)
val avRem = collection.mutable.Map[NString, ArrayBuffer[objRem]]()
for (rv <- new ASelect {
val idGds = asNLong("idGds")
val idCons = asNLong("idCons")
val idStock = asNLong("idStock")
val gidMaster = asNGid("gidMaster")
val gidSTKAcc = asNGid("gidSTKAcc")
val gidSuite = asNGid("gidSuite")
val gidSlitAccount = asNGid("gidSlitAccount")
val nQtyBaseMsr = asNNumber("nQtyBaseMsr")
SQL"""
SELECT sum(-r.nQtyBaseMsr) nQtyBaseMsr
,r.idGds
,r.idStock
,r.gidMaster
,r.gidSTKAcc
,r.gidSuite
,r.gidSlitAccount
,r.idCons
from (select dr.idgds
,dr.gidMaster
,dr.gidObjNeed
,sum(dr.nQtyBase) nQtyBase
from (
select *
from unnest(${idavGds}
,${gidavMaster}
,${gidaObjNeed}
,${naQtyBase}
) as x(idGds, gidMaster, gidObjNeed, nQtyBase)
) dr
group by dr.idgds
,dr.gidmaster
,dr.gidObjNeed
) d
join Stk_RegistryGdsMov r on r.idGds = d.idGds and r.gidSlitAccount = d.gidObjNeed and r.gidMaster = d.gidMaster
where r.idDepOwner = $idpDepOwner
and r.sType = 'res'
and r.idStock in (select st.idchild
from stk_stocktree st
where st.idparent = $idpStock)
group by r.idGds
,r.idStock
,r.gidMaster
,r.gidSTKAcc
,r.gidSuite
,r.gidSlitAccount
,r.idCons
having sum(-r.nQtyBaseMsr) > 0"""
}) {
val sKey = rv.idGds() + "_" + rv.gidMaster().nvl("0".ng) + "_" + rv.gidSlitAccount().nvl("0".ng)
avRem.getOrElseUpdate(sKey, ArrayBuffer[objRem]()) += objRem(
idGds = rv.idGds()
, idStock = rv.idStock()
, gidSlitAccount = rv.gidSlitAccount()
, gidSTKAcc = rv.gidSTKAcc()
, gidMaster = rv.gidMaster()
, gidSuite = rv.gidSuite()
, nQtyBase = rv.nQtyBaseMsr()
, idCons = rv.idCons()
)
}
apObjNeed.foreach { vObjNeed =>
val sKey = vObjNeed.idGds + "_" + vObjNeed.gidMaster.nvl("0".ng) + "_" + vObjNeed.gidObjNeed.nvl("0".ng)
if (avRem.contains(sKey)) {
avRem(sKey).filter(f => f.nQtyBase > 0.nn).foreach { rem =>
lazy val ropGds = Bs_GoodsApi().load(vObjNeed.idGds)
val nvQtyBase = vObjNeed.nQtyBase.min(rem.nQtyBase)
avOperationData += OperationData(
gidParent = vObjNeed.gidDet
, gidDet = vObjNeed.gidDet
, gidSrcObject = vObjNeed.gidSrcObject
, idGds = vObjNeed.idGds
, idCons = rem.idCons
, gidSuite = rem.gidSuite
, idStock = rem.idStock
, gidMaster = rem.gidMaster
, gidSTKAcc = rem.gidSTKAcc
, gidSlitAccountRes = rem.gidSlitAccount
, nQtyBase = nvQtyBase
, nQtyBase2 = {
if (vObjNeed.nConvertBaseToBase2 > 0.nn) {
(nvQtyBase / vObjNeed.nConvertBaseToBase2).round(ropGds.get(_.nRoundPlaces2))
} else None.nn
}
, idReplacedGds = vObjNeed.idReplacedGds
, nRateReplacedGds = vObjNeed.nConvertReplacedGds
, nPrimeCostSum = (nvQtyBase * vObjNeed.nPrimeCostPrc).round(2.nn)
, nPrimeCostSumTax = (nvQtyBase * vObjNeed.nPrimeCostPrcTax).round(2.nn)
, nPrimeCostSumMng = (nvQtyBase * vObjNeed.nPrimeCostPrcMng).round(2.nn)
, dDateTime = dpExec
, idAddCondGenOper = Stk_AddCondGenOperApi().idvStornResByDetNeed
)
rem.nQtyBase -= nvQtyBase
vObjNeed.nQtyBase -= nvQtyBase
}
}
if (vObjNeed.nQtyBase > 0.nn && params.bValidateRemStorno) {
throw AppException(s"""По ТМЦ ${Bs_GoodsApi().getMnemoCode(vObjNeed.idGds)} ${Bs_GoodsApi().getHeadLine(vObjNeed.idGds)} нет достаточного количества для сторнирования резерва!""")
}
}
Используйте параллельные вычисления, если требуется множественная обработка независимых объектов#
Задачи планируются в основном потоке, а выполняются в служебных потоках пула. Основным потоком является поток в котором был создан пул для планирования задач. Данные будут сразу доступны всем пользователям при каждом завершении открывшихся сессий. Пример:
val threadCount = Asf_SettingsApi().getThreadCount(idDepOwner) //кол-во параллельных сессий
val batchSize = Asf_SettingsApi().getBatchSize(idDepOwner) //кол-во объектов в пачке для обработки
Parallel.withPool(threadCount) { pool =>
val idaInvNumb = ArrayBuffer[NLong]()
val idaObject = (for (rv <- new ASelect {
SQL(sQuery)
on(param.toArray: _*)
}) yield (rv.get("id").asNLong())).toVector
val nCount = idaObject.length
var nExec = 0
f(nExec, nCount) //анонимная функция для показа счетчика обработки в интерфейсе
idaObject.foreach{id =>
idaInvNumb += id
if (idaInvNumb.length > batchSize) {
pool.submit(InputData(idaInvNumb.toList, dPeriod, gid, idDepOwner, isAcc, isTax, isMng)) { implicit session =>
arg =>
Asf_DeprCalcTaskApi().doPack(arg.idaInvNumb, arg.dPeriod, arg.gidDoc, arg.idDepOwner, arg.isAcc, arg.isTax, arg.isMng)
} onSuccess { r =>
nExec += idaInvNumb.length
f(nExec, nCount)
} onFailure { r =>
r.raise()
}
idaInvNumb.clear()
}
}
if (idaInvNumb.nonEmpty)
Asf_DeprCalcTaskApi().doPack(idaInvNumb.toList, dPeriod, gid, idDepOwner, isAcc, isTax, isMng)
}