Tại sao tác dụng phụ được coi là xấu xa trong lập trình chức năng?


69

Tôi cảm thấy rằng tác dụng phụ là một hiện tượng tự nhiên. Nhưng nó là một cái gì đó giống như điều cấm kỵ trong các ngôn ngữ chức năng. Những lý do là gì?

Câu hỏi của tôi là cụ thể cho phong cách lập trình chức năng. Không phải tất cả các ngôn ngữ lập trình / mô hình.


6
Một chương trình không có tác dụng phụ là vô ích, vì vậy tác dụng phụ không phải là xấu cũng không phải là điều cấm kỵ. Nhưng FP mã hóa phân định mã với các tác dụng phụ, do đó, một phần lớn của mã càng tốt là các hàm miễn phí có tác dụng phụ. Điều này được khuyến khích vì các chức năng phụ và hệ thống con miễn phí dễ hiểu hơn, dễ phân tích hơn, dễ kiểm tra hơn và dễ tối ưu hóa hơn.
JacquesB

@JacquesB Nó sẽ là một câu trả lời tốt để giải thích tại sao chúng dễ hiểu hơn, dễ phân tích hơn, dễ kiểm tra hơn và dễ tối ưu hóa hơn.
ceving

Câu trả lời:


72

Viết các hàm / phương thức của bạn mà không có tác dụng phụ - vì vậy chúng là các hàm thuần túy - giúp dễ dàng lý luận về tính chính xác của chương trình của bạn.

Nó cũng giúp bạn dễ dàng soạn các hàm đó để tạo hành vi mới.

Nó cũng làm cho một số tối ưu hóa nhất định có thể, trong đó trình biên dịch có thể ghi lại các kết quả của các hàm hoặc sử dụng Loại bỏ Subexpression chung.

Chỉnh sửa: theo yêu cầu của Stewol: Bởi vì rất nhiều trạng thái của bạn được lưu trữ trong ngăn xếp (luồng dữ liệu, không phải luồng điều khiển, như Jonas đã gọi nó ở đây ), bạn có thể song song hoặc sắp xếp lại việc thực hiện các phần tính toán đó không phụ thuộc vào lẫn nhau. Bạn có thể dễ dàng tìm thấy những phần độc lập đó vì một phần không cung cấp đầu vào cho phần kia.

Trong các môi trường có trình gỡ lỗi cho phép bạn khôi phục ngăn xếp và tiếp tục tính toán (như Smalltalk), có các hàm thuần có nghĩa là bạn có thể dễ dàng thấy giá trị thay đổi như thế nào, bởi vì các trạng thái trước đó có sẵn để kiểm tra. Trong một phép tính nặng đột biến, trừ khi bạn thêm rõ ràng các hành động do / hoàn tác vào cấu trúc hoặc thuật toán của mình, bạn không thể xem lịch sử tính toán. (Điều này liên quan đến đoạn đầu tiên: viết các hàm thuần túy giúp kiểm tra tính chính xác của chương trình của bạn dễ dàng hơn .)


4
Có thể xem xét thêm một cái gì đó về đồng thời trong câu trả lời của bạn?
Stewol

5
Các chức năng miễn phí có tác dụng phụ dễ dàng hơn để kiểm tra và tái sử dụng.
LennyProgrammer

@ Lenny222: tái sử dụng là những gì tôi đã gợi ý bằng cách nói về thành phần chức năng.
Frank Shearar

@Frank: Ah, ok, duyệt quá nông. :)
LennyProgrammer

@ Lenny222: Không sao đâu; nó có lẽ là một điều tốt để đánh vần nó ra.
Frank Shearar

23

Từ một bài viết về lập trình chức năng :

