anti-pattern word cloud

Eliminate Bad Coding Practices: Recognize Anti-Patterns

By Jeff Tranter

As a developer, you're probably familiar with the concept of Design Patterns [1], but you may not have heard of the term anti-pattern. First coined by Andrew Koenig, the term anti-pattern or AntiPattern [2] refers to a commonly used programming practice that has proven to be ineffective, inefficient, or otherwise counterproductive. Anti-patterns commonly arise as solutions to problems that, while initially appearing to be appropriate and effective, have been found in practice to have more negative consequences than positive ones. 

Documenting anti-patterns can help programmers avoid such pitfalls, and in the ideal case, provide another solution that is documented, repeatable and proven effective.

About Anti-patterns

Anti-patterns can range from organizational and project management issues to specific items related to software design and programming. Common examples of the former are Bike Shed (giving disproportionate weight to trivial issues) and Brooks' Law (adding more resources to a project that is behind schedule when the project is already impacted by communication overhead).

Some programming related anti-pattern examples that you might already be familiar with include God Object (concentrating too many functions in a single part of the design) and Busy Waiting (consuming CPU cycles while waiting for something to happen, usually by repeatedly checking instead of messaging). The C++ language itself has a number of documented anti-patterns. One example is using exceptions for control flow or as simply another way to return a value from a function.

With that background, I'm sharing a number of Qt anti-patterns that you may find helpful in your development work. These were collected by polling staff on the ICS consulting team. Like most rules, these are not all hard-and-fast and sometimes it may make sense to break them. A few of them may also be somewhat controversial and reflect individual coding style preferences.

There is a formal structure for documenting design patterns, and a similar structure can be used for anti-patterns. In the interest of brevity I've only given a brief description of these anti-patterns. In the future, I think the idea of more formally documenting these somewhere, like a Wiki server, would be a valuable effort that I may explore.

Here are Some Anti-Patterns to Aid Development 

Hardcoding widget or QML sizes and positions rather than using layouts (for widgets) or positioners (for QML). Some level of doing this can be acceptable for a touchscreen application with a single fixed screen size where you want a "pixel perfect" match to a UX design, but in general this is a bad approach.

Not localizing UI text strings, either unintentionally or with the thinking that an application will never be localized. The effort and overhead of using best practices for localization with Qt is minimal and worth doing in almost all cases. At a minimum, wrap all user-visible text strings using the tr() and qsTr() functions to facilitate localization.

In QML-based applications, making heavy use of JavaScript for implementing the application's business logic is a common beginner's mistake with Qt Quick and one that we often see in customer projects that we are brought in to rescue. QML is best suited to the user interface, and business logic should be implemented in C++.

At ICS we typically promote a layered design with well-defined abstraction layers for visualization, presentation, data/business logic, and communications.

Using native (platform-specific) APIs where portable Qt classes could be used instead. Examples would include QString, networking, and file input/output. Even for applications that are not intended to be cross-platform, using the facilities provided by Qt pays off in portability, developer productivity, and reliability.

Some developers even go so far as to use C library APIs. If your code is using malloc(), atoi(), and similar functions, you should review it and use the portable Qt equivalents.

A common issue we see with QML is "copy and paste code reuse" where developers fail to identify reusable components, and instead duplicate similar code across separate QML files.

When implementing text elements in a user interface, a common anti-pattern is to hard code attributes like the font and colors. This is particularly tempting in QML. If the style then needs to be changed, this requires making many changes throughout the code, which is error prone and time consuming. A better approach is to centralize the properties in one place, making them easier to maintain.

There are many Qt anti-patterns related to the use of threading. In particular, developers often incorrectly use moveToThread(). This is a large topic and one we hope to cover in a separate blog post or webinar of its own.

Calling delete on objects that are children of QObject. Thanks to Qt's object model, you rarely need to delete a QObject-derived object in a Qt application. If your code is doing this, make sure it is really needed.

In some cases where you really need to delete an object, deleteLater() may be more appropriate.

Some designs use mutex locks in data model getters and setters because the model is populated on another thread. A "lock less" producer/consumer model using cross-thread signals and slots is often simpler and more efficient. We've made this change to customer's code on several projects and have seen dramatic increases in performance.

Directly using OpenGL functions (i.e. from #include <gl/gl.h>) will typically break Qt/QML code that needs to run on different platforms. You should instead use one of the classes derived from QAbstractOpenGLFunctions that expose all the functions for each OpenGL version and profile.

Don't override the QObject copy constructor because you think your code needs it. By design, QObject has no copy constructor or assignment operator. See the Qt documentation for the rationale of why this was done.

Using absolute paths in project files will generally make your code not buildable or runnable by someone else. Use relative paths and appropriate environment variables, as needed.

New Qt developers sometimes have trouble knowing when to create variables locally on the stack and when to create them on the heap using new, and whether they should be instance variables of a class. This can lead to errors like crashes or widgets that never appear.

Generally speaking, objects inheriting from QObject are allocated using new. Objects not inheriting from QObject are usually allocated on the stack. As always, there are exceptions to this rule (e.g. QFile and QApplication objects are usually allocated on the stack even though they are QObjects).

Qt's meta-object system provides a mechanism to automatically connect signals and slots between QObject subclasses and their children. When objects are defined with suitable object names and slots follow a naming convention, the connections are made automatically. It is generally considered bad practice to use this auto-connection facility as it makes the signal/slot connections less obvious. Using explicit calls to connect() is preferred.

On a similar note, the "new style" connect() that is supported by Qt 5 is preferred over the older Qt 4 style (which is still supported in Qt 5). The new style connect has the advantages that it is checked at compile time, has a simpler and less error prone syntax, avoids the need for macros, and is faster at run time. In some cases you may get a compile error that a method call is ambiguous - this is usually easily handled with the use of the qOverload() macro provided by Qt.

Using Qt's foreach macro to iterate over containers is now discouraged as you can use the C++11 range-based for facility. But be aware that the range-based for might force a Qt container to detach. See the documentation for QAsConst() for more details on how to avoid this.

Ignoring or suppressing C++ compiler warnings is a bad practice. It can lead to hidden issues that you spend time debugging, that the compiler was trying to alert you to. They can also mask new warnings as they get introduced into the code.

Recent versions of gcc have significantly improved warnings. I've seen it report latent issues in old code that no one was aware of but could potentially be the cause of real problems. Endeavor to get your code to build with no warnings right from the start. On a similar note, QML provides run time warnings and it is generally a bad idea to ignore these as they often indicate real issues that should be corrected. One common example is binding loops, which some developers ignore if the code seems to be working, but can indicate an issue that will come back to bite you in the future.

A controversial issue is whether to use third-party libraries that duplicate some Qt functionality, like Boost, and whether to use to C++ standard containers or Qt's containers. This can make code harder to understand, but can be acceptable if you document what libraries can be used on your project, get your development team to agree, and consistently follow your coding convention.

Over time, as C++ evolves, some Qt facilities are becoming replaced with features of the language itself. This includes such things as the previously discussed foreach macro, algorithms for standard containers, simple use cases for threading, and the need for slots where a lambda function may suffice.

Improve Coding Practices

Another useful tool for detecting bad coding practices is static analysis. Commercial static analysis tools can be very expensive, but are routinely used on software for critical systems like medical devices. Qt Creator supports the free Clang static analyzer and recent versions of Qt also support the Clazy analyzer [3] based on Clang that checks for many Qt-specific errors and poor coding practices.

I would like to thank my colleagues at ICS who contributed the anti-patterns described here. When I polled the team I received many useful suggestions, more than I could list in this one post. I aim to present more anti-patterns in a future post.