Truyền biến thành viên làm tham số phương thức


33

Trong một dự án, tôi đã tìm thấy một mã như thế này:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

Làm thế nào để tôi thuyết phục các đồng đội của mình rằng đây là mã xấu?

Tôi tin rằng đây là một biến chứng không cần thiết. Tại sao vượt qua một biến thành viên như một tham số phương thức nếu bạn đã có quyền truy cập vào nó? Đây cũng là một vi phạm đóng gói.

Bạn có thấy bất kỳ vấn đề nào khác với mã này không?


21
Điều gì làm cho bạn nghĩ rằng nó xấu?
yannis

@Yannis Rizos, bạn nghĩ nó tốt chứ? Ít nhất đây là biến chứng không cần thiết. Tại sao truyền biến là tham số phương thức, nếu bạn đã có quyền truy cập vào nó? Đây là vi phạm đóng gói, là tốt.
tika

2
Điểm hợp lệ, vui lòng chỉnh sửa câu hỏi để bao gồm chúng. Chúng tôi không thể giúp bạn thuyết phục các đồng đội của mình, nhưng chúng tôi có thể giúp bạn đánh giá mã, đó là câu hỏi của bạn.
yannis

8
Tôi muốn có một phương thức có thể tổng hợp các biến khác nhau hơn là một phương thức có hằng số 2 + 2. Tham số trên phương thức này là để sử dụng lại.
Dante

Một điểm tôi nghĩ là quan trọng ở đây là loại tham số đó. Nếu đó là loại tham chiếu tôi không thấy lợi thế nhưng nếu đó là loại giá trị thì tôi nghĩ nó có ý nghĩa vì nếu bạn sửa đổi loại biến đó, trình biên dịch sẽ cảnh báo bạn về những nơi bạn đã phá mã.
Rémi

Câu trả lời:


3

Tôi nghĩ rằng đây là một chủ đề hợp lệ, nhưng lý do bạn nhận được phản hồi trái chiều là vì cách câu hỏi được hình thành. Cá nhân, tôi đã có những trải nghiệm tương tự với nhóm của mình, nơi thông qua các thành viên là đối số là không cần thiết và làm xáo trộn mã. Chúng tôi có một lớp làm việc với một nhóm thành viên nhưng một số hàm truy cập trực tiếp vào các thành viên và các hàm khác sẽ sửa đổi cùng các thành viên thông qua các tham số (nghĩa là sử dụng các tên hoàn toàn khác nhau) và hoàn toàn không có lý do kỹ thuật nào để làm điều đó. Theo lý do kỹ thuật, tôi có nghĩa là một ví dụ mà Kate cung cấp.

Tôi khuyên bạn nên lùi lại một bước và thay vì tập trung chính xác vào việc chuyển thành viên làm tham số, hãy bắt đầu các cuộc thảo luận với nhóm của bạn về sự rõ ràng và dễ đọc. Hoặc chính thức hơn, hoặc chỉ trong hành lang, thảo luận về những gì làm cho một số phân đoạn mã dễ đọc hơn và các phân đoạn mã khác khó khăn hơn. Sau đó, xác định các biện pháp chất lượng hoặc thuộc tính của mã sạch mà một nhóm bạn muốn phấn đấu. Xét cho cùng, ngay cả khi làm việc với các dự án lĩnh vực xanh, chúng tôi dành hơn 90% thời gian để đọc và ngay khi mã được viết (giả sử 10 - 15 phút sau), nó sẽ được bảo trì trong đó khả năng đọc thậm chí còn quan trọng hơn.

Vì vậy, đối với ví dụ cụ thể của bạn ở đây, đối số tôi sẽ sử dụng là ít mã hơn luôn dễ đọc hơn nhiều mã. Một chức năng có 3 tham số sẽ khó xử lý hơn bộ não so với chức năng không có hoặc 1 tham số. Nếu có một tên biến khác, não phải theo dõi một thứ khác khi đọc mã. Vì vậy, để nhớ "int m_value" và sau đó là "int localValue" và hãy nhớ rằng cái này thực sự có nghĩa là cái kia luôn đắt hơn cho bộ não của bạn sau đó chỉ cần làm việc với "m_value".