Trong thực tế, các ứng dụng cần phải có một số tác dụng phụ. Simon Peyton-Jones, người đóng góp chính cho ngôn ngữ lập trình chức năng Haskell, nói như sau: "Cuối cùng, bất kỳ chương trình nào cũng phải thao túng trạng thái. Một chương trình không có tác dụng phụ nào là một loại hộp đen. Tất cả những gì bạn có thể nói là cái hộp trở nên nóng hơn. " ( http://oscon.blip.tv/file/324976 ) Điều quan trọng là hạn chế tác dụng phụ, xác định rõ chúng và tránh phân tán chúng trong suốt mã.



23

Bạn đã hiểu sai, lập trình chức năng thúc đẩy hạn chế tác dụng phụ để làm cho chương trình dễ hiểu và tối ưu hóa. Thậm chí Haskell cho phép bạn ghi vào tập tin.

Về cơ bản điều tôi đang nói là các lập trình viên chức năng không nghĩ tác dụng phụ là xấu, họ chỉ nghĩ đơn giản là hạn chế sử dụng tác dụng phụ là tốt. Tôi biết nó có vẻ như là một sự phân biệt đơn giản như vậy nhưng nó làm cho tất cả sự khác biệt.


Đó là lý do tại sao chúng "giống như điều cấm kỵ" - FPL khuyến khích bạn hạn chế tác dụng phụ.
Frank Shearar

+1 cho cách tiếp cận. tác dụng phụ vẫn tồn tại. thật vậy, chúng bị giới hạn
Belun

Để làm rõ, tôi đã không nói 'tại sao không cho phép tác dụng phụ trong lập trình chức năng' hoặc 'tại sao không cần tác dụng phụ'. Tôi biết Nó được cho phép trong các ngôn ngữ chức năng và đôi khi là phải. Nhưng nó rất nản lòng trong lập trình chức năng. Tại sao? Đó là câu hỏi của tôi.
Gul Sơn

@Gulshan - Vì tác dụng phụ làm cho các chương trình khó hiểu và tối ưu hóa hơn.
ChaosPandion

trong trường hợp haskell, điểm chính không phải là "hạn chế tác dụng phụ". tác dụng phụ là không thể diễn tả trong NGÔN NGỮ. những chức năng như readFileđang làm là xác định một chuỗi các hành động. chuỗi này là thuần túy về mặt chức năng và giống như một cây trừu tượng mô tả những gì phải làm. các tác dụng phụ bẩn thực tế sau đó được thực hiện bởi thời gian chạy.
sara

13

Một vài lưu ý:

  • Các chức năng không có tác dụng phụ có thể được thực hiện song song, trong khi các chức năng có tác dụng phụ thường yêu cầu một số loại đồng bộ hóa.

  • Các chức năng không có tác dụng phụ cho phép tối ưu hóa mạnh mẽ hơn (ví dụ: bằng cách sử dụng bộ đệm kết quả một cách minh bạch), vì miễn là chúng tôi nhận được kết quả đúng, thậm chí chức năng đó có thực sự được thực hiện hay không


Điểm rất thú vị: thậm chí không quan trọng chức năng có thực sự được thực thi hay không . Sẽ rất thú vị khi kết thúc với một trình biên dịch có thể loại bỏ các cuộc gọi tiếp theo đến các hàm miễn phí có hiệu lực phụ với các tham số tương đương.
Noel Widmer

1
@NoelWidmer Một cái gì đó như thế đã tồn tại. PL / SQL của Oracle cung cấp một deterministicmệnh đề cho các hàm không có tác dụng phụ, vì vậy chúng không được thực thi thường xuyên hơn mức cần thiết.
user281377

Ồ Tuy nhiên, tôi nghĩ các ngôn ngữ nên có ý nghĩa về mặt ngữ nghĩa để trình biên dịch có thể tự tìm ra nó mà không phải chỉ định một cờ rõ ràng (tôi không chắc mệnh đề là gì). Một giải pháp có thể là xác định các tham số có thể thay đổi / bất biến. Nói chung, điều này sẽ đòi hỏi một hệ thống loại mạnh trong đó các giả định về tác dụng phụ có thể được thực hiện bởi trình biên dịch. Và tính năng sẽ cần phải có thể được tắt nếu muốn. Chọn không tham gia thay vì chọn tham gia. Đó chỉ là ý kiến ​​của tôi, dựa trên kiến ​​thức hạn chế mà tôi có kể từ khi đọc câu trả lời của bạn :)
Noel Widmer

Các deterministickhoản chỉ là một từ khóa mà nói với trình biên dịch rằng đây là một chức năng xác định, so sánh với cách finaltừ khóa trong Java cho trình biên dịch rằng biến không thể thay đổi.
user281377

