Sử dụng thì quá khứ (giọng nói thụ động) để biểu thị sự bất biến? (Ví dụ: Array.Transposed so với Array.Transpose)


8

[Lưu ý: Dưới đây là một chút nặng về cú pháp và quy ước của nó, chẳng hạn như trích dẫn các quy tắc FxCop, bởi vì đây là nền tảng của tôi, nhưng mục đích của cuộc thảo luận này là không biết ngôn ngữ và các ví dụ mã được sử dụng thường được coi là mã giả. - Mike]

Tôi nghĩ rằng tất cả chúng ta sẽ đồng ý rằng các lớp thường được thiết kế sao cho các thể hiện đối tượng của chúng là bất biến ở bất cứ nơi nào thực tế và kết quả của việc gọi các thành viên của lớp sẽ không có tác dụng phụ, càng nhiều càng tốt.

Tuy nhiên, các quy ước đặt tên lịch sử có xu hướng sử dụng giọng nói tích cực: 'GetS Something', 'DoSome Breath', v.v. và điều này là tốt bởi vì nó nêu rõ hành động mà thành viên lớp sẽ thực hiện.

Tuy nhiên, bây giờ tôi bắt đầu nghĩ rằng các thành viên đôi khi có thể (hoặc có thể thường xuyên) được hưởng lợi bằng cách được đặt tên ở thì quá khứ để ám chỉ rằng một giá trị được trả về mà không ảnh hưởng đến đối tượng được tham chiếu.

Ví dụ, đối với một phương thức hoán chuyển một mảng, các kiểu đặt tên hiện tại có thể có tên là 'Array.Transpose', nhưng nếu thành viên này trả về một mảng mới , mà không chuyển đổi đối tượng được tham chiếu, thì tôi nghĩ rằng việc gọi nó là 'Array.Transposed 'Sẽ chính xác hơn và làm cho lập trình viên rõ ràng hơn.

Khi làm việc với phương thức 'Array.Transpose ()', người ta có thể vô tình thử sử dụng mã như sau:

Array myArray = new Array(); 
myArray.Transpose();

Nhưng nếu phương thức 'Transpose' trả về một mảng mới mà không ảnh hưởng đến đối tượng được tham chiếu, thì mã này không làm gì cả - nghĩa là, 'myArray' sẽ lặng lẽ không bị chuyển đổi. (Tôi cho rằng nếu 'Transpose' là một thuộc tính, thì trình biên dịch sẽ hiểu rằng phần trên không hợp pháp, nhưng hướng dẫn của FxCop nói rằng một thành viên thực hiện chuyển đổi phải là một phương thức.)

Nhưng nếu phương thức được đặt tên ở thì quá khứ là 'Array.Transposed', thì người dùng sẽ rõ ràng hơn rằng cú pháp sau là bắt buộc:

Array myArray = new Array(); 
Array transposedArray = myArray.Transposed();

Các tên khác mà tôi nghĩ sẽ phù hợp với danh mục này sẽ là các thành viên có tên 'Thay đổi kích thước' so với 'Thay đổi kích thước', 'Chuyển đổi' so với 'Chuyển đổi', v.v ... thì hiện tại sẽ được sử dụng cho các thành viên ảnh hưởng đến đối tượng được tham chiếu, trong khi thì quá khứ sẽ được sử dụng cho các thành viên trả về giá trị mới mà không ảnh hưởng đến đối tượng được tham chiếu.

Một cách tiếp cận phổ biến hơn hiện nay là tiền tố mỗi tên bằng "Nhận" hoặc "Tới", nhưng tôi nghĩ rằng điều này làm tăng thêm trọng lượng không cần thiết, trong khi thay đổi sang thì quá khứ truyền đạt ý nghĩa một cách hiệu quả.

Điều này có ý nghĩa với các lập trình viên khác ở đây, hay ý tưởng căng thẳng trong quá khứ này nghe có vẻ quá vụng về khi đọc nó?

Biên tập:

Những cuộc thảo luận tuyệt vời dưới đây, tôi cảm ơn mọi người rất nhiều. Một vài điểm tôi muốn tóm tắt dựa trên ý kiến ​​của bạn và suy nghĩ sửa đổi của riêng tôi về điều này sau đây.

Phương pháp Chaining "Fluent" Cú pháp

