Schrödinorms là gì?


52

Đây trang wiki nói:

Một schrödinorms là một lỗi chỉ xuất hiện sau khi ai đó đọc mã nguồn hoặc sử dụng chương trình theo một cách bất thường thông báo rằng nó không bao giờ nên hoạt động ở nơi đầu tiên, tại thời điểm đó chương trình nhanh chóng ngừng hoạt động cho mọi người cho đến khi được sửa. Tệp Jargon cho biết thêm: "Mặc dù ... điều này nghe có vẻ không thể, nhưng nó đã xảy ra; một số chương trình đã chứa chấp các schrödinbugs tiềm ẩn trong nhiều năm."

Những gì đang được nói đến là rất mơ hồ ..

Ai đó có thể cung cấp một ví dụ về cách một schrödinorms giống như thế nào (như với một tình huống hư cấu / đời thực) không?


15
Lưu ý rằng trích dẫn được nói đùa.

11
Tôi nghĩ rằng bạn sẽ hiểu rõ hơn về shrodinorms nếu bạn biết về con mèo của Shrodinger's: en.wikipedia.org/wiki/Shrodingftimecat
Eimantas

1
@Eimantas Tôi thực sự bây giờ bối rối hơn nhưng đó là một bài viết thú vị :)

Câu trả lời:


82

Theo kinh nghiệm của tôi, mô hình là thế này:

  • Hệ thống hoạt động, thường trong nhiều năm
  • Một lỗi được báo cáo
  • Nhà phát triển điều tra lỗi và tìm thấy một chút mã dường như hoàn toàn thiếu sót và tuyên bố rằng nó "không bao giờ có thể hoạt động được"
  • Lỗi được sửa và truyền thuyết về mã không bao giờ có thể hoạt động (nhưng đã tồn tại trong nhiều năm)

Hãy logic ở đây. Mã không bao giờ có thể làm việc ... không bao giờ có thể làm việc . Nếu nó đã làm việc thì tuyên bố là sai.

Vì vậy, tôi sẽ nói rằng một lỗi chính xác như được mô tả (đó là việc quan sát mã bị lỗi khiến nó ngừng hoạt động) là vô nghĩa.

Trong thực tế những gì đã xảy ra là một trong hai điều:

1) Nhà phát triển chưa hiểu đầy đủ về mã . Trong trường hợp này, mã thường là một mớ hỗn độn và ở đâu đó trong đó có độ nhạy lớn nhưng không rõ ràng đối với một số điều kiện bên ngoài (giả sử một phiên bản hoặc cấu hình hệ điều hành cụ thể chi phối cách thức một số chức năng hoạt động theo một cách nhỏ nhưng quan trọng). Điều kiện bên ngoài này bị thay đổi (giả sử nâng cấp hoặc thay đổi máy chủ được cho là không liên quan) và làm như vậy khiến mã bị hỏng.

Sau đó, nhà phát triển xem xét mã và, không hiểu bối cảnh lịch sử hoặc có thời gian để theo dõi mọi tình huống phụ thuộc và kịch bản có thể, tuyên bố rằng nó không bao giờ có thể hoạt động và viết lại nó.

Trong tình huống này, điều cần hiểu ở đây là ý tưởng rằng "nó không bao giờ có thể hoạt động được" là sai lầm (vì nó đã làm).

Điều đó không có nghĩa là viết lại nó là một điều xấu - thường thì không, trong khi thật tuyệt khi biết chính xác những gì sai thường làm mất thời gian và viết lại phần mã thường nhanh hơn và cho phép bạn chắc chắn rằng bạn đã sửa chữa mọi thứ.

