Designing Windows Store Apps for Performance
No matter what kind of app you’re building, a number of key usage scenarios are essential for the success of your app. A shopping app must enable users to quickly find and purchase items. A news app must be able to quickly present users with what’s going on in the world. These key scenarios are what your app does well. This is where your app provides value to the users, so you need to make sure your design and architecture support them efficiently. All your key scenarios should provide a good user experience. That means they should look good and perform well. By keeping performance in mind for your key scenarios, you will reduce the risk of making the wrong architectural decisions that would be expensive to change later in the game.
For instance, let’s assume you’re designing a news app. Needless to say, you want the app to be able to retrieve and display the latest news because that would be a key scenario for a news app. One approach is to download the news on startup, but what happens if the back end or the network is not responsive? If your app waits for the news to download, it stalls. How do you think the users will react if they have to wait several seconds every time they want to check the news? How would you like turning on your favorite news channel on TV only to discover a big “Please wait” sign for a couple of seconds? That wouldn’t work on TV, and it isn’t a good user experience for your app either. Users want to check the news at their convenience; making them wait is a bad idea.
This prompts the question: how can you design a news app that is both immediately responsive and able to show the latest news quickly? As the developer, you can control how fast the app launches and becomes responsive by optimizing the code and design, but unless you hold some secret power over the Internet, you are at its mercy. Users access the cloud through everything from high-speed, optical networks to spotty mobile connections on the outskirts of civilization, so there’s no way you can control or even predict the expected performance of their network connections. This leads to a simple conclusion: if your app has to wait for the latest news as part of startup, there’s just no way you can guarantee a good experience in all cases. To ensure a quick launch, you cannot rely on the news being immediately available.
If you ignore this fact, your app might perform well under ideal circumstances, but most likely you will see bad reviews from users who happen to spend their time in the real world where network performance isn’t always as fast and reliable as we would like it to be. You can avoid this problem by designing your experience around the fact that network connections are fickle. The only way you can control the user experience is by relying on the parts you can trust.
Designing your app with performance in mind is the topic of this chapter.
Less is more
The most common performance problem I see is apps that try to do too much at once. Of course, this problem comes in a plethora of guises. Sometimes the app is doing work in advance instead of deferring it until needed, sometimes the app tries to handle more data than it is capable of, and sometimes it is doing the same work over and over again. The examples are plentiful, but the overall problem is the same.
In many cases, the majority of this work is done on the XAML UI thread. As you learned in Chapter 2, “Platform overview,” the job of this thread is to keep the app responsive, so you don’t want to burden the UI thread with too much work. Doing so results in a slow and unresponsive app.
Regardless of the specifics, the solution to this problem is always the same: do less work. In other words, make sure the app does what is necessary to implement its features, but no more than that. Optimizing the performance of your app is all about getting rid of the nonessential elements or at least moving them out of the way on the critical path.
However simple this might sound, it is probably the best advice I can give you for building fast apps. Of course, the minimum amount of work required for any given key scenario isn’t always obvious. Identifying redundant work can be difficult, but keeping the mantra of “less is more” in mind will help you trim your app and achieve great performance. If something isn’t needed, don’t do it. If it’s needed later, defer it. If you have to perform a nontrivial operation repeatedly, consider caching the result and use that instead of doing the work again. You have many options, but they all boil down to getting rid of redundant work.
This might still be a little abstract, so let me give you an example of what I’m talking about. This example is from a sports app I worked on. One of the main features of the app was to display results of recent games. Consequently, the app would retrieve results for all of today’s and yesterday’s games at startup. Furthermore, it would retrieve the entire set of results at a fixed interval. Unlike stock prices, most game results don’t change very often and presumably yesterday’s results don’t change at all, so the majority of numbers would be unchanged between updates. Yet the app would retrieve these numbers over and over. Obviously, this approach doesn’t represent the minimal amount of work, because the same data is retrieved and processed repeatedly.
Unfortunately, that was not the only problem. The app downloaded the data as a couple of XML files, one file per day. Because of the redundancy of retrieving all the results on every request, the XML files were large. There are many good things to be said about XML, but it is rarely the optimal choice from a performance perspective. Unless the data is very verbose, XML markup tends to constitute a significant part of—and in many cases, even the majority of—the content of XML files. In this case, the XML markup dwarfed the actual content of the files. Downloading, reading, and decoding the data that way carries a noticeable overhead. For performance-critical parts of the application, you can usually find better options than XML. Processing these files repeatedly is not optimal either.
Fortunately, solving these problems is not difficult—conceptually, at least. We can easily come up with ways to improve this approach. Instead of retrieving all the results on every request, just use a timestamp to limit the retrieval to the latest results. This reduces the amount of data processed on each request significantly, and it might even eliminate the need to process any data at all on some requests. Similarly, picking more efficient encoding for the data is straightforward. There are numerous, less verbose encodings to choose from.
The solutions to the problems are almost trivial. Unfortunately, to implement these changes the team would have to redesign both the app and the back end to accommodate the different approach for getting the data. That’s a significant risk to take on late in the project. This scenario re-iterates the point of Chapter 1, “Setting the stage”: design decisions that affect performance are much less expensive to make at the beginning of the project, which is why you should scrutinize all your app’s key scenarios and look for redundant work to eliminate as you design the app.