Wednesday, August 12, 2020

How to do handle Master-Detail scenarios badly


You have a list, grid, or some other collection in your software (app/website/other).
Someone clicks, taps, or otherwise selects one of the options and is presented with a new page/window/screen/display showing more information or a different view of the option chosen.

That's the basic "master-detail" functionality.
It's straightforward, but there are at least five ways to mess up this experience.
  1. Not remember the position in the list when going back to it.
  2. Resetting the scroll position of the list when returning to it.
  3. Reloading the master list when returning to it.
  4. Resorting the master list when returning to it.
  5. Removing detail from the list and then returning to the list and seeing it there still.

1. Upon going back from the detail to the original collection, the software does not remember which item was previously selected. The focus may be moved elsewhere, or the first item in the list may be selected by default. Such behavior makes it hard to browse using only the keyboard or assistive technologies.
This is exacerbated when the resetting of the list moves the scroll position.

2. You scroll down the list, so none of the items that were initially visible are shown. You select one, view it, and then go back. Imagine then if the list has reverted back to the "top." If you want to keep looking at the options available, it's necessary to scroll back past all the items you've already seen just to get to something new.
It gets worse when the list loads in "pages" of data at a time.

3. You scroll down the list. You get to what is currently the bottom of that list until the software automatically retrieves and then displays more items so you can keep browsing. After doing this a few times, you select one, view it, and then go back. If the master list goes back to the "top" of the list, to get back to where the item you just viewed is shown, the app must repeat the calls to load more "pages" of data. 
It gets even worse when the list isn't always in the same order.

4. You scroll down the list with an expectation to look at multiple items. You reach the first item of interest, select it, view it, and then go back. How frustrating is it then if the list is in a different order? Is it reasonable, practical, realistic, or even possible to remember which items have already been viewed? How can a person be expected to find and view everything of interest in a large list that is constantly changing?
Also bad is when lists aren't updated when they should be.

5. Imagine browsing a list of favorites or bookmarked items. You select and view one of the items. While viewing it you remove the bookmark or other indication that it should be in the special list. What then should you feel if the item you've just removed is still shown on that list?


What other examples can you think of how this experience is often poorly implemented?

Monday, August 03, 2020

Fixing a really common misunderstanding about nuget.exe

It turns out that a lot of people commit a copy of nuget.exe into their code repositories.
Some of them used to be mine. Not anymore.

screenshot showing the number of copies of nuget.exe found when searchiGitHubng


tldr: don't include a copy of nuget.exe in your repository. Instead, add a reference to Nuget.CommandLine and use the copy of the exe the package provides.


It's rare to think about how NuGet works. You can just add the packages to your project, they get downloaded, referenced and everything just works.

But there are some things you might do with NuGet where you can't rely on MSBuild (or similar) to take care of things for you.

You may have to call `nuget.exe` directly.
I've had to do this many times and when doing so it's raised a tricky question.
"Where is nuget.exe located?"
Across all development machines and build/CI servers, there's no single answer.
You can't rely on it being in a particular place. unless you put it there.

So, how do you put it in a specific place?
I've never found any documentation to explain this and so put it in the repository with the code/scripts/whatever that needed it.

It never felt like the right thing to do but I didn't know any better and it seemed to work ok.
As we saw above, I'm far from the only person to have done this.
The only time this became a problem was when I needed to change something that called nuget.exe and needed a feature that was added in a newer version than the one I had checked in. Not a big deal but another reminder that this isn't the best way to do things.

A couple of weeks ago I stumbled across the NuGet.CommandLine package. Again, documentation of this package wasn't something I could find but the name had me wondering. "Could this be the way to perform the command-line NuGet operations I've always wanted?"

Some investigation was needed.

I started by downloading the package and looking inside.

Nuget Package Explorer showing nuget.exe in the tools directory of the package

Bingo! That looks exactly like what I need.

Now, how to reference it?

Fortunately, this is where some documentation did come in handy.

