Đó có phải là phong cách tốt để trở lại rõ ràng trong Ruby?


155

Đến từ nền tảng Python, nơi luôn có "cách làm đúng" (một cách "Pythonic") khi nói đến phong cách, tôi tự hỏi liệu điều đó có tồn tại với Ruby không. Tôi đã sử dụng các nguyên tắc phong cách của riêng mình nhưng tôi đang suy nghĩ về việc phát hành mã nguồn của mình và tôi muốn nó tuân thủ mọi quy tắc bất thành văn có thể tồn tại.

Đây có phải là "The Ruby Way" để gõ một cách rõ ràng returntrong các phương thức không? Tôi đã thấy nó được thực hiện có và không có, nhưng có cách nào đúng không? Có lẽ có một thời gian thích hợp để làm điều đó? Ví dụ:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end

3
Đây là một câu hỏi rất cũ, nhưng đối với những người có câu hỏi tương tự, cuốn sách Eloquent Ruby rất được khuyến khích. Đây không phải là một hướng dẫn về phong cách nhiều như giới thiệu về cách viết thành ngữ Ruby. Tôi ước nhiều người sẽ đọc nó!
Brian thân mến

Là một người được thừa hưởng một ứng dụng kế thừa lớn được phát triển trong Ruby, tôi rất đánh giá cao rằng bạn đã hỏi câu hỏi này. Điều này xứng đáng một upvote. Điều này được xả rác trong ứng dụng di sản của chúng tôi. Một trong những công việc của tôi trong nhóm này là cải thiện codebase (khó khi mới làm quen với Ruby).
Sốt

Câu trả lời:


226

Câu hỏi cũ (và "đã trả lời"), nhưng tôi sẽ ném hai xu của mình làm câu trả lời.

TL; DR - Bạn không cần phải làm vậy, nhưng nó có thể làm cho mã của bạn rõ ràng hơn rất nhiều trong một số trường hợp.

Mặc dù không sử dụng trả lại rõ ràng có thể là "cách của Ruby", nhưng thật khó hiểu với các lập trình viên làm việc với mã không quen thuộc hoặc không quen thuộc với tính năng này của Ruby.

Đó là một ví dụ hơi khó hiểu, nhưng hãy tưởng tượng có một hàm nhỏ như thế này, thêm một số vào số đã truyền và gán nó cho một biến thể hiện.

def plus_one_to_y(x)
    @y = x + 1
end

Đây có phải là một hàm trả về một giá trị hay không? Thật sự rất khó để nói ý nghĩa của nhà phát triển, vì cả hai đều gán biến thể hiện, và cũng trả về giá trị được gán.

Giả sử sau đó, một lập trình viên khác (có lẽ không quen thuộc với cách Ruby trả về dựa trên dòng mã cuối cùng được thực thi) xuất hiện và muốn đưa vào một số câu lệnh in để ghi nhật ký, và hàm này trở thành ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
end

Bây giờ chức năng bị phá vỡ nếu bất cứ điều gì mong đợi một giá trị trả về . Nếu không có gì mong đợi một giá trị trả về, nó là tốt. Rõ ràng nếu ở đâu đó xa hơn chuỗi mã, một cái gì đó gọi đây là giá trị được trả về, nó sẽ thất bại vì nó không lấy lại được những gì nó mong đợi.

Câu hỏi thực sự bây giờ là đây: có điều gì thực sự mong đợi một giá trị được trả về không? Điều này đã phá vỡ một cái gì đó hay không? Nó sẽ phá vỡ một cái gì đó trong tương lai? Ai biết! Chỉ một đánh giá mã đầy đủ của tất cả các cuộc gọi sẽ cho bạn biết.

Vì vậy, đối với tôi ít nhất, cách tiếp cận thực tiễn tốt nhất là hoặc rất rõ ràng rằng bạn đang trả lại một cái gì đó nếu nó quan trọng, hoặc không trả lại bất cứ điều gì khi nó không.

Vì vậy, trong trường hợp hàm demo nhỏ của chúng tôi, giả sử chúng tôi muốn nó trả về một giá trị, nó sẽ được viết như thế này ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    return @y
end

Và nó sẽ rất rõ ràng với bất kỳ lập trình viên nào rằng nó trả về một giá trị, và khó hơn nhiều để họ phá vỡ nó mà không nhận ra nó.

