Dear Readers!
It’s been a while since my last post again, but as explained in my previous status update, it is because I’m translating all of my posts into german. The good news is, that I’m almost finished with the translations and only a handful of those remain, that still need some work.
Anyway, the main reason for writing this post is, that I’d like to share some random thoughts (from a first timers perspective) on creating an actual application. In one of my previous posts, I already touched the subject slightly, although that time I made several mistakes that led to a complete disaster from an application programming perspective. The biggest mistakes were probably the lack of unit testing and the ever changing intent of the project. This time around both of these have been mitigated, and as such all “production” code was preceded by unit tests, while the intent behind the whole project remained constant the whole time.
Despite these fundamental corrections however, writing a Qt based application still turned out to be hard, as I had to catch up on a lot of under fundamentals as well, which include (in no particular order):
- understanding concurrency (nowadays you simply cannot avoid this)
- generating docs for the Qt IDE (to be able to use the built-in docs system)
- gaining a deeper understanding of the Qt library (display hierarchy, containers, graphics, etc.)
- learning enough stuff about a different large library (OpenCV) to broaden the view on lib design stlyes
- setting up a project with unit testing frameworks (Google Test and Qt Test)
- learning how to partition the project to generate libraries as well, instead of just creating a monolithic app (digging a bit deeper into build systems was also needed for this)
- gaining first experience in test driven development
- devising a sensible application structure and class interfaces (this one is the hardest so far)
- actually using a VCS (git) to manage the source files
- and some others that don’t come to mind right now.
Although some of these topics were covered (partially at least) by books I already read, it is still a lot of stuff to cover, especially considering that books ususally can serve only as a guideline and not as a source of complete solutions. For example, at first it was hard to know how granual a unit test should be, and especially how to write a test that is not brittle (I still need to improve on this). Also, sometimes it is really hard to determine if a functionality should be tested at all. Here’s an example:
C++
1
2
3
4
bool MyClass::isFormatValid(SomeClass::Format someFormat)
{
return !(convertFormat(someFormat) == OderClass::OderFormat_Invalid);
}
Let’s assume here that all functionality regarding converting a format has already been tested out, and the isFormatValid() function was only created to make the intent of such comparisons clearer. This function adds new functionality to the class indeed, so theoretically it should be tested, but writing a unit test against this would mostly test the programming language itself (the only way to get this wrong is to forget to negate the result). On one side one could argue that testing the language (especially such a fundamental aspect of it) is wasteful, but on the other hand properly written unit tests serve as the best type of documentation about what can and cannot be expected of a class, so not writing a test for this kinda sounds like not documenting your code properly. So, should we write a unit test for this function?
Probably one of the biggest surprises to me was how nontrivial it is to design even a smaller application. It’s one thing to write a class, but it is an entirely different story to write them in a manner, in which it will be an acceptable part of the whole, while they still adhere to certain coding standards. Some of the questions that already arose:
- What sort of interface should a class have?
- How and where should I try to make a class generic and where can I leave it to be dependent on an other class?
- How should the classes communicate with each other when needed?
- Which error handling strategy should be used in one or another case?
The second question for example is especially hard to answer, because in C++ one can use more than one strategies to create generic code. The difficulty does not lie in the choice of strategy, but instead the side effects both impose. Using inheritance for instance forces one to use pointers (and dynamic memory allocation), which can complicate class signatures (pass stuff by raw pointer or ref-counting pointer). If using templates, one can easily create an outright lie: a class that seems to be able to accept any type, but in reality it requires a very specific feature of another class, that is simply not present in most other classes (e.g.: being able to tell the size of its own dynamically allocated data, which is != sizeof(someClass)). A class managing undo history would be a great example for this, assuming that the class is able to constrain itself to using only a certain amount of memory.
The fourth question isn’t straightforward either, because it is not always immediately clear whether error information through a return code or through an exception should be sent. A rule of thumb I follow nowadays to decide between the two is the following: if an error can (with high certainty) be handled by the direct caller, a return code seems to be the better choice (this way the error stays local and is cheaper to handle). On the other hand, if it is unclear when and who will handle the error, an exception is the better alternative (this way we avoid building complicated error info propagation paths).
Discussing some of the other mentioned topics would be nice as well, but the post is already getting too long, so I’ll wrap it up here.
As always, thanks for reading.