Để có thêm đạn dược và ý tưởng, tôi khuyên bạn nên chọn một bản sao của Bộ luật sạch của chú Bob .


Bị bỏ rơi sau khi thấy ảnh hưởng của cuốn sách được tham khảo.
Frank Hileman

Mặc dù một phần nhỏ trong tôi rất buồn khi 5 năm sau khi viết câu trả lời, bạn chỉ lấy đi 2 điểm internet từ tôi, có một phần nhỏ khác khiến tôi tò mò nếu bạn có thể cung cấp một số tài liệu tham khảo có thể là dấu hiệu của (có lẽ là xấu) ảnh hưởng mà cuốn sách mà tôi tham khảo đã có. Điều đó có vẻ công bằng và gần như đáng giá những điểm đó
DXM

tài liệu tham khảo là bản thân tôi, nhìn thấy cá nhân ảnh hưởng đến các nhà phát triển khác. Tuy nhiên, tất cả các động lực đằng sau những cuốn sách như vậy đều tốt, bằng cách xác định rằng tất cả các mã phải tuân theo các nguyên tắc không chuẩn (nghĩa là các hướng dẫn thực sự phục vụ mục đích), những cuốn sách đó dường như làm mất đi tư duy phê phán, mà một số người hiểu là giống như sùng bái .
Frank Hileman

liên quan đến các thách thức xử lý não bộ, hãy xem xét khái niệm về tải nhận thức
jxramos

30

Tôi có thể nghĩ về một lời biện minh cho việc chuyển trường thành viên làm tham số trong phương thức (riêng tư): nó làm rõ phương thức của bạn phụ thuộc vào điều gì.

Giống như bạn nói, tất cả các trường thành viên là các tham số ngầm định của phương thức của bạn, bởi vì toàn bộ đối tượng là. Tuy nhiên, đối tượng đầy đủ có thực sự cần thiết để tính kết quả không? Nếu SomeMethodlà một phương thức nội bộ chỉ phụ thuộc vào _someField, thì nó có sạch hơn để làm cho sự phụ thuộc này rõ ràng không? Trong thực tế, làm cho sự phụ thuộc này rõ ràng cũng có thể gợi ý rằng bạn thực sự có thể cấu trúc lại đoạn mã này ra khỏi lớp của bạn! (Lưu ý tôi cho rằng chúng ta không nói về getters hoặc setters ở đây, nhưng mã thực sự tính toán một cái gì đó)

Tôi sẽ không đưa ra lập luận tương tự cho các phương thức công khai, bởi vì người gọi không biết cũng không quan tâm đến phần nào của đối tượng có liên quan để tính kết quả ...


2
Điều gì sẽ là phụ thuộc ngầm còn lại? Tôi giả sử từ ví dụ của bạn đó _someFieldlà tham số duy nhất cần thiết để tính kết quả và chúng tôi chỉ làm cho nó rõ ràng. (Lưu ý: điều này rất quan trọng. Chúng tôi đã không thêm một phụ thuộc, chúng tôi chỉ làm cho nó rõ ràng!)
Andres F.

11
-1 Nếu không có sự phụ thuộc ngầm định vào các thành viên thể hiện, thì nó phải là một thành viên tĩnh lấy giá trị làm tham số. Trong trường hợp này, mặc dù bạn đã đưa ra một lý do, tôi không nghĩ đó là một lời biện minh hợp lệ. Đó là mã xấu.
Steven Evers

2
@SnOrfus Tôi thực sự đề nghị tái cấu trúc phương thức hoàn toàn ra khỏi lớp
Andres F.

5
+1. cho "... không phải là sạch hơn để làm cho sự phụ thuộc này rõ ràng?" Abso-freaking-loutely. Ai ở đây sẽ nói "các biến toàn cầu là tốt như một quy luật chung"? Đó là như vậy COBOL-68. Nghe tôi bây giờ và tin tôi sau. Trong mã không tầm thường của chúng tôi, đôi khi, tôi sẽ tái cấu trúc để chuyển tải một cách rõ ràng nơi các biến toàn cầu lớp được sử dụng. Trong nhiều trường hợp, chúng tôi đã làm hỏng việc sử dụng một cách tùy tiện một trường riêng và đó là tài sản công cộng b) làm xáo trộn sự chuyển đổi của các trường, bằng cách "che giấu sự phụ thuộc". Bây giờ nhân số này với một chuỗi thừa kế sâu 3-5.
radarbob

