-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invariants #25
Comments
I'm a big fan of design-by-contract since I learned it in the mid 90's. That said, I see some serious issues with implementing class invariants for modern multi-threaded languages (including golang). See below a few general comments about class invariants (so not specifically about your proposal) :
See for example: Bertrand Meyer, Eiffel - The Language, Prentice Hall 1992, section 9.12 pp 127-128, and here.
Namely, it allows a method to break a class invariant during its execution. So if you get a context switch at such a point, and another public method of the same class instance is being called, assertion of the invariant will fail at the entry to that method. This is especially important for golang, being a very concurrent language (goroutines). The "greedy" and strict way to deal with that is to change this definition that a class invariant may never be broken at any time. This is very restricting. To start with, unless we deal with simple invariants, the method implementation can get very complex. Even more so, the invariant assertion mechanism needs to be invoked after every single statement. So I doubt whether it's practical at all. Another way is to protect with an instance-level mutex all public methods of that instance. This basically turns the instance to be single-threaded. So it reduces the concurrency features of the language considerably. So I believe that until the "right" way to define thread-safe invariants is found, there is little point in implementing these. This draft paper by Meyer from 2016 discusses concurrency, but I did not find there a clearcut thread-safe definition (it's a bit lengthy, though, I may have missed).
|
Hi @tsipo ,
Corrected, thanks for spotting it -- my mistake would have caused some minus points in the uni exam some fifteen years or so 😄
I agree. However, this is something that "plagues" pre and post-conditions as well as assertions -- they all suffer from differences in time of check - time of use (TOCTOU). I have dealt with concurrency so far by using immutable objects (akin to functional programming) where a post-condition after the constructor suffices to document the properties (and copy/pasting if there are multiple constructors, which is rarely the case). I find the mix of mutability and multi-threading scary, and was lucky that I didn't have to work in such a scenario. At the moment, Gocontracts allows you both to write conditions and to write an arbitrary function preamble. The preamble might include necessary locking (and unlocking), while conditions save you typing. Perhaps we need the same mechanism for the invariants? Then it's up to the programmer to decide how/what to test (and the readme should at least expose the problems related to the concurrency). |
The most interesting part of the design-by-contract, namely invariants, is still missing and I'd like to hear your feedback on how to implement it.
In an OO language, invariants should hold after the constructor and before and after invocation of each public method. They are particularly interesting way to formalize the properties on your more complex contained data structures. For example, if you internally use a sorted slice to perform binary search on it for interval queries, an invariant would be that the slice is always sorted. Another example is a directed acyclic graph with an invariant that there should be no cycles.
For a more detailed (and better) article on advantages of design-by-contract in general and invariants in particular, please see: https://cacm.acm.org/blogs/blog-cacm/227928-why-not-program-right/fulltext
Right now, gocontracts operates on individual files. To add the invariants to a type, I thought about passing the package folder (instead of individual files) to gocontracts, inspecting the type comments and iterating through all the public methods associated with the type. Since there are no constructors in Go, I'd establish the invariants in all the public methods as additional pre and postconditions.
I though that a documentation defining the invariants could look like this:
For some method:
gocontracts would append the invariants of the type to the pre and postconditions and generate the checks as:
How do you like the idea? Is the syntax appealing? Would you mind if you could only disable/enable checks on per-package basis?
In case gocontracts would still keep on operating on individual files, would it be confusing if it parsed all the other go files in the file's directory to read the invariants and then apply it to only the given file (and not establishing invariants on the functions defined in other files)?
Thanks a lot for your feedback!
The text was updated successfully, but these errors were encountered: