Вашата навременна обратна връзка е много важна за нас. Имате поне няколко начина да ни споделите какво мислите:
tmp
е епитома на лошите имена
data
, result
, array
, hash
– прекалено общи
size1
, size2
– със сигурност има някаква друга разлика между тях
a
, b
, c
, f
, x
, y
, s
и всички други букви
i
, когато се използва като индексна променлива)
curr
, prev
, el
, elem
, num
не печелите нищо, но губите много
If you work hard at being clear in your naming, you’ll also rarely need to write comments for the code. Comments are generally only needed when you failed to be clear enough in naming. Treat them as a code smell.— DHH
def make_person_an_outside_subscriber_if_all_accesses_revoked
person.update_attribute(:outside_subscriber, true) if person.reload.accesses.blank?
end
def shift_records_upward_starting_at(position)
positioned_records.update_all "position = position - 1",
["position >= ?", position]
end
def someone_else_just_finished_writing?(document)
if event = document.current_version_event
!event.by_current_creator? and event.updated_at > 1.minute.ago
end
end
Какво прави следният код?
tmp= if (self<0) then (tmp=(-1)*(self)) else (self) end
Ами този?
absolute_value = self > 0 ? self : -self
В контекста на метод, проверяващ дали число е просто:
middle = Math.sqrt(self)
vs.
upper_limit = Math.sqrt(self)
divider
означава "разделител, преграда, ..."
divisor
е делител
My guiding light is Kent Beck's rules of Simple Design (източник):
- The code must first be correct (as defined by tests);
- then it should be a clear statement of the design (what J.B.Rainsberger calls "no bad names");
- then it should contain no duplication (of text, of ideas, or of responsibility);
- and finally it must be the smallest code that meets all of the above.
It's time to stop refactoring when the code makes a reasonable stab at meeting those goals, and when any further changes would add no further benefit.
Започнете да се запознавате с документацията на следните класове/модули:
Какво прави <=>
(наричан the space shuttle operator)?
Връща положително число (1), ако първия елемент е по-голям, нула, ако са равни и отрицателно (-1), ако първия е по-малък.
Помнете го като "минус".
Какво извежда следният код и защо?
puts 'foo' == 'foo', 'foo'.equal? 'foo'
puts [] == [], [].equal? []
puts 42 == 42, 42.equal? 42
puts :foo == :foo, :foo.equal? :foo
==
сравнява по стойност, а equal?
по идентитет.
Числата и символите се интернират.
Итерира се с #each
, както всичко останало в Ruby:
primes = [2, 3, 5, 7, 11]
primes.each { |n| puts n }
primes.each do |n|
puts n
end
do
/end
се ползва, когато блокът е няколко редаnext
прескача изпълнението до края на блока и продължава
със следващия елемент, ала continue
в C/Java
.
break
прекъсва итерацията и продължава изпълнението след блока.
numbers.sort.each do |number|
next if number.odd?
break if number > 100
puts number
end
Hash
, но има и литерален синтаксис – {}
{1 => :one, 2 => :two}
numbers = {:one => :eins, :two => :zwei}
numbers[:one] # :eins
numbers[:three] # nil
numbers[:three] = :drei
numbers[:three] # :drei
numbers.fetch(:four, :keine_ahnung) # :keine_ahnung
numbers.fetch(:four) # error: KeyError
numbers = {:one => :eins, :two => :zwei}
numbers.keys # [:one, :two]
numbers.values # [:eins, :zwei]
numbers.each { |pair| puts pair }
numbers.each { |key, value| puts key, value }
numbers = {1 => 2, 3 => 4}
numbers.has_key?(:three) # false
numbers.size # 2
numbers.invert # {2=>1, 4=>3}
numbers.merge({5 => 6}) # {1=>2, 3=>4, 5=>6}
numbers.to_a # [[1, 2], [3, 4]]
Hash[1, 2, 3, 4] # {1=>2, 3=>4}
Долните два реда произвеждат еднакви хешове. Второто е 1.9+ синтаксис и ще предпочитаме него по конвенция (когато може):
{:one => 1, :two => 2}
{one: 1, two: 2}
Има интересна врътка при извикването на методи, за която ще споменем малко по-натам.
Enumerable
)
Дефинирането става с ключовата дума def
. Резултатът от функцията е
последният оценен израз, ако няма return
някъде по-рано.
def factorial(n)
if n == 1
1
else
factorial(n - 1) * n
end
end
В Ruby няма tail recursion оптимизация. Този код яде стек.
def
винаги дефинира метод в някакъв клас
def
не е в дефиниция на клас, отива като private
метод на Object
puts
е пример за нещо такова, както и методите, които дефинирате в irb
Object
е удачно само за кратки скриптове
Object
е ужасно лош стилЗа да добавите метод в съществуващ клас, например Array
, просто "отваряте" класа и дефинирате метода:
class Array
def fourty_second
self[41]
end
end
list = []
list[41] = 'The Universe'
list.fourty_second # "The Universe"
Можете да излезете от функция с return
:
def includes?(array, element)
array.each do |item|
return true if item == element
end
false
end
Разбира се, такава функция е излишна.
Може да ползвате array.include?(element)
.
Параметрите в Ruby могат да имат стойности по подразбиране:
def order(drink, size = 'large')
"A #{size} #{drink}, please!"
end
order 'tea' # "A large tea, please!"
order 'coffee', 'small' # "A small coffee, please!"
Методите в ruby могат да вземат променлив брой аргументи. Параметърът се означава със
*
и при извикване на функцията съдържа списък от аргументите.
def say_hi(name, *drinks)
"Hi, I am #{name} and I enjoy: #{drinks.join(', ')}"
end
say_hi 'Mityo', 'coffee', 'tea', 'water' # "Hi, I am Mityo and I enjoy: coffee, tea, water"
Параметърът за променлив брой аргументи може да е на всяка позиция в дефиницията:
def something(*a, b, c)
end
def something(a, *b, c)
end
Очевидно, може да има само един такъв параметър във функция.
Когато последният аргумент е хеш, може да изтървете фигурните скоби около него. Долните редове правят едно и също:
def order(drink, preferences)
end
order 'Latte', {:size => 'grande', :syrup => 'hazelnut'}
order 'Latte', :size => 'grande', :syrup => 'hazelnut'
order 'Latte', size: 'grande', syrup: 'hazelnut'
Така Ruby симулира извикане на функция с наименовани аргументи. Последният ред работи при версия 1.9+
Често ще видите код в този вид:
def order(drink, preferences = {})
end
order 'Latte'
order 'Latte', size: 'grande', syrup: 'hazelnut'
Така preferences
е незадължителен и няма нужда да го подавате, ако
нямате предпочитания.
Този "трик" с хешовете се ползва много, понякога прекалено много. Той има и ред недостатъци:
preferences[:size]
)
preferences[:size] ||= 'grande'
preferences = {size: 'grande', syrup: 'hazelnut'}.merge(preferences)
Горните недостатъци и нуждата водят до появата на истински keyword arguments в Ruby 2.0.
def order(drink, size: 'grande', syrup: nil)
message = "You ordered a #{size} #{drink}"
message << " with a #{syrup} syrup" if syrup
message
end
order 'Latte' # "You ordered a grande Latte"
order 'Latte', syrup: 'hazelnut' # "You ordered a grande Latte with a hazelnut syrup"
order 'Latte', filling: 'chocolate' # error: ArgumentError
def order(drink:, size: 'grande', syrup: nil)
message = "You ordered a #{size} #{drink}"
message << " with a #{syrup} syrup" if syrup
message
end
order drink: 'Latte' # "You ordered a grande Latte"
order syrup: 'hazelnut', drink: 'Latte' # "You ordered a grande Latte with a hazelnut syrup"
order # error: ArgumentError: missing keyword: drink
Името на метод може да завършва на ?
. Това се ползва, за методи,
които връщат лъжа или истина (предикати):
def even?(n)
n % 2 == 0
end
even? 2
even? 3
Това е само конвенция.
Името на метод може да завършва на !
.
Това се ползва, когато методът има две версии с различно поведение:
numbers = [4, 1, 3, 2, 5, 0]
numbers.sort # връща нов списък
numbers.sort! # променя списъка на място
В случая, "по-опасният" метод завършва на удивителна.
Ако имате само една версия на метод с такова име, не слагайте удивителна.
Анонимни функции в Ruby се дефинират с lambda
. Имат три начина на извикване:
pow = lambda { |a, b| a ** b }
pow.call 2, 3
pow[2, 3]
pow.(2, 3)
За нещастие, не може да извиквате така: double(2)
. Това е несъвместимо с
изтърваването на скобите при извикването на метод.
Може и така:
double = lambda do |x|
x * 2
end
Важи стандартната конвенция за { }
и do
/end
.
От 1.9 има по-симпатичен синтаксис за ламбди:
say_hi = lambda { puts 'Hi there!' }
double = lambda { |x| x * 2 }
divide = lambda { |a, b| a / b }
say_hi = -> { puts 'Hi there' }
double = ->(x) { x * 2 }
divide = ->(a, b) { a / b }
Всеки метод може да приеме допълнителен аргумент, който е "анонимна функция". Може да
го извикате от метода с yield
:
def twice
yield
yield
end
twice { puts 'Ruby rocks!' }
Блокът може да приема аргументи:
def sequence(first, last, step)
current = first
while current < last
yield current
current += step
end
end
sequence(1, 10, 2) { |n| puts n }
# Извежда 1, 3, 5, 7, 9
yield
се оценява до стойността на блока:
def calculate
result = yield(2)
"The result for 2 is #{result}"
end
calculate { |x| x ** 2 } # "The result for 2 is 4"
Или как можем да напишем filter
:
def filter(array)
result = []
array.each do |item|
result << item if yield item
end
result
end
filter([1, 2, 3, 4, 5]) { |n| n.odd? } # [1, 3, 5]
Разбира се, такъв метод в Ruby вече съществува – Enumerable#select
.
block_given?
ще ви каже дали методът е извикан с блок:
def i_can_haz_block
if block_given?
puts 'yes'
else
puts 'no'
end
end
i_can_haz_block # no
i_can_haz_block { 'something' } # yes
Ако имате ламбда, която искате да подадете като блок, може да ползвате &
:
is_odd = lambda { |n| n.odd? }
filter([1, 2, 3, 4, 5], &is_odd)
filter([1, 2, 3, 4, 5]) { |n| n.odd? }
Горните са (почти) еквиваленти. Има малка разлика в някои други случаи.
Ако искате да вземете блока като обект, има начин:
def invoke_with(*args, &block)
block.call *args
end
invoke_with(1, 2) { |a, b| puts a + b }
Може и така:
def make_block(&block)
block
end
doubler = make_block { |n| n * 2 }
doubler.call 2 # 4