(This post is based on the video, “Tell, Don’t Ask” in the Design Defined: Design Principles Explained series.)
There are an infinite number of ways to write the software for an embedded system. How do you decide which way is the right way?
Your number one priority should always be to write code that meets the system requirements. But how the code is written can have an immense impact on modularity, maintainability, and testability.
At Bresslergroup, we often employ an approach called “Tell, Don’t Ask,” or “TDA” for short.
What is “Tell, Don’t Ask”?
TDA has been popular among object-oriented programmers for years.
Essentially, it says objects should be responsible for their own data, and those objects should be the ones to make decisions based on that data, not the application at large.
Objects should be responsible for their own data, and those objects should be the ones to make decisions based on that data, not the application at large.
Rather than write code that asks an object for data and uses that data to make a decision, TDA says the code should tell objects to use the data they have to make decisions for themselves. The principle considers data, and functions that operate on the data, as part of the same class.
Example: Using TDA for a Battery-Powered Device
To demonstrate how TDA simplifies application level code, consider the example below.
This could be for something that runs off of primary cells but has backup rechargeable batteries — like a handheld medical device or a portable gaming console. The code puts the system in standby when the battery is low.
In the non-TDA example, the device asks the object what the battery percentage is. If the object responds that the battery power is low, the device will put the system in standby. In the TDA example, if the battery power is low, the object will initiate standby.
In the non-TDA example, the application is responsible for knowing that the critical battery level is different when running off of double-A batteries versus LiPo batteries. It’s also responsible for knowing the critical threshold for each and initiating standby mode.
In the TDA example, all that low-level knowledge, and the execution, is contained within the power subsystem. As you can see, the code is greatly simplified as a result.
TDA Encourages Good Programming Practices
This advice applies easily to embedded programmers using C++. It’s equally valuable for most of the embedded community who are using non-object-oriented languages like C.
For object-oriented programmers, TDA encourages good programming practices like encapsulation and event-driven design.
In embedded systems, it encourages the programmer to decompose the system into modules that are responsible for their own data. TDA aims to take low-level decisions away from the application and give that decision-making power to the modules.
This yields a number of positive effects. When code is broken into modules, the application is simpler, the code lends itself to reuse, and maintenance is easier because bugs can be spotted quickly.
TDA Safeguards Products for the Future
A future engineer who has to modify the application is unlikely to know or care how the values in the example above were chosen.
Encapsulating that decision-making in the power subsystem makes it less likely those values will be inadvertently modified or left out by a future maintenance effort.
TDA Streamlines Testing
Code that’s written using TDA can be tested more easily, too.
One consequence of the principle is that the system is easily exercised from the command line. The command line interface is essentially an extension of the powers granted to the application. If the application design is complex, the command line code to exercise the system will be complex.
Instead of manipulating the data in modules to trigger events, events can simply be dispatched by the command line. This technique is not adequate to test modules at the function level, but it helps greatly with integration tests.
TDA Makes Sense for IoT Devices
This principle lends itself well to IoT products that combine real-time data collection and an asynchronous networking module.
In these products, the application usually oversees data collection at a high level, aggregates data, and transfers it to the internet or a mobile device.
TDA frees the designer to focus on elegant and stable top-level design.
By decoupling the intricacies of the low-level system from the application layer, it frees the designer to focus on elegant and stable top-level design.
The Origins of TDA
“Tell, Don’t Ask” comes from a book called The Pragmatic Programmer: From Journeyman to Master, by Andrew Hunt and David Thomas.
Published in 1999, it’s one book in a series called “The Pragmatic Bookshelf.” It aims to provide a collection of practical suggestions to improve software development.
Since it was written, several concepts from the book, such as “rubber duck programming” and TDA, have become popular in the industry.
Putting TDA to Use
Can you use “Tell, Don’t Ask” to simplify your embedded systems? Once you begin to think about how the principle might benefit the IoT or embedded devices you’re working on, you’ll discover lots of opportunities to put it to good use.
Learn about more product design principles when you download our free Design Defined ebooks, v1 and 2!