it-swarm-vi.com

"Tác dụng phụ là gì?"

Tôi đã hiểu rõ khái niệm về tác dụng phụ.

  • Tác dụng phụ trong lập trình là gì?
  • Có phải nó phụ thuộc ngôn ngữ lập trình?
  • Có một điều như tác dụng phụ bên ngoài và bên trong?

Xin cho một số ví dụ về nguyên nhân tạo ra tác dụng phụ.

101
Amir Rezaei

A tác dụng phụ chỉ đơn giản là sửa đổi một số loại trạng thái - ví dụ:

  • Thay đổi giá trị của một biến;
  • Ghi một số dữ liệu vào đĩa;
  • Kích hoạt hoặc vô hiệu hóa một nút trong Giao diện người dùng.

Trái ngược với những gì một số người dường như đang nói:

  • Một tác dụng phụ không không phải bị ẩn hoặc bất ngờ (có thể, nhưng điều đó không liên quan gì đến định nghĩa khi áp dụng cho khoa học máy tính) ;

  • Một tác dụng phụ có không có gì liên quan đến tính không ổn định. Hàm idempotent có thể có tác dụng phụ và hàm không idempotent có thể không có tác dụng phụ (chẳng hạn như lấy ngày và giờ hệ thống hiện tại).

Nó thực sự rất đơn giản. Tác dụng phụ = thay đổi một cái gì đó ở đâu đó.

P.S. Như nhà bình luận benjol đã chỉ ra, một số người có thể kết hợp định nghĩa về tác dụng phụ với định nghĩa của hàm thuần túy , đó là một hàm (a) idempotent và (b) không có phụ- Các hiệu ứng. Một cái không bao hàm cái khác trong khoa học máy tính nói chung, nhưng các ngôn ngữ lập trình chức năng thường sẽ có xu hướng thực thi cả hai ràng buộc.

114
Aaronaught

Bất kỳ hoạt động nào sửa đổi trạng thái của máy tính hoặc tương tác với thế giới bên ngoài được cho là có tác dụng phụ. Xem Wikipedia trên Tác dụng phụ .

Ví dụ, chức năng này không có tác dụng phụ. Kết quả của nó chỉ phụ thuộc vào các đối số đầu vào của nó và không có gì về trạng thái của chương trình hoặc môi trường của nó thay đổi khi được gọi:

int square(int x) { return x * x; }

Ngược lại, việc gọi các chức năng này sẽ cung cấp cho bạn các kết quả khác nhau tùy theo thứ tự bạn gọi chúng, bởi vì chúng thay đổi điều gì đó về trạng thái của máy tính:

int n = 0;
int next_n() { return n++; }
void set_n(int newN) { n = newN; }      

Hàm này có tác dụng phụ là ghi dữ liệu vào đầu ra. Bạn không gọi hàm vì bạn muốn giá trị trả về của nó; bạn gọi nó bởi vì bạn muốn hiệu ứng của nó đối với "thế giới bên ngoài":

int Write(const char* s) { return printf("Output: %s\n", s); }
38
Kristopher Johnson

Tôi nghĩ rằng các câu trả lời hiện có là khá tốt. Tôi muốn giải thích một số khía cạnh mà IMO chưa được nhấn mạnh đủ.

Trong toán học, một hàm chỉ là một ánh xạ từ một Tuple các giá trị thành một giá trị. Vì vậy, được cung cấp một hàm f và một giá trị x, f(x) sẽ luôn có cùng kết quả y. Bạn cũng có thể thay thế f(x) bằng y ở mọi nơi trong một biểu thức và sẽ không có gì thay đổi.

Cái được gọi là hàm (hoặc thủ tục) trong nhiều ngôn ngữ lập trình là cấu trúc (đoạn mã) có thể được thực thi vì:

  1. Nó tính toán một hàm theo nghĩa toán học, tức là đã cho các giá trị đầu vào, nó trả về một kết quả, hoặc
  2. Nó tạo ra một số hiệu ứng, ví dụ: in một cái gì đó lên màn hình, thay đổi giá trị trên cơ sở dữ liệu, phóng tên lửa, ngủ trong 10 giây, gửi SMS.

