Tuesday, March 15, 2022

How you should be using (and naming) resources in XAML

... because a lot of people get it wrong!

We'll get to XAML shortly, but let's start by looking at some C# code:

It seems like a reasonable method, with an appropriate name. Doesn't it?
The name of the function clearly explains what the code does. That's a good thing. Right?

Imagine that the above was created to add a tax to a price. (This is a simplified example where tax is always the same and you can ignore the complexities of tax in the real world.) Now, is the function well named?

Think about where this method might be called from.
Will it always be evident to someone looking at the code why 20% is added to a price?
What if there were other reasons in the codebase to add 20% to a value. What's to stop them from using this function in those places too?

I asked if the above method was well named because it describes what the code does? Actually, this is a lousy way to name something.

The name should indicate the intent behind the code and suggest why it does what it does. We don't need the name to tell us what the code does. We want it to tell us what it is for. This means the name doesn't need to change when the code changes, and it clearly indicates where and why it should be used.

If we have the AddTwentyPercent method in our codebase and it is used for something other than calculating tax, what happens when the percentage for sales tax changes?

If we only change the contents of the function, we end up with something whose name does not describe what it does. Not only does it not describe it, but it also does something different. Something that could easily be misunderstood and end up with code that doesn't do what is expected or wanted. Code that is hard to reason about ("I know it says add twenty percent, but I need to remember that it actually adds 22%") and code that is harder to maintain because anyone unfamiliar with this "special case" could think it does what the name suggests.

If the tax rate changed, we could respond to this by creating a new function called--for example--'AddTwentyTwoPercent' and changing the calls to the old method related to adding the tax amount to call the new function. But how would we know which of the places that used the original function were to do with calculating tax? We'd have to go through and check them all.
We wanted to make a change to the percentage used to calculate tax. This should mean changing a single value in one place. However, we're now having to potentially review lots of code in various parts of a codebase. It should be a simple change, but a poorly named function has made this task much more complicated, time-consuming, and therefore also expensive.

Instead, what if the function had initially been named 'AddTaxToPrice'?

In that case, it would be clear what it does and where/why to use it. It should also raise a red flag if it was used for something else that happened to need to add an amount to a number that coincided with the tax percentage at that time.
If we had a function with the 'AddTaxToPrice' name, we could confidently change the contents of the function without needing to worry about where it was used or whether the name needed to be changed.
We'd have encapsulated the logic in a single place, with only a single reason to change, and minimized the chance for confusion about what the code does.

Now let's think about XAML.

Consider the following:

This might seem like a reasonable name for a resource. You may have even seen this in code presented as an example of "best practice." I actually think this is a terrible way to name a defined resource and consider it an anti-pattern. (i.e., an example of something you should NOT do.)

There are issues with this that are comparable with the C# function discussed above in the following negative ways.

  1. The name matches what the code does.
  2. The name does not indicate where or why it should be used.
  3. The code (thickness) could easily be used in very different places and for other purposes.
  4. If one of the places where this style was used needed to be changed, we'd have all the same problems as with the C# function. 

Imagine this thickness is used as the margin for Grids containing the contents of most pages in an app.
Now imagine you want to make a change to the app's design, so you have a large margin on the right-hand side of each page and add a smaller margin to the bottom of the content on each page. What do you do?

  • If you change the value of the existing resource but not the name, you get something with a name that doesn't match what it does. This leads to potential confusion for anyone (likely including yourself) reading the code in the future.
  • You could create a new resource and change all references to the old name to the new one. But, this means reviewing all uses of the original resource and only updating the appropriate ones. This could be a lot of work.

Imagine instead that a resource with the name 'StandardPageContentsMargin' had been created.

  • The exact details of this could be changed without the need to rename anything.
  • The resource's details could be changed without worrying about unexpected side effects.
  • It would be obviously wrong if someone tried to use the resource for something else. (Such as another type of control or place within the UI.)
  • You wouldn't waste time in an unnecessarily complicated and hard-to-maintain codebase.

In software development, how you name things matters. Yes, even in XAML.

Give resources names that indicate what they are for and where they should be used.

This will give you XAML code that is easier to write, review, understand, and change.

Yes, this might mean you end up creating multiple resources with the same contents/value but different names. No, the current tooling does not do a lot to help with creating similar resources. But that doesn't mean you shouldn't do it.

But, the "best practice" examples do the bad thing? - Yes, and we (I) shall (will) change them!


Post a Comment

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