Rendered at 11:02:32 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
foo42 3 hours ago [-]
An overlapping technique (covering some but not all of the circumstances you'd use assert) is to take a parse-dont-validate approach, and essentially encode the fact that an assertion has been applied to a value in its type.
How ergonomic this is will vary by language, but the general idea would be to apply the assertion logic in some sort of constructor, then prevent any operations which would break the invariant going forward. The simplest way to protect this being by making the value immutable where possible.
Users of the value who care about the invariant being true can then specify in their types that they want a non-empty-collection or a foo-id or whatever it may be, rather than asking for the wider type, then asserting.
sshine 20 minutes ago [-]
It is so nice to come here and want to say something, and someone already said it.
I'm not sure exactly what languages people think of when they consider asserting, but I presume it's Java, C# or C/C++. In Java asserts are disabled by default, so they're thought of as a debug/development utility.
I think people might be worried of asserting in production because "what if you hit an edge case in production that you haven't accounted for, and the system crashes?" And I either think "You just don't test enough", or "Parse, Don't Validate (rather than assert), report a problem and continue."
> Users of the value who care about the invariant being true can then specify in their types that they want
Now that C# has value types and Java has record classes, this kind of data modelling has become available in mainstream systems languages. I'm not a C++ shark, but I think the closest equivalent is C++20 aggregate structs.
eps 2 hours ago [-]
> Can assertions be used in production?
Yes
> What should I be asserting on?
Invariants
> Can I customize how assert behaves?
It should abort the program, logging the stack and whatever the context you pass into in, printf-style. If it doesn't abort, it just buries the issue of the program being in incorrect internal state. It should never be OK.
benj111 42 minutes ago [-]
I think part of the problem is that assert is used for different things.
You can use assert to cover a case that should never happen, you can also use assert to catch programming errors during development.
It's arguable that you don't want the second group in a production build. That should have been caught in testing.
But then there's the use where it's a lazy person's if statement. Instead of dealing with the issue assert it. I'm undecided whether this is a net good. Would the test have been implemented anyway?
iTokio 4 hours ago [-]
I love to combine assertions with « restartability ».
If you’re program has entered an unknown, failed state, just restart it from a known state.
Even better if you can divide a complex system in sub modules that can recover independently without bringing down the entire system.
Something like Erlang supervision tree. Or at least a systemd Restart=always service.
if your program is mostly stateless, and « restartable », it becomes fault tolerant, and you can use assertions liberally and easily avoid unknown/bad states.
Invariants can be enforced, and correctness preserved.
But an important question remains when an assertion is triggered, why invariants were violated?
We need to preserve context, and decide to handle or not this case.
That is easy to forget in code that is assertions oriented.
delusional 3 hours ago [-]
> We need to preserve context, and decide to handle or not this case. That is easy to forget in code that is assertions oriented.
The old solution to that, which worked very well, was coredumps. The assertion fires and your program is taken down, but just before that we save out the entire memory area of your program. That way you can come in with a debugger later and poke around.
People would often leave some memory areas (typically circular buffers) with debug values that would be useful in debugging. They'd never be used anywhere in the program, unless the programmer had to poke around manually.
I've often wished this workflow was still considered high priority on modern runtimes.
rramadass 2 hours ago [-]
You are very right. With linker maps, debug symbol files etc. we can get a good handle on what went wrong.
> People would often leave some memory areas (typically circular buffers) with debug values that would be useful in debugging. They'd never be used anywhere in the program, unless the programmer had to poke around manually.
In one Linux-based system i worked on, they had an area of memory between the heap and the stack where shared libraries are typically mapped in, sectioned off as a circular buffer via linker scripts for each module which was then used for all sorts of logging. A separate process would also map this memory area to provide a UI and also to write to disk. It was pretty neat and worked great.
johnchinjew 2 hours ago [-]
If we're trying to outline a future for assertions, I think it would help to situate them among the other mechanisms we have for ensuring correctness and explain where assertions have the right tradeoffs. For example, what unique need does a production assertion API satisfy that a normal conditional throw does not? Are there cases where production assertions are still necessary even when invariants are established through type constructors?
RossBencina 4 hours 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(); }
rramadass 4 hours ago [-]
Not this again ...
Assertions should only be thought of as predicates on state space to ensure program correctness. Everything else is just a corollary.
I have pointed to previous discussions with other HN users where the logic and rationale behind my "say so" are given. If you actually cared to read them you will find quite long back-and-forth on formal methods and a whole lot of references for edification.
crabbone 2 hours ago [-]
There's a whole big contentious point that the author completely ignored: using assertions in tests (like unit-tests). Some unit testing frameworks expect their users to use assertions to do the job, others are very much against it because they want to separate between the failures of the system under test from failures of the test. (If that matters, I'm in the later camp).
* * *
I also think that the article confuses the how assert works at present (in some languages. Obviously, not Prolog, for example :D), and how he wants it to work. Sometimes his reasoning for doing one thing or the other is based on how assert works today, and sometimes it's based on how he wishes for it to work. Both have merit, but put together don't make much sense.
As for me, I think that the bullet points the author gives for the "proper" use of assertions need to be covered by different tools. Especially if the program is to be compiled with optimizations. I don't think there can be a general rule to tell if an assertion should stay at runtime or not. Sometimes it will depend on the knowledge about the environment in which the program will run. So, you'd need "persistent assertions" and "transient assertions" for the lack of a better word, where "persistent assertion" is functionally an exception, it just checks the same thing as the "transient assertion" would, so it makes sense that they are both called "assertions".
lelanthran 1 hours ago [-]
> Some unit testing frameworks expect their users to use assertions to do the job, others are very much against it because they want to separate between the failures of the system under test from failures of the test. (If that matters, I'm in the later camp).
How useful is this distinction in practice? A failing test is going to examined in detail and that examination is going to reveal whether the system under test failed or if the test itself failed.
I guess I am asking, when is this distinction useful?
twhitmore 28 minutes ago [-]
A typical place this distinction is useful is that it determines whether the test-suite runs to completion with multiple tests, or panics/terminates hard on the first failure.
How ergonomic this is will vary by language, but the general idea would be to apply the assertion logic in some sort of constructor, then prevent any operations which would break the invariant going forward. The simplest way to protect this being by making the value immutable where possible.
Users of the value who care about the invariant being true can then specify in their types that they want a non-empty-collection or a foo-id or whatever it may be, rather than asking for the wider type, then asserting.
For those who haven't read "Parse, Don't Validate": https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
I'm not sure exactly what languages people think of when they consider asserting, but I presume it's Java, C# or C/C++. In Java asserts are disabled by default, so they're thought of as a debug/development utility.
In another thinkpiece, "It takes two to Contract", it is demonstrated how types and assertions work together in TigerBeetle: https://tigerbeetle.com/blog/2023-12-27-it-takes-two-to-cont...
I think people might be worried of asserting in production because "what if you hit an edge case in production that you haven't accounted for, and the system crashes?" And I either think "You just don't test enough", or "Parse, Don't Validate (rather than assert), report a problem and continue."
> Users of the value who care about the invariant being true can then specify in their types that they want
Now that C# has value types and Java has record classes, this kind of data modelling has become available in mainstream systems languages. I'm not a C++ shark, but I think the closest equivalent is C++20 aggregate structs.
Yes
> What should I be asserting on?
Invariants
> Can I customize how assert behaves?
It should abort the program, logging the stack and whatever the context you pass into in, printf-style. If it doesn't abort, it just buries the issue of the program being in incorrect internal state. It should never be OK.
You can use assert to cover a case that should never happen, you can also use assert to catch programming errors during development.
It's arguable that you don't want the second group in a production build. That should have been caught in testing.
But then there's the use where it's a lazy person's if statement. Instead of dealing with the issue assert it. I'm undecided whether this is a net good. Would the test have been implemented anyway?
If you’re program has entered an unknown, failed state, just restart it from a known state.
Even better if you can divide a complex system in sub modules that can recover independently without bringing down the entire system.
Something like Erlang supervision tree. Or at least a systemd Restart=always service.
if your program is mostly stateless, and « restartable », it becomes fault tolerant, and you can use assertions liberally and easily avoid unknown/bad states.
Invariants can be enforced, and correctness preserved. But an important question remains when an assertion is triggered, why invariants were violated?
We need to preserve context, and decide to handle or not this case. That is easy to forget in code that is assertions oriented.
The old solution to that, which worked very well, was coredumps. The assertion fires and your program is taken down, but just before that we save out the entire memory area of your program. That way you can come in with a debugger later and poke around.
People would often leave some memory areas (typically circular buffers) with debug values that would be useful in debugging. They'd never be used anywhere in the program, unless the programmer had to poke around manually.
I've often wished this workflow was still considered high priority on modern runtimes.
> People would often leave some memory areas (typically circular buffers) with debug values that would be useful in debugging. They'd never be used anywhere in the program, unless the programmer had to poke around manually.
In one Linux-based system i worked on, they had an area of memory between the heap and the stack where shared libraries are typically mapped in, sectioned off as a circular buffer via linker scripts for each module which was then used for all sorts of logging. A separate process would also map this memory area to provide a UI and also to write to disk. It was pretty neat and worked great.
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.
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(); }Assertions should only be thought of as predicates on state space to ensure program correctness. Everything else is just a corollary.
Some relevant past comments of mine here - https://news.ycombinator.com/item?id=48358691
I have pointed to previous discussions with other HN users where the logic and rationale behind my "say so" are given. If you actually cared to read them you will find quite long back-and-forth on formal methods and a whole lot of references for edification.
* * *
I also think that the article confuses the how assert works at present (in some languages. Obviously, not Prolog, for example :D), and how he wants it to work. Sometimes his reasoning for doing one thing or the other is based on how assert works today, and sometimes it's based on how he wishes for it to work. Both have merit, but put together don't make much sense.
As for me, I think that the bullet points the author gives for the "proper" use of assertions need to be covered by different tools. Especially if the program is to be compiled with optimizations. I don't think there can be a general rule to tell if an assertion should stay at runtime or not. Sometimes it will depend on the knowledge about the environment in which the program will run. So, you'd need "persistent assertions" and "transient assertions" for the lack of a better word, where "persistent assertion" is functionally an exception, it just checks the same thing as the "transient assertion" would, so it makes sense that they are both called "assertions".
How useful is this distinction in practice? A failing test is going to examined in detail and that examination is going to reveal whether the system under test failed or if the test itself failed.
I guess I am asking, when is this distinction useful?