Vì vậy, các hiệu ứng có thể liên quan đến trạng thái mà còn liên quan đến các khía cạnh khác như bắn tên lửa hoặc tạm dừng thực thi trong vài giây.

Thuật ngữ tác dụng phụ có thể nghe có vẻ tiêu cực nhưng thông thường, hiệu ứng của việc gọi một chức năng là mục đích của chính chức năng đó. Tôi cho rằng, vì hàm thuật ngữ ban đầu được sử dụng trong Toán học, tính toán một giá trị được coi là hiệu ứng chính của hàm trong khi mọi hiệu ứng khác được xem là tác dụng phụ . Một số ngôn ngữ lập trình sử dụng thuật ngữ thủ tục để tránh nhầm lẫn với các hàm theo nghĩa toán học.

Lưu ý rằng

  1. Một số thủ tục hữu ích cho cả giá trị trả về và tác dụng phụ của chúng.
  2. Một số thủ tục chỉ tính toán một giá trị kết quả và không có hiệu ứng khác. Chúng thường được gọi là các hàm thuần túy bởi vì tất cả những gì chúng làm là tính toán một hàm theo nghĩa toán học.
  3. Một số thủ tục, ví dụ: sleep() trong Python, chỉ hữu ích cho các hiệu ứng (phụ) của chúng ,. Chúng thường được mô hình hóa dưới dạng các hàm trả về giá trị đặc biệt None hoặc unit hoặc () hoặc ..., chỉ đơn giản chỉ ra rằng tính toán đã kết thúc chính xác.
24
Giorgio

Một tác dụng phụ là khi một hoạt động có ảnh hưởng đến một biến/đối tượng nằm ngoài mục đích sử dụng.

Nó có thể xảy ra khi bạn thực hiện cuộc gọi đến một hàm phức tạp có tác dụng phụ là thay đổi một số biến toàn cục, mặc dù đó không phải là lý do bạn gọi nó (có thể bạn đã gọi nó để trích xuất một cái gì đó từ cơ sở dữ liệu).

Tôi thừa nhận rằng tôi gặp khó khăn khi đưa ra một ví dụ đơn giản mà trông không hoàn toàn giả tạo, và các ví dụ từ những thứ tôi đã làm việc quá lâu để đăng ở đây (và vì nó liên quan đến công việc, nên có lẽ tôi không nên ).

Một ví dụ tôi đã thấy (cách đây một thời gian) là một chức năng mở kết nối cơ sở dữ liệu nếu kết nối ở trạng thái đóng. Vấn đề là nó được cho là đóng kết nối ở cuối chức năng, nhưng nhà phát triển đã quên thêm mã đó. Vì vậy, ở đây, có một tác dụng phụ ngoài ý muốn : gọi một thủ tục được cho là chỉ thực hiện một truy vấn và tác dụng phụ là kết nối vẫn mở và nếu chức năng được gọi hai lần liên tiếp, một lỗi sẽ xuất hiện cho biết kết nối đã mở.


Ok, vì bây giờ mọi người đang đưa ra ví dụ, tôi nghĩ tôi cũng sẽ như vậy;)

/*code is PL/SQL-styled pseudo-code because that's what's on my mind right now*/

g_some_global int := 0; --define a globally accessible variable somewhere.

function do_task_x(in_a in number) is
begin
    b := calculate_magic(in_a);
    if b mod 2 == 0 then
        g_some_global := g_some_global + b;
    end if;
    return (b * 2.3);
end;

Chức năng do_task_x có tác dụng chính trả về kết quả của một số phép tính và tác dụng bên có thể sửa đổi biến toàn cục.

Tất nhiên, là chính và đó là tác dụng phụ có thể mở để giải thích và có thể phụ thuộc vào việc sử dụng thực tế. Nếu tôi gọi hàm này với mục đích sửa đổi toàn cục và tôi loại bỏ giá trị được trả về hơn tôi sẽ nói rằng sửa đổi toàn cục là hiệu ứng chính.

Trong khoa học máy tính, một chức năng hoặc biểu thức được cho là có tác dụng phụ nếu nó sửa đổi một số trạng thái hoặc có tương tác quan sát được với các chức năng gọi hoặc thế giới bên ngoài.

