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.