2) Trên thực tế nó không bao giờ làm việc, chỉ là không ai đã nhận thấy . Điều này là đáng ngạc nhiên phổ biến, đặc biệt là trong các hệ thống lớn. Trong trường hợp này, một người nào đó mới bắt đầu và bắt đầu nhìn mọi thứ theo cách mà không ai từng làm trước đây, hoặc một quy trình kinh doanh thay đổi đưa một số trường hợp nhỏ trước đây vào quy trình chính, và một cái gì đó không bao giờ thực sự hoạt động (hoặc làm việc một số nhưng không phải tất cả thời gian) được tìm thấy và báo cáo.

Nhà phát triển nhìn vào nó và tuyên bố "nó không bao giờ có thể hoạt động được" nhưng người dùng nói "vô nghĩa, chúng tôi đã sử dụng nó trong nhiều năm" và họ có quyền nhưng điều gì đó họ cho là không liên quan (và thường không đề cập đến cho đến khi nhà phát triển tìm thấy điều kiện chính xác tại điểm mà họ đi "ồ vâng, chúng tôi làm điều đó ngay bây giờ và không trước đó") đã thay đổi.

Ở đây, nhà phát triển đã đúng - nó không bao giờ có thể hoạt động và không bao giờ hoạt động.

Nhưng trong cả hai trường hợp, một trong hai điều là đúng:

  • Tuyên bố "nó không bao giờ có thể làm việc" là đúng và nó chưa bao giờ có hiệu quả - mọi người chỉ nghĩ rằng nó đã làm
  • Nó đã làm việc và tuyên bố "nó không bao giờ có thể làm việc" là sai và dẫn đến sự thiếu hiểu biết (thường là hợp lý) về mã và các phụ thuộc của nó

1
Xảy ra với tôi rất thường xuyên
genesis

2
Vĩ đại cái nhìn sâu sắc vào chủ nghĩa hiện thực những tình huống này
StuperUser

1
Tôi đoán nó thường là kết quả của một khoảnh khắc "WTF". Tôi đã có một lần. Tôi đọc lại một số mã tôi đã viết và nhận ra rằng một lỗi được chú ý gần đây đã làm cho toàn bộ ứng dụng bị hỏng. Trên thực tế, sau khi kiểm tra thêm, một thành phần khác tôi đã viết rất tốt, nó đã bù đắp những sai lầm.
Thaddee Tyl

1
@Thaddee - Tôi đã thấy điều đó trước đây nhưng tôi cũng đã thấy hai lỗi trong các mô-đun mã gọi nhau hủy bỏ nhau để nó thực sự hoạt động. Nhìn vào một trong hai và họ đã tan vỡ nhưng cùng nhau họ vẫn ổn.
Jon Hopkins

7
@Jon Hopkins: Tôi cũng gặp trường hợp 2 lỗi hủy lẫn nhau và điều đó thực sự đáng ngạc nhiên. Tôi đã tìm thấy một lỗi, phát biểu câu nói bỉ ổi "nó không bao giờ có thể hoạt động được", nhìn sâu hơn để tìm ra lý do tại sao nó vẫn hoạt động và tìm thấy một lỗi khác đã sửa lỗi đầu tiên, trong hầu hết các trường hợp. Tôi thực sự choáng váng vì phát hiện này, và thực tế là chỉ với MỘT trong số các lỗi, hậu quả sẽ là thảm khốc!
Alexis Dufrenoy

54

Bởi vì mọi người đều đề cập đến mã không bao giờ nên hoạt động, tôi sẽ đưa ra một ví dụ mà tôi đã gặp, khoảng 8 năm trước về một dự án VB3 đang chết dần được chuyển đổi thành .net. Thật không may, dự án phải được cập nhật cho đến khi phiên bản .net hoàn tất - và tôi là người duy nhất hiểu được VB3 từ xa.

Có một chức năng rất quan trọng được gọi là hàng trăm lần cho mỗi phép tính - đó là tính lãi hàng tháng cho các kế hoạch lương hưu dài hạn. Tôi sẽ tái tạo những phần thú vị.

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

Phần được đánh dấu bằng một ngôi sao có mã quan trọng nhất; đó là phần duy nhất đã tính toán thực tế. Rõ ràng điều này không bao giờ nên làm việc, phải không?

