Thanks for the replies!
Yes, Lix has brought me far. Still, there won't be 12 colors in Lix.
Magic Cave(hobbies, time management)
I miss the magic cave from doctoral studies where you would work on a creative project for days, then on the thesis for days, then again on hobbies. Lots of Lix bugs got fixed in the magic cave, many posts were written here on the forums.
The downside of that magic cave is that you naturally let the thesis slip. Any time spent on hobbies could be spent on thesis, thus, even in the magic cave, one will never feel truly free. Likely, that feeling is impossible to re-attain after the age of 5. Still, it's the guiding light to what you should do with your time.
I still want to write a paper with my professor, now that math feels much more like a neglected hobby. Already behind schedule, but it's fine since prof isn't rushing either. It's a side project for both of us.
I have spent time on other hobbies, thus I shouldn't feel like having wasted all too much time.
I have loose ideas for a tile-grid-based Lemmings-inspired puzzle game with many different gadgets, to let my inner five-year-old bloat features. Rules must be really sharply defined, turn order and tile-based movement must be clearly specified. Proxima would be delighted.
No time these days, though.
When you don’t create things, you become defined by your tastes rather than ability. Your tastes only narrow and exclude people. So create. -- Jonathan Gillette
OO(software engineering)
We had a three-day seminar on Clean Code, the classic object-oriented guidelines à la Uncle Bob. I've always enjoyed OO and soaked much of this wisdom already in the years past, during magic cave. Thus, the co-workers might have gotten more out of it than I did. Nonetheless, good rehearsal.
According to the seminar, I should make more interfaces and fewer abstract base classes. This seems hard to follow when you roll your own GUI as in Lix: We have GUI Element, an abstract base class that already has important behavior, e.g., decision whether the Element should be redrawn. Then class Button inherits from Element, then TextButton and BitmapButton inherit from Button. How to avoid inheriting from classes here, and instead only inherit from interfaces? Even if TextButton and BitmapButton became decorators, Button would still inherit from Element. Anybody want to give it a shot?
I've ordered a book on UML, Kecher's UML 2.5, a German classic. Allegedly, UML is the driest thing in the software engineering universe. Doesn't matter, it's handy in real life and will fix a sizable knowledge gap.
Error Handling(software engineering)
I'm interested in error handling strategies. Reason: In C++, everything sucks or is nonstandard.
Ye olde C errore propagation is by return values: Everything returns int, negative values mean errors, zero or positive are good results. Functions return the error code and write other useful output into a buffer that you pass to the function by reference.
int foo(int x, int y, unsigned char* outBuf, size_t outBufLen)This permeates the codebase at work. Call sites then look like: Make buffer, call the function, if the function returns an error, print something to the global log stream, and abort, or maybe continue if you don't deem the error that bad. The downside is the ton of checking boilerplate around every call. And you can't declare the output
const because you have to declare its variable first, then fill it in the call. (Also some people don't like to return early and write 10 nested ifs, but that wouldn't be contemporary C anyway.)
Well, we have C++ exceptions, but they aren't part of the type system.
noexcept is a feeble attempt to fix that, it's nowhere used in legacy code. For every method, you must remember whether it throws, and what it throws, to decide whether you want to handle the error. You must read the API docs or hope that it doesn't throw.
As long as you call even one maybe-throwing method, the compiler must generate exception handling around your call. I haven't studied compiled binaries to how fast or slow it is. Even if this were no speed problem -- I don't like exceptions, they feel wrong, it can't be good design that the easiest thing to write is to let the program crash by unhandled exception.
Really the most fitting for the work codebase would be Zig-style error handling: Your functions return
either an error code
or a useful value, and there are special language features to abort execution and propagate the error. With such features, you may write the call site as if the callee returned no error; on error, it behaves like an exception that is baked into the return type. Or you can explicitly handle the error, again with syntactic sugar because handling is common.
Now, Java had checked exceptions that sound like much the same, baking the possible error into the method signature. But modern Java is moving away from them. Shall we heed this as a warning? Hmm, Zig has no virtual dispatch, not even type inheritance. And Java had some unchecked exceptions from the beginning by design: Nullpointer dereference, or out of memory. While I still firmly believe that nullpointer exception is a glaring misdesign -- the type system should handle whether your pointer may be null or not, and force you to check if applicable -- I have no strong opinion against the out-of-memory Java exception.
In C++, you can define your own error-handling type: A tagged union of error code and useful result. Then all your functions return this tagged union. You can call methods on the tagged union and the call only goes to the useful value if there hasn't been an error yet.
template <typename T>
struct OrError {
int error;
T value; // use only if no error
/* ... maybe methods to chain calls on these as long as no error ... */};The downside of the tagged union is that this is impossible to retrofit over the C-style error handling: You'd have to write a wrapper for every int-returning buffer-filling method -- there is no way to generate all the wrappers with a template because the out-buffer parameter is in a different location for each C function. Also, the entire codebase would have to agree on one such error union type, otherwise it would split into worlds that can't call each other's functions without wrapping.
If the wrappers could be autogenerated by the language, we would have a full-fledged error monad.
You could write code in the reactive paradigm where your method call gets you a value stream and a separate error stream. Again, massive boilerplate in languages that don't support it easily.
Thus, there is no clear winner. Every time, it itches enough to find a good solution, but in the end, it's least painful to stick to the convention.
The easiest code to call is still code that cannot fail.
-- Simon