Tôi có nên trích xuất chức năng cụ thể thành một chức năng không và tại sao?


29

Tôi có một phương thức lớn thực hiện 3 tác vụ, mỗi tác vụ có thể được trích xuất thành một hàm riêng biệt. Nếu tôi tạo một hàm bổ sung cho mỗi tác vụ đó, nó sẽ làm cho mã của tôi tốt hơn hay xấu hơn và tại sao?

Rõ ràng, nó sẽ tạo ra ít dòng mã hơn trong hàm chính, nhưng sẽ có các khai báo hàm bổ sung, vì vậy lớp của tôi sẽ có các phương thức bổ sung, mà tôi tin là không tốt, vì nó sẽ làm cho lớp phức tạp hơn.

Tôi nên làm điều đó trước khi tôi viết tất cả mã hay tôi nên để lại cho đến khi mọi thứ được thực hiện và sau đó giải nén các hàm?


19
"Tôi để nó cho đến khi mọi thứ hoàn thành" thường đồng nghĩa với "Nó sẽ không bao giờ được thực hiện".
Euphoric

2
Điều đó nói chung là đúng, nhưng cũng nên nhớ nguyên tắc ngược lại của YAGNI (không áp dụng trong trường hợp này, vì bạn đã cần nó).
jhocking


Chỉ muốn nhấn mạnh không tập trung quá nhiều vào việc giảm các dòng mã. Thay vào đó hãy cố gắng suy nghĩ về mặt trừu tượng. Mỗi chức năng chỉ nên có một công việc. Nếu bạn thấy rằng các chức năng của bạn đang làm nhiều hơn một công việc thì nói chung bạn nên cấu trúc lại phương thức. Nếu bạn làm theo các hướng dẫn này, gần như không thể có các chức năng quá dài.
Adrian

Câu trả lời:


35

Đây là một cuốn sách tôi thường liên kết đến, nhưng ở đây tôi lại đi: Bộ luật sạch của Robert C. Martin , chương 3, "Chức năng".

Rõ ràng, nó sẽ tạo ra ít dòng mã hơn trong hàm chính, nhưng sẽ có các khai báo hàm bổ sung, vì vậy lớp của tôi sẽ có các phương thức bổ sung, mà tôi tin là không tốt, vì nó sẽ làm cho lớp phức tạp hơn.

Bạn có thích đọc một hàm có hơn 150 dòng hay một hàm gọi 3 +50 hàm không? Tôi nghĩ rằng tôi thích lựa chọn thứ hai.

Vâng , nó sẽ làm cho mã của bạn tốt hơn theo nghĩa là nó sẽ "dễ đọc" hơn. Tạo các hàm thực hiện một và chỉ một thứ, chúng sẽ dễ bảo trì hơn và tạo ra một trường hợp thử nghiệm cho.

Ngoài ra, một điều rất quan trọng tôi đã học được với cuốn sách nói trên: chọn tên hay và chính xác cho các chức năng của bạn. Chức năng càng quan trọng, tên càng chính xác. Đừng lo lắng về độ dài của tên, nếu nó phải được gọi FunctionThatDoesThisOneParticularThingOnly, thì hãy đặt tên theo cách đó.

Trước khi thực hiện tái cấu trúc của bạn, viết một hoặc nhiều trường hợp thử nghiệm. Hãy chắc chắn rằng họ làm việc. Khi bạn đã hoàn tất việc tái cấu trúc, bạn sẽ có thể khởi chạy các trường hợp thử nghiệm này để đảm bảo mã mới hoạt động chính xác. Bạn có thể viết các bài kiểm tra "nhỏ hơn" để đảm bảo các chức năng mới của bạn hoạt động tốt tách biệt.

Cuối cùng, và điều này không trái với những gì tôi vừa viết, hãy tự hỏi mình nếu bạn thực sự cần thực hiện việc tái cấu trúc này, hãy kiểm tra câu trả lời cho " Khi nào nên tái cấu trúc ?" (cũng vậy, tìm kiếm các câu hỏi SO về "tái cấu trúc", có nhiều hơn và câu trả lời rất thú vị để đọc)

Tôi nên làm điều đó trước khi tôi viết tất cả mã hoặc tôi nên để lại cho đến khi mọi thứ được thực hiện và sau đó giải nén các hàm?