2
@Tarion Tôi không đồng ý với chú Bob về điều này. Bất cứ khi nào có thể, các phương thức nên giống như hàm và chỉ phụ thuộc vào các phụ thuộc rõ ràng. (Khi gọi các phương thức công khai trong OOP, một trong những phụ thuộc này là this(hoặc self), nhưng nó được thể hiện rõ ràng bằng chính cuộc gọi, obj.method(x)). Các phụ thuộc ngầm định khác là trạng thái của đối tượng; điều này thường làm cho mã khó hiểu hơn. Bất cứ khi nào có thể - và trong lý do - làm cho sự phụ thuộc rõ ràng và kiểu chức năng. Trong trường hợp phương thức riêng, nếu có thể, hãy vượt qua rõ ràng mọi tham số họ cần. Và vâng, nó giúp tái cấu trúc chúng ra.
Andres F.

28

Tôi thấy một lý do mạnh mẽ để chuyển các biến thành viên làm đối số hàm cho các phương thức riêng - độ tinh khiết của hàm. Các biến thành viên thực sự là một trạng thái toàn cầu theo quan điểm, hơn thế nữa, trạng thái toàn cầu có thể thay đổi nếu thành viên nói thay đổi trong quá trình thực thi phương thức. Bằng cách thay thế các tham chiếu biến thành viên bằng các tham số phương thức, chúng ta có thể thực hiện một hàm thuần túy. Các hàm thuần túy không phụ thuộc vào trạng thái bên ngoài và không có tác dụng phụ, luôn trả về cùng kết quả với cùng một bộ tham số đầu vào - do đó giúp cho việc kiểm tra và bảo trì trong tương lai dễ dàng hơn.

Chắc chắn rằng không dễ dàng cũng không thực tế để có tất cả các phương thức của bạn là phương thức thuần túy trong ngôn ngữ OOP. Nhưng tôi tin rằng bạn đạt được nhiều về mặt rõ ràng của mã bằng cách có các phương thức xử lý logic phức tạp thuần túy và sử dụng các biến không thay đổi, trong khi vẫn giữ việc xử lý trạng thái "toàn cầu" không tinh khiết trong các phương thức chuyên dụng.

Tuy nhiên, việc chuyển các biến thành viên cho một hàm công khai của cùng một đối tượng khi gọi hàm bên ngoài sẽ tạo thành một mùi mã chính theo quan điểm của tôi.


5
Supurb trả lời. Mặc dù có nhiều lý tưởng, nhưng nhiều lớp trong phần mềm ngày nay lớn hơn và xấu hơn toàn bộ chương trình khi giai đoạn "Globals is Evil" được tạo ra. Các biến lớp là, cho tất cả các mục đích thực tế, toàn cầu trong phạm vi của thể hiện của lớp. Có số lượng lớn công việc được thực hiện trong các hàm thuần túy giúp cho mã mạnh mẽ và dễ kiểm tra hơn nhiều.
mattnz

@mattnz có cách nào bạn có thể cung cấp một liên kết đến thư mục của Hwat hoặc bất kỳ cuốn sách nào của anh ấy về lập trình lý tưởng không? Tôi đã lùng sục trên mạng và không thể tìm thấy bất cứ điều gì về anh ta. Google tiếp tục cố gắng tự động sửa nó thành "cái gì".
Butussy Butkus

8

Nếu hàm được gọi nhiều lần, đôi khi chuyển biến thành viên đó vào và đôi khi chuyển một thứ khác, thì nó ổn. Ví dụ, tôi sẽ không xem xét điều này xấu cả:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

trong đó newStartDatemột số biến cục bộ và m_StartDatelà một biến thành viên.

