2 ms·
Interesting article about a worthy topic, even though I disagree with some it. Side note: I expected to see mention of design by contract and function precondit
by RossBencina 2mo ago
Interesting article about a worthy topic, even though I disagree with some it. Side note: I expected to see mention of design by contract and function preconditions/invariants/postconditions.
I don't think the article is well founded. Before you can discuss usage you need to establish the semantics for assert(). The author touches on this in the introduction but then leaves the details unexamined. In particular I'd need to know: can assertions be disabled (as with C/C++ NDEBUG)? does the project have a policy of leaving asserts in production builds? or are asserts only ever enabled for development and testing. if used in production, do you care about the overhead of checking assertions in performance critical code? if an assertion is hit does it always log and panic/terminate? or does it throw a catchable exception? What is the runtime context of the code: is it a server process with a supervision tree? is the failure paradigm "let it crash"? is it an interactive program where the only supervisor is the user? is it a use-case where a program crash is undesirable and/or safety critical? is it a library with unknown use-cases? Are the developers in full control of the program inputs and outputs that trigger asserts?
As a general principle for layered systems, when there is a policy decision to be made, lower-level code should delegate upwards to higher levels, which should implement the policy. Throwing an exception or returning an error code is frequently better than terminating (if you squint, crashing out to the supervision tree is more like throwing an exception than it is like terminating.)
> Correctness - All possible function input and output values which do not have full value coverage should have assertions covering them.
Only if you control all of the callers. Library users would prefer an invalid parameters error/exception.
> Safety - When performing operations that can have unwanted, known, or unknown side effects, assertions should be used to prevent those conditions from happening.
Why assertions? If it is safety critical, shouldn't these checks be mandatory?
> Development - Use an assertion to enforce assumptions on values and state. These assertions can optionally be compiled out of code when coupled with proper testing.
This is where preconditions/postconditions/invariants come in. If safety is important you probably want to leave them in place. A runtime contract violation should enter a fail-safe state.
> Documentation - When writing code, use assertions as self documenting guardrails around your logic. Use assertions to enforce values and state which might be unclear from documentation or hard to decipher from reading code.
I do this, but in this case you either need to be 100% sure that the exception won't get hit, 100% sure that the exception won't make it into production builds, or okay with production crashes.
> var error = system_call(...);
> assert(!error);
Writing this is equivalent to providing an arbitrary third-party with the ability to crash your application with a user-unfriendly error message. Your program should have code paths to handle all error conditions. One of them can be { print("unexpected result from system call. exiting.") exit(); }
- klibertp 2mo ago> Side note: I expected to see mention of design by contract and function preconditions/invariants/postconditions. For some reason, DbC seems to be virtually unknown to most programmers. It's incredibly strange: I was sure that DbC would be the next big step after gradual typing. It's just such a natural fit: where the type system gives up (any/dynamic), the contract system can step in. There are papers on automatically generating contracts from types (and vice versa) to allow typed values to flow through untyped code; there are papers showing how to make that performant enough; papers showing how to instrument systems to generate types and contracts from tests; etc. They are all 15-20 years old now, yet there's still nothing suggesting that the mainstream even looks that way, much less actually implements something usable.
- rramadass 2mo agoCan you share the links to all the papers that you refer to?
- klibertp 2mo agoThe ones I most likely had in mind (recovered with help from ChatGPT due to my memory being fuzzy - but most links it produced were in my bookmarks): - Matthias Felleisen, Sam Tobin-Hochstadt, “Interlanguage Migration: From Scripts to Programs” (DLS 2006) - Sam Tobin-Hochstadt, Matthias Felleisen, “The Design and Implementation of Typed Scheme” (POPL 2008) - Sam Tobin-Hochstadt, “Typed Scheme: From Scripts to Programs” (2010). - Asumu Takikawa et al., “Gradual Typing for First-Class Classes” (OOPSLA 2012) - Esteban Allende, Johan Fabry, Éric Tanter, “Cast Insertion Strategies for Gradually-Typed Objects” (DLS 2013) - Esteban Allende, Johan Fabry, Ronald Garcia, Éric Tanter, “Confined Gradual Typing” (OOPSLA 2014). - Nadia Polikarpova, Ilinca Ciupa, Bertrand Meyer, “A Comparative Study of Programmer-Written and Automatically Inferred Contracts” (ISSTA 2009). There's a lot more research and literature on the topic. Naive approaches were tried ~2010 and were shown to be performance disasters, but by ~2015 we already had those issues mostly solved. I seriously thought that every new language (or new release of an existing PL) after that would feature first-class support for contracts and gradual typing, along with built-in support for automatically generating/harvesting types and contracts from tests. It's 2026, and the mainstream still doesn't seem aware of the possibilities, much less actively going in that direction. It's nuts!