Dựa trên các cuộc thảo luận dưới đây, việc sử dụng thì quá khứ có lẽ có giá trị khi được sử dụng trên một lớp có thể thay đổi để làm rõ các thành viên nào làm hoặc không làm ảnh hưởng đến trạng thái bên trong của đối tượng được tham chiếu.

Tuy nhiên, đối với các lớp bất biến, phần lớn các trường hợp (người ta hy vọng), tôi nghĩ rằng việc sử dụng thì quá khứ có thể làm cho cú pháp trở nên khó xử một cách không cần thiết.

Ví dụ, với "cú pháp trôi chảy", chẳng hạn như những cú pháp được tạo bởi chuỗi phương thức, việc sử dụng thì quá khứ sẽ làm cho mã ít đọc hơn. Ví dụ: khi sử dụng LINQ qua C #, một chuỗi phương thức điển hình có thể trông như sau:

dataSource.Sort().Take(10).Select(x => x.ToString);

Nếu thay đổi thành thì quá khứ, điều này sẽ trở nên khó xử:

dataSource.Sorted().Taken(10).Selected(x => x.ToString);

Hãy nhớ rằng, dataSource.Sort () thực sự có ý nghĩa rõ ràng hơn dataSource.Sort (), vì các biểu thức LINQ luôn tạo ra một phép liệt kê mới mà không ảnh hưởng đến nguồn. Tuy nhiên, vì chúng tôi hoàn toàn nhận thức được điều này khi sử dụng mô hình này, việc sử dụng thì quá khứ là dư thừa và làm cho "sự trôi chảy" của việc đọc nó trở nên khó xử vì không có lợi ích thực sự.

Sử dụng các thuộc tính so với các phương thức

Một vấn đề chính được đưa ra dưới đây liên quan đến các nguyên tắc của FxCop về việc sử dụng các phương thức "Nhận" so với các thuộc tính chỉ đọc. Ví dụ: quy tắc FxCop CA1024 .

Mặc dù không được đề cập rõ ràng trong quy tắc FxCop đó, việc sử dụng các phương thức "To" dường như cũng sẽ áp dụng cho lý do này, vì các phương thức ToXXXXXX () là các phương thức chuyển đổi. Và trong trường hợp ToArray (), phương thức vừa là phương thức chuyển đổi vừa là phương thức trả về một mảng.

Sử dụng thì quá khứ cho các thuộc tính Boolean

Một số gợi ý rằng thì quá khứ có thể bao hàm một giá trị boolean, ví dụ: thuộc tính 'Thành công' hoặc 'Đã kết nối'. Tôi tin rằng các câu trả lời bên dưới nhấn mạnh việc sử dụng 'Is', 'Has' hoặc 'Are' cho các thuộc tính và trường Boolean là chính xác. Thì quá khứ cũng có thể giúp về vấn đề này, nhưng gần như không bằng cách sử dụng tiền tố 'Is'. Vui lòng sử dụng thì quá khứ cho các thuộc tính và trường Boolean, điều đó có ích, nhưng tôi nghĩ rằng việc đặt tiền tố tên với 'Is', 'Has' hoặc 'Are' thậm chí còn quan trọng hơn.

Kết luận chung?

Đối với vấn đề Tranpose () so với Transposed (), tôi nghĩ rằng 'skizzy' đã hiểu đúng, cho rằng đó phải là Transpose () cho một hành động đột biến so với GetTransposed () cho kết quả không đột biến. Điều này làm cho nó rõ ràng 100%. Nhưng, một lần nữa, tôi nghĩ rằng điều này chỉ có thể áp dụng cho một lớp có thể thay đổi, chẳng hạn như một mảng.

Đối với các lớp không thay đổi, đặc biệt là khi có thể sử dụng cú pháp chuỗi phương thức trôi chảy, thì tôi nghĩ rằng cú pháp này sẽ trở nên khó xử không cần thiết. Ví dụ: so sánh:

var v = myObject.Transpose().Resize(3,5).Shift(2);

đến:

var v = myObject.GetTransposed().GetResized(3,5).GetShifted(2);

Hmmm ... thực sự, ngay cả ở đây dường như làm rõ 100% rằng kết quả không ảnh hưởng đến nguồn, nhưng tôi không chắc chắn.

Tôi đoán đối với một mô hình như LINQ nơi nó được biết đến, việc sử dụng "Nhận" và / hoặc thì quá khứ là không cần thiết, nhưng đối với một API mới, ban đầu nó có thể có một số giá trị! Nếu API chỉ chứa các đối tượng không thay đổi, một khi người dùng hiểu điều này, việc sử dụng "Nhận" và / hoặc thì quá khứ chỉ làm giảm khả năng đọc.

Vì vậy, tôi nghĩ rằng không có câu trả lời dễ dàng ở đây. Tôi đoán người ta cần suy nghĩ về toàn bộ API của bạn và chọn cẩn thận!

- Mike


Tôi chắc chắn hy vọng bạn chỉ cần quên các parens cho việc gọi phương thức (hoặc chúng là tùy chọn trong ngôn ngữ mà các ví dụ này sử dụng, mặc dù điều đó rất hiếm) và bạn không có ý định chúng là các thuộc tính ...

Xin chào, có, đó là một tài sản và nó rất mãnh liệt. Bằng cách là một tài sản, điều rõ ràng hơn nữa là thuộc tính 'Transpose' (hoặc 'Transposed') trả về một giá trị và không nên có tác dụng phụ. Trong thực tế, trình biên dịch nên chọn cách sử dụng mã không chính xác ở trên, nhưng ý tưởng ở đây là cố gắng rõ ràng hơn cho người dùng - trước khi trình biên dịch chọn nó.
Mike Rosenblum

Tôi nghĩ rằng đó là một ý tưởng rất tồi để sử dụng các thuộc tính cho mọi thứ như tạo ra một đối tượng hoàn toàn mới. Và để sao lưu nó, MSDN cho biết một phương thức có thể thích hợp hơn với một thuộc tính nếu "Phương thức này thực hiện một hoạt động tiêu tốn thời gian. Phương thức này chậm hơn đáng kể so với thời gian cần thiết để đặt hoặc nhận giá trị của một trường."

Nguồn tốt, cảm ơn rất nhiều cho điều đó. Vì vậy, ví dụ 'Chuyển đổi' của tôi có thể là lý tưởng, vì nó vượt qua quy tắc FxCop CA1024 vì thành viên này (a) thực hiện chuyển đổi và (b) trả về một mảng. Điều đó nói rằng, cuộc thảo luận về hiện tại so với thì quá khứ vẫn được áp dụng, nhưng tôi chắc chắn sẽ sử dụng các phương thức ToXXXXX () hoặc GetXXXX () thay vì các thuộc tính khi trả về kết quả chuyển đổi hoặc mảng!
Mike Rosenblum

Tôi nghĩ bạn có nghĩa là các đối tượng nên bất biến, không phải các lớp. Các lớp nói chung luôn luôn bất biến, ít nhất là trong các ngôn ngữ gõ tĩnh.
Nemanja Trifunovic

Câu trả lời:


7

Thì quá khứ có vẻ khó xử. Tôi nghĩ rằng bạn dự định từ này là một tính từ.

Đây là gợi ý của tôi cho vấn đề bạn yêu cầu: thay vì Transpose()gọi nó GetTransposed(). Bằng cách đó, bạn biết rằng bạn đang nhận được một cái gì đó và không sửa đổi đối tượng.


1
Tôi nghĩ rằng, cuối cùng, đây thực sự là câu trả lời "đúng". Tôi sẽ không đánh dấu nó là "chính xác" bởi vì (1) một số người khác, đặc biệt là 'qes' và 'Nghệ thuật nhà phát triển' đã đưa ra một số nhận xét thực sự chu đáo và (2) Tôi không thực sự nghĩ rằng có "đúng" trả lời cho vấn đề này - đây là câu hỏi "Cộng đồng Wiki". (P.se có khái niệm như vậy không?)
Mike Rosenblum

1
+1 Câu trả lời ngắn gọn súc tích! Cách này Transpose()vẫn có thể được hiểu là điều chỉnh đối tượng đã cho. Có lẽ đây nên là một cấu trúc ngôn ngữ. :) object.Transpose()so với object:Transpose().
Steven Jeuris

Tôi thích AsTransposedhơn là GetTransposedtrong các trường hợp các cuộc gọi lặp lại liên tiếp sẽ, không có tác dụng phụ, mang lại kết quả giống hệt nhau về mặt ngữ nghĩa (có thể hoặc không thể là cùng một đối tượng, nhưng không thể được hiển thị là các đối tượng khác nhau mà không sử dụng những thứ như ReferenceEqualshoặc tương đương) .
supercat

6

Nó thực sự có ý nghĩa.