Ngoài ra, người ta có thể viết nó như thế này và bỏ qua tuyên bố trả lại ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    @y
end

Nhưng tại sao phải bỏ đi từ trở lại? Tại sao không đặt nó vào đó và làm cho nó rõ ràng 100% những gì đang xảy ra? Nó thực sự sẽ không ảnh hưởng đến khả năng thực hiện mã của bạn.


6
Điểm thú vị. Tôi nghĩ rằng tôi thực sự đã gặp phải một tình huống tương tự khi tôi hỏi câu hỏi này, nơi nhà phát triển ban đầu đã sử dụng giá trị trả về ngầm định trong mã của họ và rất khó để hiểu chuyện gì đang xảy ra. Cảm ơn câu trả lời của bạn!
Sasha Chedygov

6
Tôi đã tìm thấy câu hỏi trong khi Googling trả về ngầm, vì tôi vừa bị đốt cháy bởi điều này. Tôi đã thêm một số logic vào cuối của một hàm đang hoàn toàn trả lại một cái gì đó. Hầu hết các cuộc gọi đến nó không quan tâm (hoặc kiểm tra) giá trị được trả về, nhưng một cuộc gọi đã thực hiện - và nó đã thất bại. May mắn thay, ít nhất nó đã xuất hiện trong một số thử nghiệm chức năng.
Tim Holt

16
Mặc dù đúng, điều quan trọng là mã có thể đọc được, tránh một tính năng nổi tiếng của ngôn ngữ lập trình có lợi cho tính dài dòng, theo tôi, là thực tiễn tồi. Nếu một nhà phát triển không quen thuộc với Ruby, thì có lẽ họ nên làm quen trước khi chạm vào mã. Tôi thấy hơi ngớ ngẩn khi phải tăng sự lộn xộn của mã của mình chỉ cho sự kiện trong tương lai mà một số nhà phát triển không phải là Ruby phải làm gì đó sau này. Chúng ta không nên giảm ngôn ngữ xuống mẫu số chung thấp nhất. Một phần của vẻ đẹp của Ruby là chúng ta không phải sử dụng 5 dòng mã để nói điều gì đó chỉ nên dùng 3.
Brian thân mến

25
Nếu từ returnthêm ý nghĩa ngữ nghĩa cho mã, tại sao không? Điều đó thật khó hiểu - chúng tôi không nói 5 dòng so với 3, chỉ một từ nữa. Tôi muốn thấy returnít nhất là trong các hàm (có thể không phải trong các khối) để thể hiện những gì mã thực sự làm. Đôi khi bạn phải trả về chức năng giữa - đừng bỏ qua returntừ khóa cuối cùng trên dòng cuối cùng chỉ vì bạn có thể. Tôi không ủng hộ tính dài dòng, nhưng tôi thích sự nhất quán.
mindplay.dk

6
Đây là một câu trả lời tuyệt vời. Nhưng nó vẫn có nguy cơ khiến bạn viết một phương thức Ruby dự định là một thủ tục (còn gọi là đơn vị trả về hàm), vd side_effect()và một nhà phát triển khác quyết định (ab) sử dụng giá trị trả về ngầm của thủ tục của bạn. Để khắc phục điều này, bạn có thể thực hiện các thủ tục của mình một cách rõ ràng return nil.
Alex Dean

66

Không. Phong cách Ruby tốt thường chỉ sử dụng lợi nhuận rõ ràng cho lợi nhuận sớm . Ruby là lớn về tối giản mã / ma thuật ngầm.

Điều đó nói rằng, nếu một sự trở lại rõ ràng sẽ làm cho mọi thứ rõ ràng hơn hoặc dễ đọc hơn, nó sẽ không gây hại gì.


116
Điểm tối giản mã là gì nếu nó dẫn đến chủ nghĩa tối đa nhầm lẫn?
jononomo 17/12/14

@JonCrowell Thay vào đó, nó sẽ dẫn đến chủ nghĩa tối đa tài liệu.
jazzpi

29
@jazzpi Nếu bạn phải ghi chép nhiều về mã của mình để hiểu rõ về việc bạn không thực hành chủ nghĩa tối giản, thì bạn đang tập luyện môn đánh gôn .
Schwern

2
Không khó để loại trừ lợi nhuận cuối cùng, thật khó hiểu khi BAO GỒM nó - với tư cách là một nhà phát triển Ruby, returnđã có một ý nghĩa ngữ nghĩa quan trọng như là một EXIT SỚM cho chức năng.
Devon Parsons

