Тема: Советы

Обратился ко мне начинающий луа-программист с куском не рабочего кода.
Код написан вот в таком стиле, с кучей проверок, что само по себе не плохо, но как!

 
function fn(ds, signal)
  if ds ~= nil  then
   local index = ds:Size()
   local O, H, L, C = ds:O(index),  ds:O(index), ds:O(index), ds:O(index)
   if  O ~= 0 and H ~= 0 and L ~= 0 and C ~= 0 then
      if signal  == 0 then
        return (O + H + L + C)/4
      elseif signa > 0 then
        if O >= C then
          return O
        else
          return C
        end
      else
         if O >= C then
          return C
        else
          return O
        end
      end
    else
      return 0
    end
  else
    return 0
  end
end

Уровень вложенности блоков зашкаливает!
Читать такой код совсем не было желания, а на поиск оЧепятки или логической ошибки могло уйти много времени. Быстрее написать заново.
Так как это не первое подобное обращение, решил написать здесь пару советов.

1. При использовании документированных функций, обратите внимание на возвращаемый результат, в случае ошибки.
2. При вызове собственной функции, проверяйте передаваемые параметры до! вызова функции. Если какой то параметр не проходит проверку, то в вызове функции смысла нет.
3. Соблюдайте уровень вложенности логических блоков, если он > 3, то код не читабельный, за поиск ошибки с вас попросят больше денег (время - деньги). Если по логике не выходит вложиться в эти рамки, то напишите дополнительную функцию и уберите лишнее ветвление в нее, тем самым вы понизите уровень вложенности.
4. Без нужды не используйте функции приведения типов - tostring, tonumber, это затратные по времени функции. Если сомневаетесь в типе данных, которые получаете из таблиц квик-а, проверьте их: message("price_type = type(item.price), 2)
5. Перед вычислениями нужно обработать все исключения, т.е. если для дальнейших расчетов один из параметров/переменных может привести к ошибке вычисления, то дальше считать нет смысла.
Вот так, примерно:

local function fn(ds, signal)
  -- signal по умолчанию число [-1; 1], его проверять не будем
  -- ds источник данных графика, на nil проверяется до вызова функции fn

  local index = ds:Size()
  -- вот эту проверку можно тоже сделать до вызова этой функции
  if index == 0 then reurn 0 end

  local O, H, L, C = ds:O(index),  ds:O(index), ds:O(index), ds:O(index)
  -- и эту проверку тоже, вдруг где-то еще понадобятся O, H, L, C
  if O == 0 or H == 0 or L == 0 or C == 0 then return 0 end

  -- все, исключения обработали, теперь к делу
  if signal  == 0 then
    -- вот тут второй уровень вложенности
    return (O + H + L + C)/4
  end
  
  if signal > 0 then
    if O >= C then
      -- это уже третий уровень вложенности, еще читабельно
      return O
    else
      return C
    end
  end
  -- убираем ветвление else, т.к. если до сюда дошло, то уже else (signal < 0)
  if O >= C then
   return C
  else
   return O
  end

end

6. Доступ к глобальным луа-переменным медленнее, чем к локальным. Для ускорения выполнения алгоритма многие советуют вначале скрипта делать так:

local os_time = os.time
local floor = math.floor
local tm = os_time() -- это будет быстрее, чем вызвать функцию time() из глобальной таблицы os
local a = floor(3.14) -- и так быстрее

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

local os = os
local math = math
local tm = os.time()
local a = math.floor(3.14)

это тоже быстро и будет работать, даже если вы где-то забудете объявить
local math = math
на вот такой объявление совет не распространяется smile тут никак не ошибешься при копировании.
local tonubber, tostring = tonubber, tostring
а подсветка синтаксиса останется.

В общем, успехов в начинаниях, и дополняйте раздел.

2

Re: Советы

Можно написать ещё короче.

local function max(a,b) return a>=b and a or b end
local function min(a,b) return a>=b and b or a end
local sign_func = {
  [-1]= function(o,h,c,l) return min(o,c) end,
  [0] = function(o,h,c,l) return (o+h+c+l)/4 end,
  [1] = function(o,h,c,l) return max(o,c) end
}

-- signal по умолчанию число [-1; 1], его проверять не будем
local function fn(ds, signal)

  local index = ds:Size()
  if index == 0 then return 0 end

  local O, H, L, C = ds:O(index),  ds:O(index), ds:O(index), ds:O(index)
  if O == 0 or H == 0 or L == 0 or C == 0 then return 0 end

  return sig_func[signal](O,H,C,L)
end

п.2 - сомнительный. Представьте, что у вас 3 десятка функций, которые вызываются в сотне мест. И вы решили, например, добавить аргументы в функцию или поменять их местами...

п.4 - проверки типов лучше делегировать программе
function String(x)
     if type(x) ~= "string" then return tostring(x) end
     return x
end

п.6 - доступ к глобальным переменным дольше потому что они ищутся в таблице по имени. Но модули - тоже таблицы.
Поэтому os_time() - вызов локальной функции быстрее чем os.time(), что то же самое os["time"]() - поиск функции в таблице по имени и её вызов.

По обработке ошибок есть удобный шаблон - всегда возвращать из функции пару значений - результат функции и сообщение об ошибке.

function Sqrt(x)
    if x>=0 then return math.sqrt(x), nil end
    return nil, "Error: x<0"
end

val,err = Sqrt(100)
if err then
   error( err)
end
do_something(val) 

-- можно даже игнорировать проверку ошибок
print( Sqrt(4)+Sqrt(9))
-->> 5

p.s. А лучше не изобретать велосипеды, погуглить awesome-lua (первые 3 ссылки на github) и подобрать себе библиотеку по вкусу. Рекомендую начать с Underscore.lua и Penlight.

3

Re: Советы

reader пишет:

Можно написать ещё короче.

Можно, но в скрипте больше нет места для ваших max и min и нигде больше нет для них места, зачем тогда их писать? Мало того, вы делаете дополнительную проверку.
Краткость в написании скрипта - не всегда сестра таланта.

reader пишет:

п.2 - сомнительный. Представьте, что у вас 3 десятка функций, которые вызываются в сотне мест. И вы решили, например, добавить аргументы в функцию или поменять их местами...

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

reader пишет:

п.4 - проверки типов лучше делегировать программе
function String(x)
     if type(x) ~= "string" then return tostring(x) end
     return x
end

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

reader пишет:

п.6 - доступ к глобальным переменным дольше потому что они ищутся в таблице по имени. Но модули - тоже таблицы.
Поэтому os_time() - вызов локальной функции быстрее чем os.time(), что то же самое os["time"]() - поиск функции в таблице по имени и её вызов.

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

reader пишет:

По обработке ошибок есть удобный шаблон - всегда возвращать из функции пару значений - результат функции и сообщение об ошибке.

Т.е. вы откуда то сковырнули фрагмент кода, который даже не удосужились проверить. Где функция do_something(val)?
Проверку if x >= 0 лучше делать до вызова Sqrt(x), тогда в этой функции отпадет необходимость и не нужно обрабатывать сообщение об ошибки, т.к. ее не будет.
print( Sqrt(-1)+Sqrt(9)) выдает ошибку: "attempt to perform arithmetic on a nil value" - это не похоже на описание ошибки, переданное вторым параметром в функции Sqrt(x)
Плохой примерчик.
Вообще манечка "примитивов" это не есть гуд (а в ваших примерах масло-маслянное). Если всю логику скрипта разложить на примитивы, то без основательных комментариев(и даже с ними) не будет возможности понять основную идею, заложенную в скрипте, уже через неделю.

з.ы.
А на счет погугли и подбери, 99% гугленных библиотек не приживаются в рамках терминала квик без танцев с бубнов, потому что там колеса квадратные, а когда приживаются, то выясняется, что из библиотеки нужны всего пару функций, без которых можно обойтись.