By adding `GeneratePackagePath="true"` to the PackageReference element I could have a way to get access to the version of the executable from the package.

partial view of the project file showing the PackageReference entry with the GeneratePropectPath property set

Now to use it.

By adding this property a new build parameter is created. It has the same name as the package but with two differences. Firstly it's refixed with "Pkg" and secondly, is has non-alphanumeric characters replaced with underscores. so, in this instance, the parameter is called `PkgNuGet_CommandLine`.

With this all set, I could now reference the version of nuget.exe that was downloaded as part of the package by specifying `$(PkgNuGet_CommandLine)\tools\nuget.exe`. As an example, see this targets file, which I use to automatically pack and sign release builds of packages.

That was it. Simple really, but poorly documented. I was surprised how easy it was to fix and why I've never seen anyone mention this before. Maybe other people do talk about this. Maybe other people know this already. That the package has sooooo many downloads makes me suspect that many people do already know this. But the number of repositories containing this file on GitHub also makes me certain I'm not the last person to learn about this.
If you're in the position I was, I encourage you to make the switch to using the package reference instead. Not only will you save a bit of space in the repository but, more importantly, you'll be able to learn when newer versions of the exe become available when a new version of the package becomes available.

---

I originally investigated the above while streaming on Twitch.  I like to stream there while investigating or researching topics as it forces me to articulate what I'm thinking, doing, and looking for, without getting distracted. Hopefully, it serves as an interesting way for others to learn what I'm learning. Follow me on Twitch to learn when I next do things like this and we can all learn together.

Monday, July 13, 2020

You've only added two lines - why did that take two days!

It might seem a reasonable question, but it makes some terrible assumptions:
  • lines of code = effort
  • lines of code = value
  • all lines of code are equal
None of those are true.

Why did a fix that seems so simple when looking at the changes made take two days to complete?
  • Because the issue was reported with a vague description of how to recreate it. It took me several hours to get to a reliable reproduction of the item. Some developers would have immediately gone back to the person reporting the problem and required more information before investigating. I try and do as much as I can with the information provided. I know some developers don't like having to fix bugs, and so do whatever they can to get out of it. Claiming there isn't enough is a great way to look like you're trying to help but not have to do anything. I know that reporting errors can be hard, and I'm grateful for anyone who does. I want to show appreciation for error reports by trying to do as much as possible with the information provided before asking for more details.
  • Because the reported issue was related to functionality, I'm not familiar with. The feature it was to do with was something I rarely use and is not something I've ever used in great detail. This meant it took me longer than it might to understand how to use it and the nuances of how it interacts with the software with the bug.
  • Because I took the time to investigate the real cause of the issue, not just looking at the symptoms. If some code is throwing an error, you could just wrap it in a try..catch statement and suppress the error. No error, no problem. Right? Sorry, for me, making the problem invisible isn't the same as fixing it. "Swallowing" an error can easily lead to other unexpected side-effects. I don't want to have to deal with them at a point in the future.
  • Because I investigated if there were other ways of getting to the same problem, not just the reported reproduction steps. One set of reproduction steps can easily make the error appear to be in one place when it may actually be more deep-seated. Finding the exact cause of a problem, and looking at all the ways to get there can provide valuable insights. Insights such as how the code is actually used, where there might be other places with possible (other?) problems that might need addressing, or it may show inconsistencies in the code that mean an error is caused (or handled) in one code path but not another.
  • Because I took the time to verify if there were other parts of the code that might be affected in similar ways. If a mistake led to the bug, the same error could have also been made elsewhere in the code-base. Now's a great time to check. 
  • Because when I found the cause of the issue, I looked to find the simplest way of fixing it that would have minimal risk of introducing side-effects. I don't want the quickest possible fix. I want a fix that isn't likely to cause confusion or other problems in the future.
  • Because I tested the change thoroughly and verified that it addressed the problem for all the different code paths that were affected. I don't want to rely on someone else to have to test that what I've done is correct. I don't want a bug to be found in the future and for me to have to come back to this code when I've mentally moved on. Context switching is expensive and frustrating. Having a dedicated tester have to look at the "same" change again is something I want to avoid whenever possible.