Tuy nhiên, nếu hàm chỉ được gọi với biến thành viên được truyền cho nó, thì đó là số lẻ. Các hàm thành viên hoạt động trên các biến thành viên mọi lúc. Họ có thể đang làm điều này (tùy thuộc vào ngôn ngữ bạn đang làm việc) để có một bản sao của biến thành viên - nếu đó là trường hợp và bạn không thể thấy điều đó, mã có thể đẹp hơn nếu nó làm cho toàn bộ quá trình rõ ràng.


3
Không có vấn đề gì khi phương thức được gọi với các tham số khác với biến thành viên. Điều quan trọng là nó có thể được gọi theo cách đó. Bạn không phải lúc nào cũng biết một phương thức cuối cùng sẽ được gọi như thế nào khi bạn tạo nó.
Caleb

Có lẽ bạn nên thay thế điều kiện "nếu" bằng "needHandleIncreasChages (newStartDate)" và sau đó đối số của bạn không giữ nữa.
Tarion

2

Điều mà không ai chạm vào là someMethod được bảo vệ ảo. Có nghĩa là một lớp dẫn xuất có thể sử dụng nó VÀ thực hiện lại chức năng của nó. Một lớp dẫn xuất sẽ không có quyền truy cập vào biến riêng tư và do đó không thể cung cấp một triển khai tùy chỉnh của someMethod phụ thuộc vào biến riêng. Thay vì lấy sự phụ thuộc vào biến riêng, khai báo yêu cầu người gọi chuyển nó vào.


Những gì bạn đang thiếu là biến thành viên tư nhân này có người truy cập công cộng.
tika

Vì vậy, bạn đang nói rằng phương thức ảo được bảo vệ sẽ phụ thuộc vào người truy cập công khai của một biến riêng tư? Và bạn có một vấn đề với mã hiện tại?
Michael Brown

Truy cập công cộng được tạo ra để được sử dụng. Giai đoạn.
tika

1

Các khung GUI thường có một số loại 'Chế độ xem' đại diện cho những thứ được vẽ trên màn hình và lớp đó thường cung cấp một phương thức như invalidateRect(Rect r)đánh dấu một phần của vùng vẽ của nó khi cần vẽ lại. Khách hàng có thể gọi phương thức đó để yêu cầu cập nhật một phần của khung nhìn. Nhưng một khung nhìn cũng có thể gọi phương thức riêng của nó, như:

invalidateRect(m_frame);

để gây ra vẽ lại toàn bộ khu vực. Ví dụ, nó có thể làm điều này khi lần đầu tiên được thêm vào hệ thống phân cấp chế độ xem.

Không có gì sai khi làm điều này - khung của khung nhìn là một hình chữ nhật hợp lệ và chính khung nhìn biết rằng nó muốn vẽ lại chính nó. Lớp View có thể cung cấp một phương thức riêng không có tham số và sử dụng khung của khung nhìn thay thế:

invalidateFrame();

Nhưng tại sao lại thêm một phương thức đặc biệt chỉ cho việc này khi bạn có thể sử dụng tổng quát hơn invalidateRect()? Hoặc, nếu bạn đã chọn cung cấp invalidateFrame(), rất có thể bạn sẽ triển khai nó theo cách tổng quát hơn invalidateRect():

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

Tại sao truyền biến là tham số phương thức, nếu bạn đã có quyền truy cập vào nó?

Bạn nên truyền các biến đối tượng làm tham số cho phương thức của riêng bạn nếu phương thức đó không hoạt động cụ thể trên biến thể hiện đó. Trong ví dụ trên, khung của khung nhìn chỉ là một hình chữ nhật khác theo như invalidateRect()phương pháp có liên quan.


1

Điều này có ý nghĩa nếu phương thức là một phương thức tiện ích. Ví dụ, bạn cần lấy một tên ngắn duy nhất từ ​​một số chuỗi văn bản miễn phí.

Bạn sẽ không muốn mã hóa một triển khai riêng cho từng chuỗi, thay vào đó chuyển chuỗi sang một phương thức chung có ý nghĩa.

Tuy nhiên, nếu phương thức luôn hoạt động trên một thành viên, có vẻ hơi ngu ngốc khi truyền nó dưới dạng tham số.


1