11

Bây giờ tôi chủ yếu làm việc trong mã chức năng, và từ quan điểm đó, nó dường như rõ ràng. Tác dụng phụ tạo ra gánh nặng tinh thần rất lớn đối với các lập trình viên cố gắng đọc và hiểu mã. Bạn không nhận thấy gánh nặng đó cho đến khi bạn thoát khỏi nó trong một thời gian, sau đó đột nhiên phải đọc lại mã với các tác dụng phụ.

Hãy xem xét ví dụ đơn giản này:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

Trong một ngôn ngữ chức năng, tôi biết rằng foovẫn còn 42. Tôi thậm chí không phải nhìn vào mã ở giữa, ít hiểu nó hơn hoặc nhìn vào việc triển khai các chức năng mà nó gọi.

Tất cả những thứ về đồng thời và song song hóa và tối ưu hóa đều tốt, nhưng đó là những gì các nhà khoa học máy tính đưa vào tập tài liệu. Không phải tự hỏi ai đang thay đổi biến của bạn và khi nào là điều tôi thực sự thích trong thực hành hàng ngày.


6

Rất ít ngôn ngữ làm cho nó không thể gây ra tác dụng phụ. Các ngôn ngữ hoàn toàn không có tác dụng phụ sẽ rất khó sử dụng (gần như không thể), ngoại trừ trong một khả năng rất hạn chế.

Tại sao tác dụng phụ được coi là xấu xa?

Bởi vì họ làm cho nó khó khăn hơn nhiều để lý luận về chính xác những gì một chương trình làm, và để chứng minh rằng nó làm những gì bạn mong đợi nó làm.

Ở mức rất cao, hãy tưởng tượng thử nghiệm toàn bộ trang web 3 tầng chỉ với thử nghiệm hộp đen. Chắc chắn, nó có thể thực hiện được, tùy thuộc vào quy mô. Nhưng chắc chắn có rất nhiều sự trùng lặp đang diễn ra. Và nếu có một lỗi (có liên quan đến một tác dụng phụ), sau đó bạn có thể có khả năng phá vỡ toàn bộ hệ thống để thử nghiệm hơn nữa, cho đến khi lỗi được chẩn đoán và cố định, và việc sửa chữa được triển khai tới các môi trường thử nghiệm.

Những lợi ích

Bây giờ, quy mô xuống. Nếu bạn khá giỏi trong việc viết mã miễn phí hiệu ứng phụ, bạn sẽ nhanh hơn bao nhiêu để suy luận về những gì mã hiện có đã làm? Bạn có thể viết bài kiểm tra đơn vị nhanh hơn bao nhiêu? Làm thế nào tự tin bạn sẽ cảm thấy rằng mã không có tác dụng phụ đã được đảm bảo lỗi-miễn phí, và người dùng có thể hạn chế tiếp xúc với bất kỳ lỗi nó đã có?

Nếu mã không có tác dụng phụ, trình biên dịch cũng có thể có các tối ưu hóa bổ sung mà nó có thể thực hiện. Nó có thể dễ dàng hơn nhiều để thực hiện những tối ưu hóa. Có thể dễ dàng hơn nhiều để thậm chí khái niệm hóa một tối ưu hóa cho mã miễn phí hiệu ứng phụ, điều đó có nghĩa là nhà cung cấp trình biên dịch của bạn có thể thực hiện các tối ưu hóa khó có thể xảy ra trong mã với các tác dụng phụ.

Đồng thời cũng đơn giản hơn rất nhiều để thực hiện, tự động tạo và tối ưu hóa khi mã không có tác dụng phụ. Điều này là do tất cả các mảnh có thể được đánh giá một cách an toàn theo bất kỳ thứ tự nào. Cho phép các lập trình viên viết mã đồng thời cao được coi là thách thức lớn tiếp theo mà Khoa học Máy tính cần phải giải quyết, và một trong số ít các rào cản còn lại chống lại Định luật Moore .


1
Ada làm cho nó rất khó gây ra tác dụng phụ. Nó không phải là không thể, nhưng bạn biết rõ những gì bạn làm sau đó.
mouviciel

