Các mô hình về vai trò là rất quan trọng.
-- Alex J. Murphy / thành viên RoboCop
Một điều luôn làm tôi suy nghĩ với vai trò của một Ruby coder - Các Python coder có một tài liệu gần như là chuẩn mực về phong cách viết code (PEP-8) và chúng ta, những Ruby coder, vẫn chưa có một tài liệu chính thức hướng dẫn về phong cách viết Ruby cũng như những mô hình thực hành tốt nhất. Và tôi tin rằng cần một phong cách viết code tốt. Tôi cũng tin rằng một cộng đồng lập trình lớn như cộng đồng Ruby, hẳn có đủ năng lực viết ra những tài liệu cần thiết này.
Bản tài liệu này ban đầu được viết để dùng hướng dẫn cách viết code Ruby cho nội bộ công ty chúng tôi (với sự tin tưởng từ các bạn). Trong một số thời điểm, tôi quyết định rằng công việc tôi đang làm hẳn là rất thú vị với các thành viên của cộng đồng Ruby nói chung và nó cũng cần thiết cho nội bộ các công ty khác. Và thế giới sẽ được hưởng lợi từ mô hình dựa vào cộng đồng và phản biện từ cộng đồng sẽ giúp tìm ra những bài học áp dụng, những thành ngữ và những quy định viết code Ruby.
Từ khi bắt đầu viết, tôi đã nhận được rất nhiều phản hồi, đặc biệt là các thành viên thuộc cộng đồng Ruby trên thế giới. Xin cảm ơn các bạn đã gợi ý và hỗ trợ cho tôi! Cùng nhau làm việc, chúng ta có viết ra cho một nguồn tư liệu mang lại nhiều lợi ích cho mỗi nhà phát triển code Ruby.
Bằng cách này, nếu bạn đang tìm hiểu Rails, bạn có thể muốn tham khảo thêm tài liệu Hướng dẫn viết code Ruby on Rails 3 & 4 / Ruby on Rails 3 & 4 Style Guide.
Bản hướng dẫn cách viết code Ruby giới thiệu những phương án thực hành tốt nhất mà các lập trình viên trong thế giới Ruby đã viết và được cập nhật, chỉnh sửa bởi các thành viên khác. Bản hướng dẫn vừa đưa ra phương án sử dụng thực tế tốt nhất, vừa đề cập cả những ý tưởng đã bị nhận xét là không tối ưu. Việc này sẽ giúp tránh được rủi ro cho tất cả mọi người - Việc này hẳn cũng tốt đấy chứ.
Bản hướng dẫn này được chia thành các phần bao gồm các quy tắc có liên quan đến nhau. Tôi đã cố gắng để thêm các quy tắc liên quan đằng sau (Tôi cho rằng nếu các bạn không nhất thiết phải xem, có thể bỏ qua những quy tắc đó).
Tôi không vô cớ mà đưa ra những quy tắc viết code này - chúng đều dựa trên những kinh nghiệm sâu sắc trong nghề của tôi khi làm kỹ sư phát triển phần mềm chuyên nghiệp. Các quy tắc cũng được tổng hợp từ những kiến nghị và gợi ý từ các thành viên khác, cũng như những nguồn tài liệu về lập trình Ruby được đánh giá cao, như là "Lập trình Ruby 1.9 / Programming Ruby 1.9" và "Ngôn ngữ lập trình Ruby / The Ruby Programming Language".
Bản hướng dẫn này vẫn đang tiếp tục được cập nhật - một số quy tắc đang thiếu các ví dụ mình hoạ, một số ví dụ minh họa thì không được rõ ý. Những vấn đề này sẽ được giải quyết trong thời gian tới - hãy tin là như vậy.
Bạn có thể xuất ra file PDF hay bản HTML nhờ sử dụng các công cụ Transmuter.
RuboCop là bộ phân tích mã code, dựa trên bản hướng dẫn này.
Các bản dịch ở các ngôn ngữ khác:
- Tiếng Trung Giản thể
- Tiếng Trung Phồn thể
- Tiếng Pháp
- Tiếng Đức
- Tiếng Nhật
- Tiếng Hàn Quốc
- Tiếng Bồ Đào Nha
- Tiếng Nga
- Tiếng Tây Ban Nha
- Tiếng Việt
- Bố cục trình bày khi viết code / Source code layout
- Cú pháp / Syntax
- Cách đặt tên / Naming
- Viết chú thích / Comments
- Lớp đối tượng và Module / Classes & Modules
- Exceptions
- Collections
- Numbers
- Strings
- Regular Expressions
- Percent Literals
- Metaprogramming
- Misc
- Các công cụ / Tools
Gần đây, mọi người quả quyết rằng phong cách code của tất cả mọi người đều rất tởm và không thể đọc được, ngoại trừ chính họ. Nếu bỏ đi cụm từ "ngoại trừ chính họ" thì có lẽ rằng họ đã gần đúng :D...
-- Jerry Coffin
-
Sử dụng chuẩn mã hóa
UTF-8
khi code. [link] -
Dùng hai (02) Khoảng trắng(spaces) cho mỗi tầng thụt đầu dòng (hay còn gọi là tab mềm (soft tabs)). Không sử dụng tab cứng (hard tabs). [link]
# bad - four spaces def some_method do_something end # good def some_method do_something end
-
Sử dụng dấu cuối dòng theo Unix-style. (*Người dùng BSD/Solaris/Linux/OS X được kích hoạt sẵn, người dùng Windows cần phải để ý.) [link]
-
Nếu bạn đang dùng Git, bạn nên cấu hình như sau để chặn các dấu cuối dòng của Windows:
$ git config --global core.autocrlf true
-
-
Không dùng dấu chấm phẩy (
;
) để ngăn cách các câu lệnh và biểu thức, mà hãy viết mỗi câu 1 dòng. [link]# bad puts 'foobar'; # Thừa dấu chấm phẩy puts 'foo'; puts 'bar' # hai câu lệnh trên cùng 1 dòng # good puts 'foobar' puts 'foo' puts 'bar' puts 'foo', 'bar' # câu lệnh này sẽ tự tách làm hai lệnh `puts`
-
Nếu lớp không có nội dung, nên viết trên một dòng. [link]
# bad class FooError < StandardError end # okish class FooError < StandardError; end # good FooError = Class.new(StandardError)
-
Tránh kiểu viết gom phương thức vào một dòng. Mặc dù ở một vài nơi nó vẫn được sử dụng, có một vài điểm đặc thù khiến người ta cảm thấy khó chịu khi gặp nó. Dù sao vẫn không nền viết nhiều hơn một biểu thức, câu lệnh trên một hàng. [link]
# bad def too_much; something; something_else; end # okish - dấu ; ngay sau tên hàm bắt buộc phải có def no_braces_method; body end # okish - dấu ; sau body thì không bắt buộc def no_braces_method; body; end # okish - đúng cú pháp, nhưng không có ; sau tên hàm khiến khó đọc hơn def some_method() body end # good def some_method body end
Ngoại lệ: phương thức không có body thì nên viết trên một hàng
# good def no_op; end
-
Đặt khoảng trắng trước và sau toán tử , sau dấu phẩy, dấu hai chấm và dấu chấm phẩy. Khoảng trắng có thể (hầu hết) là không bắt buộc, nhưng nó sẽ giúp code dễ đọc, dễ viết hơn. [link]
sum = 1 + 2 a, b = 1, 2 class FooError < StandardError; end
Ngoại lệ duy nhất, liên quan đến các toán tử, là toán tử mũ:
# bad e = M * c ** 2 # good e = M * c**2
-
KHÔNG DÙNG khoảng trắng sau
(
,[
hay trước]
,)
. DÙNG khoảng trắng quanh{
và trước}
. [link]# bad some( arg ).other [ 1, 2, 3 ].each{|e| puts e} # good some(arg).other [1, 2, 3].each { |e| puts e }
{
và}
cần được làm rõ một chút, nó được dùng cho cả block, hash và dùng để nhúng biến vào string.Với hash, có hai cách viết được sử dụng:
- Cách thứ nhất dễ đọc hơn, và thường được sử dụng nhiều hơn trong cộng đồng Ruby.
- Cách thứ hai có điểm mạnh là nó dùng để phân biệt giữa block và hash. Cho dù bạn chọn cách nào, thì cũng nên theo một cách thôi.
# good - khoảng trắng trước { và sau } { one: 1, two: 2 } # good - không có khoảng trắng trước { và sau } {one: 1, two: 2}
Khi dùng để nhúng vào string, không nên dùng khoảng trắng giữa cặp ngoặc nhọn.
# bad "From: #{ user.first_name }, #{ user.last_name }" # good "From: #{user.first_name}, #{user.last_name}"
-
Không có khoảng trắng sau
!
. [link]# bad ! something # good !something
-
Không dùng khoảng trắng khi khai báo khoảng (range). [link]
# bad 1 .. 3 'a' ... 'z' # good 1..3 'a'...'z'
-
when
vàcase
thụt đầu dòng cùng cấp. Style này được khuyến nghị trong cả "The Ruby Programming Language" và "Programming Ruby". [link]# bad case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end # good case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end
-
Khi gán kết quả của một biểu thức cho một biến, cặp
case
,when
hayif
,else
cũng phải cùng cấp. [link]# bad - pretty convoluted kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end # good - it's apparent what's going on kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end # good (and a bit more width efficient) kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end
-
Thêm một dòng trống giữa các phương thức, và các nhóm xử lý logic. [link]
def some_method data = initialize(options) data.manipulate! data.result end def some_method result end
-
KHÔNG DÙNG dấu phẩy sau tham số cuối cùng khi khai báo phương thức, đặc biệt khi những tham số không nằm trên những dòng riêng biệt. [link]
# bad - cách làm này giúp dễ thêm/xóa/di chuyển tham số, # nhưng không khuyến khích dùng some_method( size, count, color, ) # bad some_method(size, count, color, ) # good some_method(size, count, color)
-
Dùng khoảng trắng quanh toán tử
=
khi gán giá trị mặc định: [link]# bad def some_method(arg1=:default, arg2=nil, arg3=[]) # do something... end # good def some_method(arg1 = :default, arg2 = nil, arg3 = []) # do something... end
Trong khi một số sách về Ruby khuyên dùng cách một, nhưng cộng đồng lại thích dùng cách hai nhiều hơn.
-
Tránh dùng dấu ngắt dòng
\
khi không bắt buộc. Thực tế, chỉ dùng nó khi cần ngắt dòng string thôi. [link]# bad result = 1 - \ 2 # good (but still ugly as hell) result = 1 \ - 2 long_string = 'First part of the long string' \ ' and second part of the long string'
-
Với cách gọi phương thức nhiều lần liên tiếp, có hai kiểu thông dụng, kiểu nào cũng được cả:
.
ở đầu dòng mới (Phương án A).
ở cuối dòng cũ (Phương án B). Mặc dù dùng kiểu nào thì cũng nên dùng nhất quán một kiểu. [link]-
(Phương án A)
one.two.three .four
-
(Phương án B)
one.two.three. four
Xem thêm về cuộc thảo luận chọn cách nào: tại đây.
-
-
Khi gọi phương thức có nhiều đối số, nên xuống dòng, và các đối số này cần thụt đầu dòng bằng nhau và ở cùng cấp với đối số đầu tiên. Nếu đối số này dài thì có thể dùng cách thụt đầu dòng bình thường. [link]
# mẫu (dòng quá dài) def send_mail(source) Mailer.deliver(to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text) end # bad (thụt đầu dòng hai lần) def send_mail(source) Mailer.deliver( to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text) end # good def send_mail(source) Mailer.deliver(to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text) end # good (thụt đầu dòng bình thường) def send_mail(source) Mailer.deliver( to: '[email protected]', from: '[email protected]', subject: 'Important message', body: source.text ) end
-
Nếu mảng có nhiều phần tử, nên đưa xuống dòng và thụt đầu dòng cùng cấp. [link]
# bad menu_item = ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam'] # good menu_item = [ 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam' ] # good menu_item = ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']
-
Với số lớn, thêm dấu gạch dưới
_
cho dễ đọc. [link]# bad - có bao nhiêu số `0`? num = 1000000000 # good - dễ đọc hơn hẳn đúng không :P num = 1_000_000_000
-
Sử dụng Rdoc và convention của nó để làm API documentation. KHÔNG đặt khoảng trắng giữa khối comment và từ khóa
def
. [link] -
Giới hạn dòng ở 80 ký tự. [link]
-
Tránh dùng khoảng trắng thừa (thường gặp ở cuối dòng). [link]
-
Cuối file nên có thêm một dòng trống. [link]
-
KHÔNG DÙNG block comments. [link]
# bad =begin comment line another comment line =end # good # comment line # another comment line
-
CHỈ sử dụng
::
cho hằng số (của cả classes và modules) và các hàm khởi tạo (constructors) (vd:Array()
hoặcNokogiri::HTML()
). KHÔNG sử dụng::
cho các lời gọi hàm thông thường. [link]# bad SomeClass::some_method some_object::some_method # good SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass()
-
Chỉ dùng
()
khi khai báo phương thức có tham số. [link]# bad def some_method() # body omitted end # good def some_method # body omitted end # bad def some_method_with_parameters param1, param2 # body omitted end # good def some_method_with_parameters(param1, param2) # body omitted end
-
Nếu phương thức có tham số mặc định, đặt nó ở cuối cùng. Nếu đặt trước một tham số khác thì hậu quả thật là khôn lường :3 Ruby nó hành xử vui lắm :P [link]
# bad def some_method(a = 1, b = 2, c, d) puts "#{a}, #{b}, #{c}, #{d}" end some_method('w', 'x') # => '1, 2, w, x' some_method('w', 'x', 'y') # => 'w, 2, x, y' some_method('w', 'x', 'y', 'z') # => 'w, x, y, z' # good def some_method(c, d, a = 1, b = 2) puts "#{a}, #{b}, #{c}, #{d}" end some_method('w', 'x') # => '1, 2, w, x' some_method('w', 'x', 'y') # => 'y, 2, w, x' some_method('w', 'x', 'y', 'z') # => 'y, z, w, x'
-
TRÁNH khai báo biến song song, sẽ gây khó khăn khi đọc. Khai báo biến song song chỉ dùng khi trả kết quả phương thức, dùng với toán tử splat, hay dùng để hoán đổi giá trị hai biến. NÊN khai báo mỗi biến một hàng. [link]
# bad a, b, c, d = 'foo', 'bar', 'baz', 'foobar' # good a = 'foo' b = 'bar' c = 'baz' d = 'foobar' # good - hoán đổi giá trị hai biến a = 'foo' b = 'bar' a, b = b, a puts a # => 'bar' puts b # => 'foo' # good - method return def multi_return [1, 2] end first, second = multi_return # good - dùng với splat first, *list = [1, 2, 3, 4] # first => 1, list => [2, 3, 4] hello_array = *'Hello' # => ["Hello"] a = *(1..3) # => [1, 2, 3]
-
Tránh dùng dấu gạch dưới thừa thãi trong khai báo song song. Nên dùng
_
với tên biến cho rõ nghĩa._
cần thiết khi có một biếnsplat
, và biến này thì không cần_
. [link]# bad foo = 'one,two,three,four,five' # Phép gán không cần thiết thì không cung cấp bất kỳ thông tin hữu ích nào first, second, _ = foo.split(',') first, _, _ = foo.split(',') first, *_ = foo.split(',') # good foo = 'one,two,three,four,five' # Dấu `_` được dùng khi ta cần lấy hết ra, trừ phần tử cuối cùng. *beginning, _ = foo.split(',') *beginning, something, _ = foo.split(',') a, = foo.split(',') a, b, = foo.split(',') # Việc gán cho biến không sử dụng là không cần thiết, # nhưng nó cung cấp thông tin hữu ích cho ta. first, _second = foo.split(',') first, _second, = foo.split(',') first, *_ending = foo.split(',')
-
Đừng dùng
for
, trừ khi có lý do chính đáng. Thay vào đó hãy dùng vòng lặp.for
là một dạng củaeach
, nhưngfor
không hỗ trợ scope (each
thì có) và biến trongfor
thì có thể truy cập từ bên ngoài block. [link]arr = [1, 2, 3] # bad for elem in arr do puts elem end # ra khỏi vòng `for` ta vẫn truy cập biến `elem` được elem # => 3 # good arr.each { |elem| puts elem } elem # => NameError: undefined local variable or method `elem'
-
KHÔNG dùng
then
trongif/unless
nhiều cấp. [link]# bad if some_condition then # body omitted end # good if some_condition # body omitted end
-
LUÔN đặt điều kiện cùng cấp với
if
/unless
. [link]# bad if some_condition do_something do_something_else end # good if some_condition do_something do_something_else end
-
NÊN dùng toán tử ba ngôi (
?:
) hơn là cấu trúcif/then/else/end
. Nó thông dụng hơn và giúp code ngắn gọn súc tích hơn. [link]# bad result = if some_condition then something else something_else end # good result = some_condition ? something : something_else
-
KHÔNG nên dùng toán tử ba ngôi lồng nhau. Trong trường hợp này thì nên dùng
if/else
. [link]# bad some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else # good if some_condition nested_condition ? nested_something : nested_something_else else something_else end
-
KHÔNG dùng
if x; ...
. Trong trường hợp này thì dùng toán tử ba ngôi. [link]# bad result = if some_condition; something else something_else end # good result = some_condition ? something : something_else
-
Để ý rằng
if
casecase
có trả về kết quả. [link]# bad if condition result = x else result = y end # good result = if condition x else y end
-
DÙNG
when x then ...
cho một-dòng cases. Cú phápwhen x: ...
bị remove từ Ruby 1.9. [link] -
KHÔNG dùng
when x; ...
. Xem mục ở trên. [link] -
DÙNG
!
thay chonot
. [link]# bad - parentheses are required because of op precedence x = (not something) # good x = !something
-
Tránh dùng
!!
. [link]# bad x = 'test' # obscure nil check if !!x # body omitted end x = false # double negation is useless on booleans !!x # => false # good x = 'test' unless x.nil? # body omitted end
-
KHÔNG dùng từ khóa
and
vàor
. Thay vào đó hãy dùng&&
và||
. [link]# bad # boolean expression if some_condition and some_other_condition do_something end # control flow document.saved? or document.save! # good # boolean expression if some_condition && some_other_condition do_something end # control flow document.saved? || document.save!
-
Tránh dùng
?:
nhiều dòng; thay vào đó hãy dùngif/unless
. [link] -
Dùng
if/unless
cho single-line body. [link]# bad if some_condition do_something end # good do_something if some_condition # another good option some_condition && do_something
-
Tránh dùng
if/unless
ở cuối block khi không cần thiết. [link]# bad 10.times do # multi-line body omitted end if some_condition # good if some_condition 10.times do # multi-line body omitted end end
-
Tránh dùng
if/unless/while/until
lồng nhau. Nên dùng chung&&/||
nếu thích hợp. [link]# bad do_something if other_condition if some_condition # good do_something if some_condition && other_condition
-
Nếu điều kiện là
false
, dùngunless
thay vìif not
. Hoặc dùng||
cũng được. [link]# bad do_something if !some_condition # bad do_something if not some_condition # good do_something unless some_condition # another good option some_condition || do_something
-
KHÔNG dùng
unless
cùng vớielse
. Thay vào đó hãy viết với biểu thứctrue
. [link]# bad unless success? puts 'failure' else puts 'success' end # good if success? puts 'success' else puts 'failure' end
-
KHÔNG dùng cặp ngoặc tròn
()
vớiif/unless/while/until
. [link]# bad if (x > 10) # body omitted end # good if x > 10 # body omitted end
Tuy nhiên, có một ngoại lệ, có tên là safe assignment in condition.
-
KHÔNG dùng
while/until condition do
chowhile/until
nhiều dòng. [link]# bad while x > 5 do # body omitted end until x > 5 do # body omitted end # good while x > 5 # body omitted end until x > 5 # body omitted end
-
Đặt
while/until
ở sau trong trường hợp body một dòng. [link]# bad while some_condition do_something end # good do_something while some_condition
-
Ưu tiên
until
hơnwhile
cho biểu thứcfalse
. [link]# bad do_something while !some_condition # good do_something until some_condition
-
Dùng
Kernel#loop
thay vìwhile/until
khi cần lặp vô hạn. [link]# bad while true do_something end until false do_something end # good loop do do_something end
-
Dùng
Kernel#loop
cùng vớibreak
thay vìbegin/end/until
haybegin/end/while
kiểu lặp trước, kiểm tra sau. [link]# bad begin puts val val += 1 end while val < 0 # good loop do puts val val += 1 break unless val < 0 end
-
Với các phương thức mặc định của DSL (vd: Rake, Rails, RSpec), hay các phương thức có chứa "từ khóa" trong Ruby (vd:
attr_reader
,puts
) và các phương thức truy cập thuộc tính thì không dùng()
. Còn lại là dùng hết. [link]class Person # bad attr_reader(:name, :age) # good attr_reader :name, :age # body omitted end # bad temperance = Person.new 'Temperance', 30 # good temperance = Person.new('Temperance', 30) # bad puts(temperance.age) # good puts temperance.age # bad x = Math.sin y # good x = Math.sin(y) # bad array.delete e # good array.delete(e) # bad expect(bowling.score).to eq 0 # good expect(bowling.score).to eq(0)
-
Nếu hash đã tường minh rồi thì không cần cặp
{}
nữa. [link]# bad user.set({ name: 'John', age: 45, permissions: { read: true } }) # good user.set(name: 'John', age: 45, permissions: { read: true })
-
Nếu phương thức hệ thống thì có thể bỏ luôn cả
()
và{}
. [link]class Person < ActiveRecord::Base # bad validates(:name, { presence: true, length: { within: 1..10 } }) # good validates :name, presence: true, length: { within: 1..10 } end
-
Gọi phương thức không có tham số thì bỏ luôn
()
. [link]# bad Kernel.exit!() 2.even?() fork() 'test'.upcase() # good Kernel.exit! 2.even? fork 'test'.upcase
-
Nếu block chỉ làm một việc thì ưu tiên dùng shorthand. [link]
# bad names.map { |name| name.upcase } # good names.map(&:upcase)
-
Ưu tiên
{...}
hơndo...end
cho block một dòng. Tránh dùng{...}
cho block nhiều dòng. Luôn dùngdo...end
cho khối điều khiển và khai báo phương thức. [link]names = %w(Bozhidar Steve Sarah) # bad names.each do |name| puts name end # good names.each { |name| puts name } # bad names.select do |name| name.start_with?('S') end.map { |name| name.upcase } # good names.select { |name| name.start_with?('S') }.map(&:upcase)
-
Cân nhắc sử dụng đối số cho block tường minh để tránh việc viết block mà chỉ truyền các đối số của nó cho một block.. [link]
require 'tempfile' # bad def with_tmp_dir Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments end end # good def with_tmp_dir(&block) Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir, &block) end end with_tmp_dir do |dir| puts "dir is accessible as a parameter and pwd is set: #{dir}" end
-
Tránh dùng
return
, chỉ nên dùng trong luồng điều khiểnif..else
chẳng hạn. [link]# bad def some_method(some_arr) return some_arr.size end # good def some_method(some_arr) some_arr.size end
-
Tránh dùng
self
khi không bắt buộc. Chỉ dùng nó khi cần ghi giá trị vào biến. [link]# bad def ready? if self.last_reviewed_at > self.last_updated_at self.worker.update(self.content, self.options) self.status = :in_progress end self.status == :verified end # good def ready? if last_reviewed_at > last_updated_at worker.update(content, options) self.status = :in_progress end status == :verified end
-
As a corollary, avoid shadowing methods with local variables unless they are both equivalent. [link]
class Foo attr_accessor :options # ok def initialize(options) self.options = options # both options and self.options are equivalent here end # bad def do_something(options = {}) unless options[:when] == :later output(self.options[:message]) end end # good def do_something(params = {}) unless params[:when] == :later output(options[:message]) end end end
-
Không dùng kết quả của phép gán
=
trong biểu thức điều kiện nếu như không có cặp ngoặc tròn. Xem thêm safe assignment in condition. [link]# bad (+ a warning) if v = array.grep(/foo/) do_something(v) # some code end # good (MRI would still complain, but RuboCop won't) if (v = array.grep(/foo/)) do_something(v) # some code end # good v = array.grep(/foo/) if v do_something(v) # some code end
-
Dùng kiểu gán rút gọn khi có thể [link]
# bad x = x + y x = x * y x = x**y x = x / y x = x || y x = x && y # good x += y x *= y x **= y x /= y x ||= y x &&= y
-
Dùng
||=
để khởi tạo giá trị khi biến đó chưa được khởi tạo [link]# bad name = name ? name : 'Bozhidar' # bad name = 'Bozhidar' unless name # good - set name to 'Bozhidar', only if it's nil or false name ||= 'Bozhidar'
-
Không dùng
||=
để khởi tạo biến boolean. (Xét trường hợp biến đó mang giá trịfalse
sẵn rồi.) [link]# bad - would set enabled to true even if it was false enabled ||= true # good enabled = true if enabled.nil?
-
Dùng
&&=
để tiền xử lý biến khi nó có thể tồn tại hoặc không. Việc dùng&&=
sẽ chỉ thay đổi giá trị của biến khi nó đã tồn tại. Không cần thiết phải kiểm tra xem biến tồn tại hay chưa. [link]# bad if something something = something.downcase end # bad something = something ? something.downcase : nil # ok something = something.downcase if something # good something = something && something.downcase # better something &&= something.downcase
-
Tránh việc ngầm định việc sử dụng của toán tử
===
. [link]# bad Array === something (1..100) === 7 /something/ === some_string # good something.is_a?(Array) (1..100).include?(7) some_string =~ /something/
-
Tránh dùng
eql?
, hãy dùng==
. [link]# bad - eql? is the same as == for strings 'ruby'.eql? some_str # good 'ruby' == some_str 1.0.eql? x # eql? makes sense here if want to differentiate between Integer and Float 1
-
Tránh sử dụng các biến đặc biệt kiểu Perl (như
$:
,$;
,.. ). Chúng khá là khó hiểu và cách sử dụng chúng trong bất cứ tình huống nào ngoài các đoạn code một dòng thì không được khuyến khích. Sử dụng các alias thân thiện với con người được cung cấp bởi thư việntiếng Anh
[link]# bad $:.unshift File.dirname(__FILE__) # good require 'English' $LOAD_PATH.unshift File.dirname(__FILE__)
-
Không dùng khoảng trắng giữa tên hàm và dấu ngoặc mở
(
[link]# bad f (3 + 2) + 1 # good f(3 + 2) + 1
-
Nếu đối số đầu tiên của phương thức có dùng ngoặc tròn thì lời gọi phương thức phải có ngoặc tròn. Vd:
f((3 + 2) + 1)
. [link] -
Luôn chạy trình biên dịch Ruby với tuy chọn
-w
, nó sẽ cảnh báo nếu ta quên một quy tắc nào ở trên. [link] -
Không sử dụng các định nghĩa phương thức lồng nhau, thay vào đó hãy dùng lambda. Các định nghĩa phương thức lồng nhau thật ra đưa các phương thức trong cùng phạm vi (ví dụ lớp) như là một phương thức bên ngoài. Hơn nữa, các phương thức lồng sẽ bị định nghĩa lại mỗi lần phương thức chứa nó được gọi. [link]
# bad def foo(x) def bar(y) # body omitted end bar(x) end # good - the same as the previous, but no bar redefinition on every foo call def bar(y) # body omitted end def foo(x) bar(x) end # also good def foo(x) bar = ->(y) { ... } bar.call(x) end
-
Dùng cú pháp lambda mới cho block có một dòng. Nếu nhiều dòng thì dùng từ khóa
lambda
. [link]# bad l = lambda { |a, b| a + b } l.call(1, 2) # correct, but looks extremely awkward l = ->(a, b) do tmp = a * 7 tmp * b / 50 end # good l = ->(a, b) { a + b } l.call(1, 2) l = lambda do |a, b| tmp = a * 7 tmp * b / 50 end
-
Khi dùng lambda với biến, nên đặt biến trong ngoặc tròn. [link]
# bad l = ->x, y { something(x, y) } # good l = ->(x, y) { something(x, y) }
-
Lambda không có tham số thì bỏ cặp ngoặc tròn đi. [link]
# bad l = ->() { something } # good l = -> { something }
-
Ưu tiên
proc
hơnProc.new
. [link]# bad p = Proc.new { |n| puts n } # good p = proc { |n| puts n }
-
Ưu tiên
proc.call()
hơnproc[]
hayproc.()
cho cả lambdas và procs. [link]# bad - looks similar to Enumeration access l = ->(v) { puts v } l[1] # also bad - uncommon syntax l = ->(v) { puts v } l.(1) # good l = ->(v) { puts v } l.call(1)
-
Dùng tiền tố
_
cho biến không dùng trong block và biến cục bộ. Hoặc chỉ dùng_
cũng được. [link]# bad result = hash.map { |k, v| v + 1 } def something(x) unused_var, used_var = something_else(x) # some code end # good result = hash.map { |_k, v| v + 1 } def something(x) _unused_var, used_var = something_else(x) # some code end # good result = hash.map { |_, v| v + 1 } def something(x) _, used_var = something_else(x) # some code end
-
Dùng
$stdout/$stderr/$stdin
thay choSTDOUT/STDERR/STDIN
.STDOUT/STDERR/STDIN
là những hằng số, và ta có thẻ thay đổi mấy hằng số đó. [link] -
Dùng
warn
thay cho$stderr.puts
. Dùngwarn
sẽ rõ nghĩa hơn. Đồng thời cho phép ta bỏ qua warning nếu cần (bằng cách set warn level về 0 thông qua-W0
). [link] -
Ưu tiên dùng
sprintf
và bí danhformat
của nó hơn là phương thứcString#%
. [link]# bad '%d %d' % [20, 10] # => '20 10' # good sprintf('%d %d', 20, 10) # => '20 10' # good sprintf('%{first} %{second}', first: 20, second: 10) # => '20 10' format('%d %d', 20, 10) # => '20 10' # good format('%{first} %{second}', first: 20, second: 10) # => '20 10'
-
Ưu tiên dùng
Array#join
hơn làArray#*
với tham số là string. [link]# bad %w(one two three) * ', ' # => 'one, two, three' # good %w(one two three).join(', ') # => 'one, two, three'
-
Dùng
Array()
thay vì kiểm traArray
một cách tường minh hay[*var]
, [link]# bad paths = [paths] unless paths.is_a? Array paths.each { |path| do_something(path) } # bad (always creates a new Array instance) [*paths].each { |path| do_something(path) } # good (and a bit more readable) Array(paths).each { |path| do_something(path) }
-
Khi xử lý logic dải mà phức tạp, dùng dải (range) hoặc
Comparable#between?
khi có thể. [link]# bad do_something if x >= 1000 && x <= 2000 # good do_something if (1000..2000).include?(x) # good do_something if x.between?(1000, 2000)
-
Ưu tiên dùng các phương thức có sẵn hơn là so sánh tường minh với
==
. So sánh với số cũng chấp nhận được. [link]# bad if x % 2 == 0 end if x % 2 == 1 end if x == nil end # good if x.even? end if x.odd? end if x.nil? end if x.zero? end if x == 0 end
-
Khi kiểm tra điều kiện thì không cần kiểm tra với
nil
trừ khi làm với boolean. [link]# bad do_something if !something.nil? do_something if something != nil # good do_something if something # good - dealing with a boolean def value_set? !@some_boolean.nil? end
-
Avoid the use of
BEGIN
blocks. [link] -
Do not use
END
blocks. UseKernel#at_exit
instead. [link]# bad END { puts 'Goodbye!' } # good at_exit { puts 'Goodbye!' }
-
Tránh dùng flip-flops. [link]
-
Tránh dùng điều kiện lồng. Thay vào đó hãy kiểm tra dữ liệu trước. [link]
# bad def compute_thing(thing) if thing[:foo] update_with_bar(thing) if thing[:foo][:bar] partial_compute(thing) else re_compute(thing) end end end # good def compute_thing(thing) return unless thing[:foo] update_with_bar(thing[:foo]) return re_compute(thing) unless thing[:foo][:bar] partial_compute(thing) end
Ưu tiên dùng
next
trong vòng lặp thay vì khối điều kiện.# bad [0, 1, 2, 3].each do |item| if item > 1 puts item end end # good [0, 1, 2, 3].each do |item| next unless item > 1 puts item end
-
Ưu tiên dùng
map
hơncollect
,find
hơndetect
,select
hơnfind_all
,reduce
hơninject
vàsize
hơnlength
. [link] -
Đừng dùng
count
để thay chosize
. Với đối tượngEnumerable
hay làArray
, nó sẽ duyệt qua từng phần tử để đếm số lượng phần tử, sẽ rất tốn thời gian. [link]# bad some_hash.count # good some_hash.size
-
Dùng
flat_map
thay chomap
+flatten
. Cái này không áp dụng cho mảng sâu hơn 2 cấp, vd: nếuusers.first.songs == ['a', ['b','c']]
, thì nên dùngmap + flatten
hơn làflat_map
.flat_map
làm giảm đi 1 cấp, trong khiflatten
làm phẳng hết về còn 1 cấp thôi. [link]# bad all_songs = users.map(&:songs).flatten.uniq # good all_songs = users.flat_map(&:songs).uniq
-
Ưu tiên dùng
reverse_each
hơnreverse.each
vì một số lớp màinclude Enumerable
sẽ hoạt động hiệu quả hơn. [link]# bad array.reverse.each { ... } # good array.reverse_each { ... }
Khó khăn duy nhất trong lập trình là việc lưu giữ thông tin không hợp lệ và việc đặt tên.
-- Phil Karlton
-
Dùng tiếng Anh để đặt tên. [link]
# bad - Dùng các ký tự không thuộc bảng mã ascii заплата = 1_000 # bad - tên này là tiếng Bulgarian zaplata = 1_000 # good salary = 1_000
-
Dùng
snake_case
cho tên phương thức, biến và nhãn (symbol). [link]# bad :'some symbol' :SomeSymbol :someSymbol someVar = 5 def someMethod # some code end def SomeMethod # some code end # good :some_symbol def some_method # some code end
-
Dùng
CamelCase
cho tên lớp và module. (Giữ những từ viết tắt như HTTP, RFC, XML viết hoa) [link]# bad class Someclass # some code end class Some_Class # some code end class SomeXml # some code end class XmlSomething # some code end # good class SomeClass # some code end class SomeXML # some code end class XMLSomething # some code end
-
Dùng
snake_case
cho tên file, vd:hello_world.rb
. [link] -
Dùng
snake_case
cho tên thư mục, vd:lib/hello_world/hello_world.rb
. [link] -
Mỗi file chỉ nên có một lớp/module. Tên của file chính là tên của class/module nhưng thay PascalCase thành snake_case. [link]
-
Dùng
SCREAMING_SNAKE_CASE
cho hằng số. [link]# bad SomeConst = 5 # good SOME_CONST = 5
-
Phương thức trả về boolean thì nên có thêm
?
đằng sau (vd:Array#empty?
). Phương thức không trả về boolean thì không dùng. [link] -
Tránh đặt tiền tố cho tên phương thức với các động từ bổ trợ như
is
,does
, haycan
. Những từ này không cần thiết và nghĩa quá chung chung, không đồng nhất với các phương thức boolean của ngôn ngữ:empty?
hayinclude?
. [link]# bad class Person def is_tall? true end def can_play_basketball? false end def does_like_candy? true end end # good class Person def tall? true end def basketball_player? false end def likes_candy? true end end
-
Tên của những phương thức có thể là nguy hiểm (vd: phương thức mà chỉnh sửa
self
hay các thuộc tính, hay phương thứcexit!
(không thực hiện việcfinalize
nhưexit
), vv) cần kết thúc bằng một dấu chấm than!
nếu như đã có một phiên bản an toàn của nó rồi (safe version). [link]# bad - there is no matching 'safe' method class Person def update! end end # good class Person def update end end # good class Person def update! end def update end end
-
Nếu có phương thức nguy hiểm thì nên có thêm phương thức an toàn. [link]
class Array def flatten_once! res = [] each do |e| [*e].each { |f| res << f } end replace(res) end def flatten_once dup.flatten_once! end end
-
Khi dùng
reduce
với block ngắn, đặt tên các tham số là|a, e|
(accumulator, element). [link] -
Khi định nghĩa binary operators, đặt tên tham số là
other
(ngoại trừ hai toán tử<<
và[]
là ngoại lệ, bởi nó mang ý nghĩa khác). [link]def +(other) # body omitted end
Good code is its own best documentation. As you're about to add a comment, ask yourself, "How can I improve the code so that this comment isn't needed?" Improve the code and then document it to make it even clearer.
-- Steve McConnell
-
Write self-documenting code and ignore the rest of this section. Seriously! [link]
-
Write comments in English. [link]
-
Use one space between the leading
#
character of the comment and the text of the comment. [link] -
Comments longer than a word are capitalized and use punctuation. Use one space after periods. [link]
-
Avoid superfluous comments. [link]
# bad counter += 1 # Increments counter by one.
-
Keep existing comments up-to-date. An outdated comment is worse than no comment at all. [link]
Good code is like a good joke - it needs no explanation.
-- Russ Olsen
- Avoid writing comments to explain bad code. Refactor the code to make it self-explanatory. (Do or do not - there is no try. --Yoda) [link]
-
Annotations should usually be written on the line immediately above the relevant code. [link]
-
The annotation keyword is followed by a colon and a space, then a note describing the problem. [link]
-
If multiple lines are required to describe the problem, subsequent lines should be indented three spaces after the
#
(one general plus two for indentation purpose). [link]def bar # FIXME: This has crashed occasionally since v3.2.1. It may # be related to the BarBazUtil upgrade. baz(:quux) end
-
In cases where the problem is so obvious that any documentation would be redundant, annotations may be left at the end of the offending line with no note. This usage should be the exception and not the rule. [link]
def bar sleep 100 # OPTIMIZE end
-
Use
TODO
to note missing features or functionality that should be added at a later date. [link] -
Use
FIXME
to note broken code that needs to be fixed. [link] -
Use
OPTIMIZE
to note slow or inefficient code that may cause performance problems. [link] -
Use
HACK
to note code smells where questionable coding practices were used and should be refactored away. [link] -
Use
REVIEW
to note anything that should be looked at to confirm it is working as intended. For example:REVIEW: Are we sure this is how the client does X currently?
[link] -
Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project's
README
or similar. [link]
-
Use a consistent structure in your class definitions. [link]
class Person # extend and include go first extend SomeModule include AnotherModule # inner classes CustomErrorKlass = Class.new(StandardError) # constants are next SOME_CONSTANT = 20 # afterwards we have attribute macros attr_reader :name # followed by other macros (if any) validates :name # public class methods are next in line def self.some_method end # initialization goes between class methods and other instance methods def initialize end # followed by other public instance methods def some_method end # protected and private methods are grouped near the end protected def some_protected_method end private def some_private_method end end
-
Don't nest multi-line classes within classes. Try to have such nested classes each in their own file in a folder named like the containing class. [link]
# bad # foo.rb class Foo class Bar # 30 methods inside end class Car # 20 methods inside end # 30 methods inside end # good # foo.rb class Foo # 30 methods inside end # foo/bar.rb class Foo class Bar # 30 methods inside end end # foo/car.rb class Foo class Car # 20 methods inside end end
-
Prefer modules to classes with only class methods. Classes should be used only when it makes sense to create instances out of them. [link]
# bad class SomeClass def self.some_method # body omitted end def self.some_other_method # body omitted end end # good module SomeModule module_function def some_method # body omitted end def some_other_method # body omitted end end
-
Favor the use of
module_function
overextend self
when you want to turn a module's instance methods into class methods. [link]# bad module Utilities extend self def parse_something(string) # do stuff here end def other_utility_method(number, string) # do some more stuff end end # good module Utilities module_function def parse_something(string) # do stuff here end def other_utility_method(number, string) # do some more stuff end end
-
When designing class hierarchies make sure that they conform to the Liskov Substitution Principle. [link]
-
Always supply a proper
to_s
method for classes that represent domain objects. [link]class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def to_s "#{@first_name} #{@last_name}" end end
-
Use the
attr
family of functions to define trivial accessors or mutators. [link]# bad class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def first_name @first_name end def last_name @last_name end end # good class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end
-
Avoid the use of
attr
. Useattr_reader
andattr_accessor
instead. [link]# bad - creates a single attribute accessor (deprecated in Ruby 1.9) attr :something, true attr :one, :two, :three # behaves as attr_reader # good attr_accessor :something attr_reader :one, :two, :three
-
Consider using
Struct.new
, which defines the trivial accessors, constructor and comparison operators for you. [link]# good class Person attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end # better Person = Struct.new(:first_name, :last_name) do end
-
Don't extend an instance initialized by
Struct.new
. Extending it introduces a superfluous class level and may also introduce weird errors if the file is required multiple times. [link]# bad class Person < Struct.new(:first_name, :last_name) end # good Person = Struct.new(:first_name, :last_name)
-
Consider adding factory methods to provide additional sensible ways to create instances of a particular class. [link]
class Person def self.create(options_hash) # body omitted end end
-
Prefer duck-typing over inheritance. [link]
# bad class Animal # abstract method def speak end end # extend superclass class Duck < Animal def speak puts 'Quack! Quack' end end # extend superclass class Dog < Animal def speak puts 'Bau! Bau!' end end # good class Duck def speak puts 'Quack! Quack' end end class Dog def speak puts 'Bau! Bau!' end end
-
Avoid the usage of class (
@@
) variables due to their "nasty" behavior in inheritance. [link]class Parent @@class_var = 'parent' def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = 'child' end Parent.print_class_var # => will print 'child'
As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
-
Assign proper visibility levels to methods (
private
,protected
) in accordance with their intended usage. Don't go off leaving everythingpublic
(which is the default). After all we're coding in Ruby now, not in Python. [link] -
Indent the
public
,protected
, andprivate
methods as much as the method definitions they apply to. Leave one blank line above the visibility modifier and one blank line below in order to emphasize that it applies to all methods below it. [link]class SomeClass def public_method # some code end private def private_method # some code end def another_private_method # some code end end
-
Use
def self.method
to define class methods. This makes the code easier to refactor since the class name is not repeated. [link]class TestClass # bad def TestClass.some_method # body omitted end # good def self.some_other_method # body omitted end # Also possible and convenient when you # have to define many class methods. class << self def first_method # body omitted end def second_method_etc # body omitted end end end
-
Prefer
alias
when aliasing methods in lexical class scope as the resolution ofself
in this context is also lexical, and it communicates clearly to the user that the indirection of your alias will not be altered at runtime or by any subclass unless made explicit. [link]class Westerner def first_name @names.first end alias given_name first_name end
Since
alias
, likedef
, is a keyword, prefer bareword arguments over symbols or strings. In other words, doalias foo bar
, notalias :foo :bar
.Also be aware of how Ruby handles aliases and inheritance: an alias references the method that was resolved at the time the alias was defined; it is not dispatched dynamically.
class Fugitive < Westerner def first_name 'Nobody' end end
In this example,
Fugitive#given_name
would still call the originalWesterner#first_name
method, notFugitive#first_name
. To override the behavior ofFugitive#given_name
as well, you'd have to redefine it in the derived class.class Fugitive < Westerner def first_name 'Nobody' end alias given_name first_name end
-
Always use
alias_method
when aliasing methods of modules, classes, or singleton classes at runtime, as the lexical scope ofalias
leads to unpredictability in these cases. [link]module Mononymous def self.included(other) other.class_eval { alias_method :full_name, :given_name } end end class Sting < Westerner include Mononymous end
-
Ưu tiên dùng
raise
hơnfail
. [link]# bad fail SomeException, 'message' # good raise SomeException, 'message'
-
Không cần phải chỉ rõ
RuntimeError
khiraise
mà có hai đối số. [link]# bad raise RuntimeError, 'message' # good - RuntimeError được raise mặc định raise 'message'
-
Khi ném exception, ưu tiên dùng phương án hai tham số: một class và một message hơn là một claas được khởi tạo với message. [link]
# bad raise SomeException.new('message') # Lưu ý rằng lệnh sau `raise SomeException.new('message'), backtrace` là sai. # good raise SomeException, 'message' # Tương đương với `raise SomeException, 'message', backtrace`.
-
KHÔNG return từ
ensure
block. Nếu làm vậy, dù có exception được ném ra nhưng phương thức vẫn trả kết quả về, nên đôi khi ta sẽ bỏ qua exception. [link]# bad def foo raise ensure return 'very bad idea' end
-
Dùng từ khóa
begin
một cách ngầm định khi có thể. [link]# bad def foo begin # main logic goes here rescue # failure handling goes here end end # good def foo # main logic goes here rescue # failure handling goes here end
-
Mitigate the proliferation of
begin
blocks by using contingency methods (a term coined by Avdi Grimm). [link]# bad begin something_that_might_fail rescue IOError # handle IOError end begin something_else_that_might_fail rescue IOError # handle IOError end # good def with_io_error_handling yield rescue IOError # handle IOError end with_io_error_handling { something_that_might_fail } with_io_error_handling { something_else_that_might_fail }
-
Đừng bỏ qua exceptions. [link]
# bad begin # an exception occurs here rescue SomeError # the rescue clause does absolutely nothing end # bad do_something rescue nil
-
Bắt đúng lỗi được ném ra. [link]
# bad - this catches exceptions of StandardError class and its descendant classes read_file rescue handle_error($!) # good - this catches only the exceptions of Errno::ENOENT class and its descendant classes def foo read_file rescue Errno::ENOENT => ex handle_error(ex) end
-
Không dùng exceptions trong luông điều khiển. Thay vào đó hãy kiểm tra hợ lệ trước. [link]
# bad begin n / d rescue ZeroDivisionError puts 'Cannot divide by 0!' end # good if d.zero? puts 'Cannot divide by 0!' else n / d end
-
Tránh việc bắt lớp
Exception
. Việc này sẽ gọi đến hàmexit
, có thể bạn sẽ phải tắt cứng process bằng lệnhkill -9
. [link]# bad begin # calls to exit and kill signals will be caught (except kill -9) exit rescue Exception puts "you didn't really want to exit, right?" # exception handling end # good begin # a blind rescue rescues from StandardError, not Exception as many # programmers assume. rescue => e # exception handling end # also good begin # an exception occurs here rescue StandardError => e # exception handling end
-
Bắt exception theo thứ tự từ thấp đến cao, bắt thằng con trước, rồi bắt cha sau. [link]
# bad begin # some code rescue StandardError => e # some handling rescue IOError => e # some handling that will never be executed end # good begin # some code rescue IOError => e # some handling rescue StandardError => e # some handling end
-
Trong
ensure
, nên giải phóng tài nguyên cho hệ thống. [link]f = File.open('testfile') begin # .. process rescue # .. handle error ensure f.close if f end
-
Nếu như tài nguyên được giải phóng tự động thì không cần phải làm thủ công. [link]
# bad - bạn muốn đóng file một cách tường minh qua lệnh `close` f = File.open('testfile') # some action on the file f.close # good - file descriptor được đóng tự động khi dùng xong File.open('testfile') do |f| # some action on the file end
-
Ưu tiên dùng những exception mặc định của hệ thống hơn là định nghĩa là lớp mới. [link]
-
Ưu tiên khai báo mảng hay hash bằng cặp ngoặc hơn là tạo thể hiện, bởi vì đôi khi ta phải truyền đối số. [link]
# bad arr = Array.new hash = Hash.new # good arr = [] hash = {}
-
Ưu tiên dùng
%W
để tạo mảng. Lưu ý chỉ dùng khi mảng có vài phần tử. [link]# bad STATES = ['draft', 'open', 'closed'] # good STATES = %w(draft open closed)
-
Ưu tiên dùng
%i
nếu muốn tạo mảng các nhãn (symbol). [link]# bad STATES = [:draft, :open, :closed] # good STATES = %i(draft open closed)
-
Tránh thêm dấu phẩy sau phẩn tử cuối cùng của
Array
hayHash
, đặc biệt là khi các phần tử đó không nằm trên các dòng khác nhau. [link]# bad - easier to move/add/remove items, but still not preferred VALUES = [ 1001, 2020, 3333, ] # bad VALUES = [1001, 2020, 3333, ] # good VALUES = [1001, 2020, 3333]
-
Tránh việc tạo mảng nhiều phần tử quá mà không dùng hết. [link]
arr = [] arr[100] = 1 # now you have an array with lots of nils
-
Khi truy cập phần tử đầu tiên/cuối cùng trong mảng, ưu tiên dùng
first
haylast
hơn[0]
,[-1]
. [link] -
Dùng
Set
thay vìArray
khi làm việc với mảng các phần tử độc nhất.Set
là tập hợp các phần tử không có thứ tự và không lặp lại. Đây là một biến thể củaArray
, nó là sự kết hợp các tính năng củaArray
và khả năng tìm kiếm nhanh củaHash
. [link] -
Khi làm việc với hash, ưu tiên dùng nhãn hơn string khi đặt key. [link]
# bad hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # good hash = { one: 1, two: 2, three: 3 }
-
Tránh việc sử dụng đối tượng không bất biến cho hash key. [link]
-
Dùng cú pháp của Ruby 1.9 để tạo hash. [link]
# bad hash = { :one => 1, :two => 2, :three => 3 } # good hash = { one: 1, two: 2, three: 3 }
-
Không trộn cú pháp của Ruby 1.9 với kiểu cũ. [link]
# bad { a: 1, 'b' => 2 } # good { :a => 1, 'b' => 2 }
-
Dùng
Hash#key?
thay choHash#has_key?
,Hash#value?
thay choHash#has_value?
. Xem thêm: here [link]# bad hash.has_key?(:test) hash.has_value?(value) # good hash.key?(:test) hash.value?(value)
-
Dùng
Hash#each_key
thay choHash#keys.each
, vàHash#each_value
thay choHash#values.each
. [link]# bad hash.keys.each { |k| p k } hash.values.each { |v| p v } hash.each { |k, _v| p k } hash.each { |_k, v| p v } # good hash.each_key { |k| p k } hash.each_value { |v| p v }
-
Dùng
Hash#fetch
khi biết chắc rằng hash có key đó hay không. [link]heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' } # bad - if we make a mistake we might not spot it right away heroes[:batman] # => 'Bruce Wayne' heroes[:supermann] # => nil # good - fetch raises a KeyError making the problem obvious heroes.fetch(:supermann)
-
Khi dùng
Hash#fetch
, có thể thêm giá trị mặc định vào thay vì dùng logic. [link]batman = { name: 'Bruce Wayne', is_evil: false } # bad - if we just use || operator with falsy value we won't get the expected result batman[:is_evil] || true # => true # good - fetch work correctly with falsy values batman.fetch(:is_evil, true) # => false
-
Ưu tiên dùng block thay vì giá trị mặc định trong
Hash#fetch
, vì có thể sẽ làm chậm chương trình đi, hoặc có những tác dụng phụ. [link]batman = { name: 'Bruce Wayne' } # bad - if we use the default value, we eager evaluate it # so it can slow the program down if done multiple times batman.fetch(:powers, obtain_batman_powers) # obtain_batman_powers is an expensive call # good - blocks are lazy evaluated, so only triggered in case of KeyError exception batman.fetch(:powers) { obtain_batman_powers }
-
Dùng
Hash#values_at
khi muốn lấy nhiều giá trị liên tiếp trong hash ra. [link]# bad email = data['email'] username = data['nickname'] # good email, username = data.values_at('email', 'nickname')
-
Lưu ý rằng hash trừ Ruby 1.9 là có thứ tự. [link]
-
KHÔNG chỉnh sửa một collection khi đang duyệt qua các phần tử của nó. [link]
-
Khi truy cập vào các phần tử của collection, tránh việc truy cập trực tiếp thông qua
[n]
, hãy dùng các phương án thay thế nếu có thể. Việc này sẽ giúp tránh được lỗi gọi[]
onnil
[link]# bad Regexp.last_match[1] # good Regexp.last_match(1)
-
Khi cấp quyền truy cập vào một collection, nên cung cấp một phương án giúp users khỏi phải check
nil
sau khi nhận kết quả trả về. [link]# bad def awesome_things @awesome_things end # good def awesome_things(index = nil) if index && @awesome_things @awesome_things[index] else @awesome_things end end
-
Dùng
Integer
để kiểm tra kiểu của một số.Fixnum
thì phụ thuộc vào nền tảng (hệ điều hành chẳng hạn), với hệ thống32-bit
và64-bit
thì kết quả nó sẽ khác nhau. [link]timestamp = Time.now.to_i # bad timestamp.is_a? Fixnum timestamp.is_a? Bignum # good timestamp.is_a? Integer
-
Ưu tiên dùng kiểu nhúng vào String hoặc là
format
hơn là nối chuỗi. [link]# bad email_with_name = user.name + ' <' + user.email + '>' # good email_with_name = "#{user.name} <#{user.email}>" # good email_with_name = format('%s <%s>', user.name, user.email)
-
Với String tuyệt đối, hiện có hai kiểu dùng: dấu nháy đơn (Option A) và dấu nháy kép (Option B). [link]
-
(Option A)
# bad name = "Bozhidar" # good name = 'Bozhidar'
-
(Option B)
# bad name = 'Bozhidar' # good name = "Bozhidar"
Tài liệu này sử dụng kiểu thứ nhất.
-
-
Không dùng cú pháp
?x
nữa, Ruby 1.9 đã bỏ rồi. [link]# bad char = ?c # good char = 'c'
-
Khi nhúng biến toàn cục vào String, không nên bỏ cặp ngoặc nhọn đi. [link]
class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end # bad - hợp lệ, nhưng khó đọc def to_s "#@first_name #@last_name" end # good def to_s "#{@first_name} #{@last_name}" end end $global = 0 # bad puts "$global = #$global" # good puts "$global = #{$global}"
-
Không cần phải dùng
Object#to_s
khi in đối tượng ra, mặc định nó có rồi. [link]# bad message = "This is the #{result.to_s}." # good message = "This is the #{result}."
-
Tránh sử dụng
String#+
khi muốn tạo string với nhiều dữ liệu. Thay vào đó, hãy dùngString#<<
.String#+
sẽ tạo một đối tượng mới, do đó sẽ chậm hơn,String#<<
ngược lại chỉ thêm vào. [link]# bad html = '' html += '<h1>Page title</h1>' paragraphs.each do |paragraph| html += "<p>#{paragraph}</p>" end # good and also fast html = '' html << '<h1>Page title</h1>' paragraphs.each do |paragraph| html << "<p>#{paragraph}</p>" end
-
Trong một số trường hợp cụ thể cần tốc độ, không dùng
String#gsub
. [link]url = 'http://example.com' str = 'lisp-case-rules' # bad url.gsub('http://', 'https://') str.gsub('-', '_') # good url.sub('http://', 'https://') str.tr('-', '_')
-
Khi sử dụng heredocs cho string nhiều dòng, để ý rằng nó sẽ bỏ qua mấy khoảng trắng, cho nên tốt nhất nên làm một ký tự
|
ở đầu dòng để làm mốc. [link]code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |end END # => "def test\n some_method\n other_method\nend\n"
-
Sử dụng cú pháp
~
của Ruby 2.3 để hiển thị tốt hơn. [link]# bad - using Powerpack String#strip_margin code = <<-END.strip_margin('|') |def test | some_method | other_method |end END # also bad code = <<-END def test some_method other_method end END # good code = <<~END def test some_method other_method end END
Có một số người, khi gặp một vấn đề, họ sẽ nghĩ "OK, tôi sẽ dùng biểu thức chính quy (regular expressions)" Và giờ họ phải đổi mặt với hai vấn đề.
-- Jamie Zawinski
-
Đùng dùng regex nếu bạn đơn thuần chỉ cần tìm kiếm một đoạn text trong string:
string['text']
[link] -
For simple constructions you can use regexp directly through string index. [link]
match = string[/regexp/] # get content of matched regexp first_group = string[/text(grp)/, 1] # get content of captured group string[/text (grp)/, 1] = 'replace' # string => 'text replace'
-
Use non-capturing groups when you don't use the captured result. [link]
# bad /(first|second)/ # good /(?:first|second)/
-
Don't use the cryptic Perl-legacy variables denoting last regexp group matches (
$1
,$2
, etc). UseRegexp.last_match(n)
instead. [link]/(regexp)/ =~ string ... # bad process $1 # good process Regexp.last_match(1)
-
Avoid using numbered groups as it can be hard to track what they contain. Named groups can be used instead. [link]
# bad /(regexp)/ =~ string # some code process Regexp.last_match(1) # good /(?<meaningful_var>regexp)/ =~ string # some code process meaningful_var
-
Character classes have only a few special characters you should care about:
^
,-
,\
,]
, so don't escape.
or brackets in[]
. [link] -
Be careful with
^
and$
as they match start/end of line, not string endings. If you want to match the whole string use:\A
and\z
(not to be confused with\Z
which is the equivalent of/\n?\z/
). [link]string = "some injection\nusername" string[/^username$/] # matches string[/\Ausername\z/] # doesn't match
-
Use
x
modifier for complex regexps. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored. [link]regexp = / start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end /x
-
For complex replacements
sub
/gsub
can be used with a block or a hash. [link]words = 'foo bar' words.sub(/f/, 'f' => 'F') # => 'Foo bar' words.gsub(/\w+/) { |word| word.capitalize } # => 'Foo Bar'
-
Dùng
%()
(hoặc%Q
) cho string một-dòng với yêu cầu là nó phải vừa in ra được mà vừa nhúng được. Với string nhiều dòng, ưu tiên dùng heredocs. [link]# bad (no interpolation needed) %(<div class="text">Some text</div>) # should be '<div class="text">Some text</div>' # bad (no double-quotes) %(This is #{quality} style) # should be "This is #{quality} style" # bad (multiple lines) %(<div>\n<span class="big">#{exclamation}</span>\n</div>) # should be a heredoc. # good (requires interpolation, has quotes, single line) %(<tr><td class="name">#{name}</td>)
-
Tránh dùng
%q
trừ khi bạn có string mà chứa cả'
và"
. String bình thường vẫn dễ đọc hơn, trừ khi nó có chứa nhiều ký tự đặc biệt quá. [link]# bad name = %q(Bruce Wayne) time = %q(8 o'clock) question = %q("What did you say?") # good name = 'Bruce Wayne' time = "8 o'clock" question = '"What did you say?"' quote = %q(<p class='quote'>"What did you say?"</p>)
-
Chỉ dùng
%r
khi regex khớp ít nhất một ký tự/
[link]# bad %r{\s+} # good %r{^/(.*)$} %r{^/blog/2016/(.*)$}
-
Tránh dùng
%x
trừ khi bạn muốn thực thi một command với dấu nháy ngược trong lệnh, mà thường hiếm khi gặp trường hợp này lắm. [link]# bad date = %x(date) # good date = `date` echo = %x(echo `date`)
-
Tránh dùng
%s
. Cộng đồng đã chọn:"some string"
là cách để tạo symbol mà có khoảng trắng rồi. [link] -
Ưu tiên
()
khi dùng với%
, ngoại trừ%r
. [link]# bad %w[one two three] %q{"Test's king!", John said.} # good %w(one two three) %q("Test's king!", John said.)
-
Tránh những metaprogramming không cần thiết. [link]
-
Không viết thêm phương thức cho lớp của hệ thống khi viết thư viện (monkey-patch) [link]
-
The block form of
class_eval
is preferable to the string-interpolated form. [link]-
when you use the string-interpolated form, always supply
__FILE__
and__LINE__
, so that your backtraces make sense:class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
-
define_method
is preferable toclass_eval{ def ... }
-
-
When using
class_eval
(or othereval
) with string interpolation, add a comment block showing its appearance if interpolated (a practice used in Rails code): [link]# from activesupport/lib/active_support/core_ext/string/output_safety.rb UNSAFE_STRING_METHODS.each do |unsafe_method| if 'String'.respond_to?(unsafe_method) class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}(*params, &block) # def capitalize(*params, &block) to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block) end # end def #{unsafe_method}!(*params) # def capitalize!(*params) @dirty = true # @dirty = true super # super end # end EOT end end
-
Avoid using
method_missing
for metaprogramming because backtraces become messy, the behavior is not listed in#methods
, and misspelled method calls might silently work, vd:nukes.launch_state = false
. Consider using delegation, proxy, ordefine_method
instead. If you must usemethod_missing
: [link]-
Be sure to also define
respond_to_missing?
-
Only catch methods with a well-defined prefix, such as
find_by_*
-- make your code as assertive as possible. -
Call
super
at the end of your statement -
Delegate to assertive, non-magical methods:
# bad def method_missing?(meth, *params, &block) if /^find_by_(?<prop>.*)/ =~ meth # ... lots of code to do a find_by else super end end # good def method_missing?(meth, *params, &block) if /^find_by_(?<prop>.*)/ =~ meth find_by(prop, *params, &block) else super end end # best of all, though, would to define_method as each findable attribute is declared
-
-
Prefer
public_send
oversend
so as not to circumventprivate
/protected
visibility. [link]# We have an ActiveModel Organization that includes concern Activatable module Activatable extend ActiveSupport::Concern included do before_create :create_token end private def reset_token # some code end def create_token # some code end def activate! # some code end end class Organization < ActiveRecord::Base include Activatable end linux_organization = Organization.find(...) # BAD - violates privacy linux_organization.send(:reset_token) # GOOD - should throw an exception linux_organization.public_send(:reset_token)
-
Prefer
__send__
oversend
, assend
may overlap with existing methods. [link]require 'socket' u1 = UDPSocket.new u1.bind('127.0.0.1', 4913) u2 = UDPSocket.new u2.connect('127.0.0.1', 4913) # Won't send a message to the receiver obj. # Instead it will send a message via UDP socket. u2.send :sleep, 0 # Will actually send a message to the receiver obj. u2.__send__ ...
-
Write
ruby -w
safe code. [link] -
Avoid hashes as optional parameters. Does the method do too much? (Object initializers are exceptions for this rule). [link]
-
Avoid methods longer than 10 LOC (lines of code). Ideally, most methods will be shorter than 5 LOC. Empty lines do not contribute to the relevant LOC. [link]
-
Avoid parameter lists longer than three or four parameters. [link]
-
If you really need "global" methods, add them to Kernel and make them private. [link]
-
Use module instance variables instead of global variables. [link]
# bad $foo_bar = 1 # good module Foo class << self attr_accessor :bar end end Foo.bar = 1
-
Use
OptionParser
for parsing complex command line options andruby -s
for trivial command line options. [link] -
Prefer
Time.now
overTime.new
when retrieving the current system time. [link] -
Code in a functional way, avoiding mutation when that makes sense. [link]
-
Do not mutate parameters unless that is the purpose of the method. [link]
-
Avoid more than three levels of block nesting. [link]
-
Be consistent. In an ideal world, be consistent with these guidelines. [link]
-
Use common sense. [link]
Here are some tools to help you automatically check Ruby code against this guide.
RuboCop is a Ruby code style checker based on this style guide. RuboCop already covers a significant portion of the Guide, supports both MRI 1.9 and MRI 2.0 and has good Emacs integration.
RubyMine's code inspections are partially based on this guide.
The guide is still a work in progress - some rules are lacking examples, some rules don't have examples that illustrate them clearly enough. Improving such rules is a great (and simple way) to help the Ruby community!
In due time these issues will (hopefully) be addressed - just keep them in mind for now.
Nothing written in this guide is set in stone. It's my desire to work together with everyone interested in Ruby coding style, so that we could ultimately create a resource that will be beneficial to the entire Ruby community.
Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!
You can also support the project (and RuboCop) with financial contributions via Gratipay.
It's easy, just follow the contribution guidelines.
This work is licensed under a Creative Commons Attribution 3.0 Unported License
A community-driven style guide is of little use to a community that doesn't know about its existence. Tweet about the guide, share it with your friends and colleagues. Every comment, suggestion or opinion we get makes the guide just a little bit better. And we want to have the best possible guide, don't we?
Cheers,
Bozhidar