I don't like having to fix bugs. Partly because they can feel like the result of a previous failure on my part. The other reason I don't like fixing bugs is that I'd prefer to be working on new things.

What's worse than having to fix a bug?
Having to fix the same bug repeatedly.
I take the time to make sure any bug is totally fixed any time it is encountered so that it doesn't need to be faced, investigated, fixed, and tested more than once.


Tuesday, June 30, 2020

Announcing: Rapid XAML Toolkit release 0.10

Yes, 0.10 means it's getting excitingly close to a full 1.0 release and the removal of the preview tag.
Wider events and that some of what I'm trying to do with the toolkit has turned out to be much, much harder than I originally expected and means it's taking longer than I hoped but progress is being made.


What's new?

There are three big new items with this release:


  • 1- Custom XAML Analysis

I compare the XAML Analysis functionality to Roslyn Analyzers (and code-fixes) for C# (or VB.NET). The big difference is that you can create your own Roslyn Analyzers to check (and fix) code in any way you wish.

There are more details in the official docs, but the way it works is:

- Create a 'Custom Rapid XAML Analyzer' project (This template is now part of the toolkit.)
- Specify the type of element the analyzer relates to.
- Add the logic for analyzing the element and indicating what actions, if any, should be displayed.

It means you can easily create things like this:


The code for the above is a proof-of-concept and can be seen here. Hopefully, it's easy to read.
The method queries an object representing the XAML in the document and returns a response that handles what to display and what, if anything, to do to fix it. The tool takes care of all the interaction with Visual Studio and making the changes. You can't do *everything* but hopefully, it's most, if not all you need.


  • 2- Perform XAML Analysis at build time

The Visual Studio extension does analysis at design-time. It gives feedback on the documents you have open.
This new package allows for the evaluation of ALL the .xaml files in a project when the project is built. This means you won't miss something in another file. It means you can check files that anyone works on, even if they don't have the extension installed. It even means you can incorporate analysis checks as part of a CI/DevOps process. Don't want someone to check-in invalid XAML to your repo?--now you can. ;)
Yes, this also works with custom analyzers.




What's still to come?

There are a number of little things I still want to add before calling it version 1.0.
There are also a few bugs that need fixing and some experimental ideas that might get in for the 1.0 release.


Background

If you don't know. The Rapid XAML Toolkit started out as an open-source project in a Microsoft repo on GitHub as a collection of tools to make it easier to create and work with XAML files, whether that be in WPFUWP, or Xamarin.Forms apps. Last year the decision was made that it wouldn't be released by Microsoft and the functionality wouldn't be incorporated into Visual Studio. So, I, as a major contributor, took ownership of the repo and will be releasing it myself.
I believe it contains useful functionality that will empower every XAML developer on the planet to achieve more.


What now?


If you had the preview version installed (the one called "Rapid XAML (Preview)"), you should uninstall that first.

  • Give feedback - in whatever way works for you. Here, on twitter, or on GitHub.


Monday, June 15, 2020

Never set 'Copy To Output Directory' to 'Copy always'

What if there was a simple setting that could save you hours?

Visual Studio Properties window showing the "Copy to Output Directory" setting

I thought there was something wrong with the Test Explorer.
I'd make a change in a configuration file that was used by some of my automated tests. Then I'd run all the tests again to see if that broke/fixed anything.
Only, it would take a couple of minutes - before the tests even started! Because the code was being recompiled first.
I would get frustrated.
I would lose my flow.
It was not an enjoyable or productive situation to be in.

And, I thought the culprit was the test harness. Why was it triggering a rebuild of the code? And a rescanning of the built assemblies to look for tests? When nothing in the code had changed?
All that was changed was an external file that was referenced by some of the tests.