@mouviciel: Tôi nghĩ rằng có ít nhất một vài ngôn ngữ hữu ích ngoài kia khiến cho các tác dụng phụ trở nên rất khó khăn và cố gắng đưa chúng vào Monads.
Merlyn Morgan-Graham

4

Các tác dụng phụ giống như "rò rỉ" trong mã của bạn sẽ cần được xử lý sau, bởi bạn hoặc một số đồng nghiệp không nghi ngờ.

Các ngôn ngữ chức năng tránh các biến trạng thái và dữ liệu có thể thay đổi như một cách làm cho mã ít phụ thuộc vào ngữ cảnh và nhiều mô đun hơn. Modularity đảm bảo rằng công việc của một nhà phát triển sẽ không ảnh hưởng / làm suy yếu công việc của người khác.

Mở rộng tỷ lệ phát triển với quy mô nhóm, là "chén thánh" của phát triển phần mềm ngày nay. Khi làm việc với các lập trình viên khác, một vài thứ quan trọng như mô đun. Ngay cả những tác dụng phụ đơn giản nhất của logic cũng khiến việc cộng tác trở nên vô cùng khó khăn.


+1 - "hoặc một số đồng nghiệp không nghi ngờ"
Merlyn Morgan-Graham

1
-1 cho các tác dụng phụ là "rò rỉ cần được xử lý." Tạo "tác dụng phụ" (mã không thuần chức năng) là toàn bộ mục đích viết bất kỳ chương trình máy tính không tầm thường nào.
Mason Wheeler

Nhận xét này đến sáu năm sau, nhưng có tác dụng phụ và sau đó có tác dụng phụ. Loại tác dụng phụ mong muốn, thực hiện I / O, v.v, thực sự cần thiết cho bất kỳ chương trình nào, bởi vì bạn cần đưa kết quả của mình cho người dùng bằng cách nào đó - nhưng loại tác dụng phụ khác, trong đó mã của bạn thay đổi trạng thái mà không tốt Lý do như làm I / O, thực sự là một "rò rỉ" sẽ cần phải xử lý sau. Ý tưởng cơ bản là phân tách truy vấn lệnh : một hàm trả về giá trị sẽ không có tác dụng phụ.
rmunn

4

Chà, IMHO, điều này khá đạo đức giả. Không ai thích tác dụng phụ, nhưng mọi người đều cần chúng.

Điều nguy hiểm về tác dụng phụ là nếu bạn gọi một chức năng, thì điều này có thể có ảnh hưởng không chỉ đến cách hành xử của chức năng khi nó được gọi lần sau, mà có thể nó có tác dụng này đối với các chức năng khác. Do đó, tác dụng phụ giới thiệu hành vi không thể đoán trước và phụ thuộc không cần thiết.

Các mô hình lập trình như OO và chức năng đều giải quyết vấn đề này. OO làm giảm vấn đề bằng cách áp đặt một mối quan tâm. Điều này có nghĩa là trạng thái ứng dụng, bao gồm rất nhiều dữ liệu có thể thay đổi, được gói gọn trong các đối tượng, mỗi đối tượng chỉ chịu trách nhiệm duy trì trạng thái của riêng nó. Bằng cách này, nguy cơ phụ thuộc được giảm xuống và các vấn đề bị cô lập hơn nhiều và dễ theo dõi hơn.

Lập trình hàm có một cách tiếp cận triệt để hơn, trong đó trạng thái ứng dụng đơn giản là bất biến theo quan điểm của lập trình viên. Đây là một ý tưởng hay, nhưng tự nó làm cho ngôn ngữ trở nên vô dụng. Tại sao? Bởi vì BẤT K I hoạt động I / O nào cũng có tác dụng phụ. Ngay khi bạn đọc từ bất kỳ luồng đầu vào nào, trạng thái ứng dụng của bạn có thể sẽ thay đổi, vì lần sau khi bạn gọi cùng một chức năng, kết quả có thể sẽ khác. Bạn có thể đang đọc dữ liệu khác nhau, hoặc - cũng có thể - hoạt động có thể thất bại. Điều này cũng đúng với đầu ra. Ngay cả đầu ra là một hoạt động với các tác dụng phụ. Điều này không có gì bạn nhận ra thường xuyên hiện nay, nhưng hãy tưởng tượng bạn chỉ có 20K cho đầu ra của mình và nếu bạn xuất ra nữa, ứng dụng của bạn gặp sự cố vì bạn hết dung lượng đĩa hoặc bất cứ điều gì.