Từ Wikipedia - Tác dụng phụ

Một hàm, theo nghĩa toán học, là một ánh xạ từ đầu vào đến đầu ra. Hiệu quả dự định của việc gọi một hàm là để nó ánh xạ đầu vào tới đầu ra mà nó trả về. Nếu hàm làm bất cứ điều gì khác, nó không thành vấn đề, nhưng nếu nó có bất kỳ hành vi nào không ánh xạ đầu vào vào đầu ra, thì hành vi đó được biết là một tác dụng phụ.

Nói một cách tổng quát hơn, một hiệu ứng phụ là bất kỳ hiệu ứng nào không phải là hiệu ứng dự định của người thiết kế công trình.

Một hiệu ứng là bất cứ điều gì ảnh hưởng đến một diễn viên. Nếu tôi gọi một chức năng gửi cho bạn gái của tôi một tin nhắn văn bản chia tay, điều đó ảnh hưởng đến một loạt các diễn viên, tôi, cô ấy, mạng của công ty điện thoại di động, v.v ... Hiệu ứng dự định duy nhất của việc gọi một chức năng miễn phí tác dụng phụ, là cho chức năng để trả lại cho tôi một ánh xạ từ đầu vào của tôi. Vì vậy đối với:

   public void SendBreakupTextMessage() {
        Messaging.send("I'm breaking up with you!")
   }

Nếu điều này được dự định là một hàm, thì điều duy nhất nó nên làm là trả về void. Nếu nó là hiệu ứng phụ miễn phí, nó thực sự không nên gửi tin nhắn văn bản.

Trong hầu hết các ngôn ngữ lập trình, không có cấu trúc cho một hàm toán học. Không có cấu trúc được dự định sẽ được sử dụng như vậy. Đó là lý do tại sao hầu hết các ngôn ngữ nói rằng bạn có phương pháp hoặc thủ tục. Theo thiết kế, chúng được dự định để có thể làm nhiều hiệu ứng hơn. Theo cách nói lập trình thông thường, không ai thực sự quan tâm đến ý định của phương thức hay thủ tục là gì, vì vậy khi ai đó nói chức năng này có tác dụng phụ, thì thực tế, ý nghĩa của cấu trúc này không hành xử giống như một hàm toán học. Và khi ai đó nói rằng hàm này không có tác dụng phụ, nghĩa là, cấu trúc này hoạt động hiệu quả như một hàm toán học.

Theo định nghĩa, một hàm thuần túy luôn có tác dụng phụ miễn phí. Hàm thuần túy, là một cách để nói, hàm này, mặc dù nó sử dụng một cấu trúc cho phép nhiều hiệu ứng hơn, nhưng chỉ có tác dụng tương đương với hàm của hàm toán học.

Tôi thách thức bất cứ ai nói với tôi khi chức năng miễn phí tác dụng phụ sẽ không thuần túy. Trừ khi hiệu ứng dự định chính của ngữ cảnh của câu sử dụng thuật ngữ thuần túy và hiệu ứng phụ miễn phí không phải là hiệu ứng dự định toán học của một hàm, thì những cái đó luôn luôn bằng nhau.

Như vậy, đôi khi, mặc dù hiếm hơn, và tôi tin rằng đây là sự khác biệt thiếu và cũng gây hiểu lầm cho mọi người (vì đó không phải là giả định phổ biến nhất) trong câu trả lời được chấp nhận, nhưng đôi khi người ta cho rằng tác dụng dự định của chức năng lập trình là để ánh xạ đầu vào thành đầu ra, trong đó đầu vào không bị ràng buộc với các tham số rõ ràng của hàm, nhưng đầu ra bị ràng buộc với giá trị trả về rõ ràng. Nếu bạn cho rằng đó là hiệu ứng mong muốn, thì một hàm đọc tệp và trả về một kết quả khác dựa trên những gì trong tệp vẫn không có tác dụng phụ, vì bạn đã cho phép các đầu vào đến từ những nơi khác trong hiệu ứng dự định của bạn.

Vậy, tại sao điều này lại quan trọng?