Nếu mã đã có sẵn và hoạt động và bạn không có thời gian cho lần phát hành tiếp theo, đừng chạm vào nó. Mặt khác, tôi nghĩ rằng người ta nên thực hiện các chức năng nhỏ bất cứ khi nào có thể và như vậy, tái cấu trúc bất cứ khi nào có thời gian trong khi vẫn đảm bảo rằng mọi thứ hoạt động như trước (trường hợp thử nghiệm).


10
Trên thực tế, Bob Martin đã cho thấy nhiều lần rằng anh ta thích 7 chức năng với 2 đến 3 dòng trên một chức năng với 15 dòng (xem tại đây trang web.google.com.vn/site/unclebobconsultingllc/ đấm ). Và đó là nơi mà rất nhiều nhà phát triển thậm chí có kinh nghiệm sẽ chống lại. Cá nhân, tôi nghĩ rằng rất nhiều "nhà phát triển có kinh nghiệm" gặp khó khăn khi chấp nhận rằng họ vẫn có thể cải thiện một điều cơ bản như xây dựng trừu tượng hóa với các chức năng sau> 10 năm mã hóa.
Doc Brown

+1 chỉ để tham khảo một cuốn sách, theo ý kiến ​​khiêm tốn của tôi, nên có trong kệ của bất kỳ công ty phần mềm nào.
Fabio Marcolini

3
Tôi có thể đang diễn giải ở đây, nhưng một cụm từ trong cuốn sách đó vang vọng trong đầu tôi gần như mỗi ngày là "mỗi chức năng chỉ nên làm một việc, và làm tốt". Có vẻ đặc biệt có liên quan ở đây vì OP cho biết "chức năng chính của tôi làm ba việc"
wakjah

Bạn hoàn toàn đúng!
Jalayn

Phụ thuộc vào bao nhiêu ba chức năng riêng biệt đan xen. Có thể dễ dàng theo dõi một khối mã tất cả ở một nơi hơn ba khối mã liên tục dựa vào nhau.
dùng253751

13

Vâng, rõ ràng. Nếu dễ thấy và tách các "nhiệm vụ" khác nhau của chức năng đơn.

  1. Khả năng đọc - Các hàm với tên tốt làm cho nó rõ ràng mã nào làm mà không cần phải đọc mã đó.
  2. Khả năng sử dụng lại - Dễ dàng sử dụng chức năng thực hiện một việc ở nhiều nơi hơn là có chức năng thực hiện những việc bạn không cần.
  3. Khả năng kiểm tra - Dễ dàng hơn để kiểm tra chức năng, có một "chức năng" được xác định, một chức năng có nhiều trong số chúng

Nhưng có thể có vấn đề với điều này:

  • Thật không dễ dàng để xem làm thế nào để tách chức năng. Điều này có thể yêu cầu tái cấu trúc bên trong hàm trước, trước khi bạn chuyển sang phân tách.
  • Các chức năng có trạng thái nội bộ rất lớn, được thông qua xung quanh. Điều này thường gọi cho một số loại giải pháp OOP.
  • Thật khó để nói chức năng nào nên làm. Đơn vị kiểm tra nó và tái cấu trúc nó cho đến khi bạn biết.

5

Vấn đề bạn đang đặt ra không phải là vấn đề về mã hóa, quy ước hay thực hành mã hóa, mà là vấn đề về khả năng đọc và cách các trình soạn thảo văn bản hiển thị mã bạn viết. Vấn đề tương tự là xin lỗi cũng trong bài viết:

Bạn có thể phân chia các hàm và phương thức dài thành các hàm nhỏ hơn mặc dù chúng sẽ không được gọi bởi bất kỳ thứ gì khác không?

Việc tách một chức năng thành các chức năng phụ có ý nghĩa khi thực hiện một hệ thống lớn với mục đích gói gọn các chức năng khác nhau mà nó sẽ được cấu thành. Dù sao, sớm hay muộn, bạn sẽ thấy mình có một số chức năng lớn. Một số trong số chúng không thể đọc được và khó duy trì khi bạn giữ chúng dưới dạng các hàm dài đơn hoặc tách chúng là các hàm nhỏ hơn. Điều này đặc biệt đúng đối với các chức năng mà các hoạt động bạn thực hiện, không cần thiết ở bất kỳ nơi nào khác trong hệ thống của bạn. Cho phép chọn một trong những chức năng dài như vậy và xem xét nó trong một cái nhìn rộng hơn.