Phải mất rất nhiều gỡ lỗi, nhưng cuối cùng tôi đã tìm ra nguyên nhân: IsYearlyInterestModeTrue, và Not IsYearlyInterestModecũng đúng. Đó là bởi vì ở đâu đó dọc theo dòng, ai đó đã đặt nó thành một số nguyên, sau đó trong một hàm được cho là đặt nó thành đúng tăng dần (nếu 0 Falsethì nó sẽ được đặt thành 1, đó là VB True, vì vậy tôi có thể thấy logic ở đó), sau đó đưa nó trở lại một boolean. Và tôi bị bỏ lại với một điều kiện không bao giờ có thể xảy ra và vẫn luôn xảy ra.


7
Phần kết: Tôi không bao giờ sửa chức năng đó; Tôi chỉ vá các trang web cuộc gọi thất bại để gửi 2 trong như tất cả các trang web khác.
cấu hình

Vì vậy, bạn có nghĩa là nó được sử dụng khi mọi người hiểu sai mã?
Pacerier

1
@Pacerier: Thường xuyên hơn khi mã là một mớ hỗn độn đến mức nó chỉ hoạt động chính xác một cách tình cờ. Trong ví dụ của tôi, không có nhà phát triển nào có ý IsYearlyInterestModeđịnh đánh giá là đúng và không đúng; nhà phát triển ban đầu đã thêm một vài dòng (bao gồm một trong những người ifthực sự không hiểu cách thức hoạt động của nó - nó chỉ hoạt động để nó đủ tốt.
cấu hình

16

Không biết một ví dụ trong thế giới thực, nhưng để đơn giản hóa nó với một tình huống ví dụ:

  • Một lỗi không được chú ý trong một thời gian, vì ứng dụng không chạy mã trong các điều kiện khiến nó bị lỗi.
  • Ai đó nhận thấy nó bằng cách làm một cái gì đó bên ngoài sử dụng bình thường (hoặc kiểm tra nguồn).
  • Bây giờ lỗi đã được nhận thấy, ứng dụng cũng bị lỗi cho đến khi điều kiện bình thường, cho đến khi lỗi được sửa.

Điều này có thể xảy ra vì lỗi sẽ làm hỏng một số trạng thái của ứng dụng gây ra lỗi trong các điều kiện bình thường trước đây.


4
Một lời giải thích là đã có những lỗi ngẫu nhiên trong phần mềm, rằng không ai có thể liên kết với nhau về mặt tinh thần. Do đó, những lỗi đó được cho là nguyên nhân tự nhiên (chẳng hạn như lỗi phần cứng ngẫu nhiên). Khi mã nguồn được đọc, mọi người có thể liên kết tất cả các lỗi ngẫu nhiên trước đó với nguyên nhân này và sẽ nhận ra rằng nó không bao giờ nên hoạt động ở nơi đầu tiên.
rwong

4
Giải thích thứ hai là có một phần trong phần mềm được triển khai với mô hình chuỗi trách nhiệm. Mỗi trình xử lý được viết một cách mạnh mẽ, mặc dù một trình xử lý đó có một lỗi nghiêm trọng. Bây giờ, trình xử lý thứ nhất sẽ luôn luôn thất bại, nhưng do trình xử lý thứ hai (có trách nhiệm chồng chéo) cố gắng hoàn thành cùng một nhiệm vụ, hoạt động chung dường như đã thành công. Nếu có bất kỳ thay đổi nào trong mô-đun thứ hai, chẳng hạn như thay đổi trong khu vực trách nhiệm, nó sẽ gây ra lỗi tổng thể, mặc dù lỗi thực sự nằm ở một vị trí khác.
rwong

13

Một ví dụ thực tế. Tôi không thể hiển thị mã, nhưng hầu hết mọi người sẽ liên quan đến điều này.

Chúng tôi có một thư viện nội bộ lớn về các chức năng tiện ích nơi tôi làm việc. Một ngày nọ tôi đang tìm kiếm một chức năng để làm một việc cụ thể và tôi thấy Frobnicate()hãy thử sử dụng nó. Uh-oh: hóa ra Frobnicate()luôn trả về mã lỗi.

Đi sâu vào thực hiện, tôi thấy một số lỗi logic cơ bản Frobnicate()khiến nó luôn bị lỗi. Trong kiểm soát nguồn tôi có thể thấy rằng hàm đã không được sửa đổi kể từ khi nó được viết, có nghĩa là hàm này chưa bao giờ hoạt động như dự định. Tại sao không ai nhận thấy điều này? Tôi tìm kiếm trong phần còn lại của việc nhập ngũ nguồn và thấy rằng tất cả những người gọi hiện tại Frobnicate()đang bỏ qua giá trị trả về (và do đó có chứa các lỗi tinh vi của chính họ). Nếu tôi thay đổi các hàm đó để kiểm tra giá trị trả về như chúng cần, thì chúng cũng bắt đầu thất bại.

Đây là trường hợp phổ biến của điều kiện # 2 mà Jon Hopkins đã đề cập trong câu trả lời của mình và nó rất phổ biến trong các thư viện nội bộ lớn.


... đó là một lý do tốt để tránh viết thư viện nội bộ bất cứ nơi nào có thể sử dụng thư viện bên ngoài. Nó sẽ được thử nghiệm nhiều hơn và do đó có ít những bất ngờ khó chịu như vậy (tốt hơn là các thư viện nguồn mở, bởi vì bạn có thể sửa chúng nếu chúng vẫn hoạt động).
Jan Hudec

Vâng, nhưng nếu các lập trình viên bỏ qua các mã trả về đó không phải là lỗi của thư viện. (Nhân tiện, lần cuối cùng bạn kiểm tra mã số lại là khi printf()nào?)
JensG

Đây chính xác là lý do tại sao các trường hợp ngoại lệ được kiểm tra được phát minh.
Kevin Krumwiede

10

Đây là một Schrödinorms thực sự tôi thấy trong một số mã hệ thống. Một daemon gốc cần giao tiếp với một mô-đun hạt nhân. Vì vậy, mã hạt nhân tạo ra một số mô tả tập tin:

int pipeFDs[1];

sau đó thiết lập liên lạc qua một đường ống sẽ được gắn vào một đường ống có tên:

int pipeResult = pipe(pipeFDs);

Điều này không nên làm việc. pipe()ghi hai mô tả tập tin vào mảng, nhưng chỉ có không gian cho một. Nhưng trong khoảng bảy năm, nó đã làm việc; mảng đã xảy ra trước khi một số không gian chưa sử dụng trong bộ nhớ bị biến thành một bộ mô tả tệp.

Sau đó, một ngày nọ, tôi phải chuyển mã sang một kiến ​​trúc mới. Nó ngừng hoạt động, và lỗi không bao giờ nên hoạt động đã được phát hiện.


5

Một hệ quả tất yếu đối với Schrödinorms là Heisenorms - mô tả một lỗi biến mất (hoặc thỉnh thoảng xuất hiện) khi cố gắng điều tra và / hoặc sửa nó.

Heisenbugs là những kẻ gây ra sự thông minh nhỏ bé huyền thoại chạy và ẩn nấp khi một trình gỡ lỗi được tải, nhưng thoát ra khỏi đồ gỗ một khi bạn đã ngừng xem.

Trong thực tế, những điều này thường được gây ra bởi một hoặc một trong những điều sau đây:

  • tác động tối ưu hóa, trong đó mã được biên dịch -DDEBUGđược tối ưu hóa ở mức khác với bản phát hành
  • sự khác biệt thời gian tinh tế do các xe buýt hoặc ngắt giao tiếp trong thế giới thực khác biệt tinh tế với tải giả "hoàn hảo" mô phỏng

Cả hai đều nhấn mạnh tầm quan trọng của việc kiểm tra mã phát hành trên thiết bị phát hành, cũng như kiểm tra đơn vị / mô-đun / hệ thống bằng trình giả lập.


Tại sao tôi không nhận thấy câu trả lời của S.Lote và bình luận của delnan trước khi tôi đăng bài này?
Andrew

Tôi có ít kinh nghiệm nhưng đã tìm thấy một vài điều này. Tôi đã làm việc trong môi trường NDK của Android. Khi trình gỡ lỗi tìm thấy điểm dừng, nó chỉ tạm dừng các luồng Java, không phải các lệnh C ++, thực hiện một số cuộc gọi vì các phần tử được khởi tạo trên C ++. Nếu không có trình gỡ lỗi, mã Java sẽ chạy nhanh hơn C ++ và cố gắng sử dụng các giá trị chưa được khởi tạo.
MLProgrammer-CiM

Tôi đã phát hiện ra một con bọ hung trong việc sử dụng API cơ sở dữ liệu Django của chúng tôi vài tháng trước: Khi nào DEBUG = True, tên của "tham số" lập luận thay đổi truy vấn SQL thô. Chúng tôi đã sử dụng nó như một từ khóa arg cho rõ ràng do độ dài của truy vấn, nó đã bị DEBUG = False
hỏng

2

Tôi đã thấy một vài Schödinbugs và luôn vì lý do tương tự:

Chính sách của công ty yêu cầu mọi người phải sử dụng một chương trình.
Không ai thực sự sử dụng nó (chủ yếu là vì không có đào tạo cho nó.)
Nhưng họ không thể nói với quản lý điều này. Vì vậy, mọi người phải nói rằng "Tôi đã sử dụng chương trình này được 2 năm và chưa bao giờ gặp phải lỗi này cho đến ngày hôm nay."
Chương trình không bao giờ thực sự hoạt động, ngoại trừ một số ít người dùng (bao gồm cả các nhà phát triển đã viết nó.)

Trong một trường hợp, chương trình đã được thử nghiệm rất nhiều, nhưng không phải trên cơ sở dữ liệu thực (được coi là quá nhạy cảm, vì vậy một phiên bản giả mạo đã được sử dụng.)


1

Tôi có một ví dụ từ lịch sử của riêng tôi, đây là khoảng 25 năm trước. Tôi là một đứa trẻ làm lập trình đồ họa thô sơ trong Turbo Pascal. TP có một thư viện gọi là BGI bao gồm một số chức năng cho phép bạn sao chép một vùng trên màn hình vào một khối bộ nhớ dựa trên con trỏ, và sau đó làm mờ nó ở nơi khác. Kết hợp với xor-blits trên màn hình đen trắng, nó có thể được sử dụng để làm hoạt hình đơn giản.

Tôi muốn tiến thêm một bước và tạo ra các họa tiết. Tôi đã viết một chương trình vẽ các khối lớn và điều khiển để tô màu chúng, giống như bạn đã sao chép chúng dưới dạng pixel, tạo ra một chương trình vẽ đơn giản để tạo các họa tiết, sau đó nó có thể sao chép vào bộ nhớ. Chỉ có một vấn đề, để sử dụng các họa tiết bị xóa này, chúng sẽ phải được lưu vào một tệp để các chương trình khác có thể đọc chúng. Nhưng TP không có cách nào để tuần tự phân bổ bộ nhớ dựa trên con trỏ. Các hướng dẫn sử dụng nói rõ rằng họ không thể được ghi vào hồ sơ.

Tôi đã đưa ra một đoạn mã đã làm, thành công, ghi vào tệp. Và bắt đầu viết một chương trình thử nghiệm làm mờ một sprite từ chương trình vẽ của tôi trên nền - trên đường tôi tạo ra một trò chơi. Và nó đã làm việc, đẹp. Ngày hôm sau, nó đã ngừng hoạt động. Nó không cho thấy gì ngoài một mớ hỗn độn bị cắt xén. Nó không bao giờ làm việc nữa. Tôi đã tạo ra một sprite mới, và nó hoạt động hoàn hảo - cho đến khi nó không hoạt động, và nó lại là một mớ hỗn độn bị cắt xén.

Phải mất một thời gian dài nhưng cuối cùng tôi cũng hiểu được chuyện gì đang xảy ra. Theo tôi nghĩ, chương trình vẽ không phải là lưu dữ liệu pixel đã sao chép vào tệp - nó đang lưu chính con trỏ. Khi chương trình tiếp theo đọc tệp, nó đã kết thúc với một con trỏ tới cùng một khối bộ nhớ - vẫn chứa những gì chương trình cuối cùng đã viết ở đó (đây là trên MS-DOS, quản lý bộ nhớ không tồn tại). Nhưng nó đã hoạt động ... ngay cho đến khi bạn khởi động lại, hoặc chạy bất cứ thứ gì đã sử dụng lại cùng một vùng bộ nhớ, và sau đó bạn có một mớ hỗn độn bị cắt xén vì bạn đang làm mờ một loạt dữ liệu hoàn toàn không liên quan đến khối bộ nhớ video.

Nó không bao giờ nên hoạt động, nó thậm chí không bao giờ có vẻ hoạt động (và trên bất kỳ hệ điều hành thực nào nó sẽ không có) nhưng nó vẫn hoạt động, và một khi nó bị hỏng - nó vẫn bị hỏng.


0

Điều này xảy ra mọi lúc khi mọi người sử dụng trình gỡ lỗi.

Môi trường gỡ lỗi khác với môi trường thực tế - không có trình gỡ lỗi - môi trường sản xuất.

Chạy với trình gỡ lỗi có thể che giấu những thứ như tràn ngăn xếp vì các khung ngăn xếp của trình gỡ lỗi che giấu lỗi.


Tôi không nghĩ rằng nó đề cập đến sự khác biệt giữa mã chạy trong trình gỡ lỗi và khi được biên dịch.
Jon Hopkins

26
Đó không phải là một schrödinbug, đó là một heisenbug .

@delnan: Nó ở rìa, IMO. Tôi thấy đó là một điều không xác định bởi vì có những mức độ tự do không thể biết được. Tôi thích dự trữ bọ rùa cho những thứ đo lường một thứ thực sự làm phiền một thứ khác (ví dụ: điều kiện cuộc đua, cài đặt tối ưu hóa, giới hạn băng thông mạng, v.v.)
S.Lott

@ S.Lott: Tình huống bạn mô tả không liên quan đến việc quan sát thay đổi mọi thứ bằng cách làm rối tung các khung stack hoặc tương tự. (Ví dụ tồi tệ nhất mà tôi từng thấy là trình gỡ lỗi sẽ yên tâm và "thực hiện" chính xác các tải giá trị thanh ghi phân đoạn không hợp lệ trong chế độ một bước. Kết quả là một số thói quen trong RTL được vận chuyển mặc dù tải con trỏ chế độ thực trong khi ở chế độ được bảo vệ Vì nó chỉ được sao chép và không bị hủy bỏ nên nó đã hoạt động hoàn hảo.)
Loren Pechtel

0

Tôi chưa bao giờ nhìn thấy một con bọ hung thực sự và tôi không nghĩ chúng có thể tồn tại - việc tìm kiếm nó sẽ không phá vỡ mọi thứ.

Thay vào đó, một cái gì đó đã thay đổi làm lộ ra một lỗi đang ẩn giấu từ lâu. Bất cứ điều gì thay đổi vẫn được thay đổi và do đó, lỗi vẫn hiển thị trong khi cùng lúc ai đó tìm thấy lỗi.

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.