Lý do chính để có một biến thành viên trong một lớp là để cho phép bạn đặt nó ở một nơi và có sẵn giá trị của nó cho mọi phương thức khác trong lớp. Nói chung, do đó bạn sẽ mong đợi rằng không cần truyền biến thành viên vào một phương thức của lớp.

Tuy nhiên tôi có thể nghĩ ra một vài lý do tại sao bạn có thể muốn chuyển biến thành viên vào một phương thức khác của lớp. Đầu tiên là nếu bạn cần đảm bảo rằng giá trị của biến thành viên cần được sử dụng không thay đổi khi được sử dụng với phương thức được gọi, ngay cả khi phương thức đó sẽ cần thay đổi giá trị biến thành viên thực tế tại một số điểm trong quy trình. Lý do thứ hai liên quan đến lý do thứ nhất là bạn có thể muốn đảm bảo tính bất biến của một giá trị trong phạm vi của chuỗi phương thức - ví dụ như khi thực hiện cú pháp trôi chảy.

Với tất cả những điều này, tôi sẽ không đi xa để nói rằng mã là "xấu" như vậy nếu bạn chuyển một biến thành viên cho một trong các phương thức của lớp. Tuy nhiên, tôi sẽ đề nghị rằng nó thường không lý tưởng, vì nó có thể khuyến khích rất nhiều sự sao chép mã trong đó tham số được gán cho một biến cục bộ chẳng hạn, và các tham số bổ sung thêm "nhiễu" trong mã không cần thiết. Nếu bạn là người hâm mộ cuốn sách Clean Code, bạn sẽ biết rằng nó nên đề cập rằng bạn nên giữ số lượng tham số phương thức ở mức tối thiểu và chỉ khi không có cách nào hợp lý hơn để phương thức truy cập vào tham số .


1

Một số điều đến với tôi khi nghĩ về điều này:

  1. Nói chung, chữ ký phương thức với ít tham số dễ hiểu hơn; một phương pháp lý do lớn đã được phát minh là loại bỏ các danh sách tham số dài bằng cách kết hợp chúng với dữ liệu mà chúng hoạt động.
  2. Việc tạo chữ ký phương thức phụ thuộc vào biến thành viên sẽ khiến việc thay đổi các biến đó trong tương lai khó khăn hơn, bởi vì bạn không chỉ cần thay đổi bên trong phương thức mà còn ở mọi nơi phương thức cũng được gọi. Và vì someMethod trong ví dụ của bạn được bảo vệ, các lớp con cũng sẽ cần phải thay đổi.
  3. Các phương thức (công khai hoặc riêng tư) không phụ thuộc vào bên trong lớp không cần phải nằm trong lớp đó. Họ có thể được đưa vào các phương pháp tiện ích và rất vui. Họ bên cạnh không có doanh nghiệp là một phần của lớp đó. Nếu không có phương thức nào khác phụ thuộc vào biến đó sau khi bạn di chuyển (các) phương thức, thì biến đó cũng sẽ đi! Nhiều khả năng biến phải nằm trên đối tượng của chính nó với phương thức hoặc phương thức hoạt động trên nó trở nên công khai và được lớp cha mẹ sáng tác.
  4. Truyền dữ liệu xung quanh cho các chức năng khác nhau như lớp của bạn là một chương trình thủ tục với các biến toàn cục chỉ xuất hiện khi đối mặt với thiết kế OO. Đó không phải là mục đích của các biến thành viên và các hàm thành viên (xem bên trên) và có vẻ như lớp của bạn không gắn kết lắm. Tôi nghĩ rằng đó là một mùi mã gợi ý một cách tốt hơn để nhóm dữ liệu và phương pháp của bạn.

0

Bạn bị thiếu nếu tham số ("đối số") là tham chiếu hoặc chỉ đọc.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

Nhiều nhà phát triển, thường chỉ định trực tiếp các trường bên trong của một biến, trong các phương thức constructor. Có một số trường hợp ngoại lệ.

Hãy nhớ rằng "setters" có thể có các phương thức bổ sung, không chỉ là phép gán và đôi khi, thậm chí là các phương thức ảo hoặc gọi các phương thức ảo.

Lưu ý: Tôi đề nghị giữ các trường bên trong của các thuộc tính là "được bảo vệ".

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.