Đó là tất cả về kiểm soát và giữ nó. Nếu bạn gọi một hàm và nó làm một cái gì đó khác thì trả về một giá trị, thật khó để suy luận về hành vi của nó. Bạn sẽ cần phải nhìn vào bên trong hàm để tìm mã thực tế để đoán nó đang làm gì và khẳng định tính chính xác của nó. Tình huống lý tưởng là rất rõ ràng và dễ dàng biết đầu vào mà hàm đang sử dụng là gì và nó không làm gì khác sau đó trả lại một đầu ra cho nó. Bạn có thể thư giãn một chút và nói rằng biết chính xác đầu vào mà nó đang sử dụng không hữu ích như chắc chắn nó không làm gì khác mà bạn có thể không biết sau đó trả về một giá trị, vì vậy có thể bạn hài lòng với việc chỉ thực thi rằng nó không làm gì khác sau đó ánh xạ đầu vào, bất kể nó lấy từ đâu, đến đầu ra.

Trong hầu hết các trường hợp, quan điểm của một chương trình là có các hiệu ứng khác sau đó ánh xạ mọi thứ đi vào những thứ sắp ra. Ý tưởng kiểm soát hiệu ứng phụ là bạn có thể tổ chức mã theo cách dễ hiểu và lý do hơn. Nếu bạn đặt tất cả các tác dụng phụ lại với nhau, ở một nơi rất rõ ràng và trung tâm, bạn sẽ dễ dàng biết nơi để tìm và tin tưởng rằng đây là tất cả những gì đang xảy ra, không còn nữa. Nếu bạn cũng có đầu vào rất rõ ràng, nó sẽ giúp kiểm tra hành vi cho các đầu vào khác nhau và nó dễ sử dụng hơn, vì bạn không cần thay đổi đầu vào ở nhiều nơi khác nhau, một số nơi có thể không rõ ràng, chỉ là để có được những gì bạn muốn.

Bởi vì điều hữu ích nhất để hiểu, lý do và kiểm soát hành vi của một chương trình là để tất cả các đầu vào được nhóm lại rõ ràng với nhau và rõ ràng, cũng như tất cả các tác dụng phụ được nhóm lại với nhau và rõ ràng, đây thường là những gì mọi người nói về khi họ nói tác dụng phụ, tinh khiết, vv.

Bởi vì hữu ích nhất là nhóm các tác dụng phụ và nhân chứng của họ, đôi khi mọi người sẽ chỉ có nghĩa như vậy, và phân biệt nó bằng cách nói rằng nó không thuần túy, nhưng vẫn "không có tác dụng phụ". Nhưng tác dụng phụ liên quan đến "hiệu ứng chính dự định" giả định, vì vậy đó là một thuật ngữ theo ngữ cảnh. Điều này tôi thấy ít được sử dụng, mặc dù đáng ngạc nhiên là nó được nói đến rất nhiều trong chủ đề này.

Cuối cùng, idempotent có nghĩa là gọi hàm này nhiều lần với cùng một đầu vào (không quan trọng chúng đến từ đâu) sẽ luôn dẫn đến các hiệu ứng tương tự (tác dụng phụ hay không).

3
Didier A.

Trong lập trình, tác dụng phụ là khi một thủ tục thay đổi một biến từ bên ngoài phạm vi của nó. Tác dụng phụ không phụ thuộc vào ngôn ngữ. Có một số loại ngôn ngữ nhằm loại bỏ tác dụng phụ (ngôn ngữ chức năng thuần túy), nhưng tôi không chắc có ngôn ngữ nào yêu cầu tác dụng phụ hay không, nhưng tôi có thể sai.

Theo tôi biết, không có tác dụng phụ bên trong và bên ngoài.

2
indyK1ng

Đây là một ví dụ đơn giản:

int _totalWrites;
void Write(string message)
{
    // Invoking this function has the side effect of 
    // incrementing the value of _totalWrites.
    _totalWrites++;
    Debug.Write(message);
}

Định nghĩa về tác dụng phụ không đặc trưng cho lập trình nên chỉ cần tưởng tượng các tác dụng phụ của thuốc hoặc ăn quá nhiều thực phẩm.

0
ChaosPandion