Vì vậy, có, tác dụng phụ là khó chịu và nguy hiểm từ quan điểm của một lập trình viên. Hầu hết các lỗi đến từ cách một số phần nhất định của trạng thái ứng dụng được lồng vào nhau theo cách gần như tối nghĩa, thông qua các tác dụng phụ không cần thiết và thường xuyên. Từ quan điểm của người dùng, tác dụng phụ là điểm sử dụng máy tính. Họ không quan tâm đến những gì xảy ra bên trong hoặc cách thức tổ chức. Họ làm một cái gì đó và mong đợi máy tính THAY ĐỔI tương ứng.


lập trình logic thú vị không chỉ không có tác dụng phụ chức năng; nhưng bạn thậm chí không thể thay đổi giá trị của một biến sau khi được gán.
Ilan

@Ilan: Điều này cũng đúng với một số ngôn ngữ chức năng và nó là một phong cách dễ áp ​​dụng.
back2dos

"Lập trình hàm thực hiện một cách tiếp cận triệt để hơn, trong đó trạng thái ứng dụng đơn giản là bất biến theo quan điểm của lập trình viên. Đây là một ý tưởng hay, nhưng lại khiến ngôn ngữ trở nên vô dụng. Tại sao? Bởi vì BẤT K I hoạt động nào hiệu ứng ": FP không cấm tác dụng phụ, nó hạn chế chúng khi không cần thiết. Ví dụ (1) I / O -> tác dụng phụ là cần thiết; (2) tính toán một hàm tổng hợp từ một chuỗi các giá trị -> hiệu ứng phụ (ví dụ: đối với vòng lặp có biến tích lũy) không cần thiết.
Giorgio

2

Bất kỳ tác dụng phụ nào cũng đưa ra các tham số đầu vào / đầu ra bổ sung phải được tính đến khi kiểm tra.

Điều này làm cho việc xác thực mã phức tạp hơn nhiều vì môi trường không thể chỉ giới hạn ở mã được xác thực, mà phải mang lại một số hoặc tất cả môi trường xung quanh (toàn cầu được cập nhật trong mã đó, do đó phụ thuộc vào điều đó mã, lần lượt phụ thuộc vào việc sống bên trong một máy chủ Java EE đầy đủ ....)

Bằng cách cố gắng tránh các tác dụng phụ, bạn hạn chế lượng ngoại ứng cần thiết để chạy mã.


1

Theo kinh nghiệm của tôi, thiết kế tốt trong lập trình hướng đối tượng bắt buộc sử dụng các hàm có tác dụng phụ.

Ví dụ, lấy một ứng dụng UI cơ bản. Tôi có thể có một chương trình đang chạy với một đồ thị đối tượng đại diện cho trạng thái hiện tại của mô hình miền của chương trình của tôi. Các thông báo đến các đối tượng trong biểu đồ đó (ví dụ, thông qua các cuộc gọi phương thức được gọi từ bộ điều khiển lớp UI). Biểu đồ đối tượng (mô hình miền) trên heap được sửa đổi để đáp ứng với các thông báo. Người quan sát mô hình được thông báo về bất kỳ thay đổi nào, giao diện người dùng và có thể các tài nguyên khác được sửa đổi.

Khác xa với sự xấu xa, sự sắp xếp chính xác của các hiệu ứng phụ sửa đổi heap và sửa đổi màn hình này là cốt lõi của thiết kế OO (trong trường hợp này là mô hình MVC).

Tất nhiên, điều đó không có nghĩa là các phương pháp của bạn nên có tác dụng phụ độc lập. Và các chức năng miễn phí có tác dụng phụ có một vị trí trong việc cải thiện khả năng đọc và đôi khi hiệu suất của mã của bạn.