14
@DevonParsons Làm thế nào một người nào đó có thể nhầm lẫn một returndòng trên dòng cuối cùng là một EXIT SỚM?
DarthPaghius

31

Cá nhân tôi sử dụng returntừ khóa để phân biệt giữa những gì tôi gọi là phương thức chức năng , tức là phương thức được thực thi chủ yếu cho giá trị trả về của chúng và phương thức thủ tục được thực thi chủ yếu cho các tác dụng phụ của chúng. Vì vậy, các phương thức trong đó giá trị trả về là quan trọng, hãy lấy thêm một returntừ khóa để thu hút sự chú ý đến giá trị trả về.

Tôi sử dụng cùng một cách phân biệt khi gọi các phương thức: phương thức chức năng có dấu ngoặc đơn, phương thức thủ tục không.

Và cuối cùng nhưng không kém phần quan trọng, tôi cũng sử dụng sự khác biệt đó với các khối: các khối chức năng có được các dấu ngoặc nhọn, các khối thủ tục (tức là các khối "làm" một cái gì đó) get do/ end.

Tuy nhiên, tôi cố gắng không tôn giáo về nó: với các khối, dấu ngoặc nhọn và do/ endcó các ưu tiên khác nhau, và thay vì thêm dấu ngoặc rõ ràng để phân biệt một biểu thức, tôi chỉ chuyển sang kiểu khác. Điều tương tự cũng xảy ra đối với việc gọi phương thức: nếu việc thêm dấu ngoặc đơn xung quanh danh sách tham số làm cho mã dễ đọc hơn, tôi sẽ thực hiện nó, ngay cả khi phương thức được đề cập có bản chất là thủ tục.


1
Ngay bây giờ tôi bỏ qua một sự trở lại trong bất kỳ "một lớp lót" nào, tương tự như những gì bạn đang làm, và tôi làm mọi thứ khác như nhau. Thật tốt khi biết tôi không hoàn toàn lạc lối trong phong cách của mình. :)
Sasha Chedygov

@Jorg: Bạn có sử dụng trả lại sớm trong mã của bạn? Điều đó có gây ra bất kỳ nhầm lẫn với chương trình thủ tục / chức năng của bạn?
Andrew Grimm

1
@Andrew Grimm: Có, và Có. Tôi đang nghĩ đến việc đảo ngược nó. Phần lớn các phương thức của tôi là trong suốt tham chiếu / thuần túy / chức năng / bất cứ điều gì bạn muốn gọi nó bằng mọi cách, vì vậy sẽ rất hợp lý khi sử dụng dạng ngắn hơn ở đó. Và các phương thức được viết theo kiểu chức năng có xu hướng không có lợi nhuận sớm, vì điều đó hàm ý khái niệm "bước", không có nhiều chức năng. Mặc dù, tôi không thích trở lại ở giữa. Tôi hoặc sử dụng chúng lúc đầu như những người bảo vệ hoặc ở cuối để đơn giản hóa luồng điều khiển.
Jörg W Mittag

Câu trả lời tuyệt vời, nhưng thực sự tốt hơn là gọi các phương thức thủ tục bằng () và phương thức chức năng mà không cần - xem câu trả lời của tôi dưới đây để biết lý do
Alex Dean

13

Trên thực tế, điều quan trọng là phân biệt giữa:

  1. Hàm - phương thức được thực hiện cho giá trị trả về của chúng
  2. Quy trình - phương thức được thực hiện cho các tác dụng phụ của chúng

Ruby không có cách phân biệt riêng biệt - điều này khiến bạn dễ bị tổn thương khi viết thủ tục side_effect()và một nhà phát triển khác quyết định lạm dụng giá trị trả về ngầm của quy trình của bạn (về cơ bản coi nó là một hàm không tinh khiết).

Để giải quyết này, phải mất một lá ra khỏi Scala và Haskell của cuốn sách và có bạn thủ tục rõ ràng trở lại nil(aka Unithoặc ()bằng các ngôn ngữ khác).

Nếu bạn làm theo điều này, sau đó sử dụng returncú pháp rõ ràng hay không chỉ trở thành vấn đề của phong cách cá nhân.