Theo tôi, những gì bạn đang nói về nên được coi là một quy tắc chung khi thiết kế API hoặc thư viện. Chỉ sau đó nó sẽ cung cấp một lợi ích hữu hình nếu hệ thống phù hợp trong tất cả các lớp.

Một vấn đề khác là trong nhiều trường hợp, người dùng giao diện không quan tâm lắm đến kết quả đạt được.

Transpose () sẽ hoán chuyển đối tượng hiện tại? Transpose () sẽ tạo một thể hiện chuyển đổi mới và trả về tham chiếu cho nó? Hoặc có lẽ nó sẽ chuyển đổi một lần, lưu trữ các kết quả trong một bản sao và trả lại nó trong các cuộc gọi tiếp theo mà không ảnh hưởng đến đối tượng hiện tại?

Người dùng thường chỉ muốn nhận được kết quả chuyển đổi, đó là lý do tại sao không nhiều người sẽ quan tâm đến quy ước đặt tên mà bạn đề xuất.

Nhưng theo đúng nghĩa của nó, nó là một đề xuất hợp lệ.


4

Tôi rất không đồng ý. Thì quá khứ là vụng về và không được sử dụng phổ biến (đó là lý do đủ để bản thân không thích nó).

Nếu phương thức sửa đổi đối tượng mà nó được gọi, nó không nên tự trả về - điều này cho thấy rõ rằng nó sửa đổi đối tượng.

Tương tự, nếu phương thức không sửa đổi đối tượng mà nó được gọi và thay vào đó xây dựng một đối tượng mới, phương thức sẽ trả về một đối tượng cùng loại. Đây là một quy ước rõ ràng và đã phổ biến.

Chữ ký phương thức, theo ý kiến ​​của tôi, là một quy ước rõ ràng hơn và được sử dụng phổ biến hơn nhiều để chỉ ra nếu phương thức trả về một đối tượng mới hoặc sửa đổi callee.


Xin chào, vâng, cách tiếp cận thì quá khứ không được sử dụng phổ biến, đó là lý do tại sao tôi đặt ra câu hỏi ở đây. (Tôi nghĩ nó có ý nghĩa, nhưng tiền lệ được tính rất nhiều.) Quan điểm của bạn về chữ ký phương thức là một điều tốt, nhưng, thật không may, là một điều cực kỳ tinh tế bởi vì các trình biên dịch thường không thực thi rằng kết quả của một phương thức được sử dụng . Vì vậy, một trình biên dịch không thể bắt lỗi khi gọi myObject.Resize () và không sử dụng giá trị trả về. Đề xuất của tôi là việc đặt tên phương thức "Thay đổi kích thước" sẽ làm cho nó rõ ràng hơn với lập trình viên rằng result = myObject.Resized () là bắt buộc.
Mike Rosenblum

1
Trình biên dịch chắc chắn có thể cảnh báo rằng kết quả của một phương thức không được sử dụng, điều này nhiều hơn những gì họ có thể làm đối với bất kỳ hành vi nào dựa trên thì của động từ trong tiếng Anh được sử dụng trong một định danh, vì vậy đó là một điểm không hợp lệ. Tôi nói rằng việc đặt tên trong thì quá khứ sẽ không làm cho nó rõ ràng hơn bởi vì: 1. Nó không phải là một quy ước được thiết lập. 2. Có những quy ước được thiết lập khác 3. Sẽ có nhiều từ sẽ đặc biệt lúng túng hoặc hoàn toàn không hợp lệ trong thì quá khứ, trong khi chữ ký phương thức sẽ không phải chịu sự nhầm lẫn đó.
quentin-starin

Trình biên dịch chắc chắn có thể cảnh báo cho điều này, nhưng chúng thường chỉ làm như vậy cho các thuộc tính. Các giá trị trả về từ các phương thức không được sử dụng thường bị bỏ qua bởi hầu hết các trình biên dịch trên các cài đặt tiêu chuẩn.
Mike Rosenblum

Điều đó nói rằng, tôi càng nghĩ về nhận xét của bạn và quy tắc FxCop CA1024 (msdn.microsoft.com/en-us/l Library / ms182181 (VS.80) .aspx), các ví dụ của tôi là tất cả các tình huống xảy ra chuyển đổi và / hoặc trong đó một mảng đang được trả về. Kết quả là những cái này nên sử dụng cách đặt tên phương thức GetXXXXX () hoặc ToXXXXX () và tôi nghĩ rằng điều này là đúng. (Đặc biệt vì đây là quy ước đã được thiết lập.) Tôi nghĩ rằng việc đặt tên phương thức trong thì đã qua, như trong "GetTransposed ()", vẫn hữu ích ở đây, nhưng chỉ một chút thôi.
Mike Rosenblum