1
Người quan sát (bao gồm cả UI) nên tìm hiểu về các sửa đổi thông qua đăng ký các thông điệp / sự kiện mà đối tượng của bạn gửi đi. Đây không phải là một tác dụng phụ trừ khi đối tượng trực tiếp sửa đổi người quan sát - đó sẽ là thiết kế xấu.
ChrisF

1
@ChrisF Chắc chắn đó là tác dụng phụ. Thông báo được chuyển đến người quan sát (trong ngôn ngữ OO rất có thể là một cuộc gọi phương thức trên giao diện) sẽ dẫn đến trạng thái của thành phần UI trên heap thay đổi (và các đối tượng heap này hiển thị cho các phần khác của chương trình). Thành phần UI không phải là tham số của phương thức hoặc giá trị trả về. Trong một ý nghĩa chính thức, để một chức năng không có tác dụng phụ, nó phải là idempotent. Thông báo trong mẫu MVC là không, ví dụ như UI có thể hiển thị danh sách các tin nhắn mà nó đã nhận được - bảng điều khiển - gọi nó hai lần dẫn đến trạng thái chương trình khác.
flamingpenguin

0

Cái ác là một chút trên đầu trang .. tất cả phụ thuộc vào bối cảnh sử dụng ngôn ngữ.

Một xem xét khác cho những người đã được đề cập là nó làm cho bằng chứng về tính chính xác của một chương trình đơn giản hơn nhiều nếu không có tác dụng phụ chức năng.


0

Như các câu hỏi ở trên đã chỉ ra, các ngôn ngữ chức năng không ngăn quá nhiều mã có tác dụng phụ như cung cấp cho chúng tôi các công cụ để quản lý tác dụng phụ nào có thể xảy ra trong một đoạn mã nhất định và khi nào.

Điều này hóa ra có những hậu quả rất thú vị. Đầu tiên, và rõ ràng nhất, có rất nhiều điều bạn có thể làm với mã miễn phí tác dụng phụ, đã được mô tả. Nhưng cũng có những thứ khác chúng ta có thể làm, ngay cả khi làm việc với mã có tác dụng phụ:

  • Trong mã có trạng thái có thể thay đổi, chúng ta có thể quản lý phạm vi của trạng thái theo cách để đảm bảo tĩnh rằng nó không thể rò rỉ bên ngoài một chức năng nhất định, điều này cho phép chúng ta thu thập rác mà không cần sơ đồ kiểu đếm tham chiếu hoặc quét và quét , nhưng vẫn chắc chắn rằng không có tài liệu tham khảo tồn tại. Các đảm bảo tương tự cũng hữu ích để duy trì thông tin nhạy cảm về quyền riêng tư, v.v. (Điều này có thể đạt được bằng cách sử dụng đơn vị ST trong haskell)
  • Khi sửa đổi trạng thái chia sẻ trong nhiều luồng, chúng ta có thể tránh sự cần thiết của khóa bằng cách theo dõi các thay đổi và thực hiện cập nhật nguyên tử khi kết thúc giao dịch hoặc quay lại giao dịch và lặp lại nếu một luồng khác thực hiện sửa đổi xung đột. Điều này chỉ có thể đạt được bởi vì chúng tôi có thể đảm bảo rằng mã không có hiệu ứng nào ngoài các sửa đổi trạng thái (mà chúng tôi có thể vui vẻ từ bỏ). Điều này được thực hiện bởi đơn vị STM (Bộ nhớ giao dịch phần mềm) trong Haskell.
  • chúng ta có thể theo dõi các tác động của mã và hộp cát tầm thường, lọc bất kỳ hiệu ứng nào có thể cần thực hiện để đảm bảo an toàn, do đó cho phép (ví dụ) người dùng nhập mã được thực thi an toàn trên trang web

0

Trong các cơ sở mã phức tạp, các tương tác phức tạp của các tác dụng phụ là điều khó khăn nhất mà tôi tìm thấy để giải thích. Tôi chỉ có thể nói cá nhân theo cách mà bộ não của tôi hoạt động. Tác dụng phụ và trạng thái dai dẳng và đột biến đầu vào, v.v khiến tôi phải suy nghĩ về "khi nào" và "nơi" xảy ra lý do về tính chính xác, không chỉ là "những gì" đang xảy ra trong từng chức năng riêng lẻ.