Chuyên nghiệp:

  • Khi bạn đọc nó, bạn có một ý tưởng hoàn chỉnh về tất cả các hoạt động của chức năng (bạn có thể đọc nó dưới dạng một cuốn sách);
  • Nếu bạn muốn gỡ lỗi nó, bạn có thể thực hiện từng bước mà không cần chuyển sang bất kỳ tệp / phần nào khác của tệp;
  • Bạn có quyền tự do truy cập / sử dụng bất kỳ biến nào được khai báo ở bất kỳ giai đoạn nào của hàm;
  • Thuật toán mà hàm thực hiện được chứa đầy đủ trong hàm (được đóng gói);

Contra:

  • Nó mất nhiều trang trên màn hình của bạn;
  • Phải mất nhiều thời gian để đọc nó;
  • Không dễ để ghi nhớ tất cả các bước khác nhau;

Bây giờ hãy tưởng tượng để chia chức năng dài thành một số chức năng phụ và xem xét chúng với triển vọng rộng lớn hơn.

Chuyên nghiệp:

  • Ngoại trừ các hàm rời, mỗi hàm mô tả bằng các từ (tên của các hàm phụ) các bước khác nhau được thực hiện;
  • Phải mất rất ít thời gian để đọc từng chức năng / chức năng phụ;
  • Rõ ràng các tham số và biến số bị ảnh hưởng tại mỗi chức năng phụ (tách mối quan tâm);

Contra:

  • Thật dễ dàng để tưởng tượng những gì một chức năng như "sin ()" làm, nhưng không dễ để tưởng tượng những chức năng phụ của chúng ta làm gì;
  • Thuật toán hiện không được chấp nhận, hiện được phân phối trong các hàm phụ (không có tổng quan);
  • Khi gỡ lỗi từng bước một, bạn sẽ dễ dàng quên chức năng gọi mức độ sâu mà bạn đang đến (nhảy ở đây và ở đó trong các tệp dự án của bạn);
  • Bạn có thể dễ dàng mất ngữ cảnh khi đọc các chức năng phụ khác nhau;

Cả hai giải pháp đều có pro và contra. Giải pháp tốt nhất thực tế sẽ là có các trình soạn thảo cho phép mở rộng, nội tuyến và cho toàn bộ chiều sâu, mỗi hàm gọi vào nội dung của nó. Mà sẽ làm, tách chức năng trong các chức năng phụ là giải pháp tốt nhất duy nhất.


2

Đối với tôi có bốn lý do để trích xuất các khối mã thành các hàm:

  • Bạn đang sử dụng lại nó : bạn vừa sao chép một khối mã vào bảng tạm. Thay vì chỉ dán nó, hãy đặt nó vào một hàm và thay thế khối bằng một lệnh gọi hàm ở cả hai bên. Vì vậy, bất cứ khi nào bạn cần thay đổi khối mã đó, bạn chỉ cần thay đổi chức năng duy nhất đó thay vì thay đổi mã nhiều nơi. Vì vậy, bất cứ khi nào bạn sao chép một khối mã, bạn phải thực hiện một chức năng.

  • Đó là một cuộc gọi lại : Đó là một trình xử lý sự kiện hoặc một loại mã người dùng nào đó là thư viện hoặc một cuộc gọi khung. (Tôi khó có thể tưởng tượng điều này mà không thực hiện các chức năng.)

  • Bạn tin rằng nó sẽ được sử dụng lại , trong dự án hiện tại hoặc có thể ở một nơi khác: bạn vừa viết một khối tính toán một chuỗi con dài nhất của hai mảng. Ngay cả khi chương trình của bạn chỉ gọi chức năng này một lần, tôi sẽ tin rằng cuối cùng tôi cũng sẽ cần chức năng này trong các dự án khác.

  • Bạn muốn tự viết mã : Vì vậy, thay vì viết một dòng bình luận qua một khối mã tóm tắt những gì nó làm, bạn trích xuất toàn bộ thành một hàm và đặt tên cho những gì bạn sẽ viết vào một bình luận. Mặc dù tôi không phải là người hâm mộ điều này, vì tôi muốn viết ra tên của thuật toán được sử dụng, lý do tại sao tôi đã chọn thuật toán đó, v.v. Tên hàm sẽ quá dài sau đó ...


