Wednesday, October 05, 2016

Converters are bad, so it's good that the AU means fewer are needed #UWPLunch

All this month, I'm taking some time each day to explore (and document) things that are related to UWP development that I haven't fully investigated or used before. While doing it over lunch each day I'm calling it #UWPLunch.

On the surface, converters (implementations of IValueConverter) are powerful tools but I think they're massively overused and encourage doing some things which are actually bad.

The MVVM pattern is great. It's biggest benefits are to enable code reuse and improve the ease of testing. At some point in time, many people confused the pattern and the goals as being based on not writing "code behind". Specifically, they see this as meaning there should be as little as possible code written in the XAML.cs files (ideally none.) Instead, they write more behaviors, converters, or XAML. Here's my issue. Each of these are all code (yes, even XAML) that exists in the UI layer of the app. Just like anything in the code behind file. So my first objection is that there's an artificial distinction between the file or language (XAML vs C#) that UI related code is written in.

But it goes deeper.

If I have a VM that surfaces some data that must be converted before being displayed, I've now got two classes that must be used together. This coupling makes reuse harder. I can't just move one thing around, I need to know that I need another thing with it. Unfortunately, there's no standard way of indicating the VMs dependency. (And I doubt there'd be any benefit in creating such a thing anyway.) Not only do we have two separate but related classes, the business logic for how something should be displayed has been split. This makes testing harder. There's no single item under test in this instance so tests become more complicated. That the logic is in a behavior or a converter also makes it harder to test as it can't be as easily instantiated. It's normally necessary to end up running tests on such objects via the UI. Not only are such tests harder to write they're also slower to run. (UI tests are slower to run than pure code.) Tests that are hard to write or slow to run end up being run less often and eventually abandoned, bringing less benefit and eventually none.

The overuse of something as potentially helpful as a converter can actually undo some of the benefits of the MVVM pattern.

In the past, I've hit issues with performance related to the use of converters. This should no longer be an issue unless they're doing something very complicated or you're using a very large number of them. However, it's made me wary of using them. They also run on the UI thread so can impact performance there.
Generally, I try and avoid the use of converters. Instead I move the logic into the VM itself. All nice and cozy and contained. (Don't worry, I can still encapsulate logic and avoid duplication with this.)
The biggest exception to this is for Visibility. For lots, and lots, of circumstances, it's necessary to make things visible, or not, based on specific (normally boolean) criteria. I have a converter which can take a large number of different inputs (bools, numbers, lists, strings, etc.) and turn them into a Visibility value. It's generally very useful and I use it a lot.

That may not be the case going forward though as in the Anniversary Update, compiled bindings support implicit visibility conversion for booleans.

That means you can put this in the code behind (or associated VM).

 public bool ShowMe { get; set; } = true;
 public bool DontShowMe { get; set; } = false;

And use them to control visibility like this:

 <TextBlock Text="Now you see me" Visibility="{x:Bind ShowMe}" />
 <TextBlock Text="Now you don't" Visibility="{x:Bind DontShowMe}" />

which would only display the first TextBlock.

A couple of points of note.

This only works if the minimum version of the project is the AU.
The implicit conversion only works if the app is running on a machine running the update. The above targeting means that the app will only run on such. In theory, you could include it in code that targets earlier versions and selectively include it if you can detect you're running on a machine with the update but you'd still need to support explicit conversion for when running on older machines and so would require extra code.
The other downside is that you have to bind to booleans for the code to even compile as that's all that's supported. I'll need to keep my own converter around for a bit longer to enable me to do more interesting things like hiding zero length strings or empty lists. Either that or add extra properties to the VM for binding.


Post a Comment

I get a lot of comment spam :( - moderation may take a while.