3

Nghe có vẻ hợp lý và có những tiền lệ - Python có cả list.sort()sorted(seq). Đầu tiên là một phương thức, một phương thức khác là một hàm hoạt động trên bất kỳ lần lặp nào và trả về một danh sách.


list.sort () có trả lại gì không?
quentin-starin

Không, nó không.
Adam Byrtek

2

Trong thư viện bộ sưu tập Scala, có một vài phương thức ở thì quá khứ:

  • groupedtrả về một trình vòng lặp lặp qua các phần tử của chuỗi trong các nphần tử
  • sorted trả về một phiên bản đã sắp xếp của bộ sưu tập
  • updated trả về một phiên bản mới của bộ sưu tập với một yếu tố đã thay đổi

Tuy nhiên, tôi không chắc liệu đây là một quyết định có ý thức hay đơn giản là họ đã hết tên, bởi vì thư viện bộ sưu tập của Scala rất phong phú. Ví dụ, cũng có groupBysortBycác phương thức, và updatephương thức này là đặc biệt, bởi vì trình biên dịch viết lại phép gán

foo(bar) = baz

vào

foo.update(bar, baz)

Các phiên bản cũ hơn của Scala thực sự đã sử dụng updatecho các bộ sưu tập bất biến, nhưng việc updatetrả lại một phiên bản mới thay vì đột biến là rất khó xử, vì rõ ràng bạn phải gán giá trị trả về cho một thứ gì đó, ví dụ: "cập nhật" một mục trong bản đồ bất biến sẽ là một thứ gì đó giống

val newMap = oldMap(key) = value // HUH ??!!??

Bây giờ thì đúng là vậy

val newMap = oldMap.updated(key -> value)

Trong một ngôn ngữ có hệ thống loại tốt, các loại thường sẽ cho bạn biết liệu thói quen có làm thay đổi các đối số của nó hay không. Ví dụ, trong Haskell, loại phiên bản không thay đổi sẽ giống như

transpose :: Array -> Array

trong khi đó một phiên bản có thể thay đổi sẽ giống như

transpose :: State Array

1

Khi tôi thấy một phương thức / thuộc tính trong thì thông qua, tôi ngay lập tức cho rằng nó trả về một giá trị boolean. Đề xuất của bạn có thể có ý nghĩa về mặt logic, nhưng nó chỉ có vẻ lạ và khó xử. Nếu tôi phải giải thích cho ai đó tại sao nó có ý nghĩa thì có lẽ đó không phải là một quy ước tốt.


2
Đó là lý do tại sao đó là một quy ước tốt để tiền tố các giá trị boolean với "là". ; p Hơn nữa, lý do anh giải thích là vì đó không phải là một quy ước. Một số câu trả lời cho câu hỏi này phụ thuộc quá nhiều vào các quy ước hiện có. Bạn cần có thể bước sang một bên và phán xét nó mà không cần một quy ước đã biết.
Steven Jeuris

0

Tôi không phải là một fan hâm mộ lớn của các tên biến quá khứ cho tính bất biến - đó là một quy ước hơi bất thường và tôi nghĩ rằng nó sẽ gây nhầm lẫn, đặc biệt đối với những người không đặc biệt mạnh về ngữ pháp tiếng Anh (và tôi chỉ nói đùa một nửa ở đó ...)

Ngoài ra, quy ước này loại bỏ cơ hội sử dụng thì quá khứ để biểu thị một thứ khác mà tôi nghĩ là quan trọng hơn: mô tả trạng thái của đối tượng được mô tả, ví dụ: "các mục đã xử lý" cho bạn biết rõ rằng các mục trong bộ sưu tập đã được xử lý, đó là thông tin logic quan trọng để truyền đạt.

Một ý tưởng tốt hơn: làm cho mọi thứ bất biến . Sau đó, bạn không cần bất kỳ sự phân biệt ngôn ngữ ưa thích. Bạn biết bạn muốn :-)


1
Đó là lý do tại sao đó là một quy ước tốt để tiền tố các giá trị boolean với "là" và "có".
Steven Jeuris
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.