Обновить
52
0
Dmitry Non @Nondv

Software Engineer

Отправить сообщение
В вашем написании код приходится читать дважды, в разных направлениях


В случае небольшого скрипта это действительно может показаться странным. Хотя, например, в JS это не особо кого-то напрягает. Условно говоря:
var callback = function() { ... }
someFunction(callback);


Смысл моего метода как раз-таки в том, чтобы облегчить поверхностное чтение кода, без углубления в детали, если сравнивать с вариантом, когда все вспомогательные функции вынесены в методы. Когда вы начинаете погружаться в детали, вам в любом случае придется прыгать туда-сюда. Зачастую, с помощью поиска.
Ваш же вариант действительно выглядит корректно, однако, я его даже не рассматриваю (о, кажется, я Вас процитировал), потому что темой является именно замена однострочных методов на лямбды (наверняка ведь в Вашей практике встречались такие однострочники).
users.each { |user| ... } можно же писать


Можно, но, имхо, не самая удачная практика, ибо вы создаете две переменные с практически идентичными названиями. Одна опечатка и найти проблему может быть сложно. Лично я стараюсь либо во множественном числе писать user_list, либо, если это блок, в единственном писать по первой букве: users.each { |u| ... }.
По-моему, это лучше чем rubles_per_unit, т.к. не привязано к валюте


А я вот считаю, что именно по этой самой причине rubles_per_unit лучше, т.к. несет больше информации о содержимом (мы ведь разбираем конкретный xml с валютами по отношению к рублю).

Ну так я ведь в посте и написал, что эксперимент удален от реальности:)


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


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

ок, к черту руби.

Собственно, я сам провел эксперимент для функции идентичности.


N = 10_000_000

def method(x)
  id = ->(x) { x }
  id[x]
end

t = Time.now
N.times { |i| method(i) }
puts "Lambda: #{Time.now - t}"

в 6 раз медленнее, чем аналогичный код с методом.

А мой эксперимент более чем красноречиво говорит о том, что использование лямбды медленнее, чем использование метода. Не понимаю, к чему Вы придираетесь. Да, мой способ оценивания, мягко говоря, кустарный, но зато наглядный. Думаю, очевидно, что операция возведения в квадрат выполняется за фиксированное время.

И да. Мне всегда казалось, что лямбда — это объект-функция. Синтаксис -> (x) { x * x } является сахаром по отношению к lambda { |x| x * x }, который инициализирует объект класса Proc с некоторыми особенностями.


В JS же синтаксис function f(x) { return x * x } является сахаром по отношению к var f = function() { return x * x } (источник — Крокфорд), что по сути является присвоением лямбды локальной переменной.

Все очень просто.


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


Так понятнее, на что именно тратится время?


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


В руби, кстати, proc { |x| x} является сахаром по отношению к Proc.new { |x| x }, а лямбда — это как раз-таки объект Proc

я знаю о замыканиях, уверяю Вас. В сниппете замыкание не используется (де факто), есть просто вкладывание.


Однако, я действительно оказался несколько голословен, ибо держал в голове вот это:
https://google.github.io/styleguide/javascriptguide.xml#Nested_functions


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


P.S. Погуглив на эту тему, нашел множество обсуждений на stackoverflow и вот эту статью:
http://code.tutsplus.com/tutorials/stop-nesting-functions-but-not-all-of-them--net-22315
но не уверен, что эо можно назвать авторитетным источником.

Пример синтетический, поэтому дискретизация выглядит несколько избыточно.


Однако относительно Вашего куска кода:


  • rate при наличии rates — не самое удачное имя для переменной
  • Не совсем ясно, что этот rate обозначает, так что стоило назвать переменную хотя бы rubles_per_unit, как в посте
  • Разнообразные куски кода, которые выделяются доп. отступом, усложняют понимание кода. В данном случае, это не заметно, если код будет большим и сложным, то эти куски кода еще и смешивают уровни абстракции (что не есть хорошо, если почитать Роберта Мартина, да Вы и сами это прекрасно понимаете, наверное)

Неужели Ваши 4 строки выглядят понятнее, чем эти три?


rubles_per_unit = ->(r) { r['Value'].to_f / r['Nominal'].to_f }
rate_hash_element = ->(r) { [r['CharCode'], rubles_per_unit[r]] }

rates.map(&rate_hash_element).to_h

Кстати, сам метод rubles_per_unit специфичен для объектов из xml ЦБ, при этом имя у него весьма абстрактное, так что использование лямбды в данном случае только подчеркивает его специфичность.

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


rates.map(&rate_hash_element).to_h

Ситуации разные, иногда это даже ухудшает читаемость. Иногда же наоборот, повышает.
Ruby немного почерпнул из Perl. И поэтому TMTOWTDI. А из всех вариантов решения задачи нужно выбрать тот, который лучше всего вписывается в код. Я не предлагаю использовать эти лямбды повсеместно. Просто предлагаю взять это на заметку, как один из вариантов. Другое дело, если идея слишком уж неудачная (на это есть опрос).

А можете привести пару кусков кода для других языков?
Я, вроде, и понимаю, что можно использовать много где, но приходит на ум только CoffeeScript.

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


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

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


Относительно Вашего примера — тогда уж не rubles_per_unit, а value_per_unit;)
Просто хотелось продемонстрировать хоть сколько-нибудь ясно, что я предлагаю.
Пока что это не особо получилось, очевидно.


А вообще, неужели у Вас нет в практике случаев, когда вы пишите какой-нибудь приватный метод, а за ним паровозиком идут связанные именно с ним методы (вспомогательные функции)? Вот моя идея заключается в том, чтобы этот паровозик убрать и оставить только значимую логику выделенной.
Сами посудите, у Вас функция rubles_per_unit до безобразия проста, а азнимает аж 4 строки и бросается в глаза наравне с rate_hash, хотя вне нее, по сути, бесполезна (на самом деле, полезна, но будем считать, что она максимально специфична).

Добавил в пост тот же пример с валютами, но классом (просто обернул). Так, вроде, более ясно, о чем я.

Добавил в пост тот же пример с валютами, но классом (тупо обернул). Так, вроде, понятнее, о чем я.

смысл не в инкапсуляции, а в сокращении строк кода и вызуальном выделении основных методов (основных != публичных)


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

Вы тестируете однострочные вспомогательные функции?


def some_method
  # ...
  some_private_method
end

private

def some_private_method
  some_private_method_2
  ...
end
...

Простите за некропостинг, но все же.


В Ruby оператор switch (который case) работает по-особенному.
На объектах есть перегружаемый оператор ===, который в книге Мацумото называется case-equality operator, созданный специально для него.


Собственно:


case(object)
when cond1 then 1
when cond2 then 2
end

эквивалентно:


if cond1 === object
  1
elsif cond2 === object
  2
end

Эта часть языка мне не нравится, поскольку может ввести в заблуждение (и вводит!).
Поэтому лично я предпочитаю использовать его форму без параметра (где не используется ===) ради простых веток:


case
when then something1
when cond2 then something2
when cond3 then something3
end

vs


if cond1
  something1
elsif cond2
  something2
elsif cond3
  something3
end

Если честно, не совсем Вас понял.


Я в посте привел простой пример кода на JS. Подобный шаблон я несколько раз встречал в проектах и это считается говнокодом по описанным в посте причинам (пруфы можно найти).


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


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

Информация

В рейтинге
Не участвует
Дата рождения
Зарегистрирован
Активность

Специализация

Бэкенд разработчик, Фулстек разработчик
Старший