etoile / CoreObject

Distributed version control + object persistence framework

Home Page:http://coreobject.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Standardize assertions

qmathe opened this issue · comments

Currently we use assert(), ETAssert(), ETDebugAssert(), NSParameterAssert() and sometimes NSAssert() when we want a description.

imo, we should support two assertion levels:

  • release (95 % of the assertions)
  • debug (for relatively expansive checks that would slow down a release build a bit)

and possibly a third level:

  • paranoid or aggressive (for very expansive checks like validating an entire item graph… which slow down the test suite too much to be turned on all the time)

NSAssert machinery with NSAssertionHandler is useless in my experience and require a distinct macro inside C function, so we may be we could change ETAssert(), ETDebugAssert() and ETAssertUnreachable() to simply wrap assert(). Then we would support:

  • ETAssert
  • ETDebugAssert
  • may be ETValidationAssert, ETParanoidAssert or ETSlowAssert
    • might be easier to keep commented out code that we turn on when needed
  • ETAssertUnreachable

We could an ETAssert variation that takes a description and a variable number of arguments to replace NSAssert use cases we have currently.

Agree. For paranoid assertions, it should be a preprocessor definition specific to CO, since if you want paranoid checks in CO you probably don't want paranoid checks in other libraries.
I'd say define COREOBJECT_PARANOID and check it with #ifdef COREOBJECT_PARANOID or something like that.

edit: we wouldn't define COREOBJECT_PARANOID anywhere, but the CI build script could run the tests twice, once with COREOBJECT_PARANOID set, for example.

Sounds good to me. I'll take care of this then.

Can ETAssert be used in C functions?
If not we might want to alter it so it can be.. just read C99's __func__ (which I assume works for ObjC as well) instead of the SEL.

Just to make sure I understand, ETAssert is always active, and ETDebugAssert is only compiled in debug builds?

I think I added most of the C assert calls, I meant these to be always active so I would change them to ETAssert

In its current state, ETAssert() cannot be used in C functions… so I was proposing to change ETAssert(), ETDebugAssert() and ETAssertUnreachable() to wrap assert() rather than NSAssert() as you outline it.

I'll take a look at __func__, sounds like a good trick.

Yes, ETAssert is compiled in both debug and release builds, while ETDebugAssert is only compiled when ETDebugAssertionEnabled is defined.
We could turn it on when DEBUG is defined too, but I never found a clear documentation that explains the DEBUG macro design and whether we can be sure it never alters the compiler behavior. Do you know if it's part of the C standard or whether it's just a common practice for controlling logging or assertions?

IMO it's best to avoid assert. It's controlled by the NDEBUG macro (if NDEBUG is defined the asserts are removed at compile time) which according to this is not defined by the standard library, but it's common/universal for build systems to define NDEBUG automatically on release builds. e.g. I'm using cmake for other projects and cmake is hardcoded to define NDEBUG for release builds. I'm pretty sure Xcode defines it on release builds too.

So if you use assert you introduce more uncertainty over whether the assertions will run, depending on the build environment.

Since an assert is trivial to write, we might as well just implement it ourselves. (just print a message to stderr with __FILE__, __LINE__, __func__, stringify the expression, and call exit(1).)

I have a few macros that do basically this in Hypergraph. It's split into three levels: assume, assert, and ensure. Assume is like, "I'm pretty sure this is true and don't want to check in release builds". Assert is, "if this isn't true it might be indicative of a problem", and it throws an exception in debug builds and simply logs the assertion in release builds. Ensure is "this must always be true or we can't continue" and always throws an exception no matter what.

I like this approach because it focuses on how severe the assertion being false is as well as how certain you are of that assertion. Assumes can be littered everywhere, helping to document the code and also debugging. Ensures make certain that something is true before continuing and are useful for very critical conditions. Asserts fall somewhere in the middle, giving users useful debugging information without crashing their programs.