Để phân biệt rõ hơn giữa các chức năng và thủ tục:

  1. Sao chép ý tưởng hay của Jorg W Mittag về việc viết các khối chức năng với dấu ngoặc nhọn và các khối thủ tục với do/end
  2. Khi bạn gọi thủ tục, hãy sử dụng (), trong khi đó khi bạn gọi hàm, đừng

Lưu ý rằng Jörg W Mittag thực sự ủng hộ cách khác xung quanh - tránh ()s cho các thủ tục - nhưng đó là không nên bởi vì bạn muốn phụ ảnh hưởng phương pháp invocations được phân biệt rõ ràng từ các biến, đặc biệt là khi arity là 0. Xem các hướng dẫn phong cách Scala trên phương pháp gọi cho chi tiết.


Nếu tôi cần giá trị trả về từ function_afunction_ađược viết để gọi (và chỉ có thể hoạt động bằng cách gọi) procedure_b, thì đó có function_athực sự là một chức năng không? Nó trả về một giá trị, đáp ứng yêu cầu của bạn cho các hàm, nhưng nó có tác dụng phụ, đáp ứng các yêu cầu của thủ tục.
Devon Parsons

2
Bạn nói đúng - function_acó thể chứa một cây các procedure_...cuộc gọi, vì thực sự procedure_acó thể chứa một cây các function_...cuộc gọi. Giống như Scala nhưng không giống như Haskell, Ruby không thể đảm bảo rằng một chức năng không có tác dụng phụ.
Alex Dean

8

Hướng dẫn về phong cách nói rằng bạn không nên sử dụng returncâu lệnh cuối cùng của mình. Bạn vẫn có thể sử dụng nó nếu nó không phải là cái cuối cùng . Đây là một trong những quy ước mà cộng đồng tuân thủ nghiêm ngặt, và bạn cũng nên làm như vậy nếu bạn có kế hoạch cộng tác với bất kỳ ai sử dụng Ruby.


Điều đó đang được nói, lập luận chính cho việc sử dụng returns rõ ràng là nó gây nhầm lẫn cho những người đến từ các ngôn ngữ khác.

  • Thứ nhất, điều này không hoàn toàn dành riêng cho Ruby. Ví dụ Perl cũng có lợi nhuận ngầm.
  • Thứ hai, phần lớn những người áp dụng điều này đến từ các ngôn ngữ Algol. Hầu hết trong số đó là cách "cấp thấp" hơn Ruby, do đó bạn phải viết nhiều mã hơn để hoàn thành công việc.

Một heuristic phổ biến cho độ dài phương thức (không bao gồm getters / setters) trong java là một màn hình . Trong trường hợp đó, bạn có thể không thấy định nghĩa phương thức và / hoặc đã quên về nơi bạn trở về.

Mặt khác, trong Ruby, tốt nhất là nên sử dụng các phương thức dài dưới 10 dòng . Cho rằng, người ta sẽ tự hỏi tại sao anh ta phải viết thêm ~ 10% báo cáo, khi chúng được ngụ ý rõ ràng.


Vì Ruby không có các phương thức void và mọi thứ ngắn gọn hơn nhiều, nên bạn chỉ cần thêm chi phí cho bất kỳ lợi ích nào nếu bạn returnsử dụng s rõ ràng .


1
"Đây là một trong những quy ước mà cộng đồng tuân thủ nghiêm ngặt" Kinh nghiệm của tôi là không, mọi người không tuân thủ nghiêm ngặt trừ khi họ là những nhà phát triển Ruby rất có kinh nghiệm.
Tim Holt