Tôi không thể chỉ tập trung vào "cái gì". Tôi không thể kết luận sau khi kiểm tra kỹ lưỡng một chức năng gây ra tác dụng phụ rằng nó sẽ lan truyền không khí đáng tin cậy trong toàn bộ mã sử dụng nó, vì người gọi vẫn có thể sử dụng sai nó bằng cách gọi sai thời điểm, từ sai luồng, sai đặt hàng. Trong khi đó, một chức năng không gây ra tác dụng phụ và chỉ trả về một đầu ra mới được cung cấp một đầu vào (mà không chạm vào đầu vào) thì không thể sử dụng sai theo cách này.

Nhưng tôi là một kiểu người thực dụng, tôi nghĩ, hoặc ít nhất là cố gắng, và tôi không nghĩ rằng chúng ta nhất thiết phải dập tắt tất cả các tác dụng phụ đến mức tối thiểu để suy luận về tính chính xác của mã của chúng tôi (ít nhất là Tôi sẽ thấy điều này rất khó thực hiện trong các ngôn ngữ như C). Nơi tôi thấy rất khó để suy luận về tính đúng đắn là khi chúng ta có sự kết hợp giữa các luồng điều khiển phức tạp và các tác dụng phụ.

Các luồng điều khiển phức tạp đối với tôi là những luồng giống như biểu đồ, thường là đệ quy hoặc giống đệ quy (hàng đợi sự kiện, ví dụ, không gọi trực tiếp các sự kiện theo cách đệ quy nhưng về bản chất là "giống như đệ quy"), có thể thực hiện mọi việc trong quá trình duyệt qua cấu trúc biểu đồ được liên kết thực tế hoặc xử lý hàng đợi sự kiện không đồng nhất có chứa hỗn hợp các sự kiện chiết trung để xử lý chúng ta đến tất cả các loại phần khác nhau của codebase và tất cả đều gây ra các tác dụng phụ khác nhau. Nếu bạn đã cố gắng rút ra tất cả các địa điểm cuối cùng bạn sẽ có mã, nó sẽ giống như một biểu đồ phức tạp và có khả năng với các nút trong biểu đồ mà bạn không bao giờ mong đợi sẽ ở đó trong thời điểm đó và cho rằng tất cả chúng đều ở đó gây ra tác dụng phụ,

Các ngôn ngữ chức năng có thể có các luồng điều khiển cực kỳ phức tạp và đệ quy, nhưng kết quả rất dễ hiểu về mặt chính xác vì không có tất cả các loại tác dụng phụ chiết trung đang diễn ra trong quá trình. Chỉ khi các luồng điều khiển phức tạp đáp ứng các tác dụng phụ chiết trung mà tôi cảm thấy đau đầu khi cố gắng hiểu toàn bộ những gì đang diễn ra và liệu nó sẽ luôn luôn làm đúng.

Vì vậy, khi tôi gặp những trường hợp đó, tôi thường cảm thấy rất khó khăn, nếu không nói là không thể cảm thấy rất tự tin về tính chính xác của mã đó, hãy để một mình rất tự tin rằng tôi có thể thay đổi mã đó mà không vấp phải điều gì đó bất ngờ. Vì vậy, giải pháp cho tôi là đơn giản hóa luồng điều khiển hoặc giảm thiểu / thống nhất các tác dụng phụ (bằng cách thống nhất, ý tôi là chỉ gây ra một loại tác dụng phụ cho nhiều thứ trong một giai đoạn cụ thể trong hệ thống, chứ không phải hai hoặc ba hoặc một tá). Tôi cần một trong hai điều đó xảy ra để cho phép bộ não đơn giản của tôi cảm thấy tự tin về tính chính xác của mã tồn tại và tính chính xác của những thay đổi tôi giới thiệu. Khá dễ dàng để tự tin về tính chính xác của mã giới thiệu các tác dụng phụ nếu các tác dụng phụ là thống nhất và đơn giản cùng với luồng điều khiển, như vậy:

for each pixel in an image:
    make it red

Thật dễ dàng để lý giải về tính chính xác của mã như vậy, nhưng chủ yếu là do các tác dụng phụ quá đồng đều và luồng điều khiển quá đơn giản. Nhưng hãy nói rằng chúng tôi đã có mã như thế này:

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

Sau đó, đây là mã giả quá đơn giản, thường bao gồm nhiều chức năng hơn và các vòng lặp lồng nhau và nhiều thứ khác sẽ phải tiếp tục (cập nhật nhiều bản đồ kết cấu, trọng lượng xương, trạng thái lựa chọn, v.v.), nhưng ngay cả mã giả cũng gây khó khăn Lý do về tính chính xác vì sự tương tác của dòng điều khiển phức tạp giống như đồ thị và các tác dụng phụ đang diễn ra. Vì vậy, một chiến lược để đơn giản hóa đó là trì hoãn việc xử lý và chỉ tập trung vào một loại tác dụng phụ tại một thời điểm:

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

... Một cái gì đó cho hiệu ứng này như là một bước lặp của đơn giản hóa. Điều đó có nghĩa là chúng tôi truyền dữ liệu nhiều lần, điều này chắc chắn sẽ phát sinh chi phí tính toán, nhưng chúng tôi thường thấy chúng tôi có thể đa luồng mã kết quả như vậy dễ dàng hơn, giờ đây, các tác dụng phụ và luồng kiểm soát đã mang tính chất đồng nhất và đơn giản hơn này. Hơn nữa, mỗi vòng lặp có thể được làm cho thân thiện với bộ đệm hơn so với di chuyển qua biểu đồ được kết nối và gây ra các tác dụng phụ khi chúng ta đi (ví dụ: sử dụng một bit song song để đánh dấu những gì cần phải đi qua để chúng ta có thể thực hiện các lần chuyển tiếp bị trì hoãn theo thứ tự tuần tự được sắp xếp sử dụng bitmasks và FFS). Nhưng quan trọng nhất, tôi thấy phiên bản thứ hai dễ dàng hơn nhiều để suy luận về tính chính xác cũng như thay đổi mà không gây ra lỗi. Vậy nên'

Và sau tất cả, chúng ta cần các tác dụng phụ xảy ra tại một số thời điểm, nếu không, chúng ta sẽ chỉ có các chức năng xuất dữ liệu mà không có nơi nào để đi. Thông thường chúng ta cần ghi lại một cái gì đó vào một tập tin, hiển thị một cái gì đó ra màn hình, gửi dữ liệu qua một ổ cắm, một cái gì đó thuộc loại này và tất cả những thứ này là tác dụng phụ. Nhưng chúng tôi chắc chắn có thể giảm số lượng tác dụng phụ không cần thiết xảy ra và cũng giảm số lượng tác dụng phụ xảy ra khi các luồng điều khiển rất phức tạp và tôi nghĩ rằng sẽ dễ dàng hơn để tránh lỗi nếu chúng tôi làm.


-1

Nó không xấu xa. Ý kiến ​​của tôi, cần phân biệt hai loại chức năng - có tác dụng phụ và không có. Hàm không có tác dụng phụ: - trả về luôn giống nhau với cùng các đối số, vì vậy, ví dụ hàm này không có bất kỳ đối số nào không có ý nghĩa. - Điều đó cũng có nghĩa là, thứ tự trong một số chức năng như vậy được gọi là không có vai trò - phải có khả năng chạy và có thể được gỡ lỗi chỉ một mình (!), Mà không cần bất kỳ mã nào khác. Và bây giờ, lol, hãy nhìn những gì JUnit làm. Một chức năng có tác dụng phụ: - có loại "rò rỉ", những gì có thể được làm nổi bật tự động - nó rất quan trọng bằng cách gỡ lỗi và tìm kiếm các lỗi, những gì thường gây ra bởi tác dụng phụ. - Bất kỳ chức năng nào có tác dụng phụ cũng có một "phần" của chính nó mà không có tác dụng phụ, những gì cũng có thể được tách ra tự động. Vì vậy, ác là những tác dụng phụ,


điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 12 câu trả lời trước
gnat
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.