Ruby и крассивый код #5: Рекомендации по стилю
февраля 24, 2011 | Published in Ruby, Основы | 26 Comments
Сегодня немного налажал с кодом и решил написать статью так сказать для закрепления представлений о хорошем коде в голове, ну и разумеется для того, чтобы поделиться имеющимися бедными познаниями с другими новичками. Более-менее опытным разработчикам все это уже, наверняка, известно, поэтому, если вы себя таковым считаете — можете не читать.
1. Используйте && и || вместо AND или OR.(в комментариях подсказывают, что они все же отличаются разным приоритетом).
Вместо:
if arr.size <= 10 OR (arr.size >= 5 AND arr[1] == 'first')
Лучше:
if arr.size <= 10 || (arr.size >= 5 && arr[1] == 'first')
2. Используйте для указания уровня вложенности кода двойные пробелы, вместо табуляций
Вместо:
class Example def ex_method(arg) puts "something" arg.each do |a| puts a end end end
Лучше:
class Example def ex_method(arg) puts "something" arg.each do |a| puts a end end end
3. Используйте осмысленные имена методов, переменных и т.д.
Вместо:
var = 36.6 m = %w{January February March April May June July Augest September October November December}
Лучше:
body_temperature = 36.6 months = %w{January February March April May June July Augest September October November December}
Bang! — методы — это методы, которые изменяют состояние объекта. не забывайте, что имена таких методов должны, как уже сложилась традиция заканчиваться знаком восклицания.
Знаком вопроса заканчиваются имена методов, которые в качестве результата работы возвращают булевый тип: true или false. В PHP такие методы еще именуют как is_<�имя метода>.
4. Страйтесь максимально отказаться от циклов в пользу итераторов. Итераторы — это Ruby Way!
Вместо:
for i in args do puts i end
Лучше:
args.each{|i| puts i}
5. Используйте фигурные скобки «{}» только для обрамления однострочных блоков кода.
Вместо:
args.each {|user| user.posts.each {|post| post.comments.each{|comment| puts comment } } }
Лучше:
args.each do |user| user.posts.each do |post| post.comments.each do |comment| puts comment end end end
Вместо:
1000.times do |num| puts num; end
Лучше:
1000.times {|num| puts num}
6. Оставляйте пробелы между операторами
Вместо:
num = 12/3*(34-5)+args.count/4
Лучше:
num = 12 / 3 * (34 - 5) + args.coutn / 4
Вместо:
if a>b&&c<b
Лучше:
if a > b && c < b
7. В Ruby все имеет значение true или false. Отказывайтесь от использования if для возврата true или false:
Вместо:
if a > b && c < b true else false end
Лучше:
a > b && c < b
8. Используйте одноименные операторы
Вместо:
a > b && c < b
Лучше:
a > b && b > c
9. Используйте параллельное присваивание
Вместо:
a = 10 b = 15 c = 20
Лучше:
a, b, c = 10, 15, 20 a, b, *c = [...] # если в коллекции больше 3 элементов, то с заберет на себя все оставшиеся после a и b значения.
10. Форматируйте или разбивайте длинные строки
Вместо:
@somethong = do_somethong :conditions => {:size => '23', :weight=> '56', :height => '165', :eyes_color=> 'blue', ...}
Лучше:
@somethong = do_something :conditions => { :size => '23', :weight=> '56', :height => '165', :eyes_color=> 'blue', ...}
Старайтесь избегать длинных строк кода и разбивать их на более короткие или форматировать в вышеприведенный способ. Оптимальной длиной считается строка кода до 70-80 символов.
Используйте специальный синтаксис для многострочных литералов:
Вместо:
@welcome_msg = "Ruby is…\nA dynamic, open source programming language \nwith a focus on simplicity and productivity.\nIt has an elegant syntax that is natural\nto read and easy to write."
Лучше:
@welcome_msg = <<msg ===== WELCOME ===== Ruby is… A dynamic, open source programming language with a focus on simplicity and productivity. It has an elegant syntax that is natural to read and easy to write. msg
11. Оператор return не является обязательным, однако его использование является хорошим стилем
def name @name end
Однако при написании более сложных и крупных по размеру методов желательно использовать return для явного указания значения, которое будет возвращено.
12. Минимизируйте код в методах и блогах
Большие методы следует разбивать на более мелкие, это будет способствовать повторному использованию кода (принципу DRY), а также сделает код более читабельным.
Вместо:
def reverse_qube_diff(a) [a, a.to_s.reverse.to_i, a - a.to_s.reverse.to_i, (a - a.to_s.reverse.to_i) ** 3] end
Лучше:
def num_reverse(n) n.to_s.reverse.to_i end def qube(n) n**3 end def reverse_qube_diff(a) [a, num_reverse(a), a - num_reverse(a), qube(a-num_reverse(a))] end
Пример не совсем хорошо показывает пользу такого подхода, но это первое, что пришло мне в голову. Если у вас есть пример лучше — напишите его в комментариях и я добавлю его в статью. Оптимальный размер метода до 7 строк. Это не означает, что вы должны все ваши методы разбивать до такой степени, главным здесь является выноска используемого несколько раз кода в отдельные метода, а также выноска крупных блоков кода.
13. Комментируйте код
Часто бывает сложно понять даже собственный код, который писался всего-то неделю назад. Для того, чтобы быстро вспомнить, что же вы там воротили, и чтобы программист работающий с вашим кодом мог быстрее войти в суть творящегося, следует обильно комментировать код. «Обильно» не значит, что закомментирован должен быть каждый метод, комментируйте, в первую очередь, API для пользователей вашего кода, методы, которые не используются на прямую могут документироваться очень кратко в формате «для себя». Небольшие, простые методы не следует документировать, это лишняя трата времени.
# Меняет порядок разрядов числа на обратный def num_reverse(n) n.to_s.reverse.to_i end def qube(n) n**3 end #Возвращает массив: #0 => число, аргумент метода #1 => числом производное от аргумента метода с обратным порядком разрядов #2 => разница между аргументом и производним от него числом с обратным подядком разрядов #3 => [2] возведенное в куб def reverse_qube_diff(a) [a, num_reverse(a), a - num_reverse(a), qube(a-num_reverse(a))] end
14. Не бойтесь синтаксического сахара
Вместо:
if @args.empty? puts "Error: Array is empty" else @args.each{|a| puts a} end
Лучше:
puts @args.empty? ? "Error: Array is empty" : @args.each{|a| a }
Используйте новый (начиная с Ruby 1.9.х) синтаксис для символов-ключей:
Вместо:
args = {:sunday => 127, :monday => 245, :tuesday => 312, :wednesday => 347, :thursday => 320, :friday => 250, :saturday => 0}
Лучше:
args = {sunday: 127, monday: 245, tuesday: 312, wednesday: 347, thursday: 320, friday: 250, saturday: 0}
15. Определяйте собственный метод to_s
Метод to_s имеется у каждого объекта и используется для строкового представления объекта. Метод puts использует метод to_s для получения строкового представления, которое и будет напечатано. Пример:
args = MyHashImp.new sunday: 127, monday: 245, tuesday: 312, wednesday: 347, thursday: 320, friday: 250, saturday: 0 class MyHashImp < Hash def initialize hash @hash = hash end def to_s output = "" max = 0 @hash.each_key do |k| max < k.size ? max = k.size : false end @hash.each{|k, v| output = output + "#{k} #{" " * (max - k.size)} store #{v}\n"} return output end end puts args
sunday store 127 monday store 245 tuesday store 312 wednesday store 347 thursday store 320 friday store 250 saturday store 0
16. Используйте пустые строки для разделения фрагментов кода
Используйте пустые строки для разделения, например, объявления методов, инициации переменных, блоков кода.
Вместо:
class MyHashImp < Hash def initialize hash @hash = hash end def to_s output = "" max = 0 @hash.each_key do |k| max < k.size ? max = k.size : false end @hash.each{|k, v| output = output + "#{k} #{" " * (max - k.size)} store #{v}\n"} return output end end
Лучше:
class MyHashImp < Hash def initialize hash @hash = hash end def to_s output = "" max = 0 @hash.each_key do |k| max < k.size ? max = k.size : false end @hash.each do |k, v| output = output + "#{k} #{" " * (max - k.size)} store #{v}\n" end return output end end
Таким образом вы разбиваете код на логические фрагменты и улучшаете его читабельность. Это особенно актуально для крупных блоков кода и методов.
17. Основной код должен относиться на первом месте
Вместо:
puts @args.empty? ? "Error: Array is empty" : @args.each{|a| a }
Лучше:
puts !@args.empty? ? @args.each{|a| a } : "Error: Array is empty"
Сюда же можно отнести использование unless вместо if. Лично мне больше нравится !<условие>, вместо unless.
18. Используйте CASE вместо группы IF или IF — ELSIF когда проверяется переменная
Вместо:
puts 1 if a = 1 puts 2 if a = 2 puts 'indefinite' if a = 10_000
Или:
if a = 1 puts 1 elsif a = 2 puts 2 elsif a = 10_000 puts "indefinite" end
Лучше:
puts case a when 1 1 When 2 2 when 10_000 "indefinite" end
19. Разбивайте регулярные выражения на более мелкие шаблоны
Вместо:
tel = '38-096-2870-430' pattern = /^([\d]{2})-([\d]{3})-([\d]{4}-[\d]{3})$/ m = tel.match(pattern)
Лучше:
tel = '38-096-2870-430' state = /([\d]{2})-/ station = /([\d]{3})-/ phone = /([\d]{4}-[\d]{3})/ pattern = /^#{state}#{station}#{phone}$/ m = tel.match(pattern)
Разбиение шаблона на логические части позволяет легче воспринимать синтаксис регулярных выражений, а также несколько упрощает их разработку и способствует чистоте кода, хотя количество строк и увеличилось.
Статья будет пополняться новыми рекомендациями. Пишите свои рекомендации в комментариях, и они будут добавлены в статью.
Некоторые из приведенных выше рекомендаций являются де-факто стандартом написания кода.
P.S. Хочу обидеть тех, кто никогда не читает комментарии, там сейчас много интересных моментов.
Рубрика : Ruby и красивый код
Лучшая благодарность автору — ваши комментарии!
февраля 24, 2011 at 23:27 (#)
1. Используйте && и || вместо AND или OR.
Вместо:
1
if arr.size = 5 AND arr[1] == ‘first’)
Лучше:
1
if arr.size = 5 && arr[1] == ‘first’)
вот это очень спорный момент , зависит от логики того что вы хотите получить
операторы || — OR и && — AND имеют разную степень связывания и ваш код в примере не равноценен
на хабре об этом уже писали
февраля 24, 2011 at 23:43 (#)
в примере к 15 пункту переменная min не используется
февраля 24, 2011 at 23:43 (#)
1. Используйте && и || вместо AND или OR.
Это разные операторы и разные функции выполняют. Первый совет бред
14. Не бойтесь синтаксического сахара
ruby-1.8.7-p330 :002 > args = {sunday: 127}
SyntaxError: compile error
(irb):2: odd number list for Hash
args = {sunday: 127}
^
(irb):2: syntax error, unexpected ‘:’, expecting ‘}’
args = {sunday: 127}
^
from (irb):2
oops?
февраля 24, 2011 at 23:54 (#)
в 17 пункте можно так:
puts @args.any? ? @args.each{|a| a } : «Error: Array is empty»
в 8 пункте: c < b > c, но c < b >= c
в 9 пункте: можно обойтись без квадратных скобок
февраля 24, 2011 at 23:57 (#)
будь неладны ббкоды )
в 8 пункте
февраля 25, 2011 at 00:00 (#)
(c меньше b) и (b больше c) не эквивалентны. Эквивалентна будет запись (b больше или равно c)
февраля 25, 2011 at 00:02 (#)
args = {sunday: 127}
такой сахар появился начиная с Ruby 1.9
февраля 25, 2011 at 00:56 (#)
>> args = {sunday: 127}
работает в ветке ruby 1.9
февраля 25, 2011 at 03:04 (#)
[b]shiroginne[/b], 14-ый совет будет работать в 1.9.2, вроде даже было в этом блоге.
февраля 25, 2011 at 05:01 (#)
«1. Используйте && и || вместо AND или OR.
Это разные операторы и разные функции выполняют. Первый совет бред»
Почему же?
февраля 25, 2011 at 08:44 (#)
По пункту 11 хочу заметить, что в Ruby стоит явно использовать оператор return, когда у вас может быть несколько точек выхода из метода.
февраля 25, 2011 at 08:48 (#)
И еще. Может стоит форматировать хэши в несколько строк, вот так:
args = {
sunday: 127,
monday: 245,
tuesday: 312,
wednesday: 347,
thursday: 320,
friday: 250,
saturday: 0
}
Как думаете?
февраля 25, 2011 at 09:05 (#)
Вертикальное форматирование кода и переменные — атавизм от языков низкого уровня: C, Pascal и.д.
Ruby возвращает результат выражения которым можно пользоваться, раньше надо было сохранять промежуточные результаты. Переменные нужны только для параметров функциий и блоков. Чем уже них область видимости тем скорей они будут удалены из памяти.
У человека два глаза и расположены они горизонтально. Широкий текст читать удобней с помощью переферийного зрения. Переверните монитор вертикально или сузьте какой либо текст и почувствуйте разничу восприятия и скорочтения.
1. Согласен лучше иместь во всем коде один подход. Это упрощает понимание.
10-13. Полностью не согласен и рекомендую делать с точностью до наоборот. На основании вышеизложенных причин «раздувания» текста.
return стоит использовать если логика возвращения результата неочевидна, но тут своит задуматься о причинах возникновения неоднозначностей.
18. Пример неудачен. Последовательные if не аналог ifelsif. В первом примере проверяються все условия, во втором они взаимоисключаются.
февраля 25, 2011 at 10:07 (#)
4) к сожалению цикли работают быстрее, нежели итераторы.
февраля 25, 2011 at 13:50 (#)
Роман, позвольте с вами не согласиться. Я несколько интересуюсь юзабилити и в разных авторитетных источниках встречал информацию о том, что текстовые блоки должны быть ограничены по размеру 70-80 символами максимум, чем окно редактора кода не текстовый блок? Это необходимо для того, чтобы сократить движения глазами, от чего глаза устают.
10, понятно, спорный момент, особенно в том несколько неудачном примере с форматированием хэша, но почему 11 — 13 пункты не нравятся?
Постфиксный if не то же самое,что и if-elsif-else, это понятно, что if-elsif-else прекращает работу, если на одном из этапов получаем true, однако в конкретно в приведенном примере результат одинаковый. В прочем, думаю, вы правы в своем замечании, сейчас поправлю.
Andy Да, там пример несколько не удачный с хэшем. Хотя, когда в метод передаются параметры хэшем, то достаточно удобно форматировать подобным образом. По крайней мере я так делаю и видел такой стиль в чужом коде.
exromany, да, тут комментарий с кодом написать, нужно коды всех символов знать=). Я если вижу какой-то непонятный набор символов в комментарии, то сразу пытаюсь его отредактировать, поэтому на будущее можете писать в удобный способ, я отредактирую, если что.
От чего же с меньше b и b больше c не эквивалентны? Это пример со строгой проверкой, когда «=» не учитывается. Или я что-то не понимаю?
Спасибо за найденый артефакт в коде (переменная min)
9 пункт подправил, спасибо!
Валентин, спасибо за инфу по поводу && и AND… Чесно признаться, не знал, что они чем-то отличаются. В свою защиту скажу, что с этой рекомендацией я сталкивался уже несколько раз. Сейчас разберусь с различиями и подправлю.
По поводу синтаксического сахара из 1.9.2. Меня уже не первый раз упрекают, но я сторонник использования новых плюшек и вообще я работаю под 1.9.2. Так что уж извиняйте =)
Андрей, я еще не видил, чтобы это было аргументом для отказа от итераторов.
P.S. Где еще можно так много полезного узнать, кроме как в комментариях? Спасибо всем!
февраля 25, 2011 at 13:56 (#)
exromany
Итератор .any?
вместо:
Все же, не самая лучшая идея, по моему, просто потому, что итераторы предназначены для других целей, а empty? специально на проверку на пустоту. Ну это, конечно, я уже придираюсь =)
февраля 25, 2011 at 14:19 (#)
>> 2. Используйте для указания уровня вложенности кода двойные пробелы, вместо табуляций
Если ваш редактор не позволяет установить ширину табуляции — выкиньте его!
февраля 25, 2011 at 15:52 (#)
> 4) к сожалению цикли работают быстрее, нежели итераторы.
Даже если это и так, в чем я лично сомневаюсь, то скорость написания программы важнее скорости ее выполнения. Ранняя оптимизация — плохая практика.
> Если ваш редактор не позволяет установить ширину табуляции – выкиньте его!
У меня в команде пять человек, каждый установит ширину табуляции [1,2,3,4,5] пробела.
Кого мне выкинуть в окно первым за такой вот совет? ;-)
> if vs ifelse приведенном примере результат одинаковый..
Не одинаковый по времени исполнения. Последовательные if выполнятся всегда, а в взаимоисклюающие не все.
13-14 я уже объяснил. Код раздувается ввысоту, а это плохо
Строки по ширине экрана(70-80) — согласен
Юзабилисты ? Когда я смотрю на интерфейс LinkedIn мне хочеться кого нибуть стукнуть за проделанную работу ;-)
февраля 25, 2011 at 20:02 (#)
2) potapuff +1 :)
Роман, пускай каждый установит ширину табуляции на экране так, как ему это удобно, но в коде это всё равно лучше оставить одним символом (\t). В конце концов, у разных программистов разные по диагонали мониторы.
4) У переменных внутри циклов область видимости отличается от тех же переменных, но внутри итераторов.
6) Чисто субъективно мне кажется, что это зависит от текста, окружающего операторы. Иногда подсветка синтаксиса решает и это бессымсленно раздувает строку.
9) [Немного снобства] Это же вроде параллельное, а не множественное присваивание.
10) admin, тут Я Вас поддержу, 80 колонок — стандарт де-факто для исходных текстов. Так ещё со времён DOS повелось. К тому же, Роман приводит спорный аргумент, что глаза расположены горизонтально, однако почему же тогда во всех без исключения газетах текст верстается в несколько колонок?
февраля 25, 2011 at 21:47 (#)
inst, чесно говоря, не знал как оно правильно называется, решил, что название «множественное присваивание» отлично отразит суть, сейчас поправлю.
февраля 28, 2011 at 07:33 (#)
«12. Минимизируйте код в методах и блогах»
наверное в блоКах?!
февраля 28, 2011 at 10:42 (#)
На счет итераторы vs циклы
Провел небольшое исследование, из которого следует, что цикл for..in работает немного медленнее, но при этом итераторы более юзабельны.
user system total real
for: 1.230000 0.000000 1.230000 ( 1.227438)
times: 1.120000 0.000000 1.120000 ( 1.126706)
each: 1.150000 0.000000 1.150000 ( 1.141455)
upto: 1.130000 0.000000 1.130000 ( 1.138592)
user system total real
for: 1.150000 0.010000 1.160000 ( 1.176419)
times: 1.130000 0.000000 1.130000 ( 1.126034)
each: 1.120000 0.000000 1.120000 ( 1.125477)
upto: 1.130000 0.000000 1.130000 ( 1.128533)
user system total real
for: 1.210000 0.000000 1.210000 ( 1.218572)
times: 1.140000 0.000000 1.140000 ( 1.145365)
each: 1.140000 0.000000 1.140000 ( 1.143632)
upto: 1.140000 0.000000 1.140000 ( 1.143849)
user system total real
for: 1.220000 0.000000 1.220000 ( 1.216285)
times: 1.120000 0.000000 1.120000 ( 1.126992)
each: 1.150000 0.000000 1.150000 ( 1.147207)
upto: 1.140000 0.000000 1.140000 ( 1.134411)
user system total real
for: 1.180000 0.000000 1.180000 ( 1.182742)
times: 1.130000 0.000000 1.130000 ( 1.126704)
each: 1.120000 0.010000 1.130000 ( 1.132910)
upto: 1.140000 0.000000 1.140000 ( 1.136653)
user avg
for: 1.198000
times: 1.128000
each: 1.136000
upto: 1.136000
февраля 28, 2011 at 11:30 (#)
16.
Сначала я хотел на этом примере напомнить про метод Object#tap, но он сюда не подходит
и в процессе получился такой отрефакторенный метод xD.
На счет Object#tap. (он же Object#returning в Rails)
Чтобы не плодить временных переменных можно делать так:
Описание Object#tap —
сентября 16, 2012 at 00:58 (#)
Очень хорошая статья.
правда есть недочеты:
1) В 1 пункте — совет не совсем корректен, использовать можно оба варианта, только нужно помнить что and и or имеют ниже приоритет (главное ниже оператора «=»), но это не делает их хуже || и &&. Правильней посоветовать использовать скобки чтоб себя обезопасить от ошибки.
2) В пункте 11 — насколько я понял, вы хотели подчеркнуть что return можно не! использовать (это тоже своего рода синтаксический сахар), но вышло скорее наоборот. Если добавить пример «как не надо делать», то всё станет на свои места.
3) В пункте 15 — вот это скорее плохой совет. Затирать встроенные методы может иногда и удобней, чем прописать вручную, или создать отдельный метод, но всегда повышает вероятность ошибок.
4) В пункте 19 — это может быть полезно при отладке, но создавать лишние переменные просто для того чтобы увеличить читабельность не целесообразно. Лучше просто разбить этот код на разные строки, как в пункте 10.
октября 22, 2012 at 11:21 (#)
В 9 пункте не произойдёт параллельного присваивания. переменной «а» присвоится массив. Массив нужно распаковать, добавив в начало унарный оператор звёздочку.
октября 22, 2012 at 11:24 (#)
Не, наврал… Извиняюсь