Có phải là thực hành tốt để sử dụng các chức năng chỉ để tập trung mã phổ biến?


20

Tôi chạy qua vấn đề này rất nhiều. Ví dụ, tôi hiện đang viết một hàm đọc và một hàm ghi và cả hai đều kiểm tra xem đó có phải buflà con trỏ NULL không và modebiến đó có nằm trong một số ranh giới nhất định không.

Đây là sao chép mã. Điều này có thể được giải quyết bằng cách di chuyển nó vào chức năng riêng của nó. Nhưng tôi có nên không? Đây sẽ là một chức năng thiếu máu khá (không làm được gì nhiều), thay vì cục bộ (vì vậy không phải mục đích chung) và không thể tự đứng vững (không thể tìm ra bạn cần nó ở đâu trừ khi bạn thấy nó ở đâu đã sử dụng). Một tùy chọn khác là sử dụng macro, nhưng tôi muốn nói về các chức năng trong bài viết này.

Vì vậy, bạn nên sử dụng một chức năng cho một cái gì đó như thế này? Những ưu và khuyết điểm là gì?


2
Công cụ một dòng cần nhiều hơn là được sử dụng ở nhiều nơi để đảm bảo di chuyển vào một chức năng riêng biệt.

1
Câu hỏi tuyệt vời. Đã có rất nhiều lần tôi tự hỏi như vậy.
rdasxy

Câu trả lời:


31

Đây là một sử dụng tuyệt vời của các chức năng.

Đây sẽ là một chức năng thiếu máu khá (không làm được gì nhiều) ...

Điều đó thật tốt. Chức năng chỉ nên làm một việc.

khá cục bộ ...

Trong một ngôn ngữ OO, làm cho nó riêng tư.

(vì vậy không phải mục đích chung)

Nếu nó xử lý nhiều hơn một trường hợp, đó mục đích chung. Hơn nữa, khái quát hóa không phải là việc sử dụng chức năng duy nhất. Họ thực sự ở đó để (1) tiết kiệm cho bạn viết cùng một mã nhiều lần, nhưng cũng (2) để chia mã thành các phần nhỏ hơn để dễ đọc hơn. Trong trường hợp này, nó đạt được cả (1) và (2). Tuy nhiên, ngay cả khi chức năng của bạn được gọi từ chỉ một nơi, nó vẫn có thể giúp với (2).

và không tự đứng vững (không thể tìm ra những gì bạn cần nó trừ khi bạn thấy nó được sử dụng ở đâu).

Hãy nghĩ ra một cái tên hay cho nó, và nó tự đứng vững. "ValidateFileParameter" hoặc một cái gì đó. Bây giờ đứng tốt trên chính nó.


6
Điểm hàng hóa tốt. Tôi sẽ thêm (3) ngăn bạn tìm kiếm một lỗi trùng lặp trong toàn bộ cơ sở mã của bạn khi bạn có thể sửa tại một nơi. Sao chép mã có thể dẫn đến một địa ngục bảo trì khá nhanh.
deadalnix

2
@deadalnix: Điểm tốt. Khi tôi đang viết tôi nhận ra rằng điểm của tôi là một sự đơn giản hóa quá mức. Viết và gỡ lỗi dễ dàng hơn chắc chắn là một lợi ích của việc phân chia mọi thứ thành các chức năng (như khả năng đơn vị kiểm tra các chức năng riêng biệt).
Kramii phục hồi Monica

11

Nó hoàn toàn nên là một chức năng.

if (isBufferValid(buffer)) {
    // ...
}

Dễ đọc hơn và dễ bảo trì hơn (nếu logic kiểm tra từng thay đổi, bạn chỉ thay đổi nó ở một nơi).

Bên cạnh đó, những thứ như thế này được sắp xếp dễ dàng, do đó bạn thậm chí không cần phải lo lắng về các chi phí gọi chức năng.

Hãy để tôi hỏi bạn một câu hỏi tốt hơn. Làm thế nào là không làm điều này một thực hành tốt?

Làm điều đúng đắn. :)


4
Nếu isBufferValid đơn giản return buffer != null;thì tôi nghĩ bạn đang làm tổn thương khả năng đọc ở đó.
pdr

5
@pdr: Trong trường hợp đơn giản đó, nó chỉ làm tổn thương khả năng đọc nếu bạn có suy nghĩ về một người thích kiểm soát và thực sự, thực sự, muốn biết cách kiểm tra mã nếu bộ đệm hợp lệ. Vì vậy, nó chủ quan trong những trường hợp đơn giản.
Spoike

