Its been a while since my last contact with C++ and I've never made a deep dive into Qt. Although I had some contact. Now is the time to visit this world again and see whether it thrills me enough to work as a C++ developer. This project should serve the following purposes:
- Get to know the current version of Qt and its ecosystem
- See how quickly my rusty C++ skills become active again
- Show how I usually develop (as far as that is possible with such a simple example)
- Document my "learnings" (based on the Today I Learned (TIL) approach as seen by Josh Branchaud)
In order to figure out some of the basic technology, a spike was developed to test certain aspects. Once this project reaches Milestone v0.5 the spike will be deleted as it is a throw away prototype.
Based on my believe that a software's primary purpose it to provide value to its user (e.g. simplify repetitive tasks for the user or provide some entertainment in this case), the functional requirements are defined as user stories. Looking into user stories I agree with this article from John Hayse:
A user story is a story your team can tell about being a user of your product.
Therefore I'm using the issue Tale of a snake - a "real" user story as a definition of the functional requirements.
Often non-functional requirements are expressed as user stories as well. I somehow don't like that approach, because non-functional requirements are often not expressed by the user.
So the NFRs for this simple (or maybe not that simple) project are as follows:
- NFR-1 - Ease of use - The game should be easy to use on a desktop computer. Users are familiar with the keyboard and as such this should be the major input device.
- NFR-2 - Supported OS - The game will be deployed to the latest versions of Windows and macOS.
- NFR-3 - Extensibility (UI) - Obviously the core functionality will not be extended. However, this project serves learning purposes and as such it should easily be possible to realise the UI with different technologies (e.g. Qt Widget, Qt Quick, OpenGL, ...).
- NFR-4 - Modular Design - Although it is, for a small project like this, oversized, a modular design should be applied. Thus it is possible to show case my experience with concepts like (e.g. MVVM/MVC, SOLID, Clean Code, ...) AND learn how they apply to C++ and Qt.
As stated in the previous section, one purpose of this project is to show case my way of tackling a development task. For this project I decided to go with some agility in mind:
- Understand requirements by defining user stories.
- Ensure a certain quality (with regards to functional and non-functional requirements) by using a test-driven approach.
- Ensure there is always an executable and versioned product by following the "GitHub Flow" approach
- Use traceability between requirements and code changes. This supports me and potential helpers during maintenance.
- Document key decisions, but use clean and solid code as the core documentation.
- Create spikes (prototypes) for learning and throw them away.
- Use state of the art technology, but generally keep the code as simple as possible so that unexperienced developers can understand the code easily.
- Use an incremental development approach so that a continuous evaluation is possible.
As already stated a simple example like this should be implemented in a simple way. However, I also have the requirement to show and test some of my knowledge (see NFR-3 and NFR-4 in Non-Functional Requirements). Therefore I decided to use the MVVM pattern to provide a general structure. I know this pattern well from .NET. It seems like it is also applied frequently in Qt Quick. Additionally it is geared towards an "easy" replacement of its parts. So NFR-3 - Extensibility (UI) should greatly benefit from this decision. Here is a first idea how it might look like:
graph TD
subgraph WidgetApp
subgraph View
MainWindow[MainWindow]
GameBoardView[GameBoardView]
MainWindow -->|contains/draws| GameBoardView
end
subgraph ViewModel
GameViewModel[GameViewModel]
MainWindow --> GameViewModel
GameBoardView --> GameViewModel
end
end
subgraph SnakeCore Library
subgraph Model
Game[Game]
Snake[Snake]
FoodGenerator[FoodGenerator - class]
Game --> Snake
Game --> FoodGenerator
end
end
GameViewModel --> Game
From my understanding of MVVM the Model should contain all the business logic. Thus it usually has a long lifecycle and should not use technology which might change within a few years. Therefore I would argue that it shouldn't depend on Qt. Although it is porbably a company decision to depend on a framework like Qt, this might change over the years. If the core business logic is then dependend on it, it needs to be revised as well.
Anyhow, Qt offers the signals/slots as well as the "garbage collection" and thus I decided to use Qt::Core as the basis for the SnakeCore
library. That means all classes within the library will inherit from QObject
. Maybe later on I can remove certain dependencies, if it shows to be obsolete.
Here I'll not use any Dependency Injection Container or something like that. Usually I then use the constructor to inject the dependencies. However, here I'm using a dedicated method on the View to inject the dependency to the ViewModel. I would argue that it will have the following benefits:
- Flexibility in Initialization: By using a setter method (
setViewModel
), you decouple the instantiation of theViewModel
from theView
itself. This separation allows you to initialize and set theViewModel
independently of creating theView
. - Late Binding: The
setViewModel
method supports late binding, meaning you can change theViewModel
instance associated with aView
dynamically at runtime if required. - Although I'm not sure whether this is required here at all. - Separation of Concerns: The constructor of
View
should ideally focus on initializing its own internal components and setting up basic functionality. Handling theViewModel
assignment through a setter method keeps the constructor cleaner.
As I'm not that familiar with this way of injection, it is more like a try. I'll need to come back later on and see whether this really holds true and improves the overall design. Additionally this so far takes only Qt Widget into account. I'll see how things work in Qt Quick, once I've completed the widget way.