1
@TimHolt chắc tôi đã rất may mắn rồi. (:
ndnenkov

1
@ndn hoàn toàn đồng ý. Điều quan trọng là khi một phương thức (hoặc lớp) bắt đầu vượt quá 5/100 dòng, thì đó là một ý tưởng tốt để xem xét lại những gì bạn đang cố gắng thực hiện. Các quy tắc thực sự của Sandi là một khung nhận thức để suy nghĩ về mã trái ngược với giáo điều độc đoán. Phần lớn mã tôi gặp không có vấn đề gì vì nó bị ràng buộc một cách giả tạo - nói chung là ngược lại - các lớp có nhiều trách nhiệm, các phương thức dài hơn nhiều lớp. Thực chất có quá nhiều người "nhảy cá mập" - trộn lẫn trách nhiệm.
Brian thân mến

1
Đôi khi quy ước vẫn là một ý tưởng tồi. Đó là một quy ước phong cách dựa trên ma thuật, làm cho các kiểu trở lại khó đoán. không cung cấp lợi thế nào ngoài việc lưu 7 tổ hợp phím và không phù hợp với hầu hết các ngôn ngữ khác. Nếu Ruby từng thực hiện việc hợp nhất và đơn giản hóa kiểu Python 3, tôi sẽ hy vọng bất cứ điều gì là đầu tiên trên khối băm.
Shayne

2
Ngoại trừ rõ ràng nó không thực sự làm cho mọi thứ dễ đọc hơn! Phép thuật và tính dễ hiểu không phải là điều ame.
Shayne

4

Tôi đồng ý với Ben Hughes và không đồng ý với Tim Holt, vì câu hỏi đề cập đến cách dứt khoát của Python và hỏi liệu Ruby có tiêu chuẩn tương tự không.

Nó làm.

Đây là một tính năng nổi tiếng của ngôn ngữ mà bất kỳ ai cũng mong muốn gỡ lỗi một vấn đề trong ruby ​​nên được mong đợi một cách hợp lý để biết về nó.


3
Tôi đồng ý với bạn về lý thuyết, nhưng trong thực tế, các lập trình viên mắc lỗi và gây rối. Trường hợp cụ thể, tất cả chúng ta đều biết rằng bạn sử dụng "==" trong điều kiện chứ không phải "=", nhưng TẤT CẢ chúng ta đã mắc lỗi đó trước đó và không nhận ra điều đó cho đến sau này.
Tim Holt

1
@TimHolt Tôi không giải quyết bất kỳ trường hợp sử dụng cụ thể nào, tôi chỉ trả lời câu hỏi. Đó là phong cách rõ ràng xấu, như được lựa chọn bởi cộng đồng, để sử dụng returntừ khóa khi không cần thiết.
Devon Parsons

3
Đủ công bằng. Tôi đang nói rằng phong cách bạn đề cập có thể dẫn đến sai lầm nếu bạn không cẩn thận. Dựa trên kinh nghiệm của tôi, cá nhân tôi ủng hộ một phong cách khác.
Tim Holt

1
@TimHolt Nhân tiện, câu hỏi này đã được hỏi trước khi viết hướng dẫn về phong cách, vì vậy những câu trả lời không nhất thiết không nhất thiết là sai, chỉ là lỗi thời. Tôi đã vấp ngã ở đây bằng cách nào đó và không thấy liên kết tôi cung cấp, vì vậy tôi đoán rằng một người khác cũng có thể kết thúc ở đây.
Devon Parsons

1
Nó không phải là một "tiêu chuẩn" chính thức. Hướng dẫn về phong cách ruby ​​được sử dụng bởi rubocop và được liên kết ở trên với tuyên bố có của Devon Parsons, được tạo ra bởi một hacker ruby ​​không cốt lõi. Vì vậy, nhận xét của Devon thực sự đơn giản là sai. Tất nhiên bạn có thể VẪN sử dụng nó nhưng nó không phải là một tiêu chuẩn.
shevy

1

Đó là vấn đề của phong cách mà bạn thích cho hầu hết các phần. Bạn phải sử dụng trả về từ khóa nếu bạn muốn trở về từ một nơi nào đó ở giữa một phương thức.


0

Giống như Ben nói. Thực tế là "giá trị trả về của phương thức ruby ​​là giá trị trả về của câu lệnh cuối cùng trong thân hàm" khiến từ khóa return hiếm khi được sử dụng trong hầu hết các phương thức ruby.

def some_func_which_returns_a_list( x, y, z)
  return nil if failed_some_early_check


  # function code 

  @list     # returns the list
end

4
bạn cũng có thể viết "return nil if fail_some_early_check" nếu ý định của bạn vẫn rõ ràng
Mike Woodhouse

@Gishu Tôi thực sự đã phàn nàn về câu lệnh if, không phải tên phương thức.
Andrew Grimm

@Andrew .. oh kết thúc còn thiếu :). Hiểu rồi. cố định để bình định các bình luận khác quá từ Mike.
Gishu

Tại sao không if failed_check then nil else @list end? Đó là thực sự chức năng .
cdunn2001

@ cdunn2001 Không rõ tại sao nếu khác thì chức năng .. lý do cho phong cách này là nó làm giảm việc lồng nhau thông qua các lối thoát sớm. IMHO cũng dễ đọc hơn.
Gishu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.