4
@pdr Tôi không biết làm thế nào nó làm tổn thương khả năng đọc theo bất kỳ tiêu chuẩn nào. Bạn đang miễn cho các nhà phát triển khác quan tâm đến cách bạn đang làm gì đó và tập trung vào những gì bạn đang làm. isBufferValidchắc chắn là dễ đọc hơn (trong cuốn sách của tôi) hơn buffer != null, bởi vì nó truyền đạt mục đích rõ ràng hơn. Và một lần nữa, không đề cập đến nó giúp bạn tiết kiệm từ sao chép cũng ở đây. Bạn cần thêm gì nữa à?
Yam Marcovic

5

IMO, đoạn mã có giá trị được chuyển đến các chức năng của riêng họ bất cứ khi nào điều này làm cho mã dễ đọc hơn , bất kể chức năng đó sẽ rất ngắn hoặc sẽ chỉ được sử dụng một lần.

Tất nhiên có giới hạn được quyết định bởi lẽ thường. Bạn không muốn có WriteToConsole(text)phương pháp với cơ thể đơn giản là Console.WriteLine(text), ví dụ. Nhưng sai lầm ở khía cạnh dễ đọc là một thực hành tốt.


2

Nói chung, nên sử dụng các hàm để loại bỏ trùng lặp trong mã.

Tuy nhiên nó có thể được đưa quá xa. Đây là một cuộc gọi phán xét.

Để lấy ví dụ về kiểm tra bộ đệm null của bạn, có lẽ tôi muốn nói rằng đoạn mã sau đủ rõ ràng và không nên được trích xuất thành một hàm riêng biệt, ngay cả khi cùng một mẫu được sử dụng ở một vài nơi.

if (buf==null) throw new BufferException("Null read buffer!");

Nếu bạn bao gồm thông báo lỗi dưới dạng tham số cho hàm kiểm tra null chung và cũng xem xét mã cần thiết để xác định hàm, thì đó không phải là lưu LỘC thuần để thay thế điều này bằng:

checkForNullBuffer(buf, "Null read buffer!");

Ngoài ra, phải đi sâu vào chức năng để xem những gì nó đang làm khi gỡ lỗi có nghĩa là lệnh gọi hàm ít "trong suốt" hơn đối với người dùng và do đó có thể được coi là ít đọc / duy trì hơn.


Có thể cho rằng, mẫu mực của bạn nói nhiều hơn về việc thiếu hỗ trợ hợp đồng bằng ngôn ngữ hơn là một nhu cầu trùng lặp thực sự. Nhưng dù sao cũng là điểm tốt.
deadalnix

1
Bạn đã bỏ lỡ điểm theo cách tôi nhìn thấy nó. Không phải bước qua hoặc phải xem xét logic mỗi khi bạn gỡ lỗi là điều tôi đang tìm kiếm. Nếu tôi muốn biết làm thế nào nó được thực hiện, tôi kiểm tra nó một lần. Phải làm điều đó mỗi lần chỉ đơn giản là ngớ ngẩn.
Yam Marcovic

Nếu thông báo lỗi ("Bộ đệm đọc Null") được lặp lại, việc sao chép đó chắc chắn sẽ được loại bỏ.
kevin cline

Vâng, đó chỉ là một ví dụ nhưng có lẽ bạn sẽ có một thông báo khác nhau ở mỗi trang gọi chức năng - ví dụ: "Bộ đệm đọc Null" và "Bộ đệm ghi Null" chẳng hạn. Trừ khi bạn muốn có cùng thông báo nhật ký / lỗi cho từng tình huống, bạn không thể sao chép lại .......
mikera

-1 Điều kiện tiên quyết.checkNotNull là một cách thực hành tốt, không phải là điều xấu. Không cần bao gồm thông báo chuỗi mặc dù. google-collections.googlecode.com/svn/trunk/javadoc/com/google/ mẹo
ripper234

2

Tập trung mã thường luôn là một ý tưởng tốt. Chúng ta phải sử dụng lại mã càng nhiều càng tốt.

Tuy nhiên, điều quan trọng cần lưu ý làm thế nào để làm điều đó. Ví dụ: khi bạn có một mã mà compute_prime_number () hoặc check_if_packet_is_bad () thì tốt. Rất có thể là thuật toán của chính chức năng sẽ phát triển sẽ được hưởng lợi.

Tuy nhiên, bất kỳ đoạn mã nào lặp lại như một văn xuôi đều không đủ điều kiện để được tập trung ngay lập tức. Điều này tệ đây. Bạn có thể ẩn các dòng mã tùy ý bên trong một hàm chỉ để ẩn mã, theo thời gian, khi nhiều phần của ứng dụng bắt đầu sử dụng, tất cả chúng phải vẫn tương thích với nhu cầu của tất cả các thông số của hàm.

Dưới đây là một số câu hỏi bạn nên hỏi trước khi hỏi

  1. Có chức năng bạn đang tạo ra ý nghĩa vốn có của riêng nó hay nó chỉ là một loạt các dòng?

  2. Những bối cảnh khác sẽ yêu cầu sử dụng các chức năng tương tự? Có khả năng là bạn có thể yêu cầu khái quát hóa API một chút trước khi sử dụng không?

  3. Điều gì sẽ là kỳ vọng của (các phần khác nhau của) ứng dụng khi bạn ném ngoại lệ?

  4. Các kịch bản để thấy rằng các chức năng sẽ phát triển là gì?