It turns out, the problem was due to my lack of understanding of how MSBuild works and how the value of the Copy to Output Directory property is used.

---

For years I've been concerned by the number of times I've heard developers told to set the value of the Copy to Output Directory property (to a value other than "Do not copy") for files that have no need to be copied to the output directory. It seemed like a waste of effort. If the file wasn't needed in the output directory, why waste effort (admittedly--not a lot of effort) copying files around? Ultimately, this didn't seem all that important. I found more important things to focus on and stopped worrying about other people copying files unnecessarily.
Maybe I shouldn't.
Maybe I should have actually learned more about the setting. It may have helped me sooner.

---

I lived with it, taking a while to rebuild and then run my tests. Until I couldn't.
A few weeks ago, I reached my breaking point. I couldn't take it anymore. I would find out why my code was being rebuilt unnecessarily. Even if it took days, I would be better off in the long run to address this delay. If it was something I was doing, I'd fix it. If it was a genuine bug in the tools, I would find out where and raise it accordingly.

You're smart enough to know that I wouldn't have gotten this far if I didn't have a conclusion to this story. I know you've also figured out from the title of this post what the solution was. Anyway...

It took me the best part of the day reading through diagnostic build logs and testing me to the limits of my web-searching abilities but I got there in the end.
The "culprits" were files with the "Copy to Output Directory" set to "Copy always".

Ok, but why?

That's a very good question, and one I asked too.

It turns out that the behavior is due to the way this value is interpreted.
If you know that you always want the latest version of the file in the output directory, under some conditions, it can be quicker (and easier) to copy the file than spend time determining if the files are different.
That's good, but why does it trigger a rebuild? Why not just copy the file built previously?
Because the file built previously may have been modified (or created) by the build. The build process interprets "Copy Always" to mean "copy the file once the build has completed, so it's the most recent version." Because the build process may modify the file, it must rebuild the project to ensure that the file is the latest version. This is easier than trying to work out if the file will be changed by the build process before it is run. (If that's possible at all.)

So, to make that clear:
- "Copy always" means "make sure I always have the most up to date version."
- Because the build process may modify the file, the build process must be run to make sure you have the most up to date version of that file.

As an alternative, "Copy if newer" won't force a rebuild. The argument for why this is the case is (in my opinion) a bit vague, but it avoids those unnecessary builds, and that's what I'm interested in here.


Given how it works, why might you ever want to use "Copy Always"?

I don't know an official reason, but the only one I could come up with is if you needed a file to have a timestamp that corresponds to the build time. That's it.
I can think of no other reason to use the setting "Copy always." Please share if you have another.



Ok. So, what can you do to make sure you're not using the "Copy always" setting?

There are three options.
  1. You could step through every file and check the property. - Very slow. Very tedious, and likely you might miss something.
  2. Open the project file in a text (or XML) editor and check or make changes there as necessary. - Slightly easier but a hassle if you have lots of projects and not something you'll want to do repeatedly.
  3. Install this extension, and it will check every project for you whenever you open one. If it finds a file with the dreaded "Copy always" setting, it will let you know in the output window. It also adds a context menu entry on the project file to fix (change to "copy if newer") all files in that project.


Partial screenshot of entry in context menu


Please install this Visual Studio extension and let me know how you find it or if there are any scenarios it doesn't handle.

.


Was the above useful?
Did you learn anything?
Have you changed a project (or projects) you work on because of this?
Have you thought about how much time this can save you? How much time is spent on each unnecessary build?
How many times each day is your code rebuilt when nothing has changed? How many days will you spend (have you spent) working on that code? How many people are/will working/work on that code-base?
Could it add up to hours?
And how much is an hour worth to you? Or the company you work for?
Isn't that at least worth buying me a coffee to say thank you?
While we can't do it in person, you can do it virtually via https://www.buymeacoffee.com/mrlacey.
I'll be very grateful for whatever you can contribute.
Or, you could sponsor me to continue to create tools like the extension mentioned above by becoming a sponsor on GitHub.