1

Tôi chắc rằng bạn đã nghe lời khuyên rằng các biến nên được phân chia càng chặt chẽ càng tốt và tôi hy vọng bạn đồng ý với nó. Vâng, các hàm là các thùng chứa phạm vi và trong các hàm nhỏ hơn, phạm vi của các biến cục bộ nhỏ hơn. Rõ ràng hơn về cách thức và thời điểm chúng được sử dụng và việc sử dụng chúng sai thứ tự hoặc trước khi chúng được khởi tạo sẽ khó hơn.

Ngoài ra, các chức năng là các thùng chứa dòng chảy logic. Chỉ có một cách duy nhất, các lối thoát được đánh dấu rõ ràng và nếu chức năng đủ ngắn, các luồng nội bộ sẽ rõ ràng. Điều này có tác dụng làm giảm độ phức tạp chu kỳ là một cách đáng tin cậy để giảm tỷ lệ khuyết tật.


0

Ngoài ra: Tôi đã viết bài này để trả lời câu hỏi của dallin (hiện đã đóng cửa) nhưng tôi vẫn cảm thấy nó có thể hữu ích cho ai đó nên ở đây đi


Tôi nghĩ rằng lý do cho các chức năng nguyên tử hóa là 2 lần và như @jozefg đề cập phụ thuộc vào ngôn ngữ được sử dụng.

Tách mối quan tâm

Lý do chính để làm điều này là để tách các đoạn mã khác nhau, do đó, bất kỳ khối mã nào không trực tiếp đóng góp vào kết quả / mục đích mong muốn của hàm là một mối quan tâm riêng biệt và có thể được trích xuất.

Giả sử bạn có tác vụ nền cũng cập nhật thanh tiến trình, cập nhật thanh tiến trình không liên quan trực tiếp đến tác vụ chạy dài nên cần được trích xuất, ngay cả khi đó là đoạn mã duy nhất sử dụng thanh tiến trình.

Nói trong JavaScript, bạn có một hàm getMyData (), trong đó 1) xây dựng một thông điệp xà phòng từ các tham số, 2) khởi tạo một tham chiếu dịch vụ, 3) gọi dịch vụ bằng thông báo xà phòng, 4) phân tích kết quả, 5) trả về kết quả. Có vẻ hợp lý, tôi đã viết hàm chính xác này nhiều lần - nhưng thực sự thể chia thành 3 hàm riêng chỉ bao gồm mã cho 3 & 5 (nếu đó) vì không có mã nào khác chịu trách nhiệm trực tiếp nhận dữ liệu từ dịch vụ .

Cải thiện trải nghiệm gỡ lỗi

Nếu bạn có các hàm hoàn toàn nguyên tử, theo dõi ngăn xếp của bạn sẽ trở thành một danh sách tác vụ, liệt kê tất cả các mã được thực hiện thành công, tức là:

  • Nhận dữ liệu của tôi
    • Xây dựng tin nhắn xà phòng
    • Tham chiếu dịch vụ khởi tạo
    • Phản hồi dịch vụ được phân tích cú pháp - LRI

sẽ thú vị hơn sau đó phát hiện ra rằng có lỗi trong khi lấy dữ liệu. Nhưng một số công cụ thậm chí còn hữu ích hơn cho việc gỡ lỗi các cây gọi chi tiết sau đó, ví dụ như Canvas của Debugger Canvas .

Tôi cũng hiểu mối quan tâm của bạn rằng có thể khó theo dõi mã được viết theo cách này bởi vì vào cuối ngày, bạn cần phải chọn một thứ tự các hàm trong một tệp trong đó cây gọi của bạn sẽ phức tạp hơn . Nhưng nếu các chức năng được đặt tên tốt (intellisense cho phép tôi sử dụng 3-4 từ trường hợp camal trong bất kỳ chức năng nào, tôi vui lòng không làm chậm tôi) và được cấu trúc với giao diện công cộng ở đầu tệp, mã của bạn sẽ đọc như mã giả cho đến nay là cách dễ nhất để có được sự hiểu biết cao về một cơ sở mã.

FYI - đây là một trong những điều "làm như tôi nói không phải như tôi làm", việc giữ nguyên tử mã là vô nghĩa trừ khi bạn kiên quyết phù hợp với nó IMHO, điều mà tôi không làm.

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.