Bạn cũng nên kiểm tra xem đã có những thứ như thế này chưa. Tôi đã thấy rất nhiều người luôn có xu hướng xác định lại các macro MIN, MAX của họ thay vì tìm kiếm những gì đã tồn tại.

Về cơ bản, câu hỏi là "Chức năng mới này có thực sự đáng để sử dụng lại hay đây chỉ là một bản sao-dán ?" Nếu là đầu tiên, nó là tốt để đi.


1
Chà, nếu mã trùng lặp không có ý nghĩa riêng, mã của bạn sẽ cho bạn biết rằng nó cần tái cấu trúc. Bởi vì những nơi xảy ra sự trùng lặp có lẽ cũng không có ý nghĩa riêng.
deadalnix

1

Mã số nên tránh. Mỗi lần bạn dự đoán nó, bạn nên tránh sao chép mã. Nếu bạn không lường trước được điều đó, hãy áp dụng quy tắc 3: tái cấu trúc trước khi cùng một đoạn mã được nhân đôi 3 lần, giải thích sự giải quyết khi nhân đôi 2 lần.

Mã trùng lặp là gì?

  • Một thói quen được lặp lại ở một vài nơi trong cơ sở mã. Tourine này phải phức tạp hơn một cuộc gọi chức năng đơn giản (khác, bạn sẽ không đạt được gì khi tính theo yếu tố).
  • Một nhiệm vụ rất phổ biến, thậm chí là một công việc tầm thường (thường là một bài kiểm tra). Điều này sẽ cải thiện việc đóng gói và ngữ nghĩa trong mã.

Hãy xem xét các ví dụ dưới đây:

if(user.getPrileges().contains("admin")) {
    // Do something
}

trở thành

if(user.isAdmin()) {
    // Do something
}

Bạn đã cải thiện việc đóng gói (bây giờ bạn có thể thay đổi các điều kiện để trở thành quản trị viên một cách minh bạch) và ngữ nghĩa của mã. Nếu một lỗi được phát hiện theo cách bạn kiểm tra xem người dùng có phải là quản trị viên hay không, bạn không cần phải ném toàn bộ cơ sở mã của mình và sửa lỗi ở mọi nơi (có nguy cơ quên một lỗi và có lỗi bảo mật trong ứng dụng của bạn).


1
Ví dụ Btw minh họa Luật Demeter.
MaR

1

DRY là về đơn giản hóa thao tác mã. Bạn vừa chạm vào một điểm tốt về nguyên tắc này: Không phải là giảm thiểu số lượng mã thông báo trong mã của bạn, mà là tạo các điểm sửa đổi duy nhất cho mã tương đương về mặt ngữ nghĩa . Có vẻ như séc của bạn luôn có cùng một ngữ nghĩa, vì vậy chúng nên được đưa vào một chức năng trong trường hợp bạn cần sửa đổi chúng.


0

Nếu bạn thấy nó trùng lặp, bạn nên tìm cách tập trung hóa nó.

Chức năng là một cách tốt (có thể không phải là tốt nhất nhưng điều đó phụ thuộc vào ngôn ngữ) Ngay cả khi chức năng bị thiếu máu như bạn nói, điều đó không có nghĩa là nó sẽ giữ nguyên như vậy.

Điều gì nếu bạn phải kiểm tra một cái gì đó là tốt?

Bạn sẽ tìm thấy tất cả những nơi bạn phải thêm kiểm tra thêm hoặc chỉ thay đổi một chức năng?


... mặt khác, nếu có khả năng rất cao là bạn sẽ không bao giờ thêm thứ gì vào đó. Là đề nghị của bạn vẫn là quá trình hành động tốt nhất trong trường hợp đó là tốt?
EpsilonVector

1
@EpsilonVector quy tắc ngón tay cái của tôi là nếu tôi phải thay đổi nó một lần thì tôi cũng có thể cấu trúc lại nó. Vì vậy, trong trường hợp của bạn tôi sẽ bỏ nó và nếu tôi phải thay đổi nó, nó sẽ trở thành một chức năng.
Thanos Papathanasiou

0

Nó hầu như luôn luôn tốt nếu các điều kiện sau được đáp ứng:

  • cải thiện khả năng đọc
  • được sử dụng trong phạm vi giới hạn

Trong phạm vi lớn hơn, bạn phải cân nhắc cẩn thận sự trùng lặp so với sự đánh đổi phụ thuộc. Ví dụ về các kỹ thuật giới hạn phạm vi: ẩn trong các phần hoặc mô-đun riêng mà không để lộ chúng công khai.

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.