Comparing objects in .NET and general thoughts on testing

I had a little task today at work, where I needed to replace the way I created a JSON object that was being returned from an API. Long story, but the challenge at the end was to make sure I didn’t screw it up, so I wanted an easy way to compare the “before” and “after” JSON. Now, I was adding a feature here, so the before and after were going to be different, but I wanted to have an easy way to check that they were different only where I expected them to be, and there weren’t any unexpected side-effects.

This let me down a rabbit hole, looking at things like this ObjectsComparer package on GitHub. I also stumbled across this JSON extension for FluentAssertions. In the end, I just wrote a script in LINQPad to dump the before & after JSON to disk, then compared it with Beyond Compare. I keep thinking that I need to put together a better testing framework for my API, so I can better automate this kind of stuff.

I don’t have a unit test project for this API, or any other really structured tests. I have a test client that can run a suite of “safe” tests that don’t alter data, and checks a few things on the results. But it’s not a very complete test set. And I have another client that runs a test cycle that does alter data, and hence needs to be run carefully, and can’t be run too frequently. (And that one isn’t very complete or thorough either.) And I have some LINQPad tests that I run mostly as smoke tests.

I’m currently working my way through the TDD section of the Clean Code series that I’ve been watching/reading. I’ve always liked the idea of TDD, but I’ve rarely worked on a system where it seemed practical. (I’m not saying that it’s not practical, just that I haven’t figured out how to effectively apply it to any of my typical work.) Regardless of TDD, I ought to be able to put together some better and more automated tests for some of my work.

Clean Code, Visual Studio, Windows 11, and a bit more on ebooks

I wanted to add some more notes about the whole Kindle, EPUB, MOBI, etc. thing that I’ve been blogging about in my last few posts. As I mentioned a few posts back, I’ve been working my way through the Clean Code learning path in O’Reilly. Since I’m probably going to lose access to O’Reilly before I’m done with that, I thought I’d buy a copy of the Clean Code book, so I could finish reading it at my leisure, and to have it for later reference. With tech books, I usually like buying a DRM-free ebook directly from the publisher, when possible. In this case, I initially had some trouble finding that, but eventually figured out that it’s purchasable through the InformIT site. I bought the Robert C. Martin Clean Code Collection ebook, which contains both Clean Code and The Clean Coder. I had a discount code, so it was about $40 total.

I copied the EPUB, MOBI, and PDF files for the book to OneDrive. There are a number of ways I can read an ebook on my iPad, if it’s DRM-free and available in multiple formats. For this one, I wound up sending it from OneDrive into my Kindle app, as a MOBI file. That method still doesn’t support EPUBs. And it will copy the file up into my Kindle library, which is nice. I don’t think I’d ever copied anything into the Kindle library that way before, but it worked fine. I also copied the PDF into GoodReader. The code listings in the MOBI version look a little weird, which is a common problem with tech ebooks, so it’s nice to have the PDF handy as an alternative.

Clean Code has some example refactorings that are fairly interesting. They’re all in Java, but I thought it might be interesting to take the original Java code for one of the examples, convert it to C#, get it working, then work through some refactoring that’s similar to what Uncle Bob does in the book/videos. I found some of the code for the examples in GitHub under the Clean Code Kata user account. (I’m not sure if that’s an “official” account for Bob Martin or his organization, but either way, the code is there.)

This idea to convert some of the examples to C# and work through them was prompted in part by a desire to set up a .NET dev environment of some sort on one of my personal machines, and to maybe experiment a bit with some of the more recent .NET stuff, like .NET 6 and VS 2022. I realized that I don’t currently have any dev stuff at all set up on my desktop PC, my MacBook, or my Lenovo laptop. The MacBook is new, so I just haven’t set any dev stuff up yet. The Lenovo was bought in 2020, and I haven’t gotten much use out of it at all. And I’ve been trying to keep the desktop PC free of any heavyweight dev tools, since I just want to keep it clean for personal productivity stuff. After going back and forth on a few possible setups, I decided to install Visual Studio 2022 (Community edition) on my Lenovo laptop. I considered just installing the .NET 6 SDK and Visual Studio Code, which would have been much more lightweight, but I’m used to using the full VS product, and I can’t see a reason not to use it. And the Lenovo is the best place to install it, since I can wipe out that machine entirely and start fresh if things get too messed up. I’ve also recently upgraded that laptop to Windows 11, so this was also an opportunity to (finally) give that a try.

Of course, I’ve had other things to do this weekend too, so I’ve only gotten as far as installing Visual Studio and git, and tweaking some settings. Maybe I’ll actually do some programming next weekend. (Or maybe I’ll get distracted by something else, and the whole thing will fall by the wayside.)

Scrum and tech learning follow-up

This post is a follow-up to my previous post on scrum and tech learning. Since then, I’ve finished reading The Elements of Scrum, via my (work) Percipio account. I’ve also downloaded the audiobook version of Scrum: The Art of Doing Twice the Work in Half the Time, from my (ACM) Skillsoft account. I normally wouldn’t think about consuming tech books in audio format, and most of them wouldn’t work that way. But maybe a scrum book would be comprehensible as audio, so I’ll give it a try.

There’s another scrum book I was considering, Scrum: A Pocket Guide, which I’ve found is also available in audio format, via the author’s YouTube channel. So, in this case, you can actually watch the author sitting at his desk, reading his own book. I don’t think I actually want to do that, in this case, but it was nice of him to do that and put it out there for free.

Meanwhile, I logged in to my ACM O’Reilly account today, to see if there was anything I really wanted to watch or read before the account gets shut down. I decided to take a look at Bob Martin’s Clean Code video series. I watched the first hour-long video. There’s about fifty hours of video in the series, all told. In O’Reilly, it’s organized as a “Learning Path” with the videos interspersed with chapters from his Clean Code book. I’ve been meaning to read that book for a while. It was published in 2008, so it’s probably a bit out of date at this point, at least in terms of some of the specifics. For the videos, you can also find them for sale at the Clean Coders web site. (They’re not cheap.) You can find a few on YouTube. Here’s a link to the first one, which is the one I watched today. (I think the one I watched on O’Reilly is actually a revised version of that.) He definitely tries to keep it interesting, with a bunch of different costumes and backdrops, including several Star Trek ones. It comes off as pretty corny, but I guess it’s better than just watching him read through the material at his desk.

With the changes we’re going through at work, I’ve decided that now is a good time to back up a bit and think about what new stuff I need to learn, or old stuff I need to reinforce and/or brush up on. I’ve probably read enough on scrum by now, though I may branch out and read up on some related topics. And the Clean Code stuff is the kind of thing I like to check out occasionally, to remind myself of some of the fundamentals of good coding, and maybe learn a few new things that I hadn’t stumbled across before.

I’ve also been listening to some of the recent podcast episodes from .NET Rocks around the twentieth anniversary of .NET, which happened back in February. (I stopped subscribing to .NET Rocks a while back, so I don’t listen to it every week, but I go in and cherry pick interesting or relevant episodes once in a while.) Listening to folks like Anders Hejlsberg, Scott Guthrie, and Miguel de Icaza reminisce about .NET was fun. And it got me thinking about what new stuff is going on with .NET that I should learn. Maybe Blazor? Or I should figure out what’s new and interesting with .NET 6? Or I should try to get back into F# again? I don’t know. Maybe I should pick up this Apress Microsoft book bundle from Humble. That would keep me out of trouble for a while, right?

Scrum at work, and tech learning subscriptions

My team at work is going through some changes right now. We’re getting shuffled to a different spot in the IT department hierarchy and getting a new boss. And we’re supposed to start doing scrum.

Actually, we were supposed to start doing scrum almost a year ago, and we kinda started doing it, but we didn’t really go all the way with it. So now, I guess, we’re supposed to go all-in. Or almost all-in. Or something like that.

Anyway, I watched some training videos for scrum last year when we were supposed to start using it, so I already have some understanding of it. But now that we’re going to be going further with it, I decided to do some more reading on it and try to learn more about it.

I wanted to read a book on scrum, and since we’re using Azure DevOps, I decided to try this one: Professional Scrum Development with Azure DevOps, from Microsoft Press. I started reading it in March, and finished it a couple of weeks ago. It wasn’t a bad book, and it does cover the Azure DevOps scrum process pretty well, but it was also pretty dry, and I’m not sure that a lot of it is really going to be relevant to me.

I’ve also considered reading Zombie Scrum Survival Guide. It might be a little cynical to assume that our implementation of scrum is going to fit the definition of “zombie scrum,” but I suspect it might, and I’m not sure what the best way would be to engage with that.

And A Scrum Book: The Spirit of the Game might also be a good general scrum book to read. Honestly, it looks a bit daunting though.

On the related general subject of tech books, I’ve recently learned that ACM is going to lose access to O’Reilly Learning (previously known as Safari) in July. That’s quite a disappointment, but not entirely surprising. ACM got access to the full Safari library in 2017. That always seemed a bit weird to me, since an ACM membership costs about $100/year, and a regular individual O’Reilly membership costs $500/year, raising the question of why anybody would buy one through O’Reilly rather than just signing up for ACM. I guess that logic finally caught up with them, so now they’re cutting it off.

O’Reilly has offered ACM members a $100 discount, so that would bring it down to $400/year, but that’s still a lot of money, and the discount is only good for the first year. I generally only read a few tech books each year, and I can generally buy them for $40 or $50 each, so I guess it’s not going to be worth it to sign up for a paid O’Reilly account.

ACM still has SkillSoft, and I think I still have access to Percipio through work, but neither of those has the broad selection of tech books and videos that O’Reilly has. (And I think SkillSoft and Percipio are actually the same thing, under different names, possibly with slightly different libraries?) I also have access to LinkedIn Learning, both through work, and (last time I checked) through the local public library. LinkedIn Learning really just has videos though, no books. And their video courses generally aren’t as in-depth as the stuff I could get through O’Reilly. I discontinued my Pluralsight subscription this year, so I don’t have that anymore, either.

For now, I guess I’ll take a look at Skillsoft/Percipio when I’m looking to read up on a new tech subject, and see what they’ve got. When I really want a specific book that they don’t have, I’ll probably just buy it from Amazon or directly from the publisher. I’ve been trying not to buy physical tech books anymore, and O’Reilly has certainly helped with that. A lot of the tech publishers still offer direct sales of DRM-free ebooks, so I’ll probably go that route when I can, rather than getting the DRM’d versions from Amazon.

Post-Thanksgiving stuff

I survived Thanksgiving, but it wasn’t a great day. The fire alarm in my apartment building went off on Thanksgiving Eve, at 11 PM. So I had to get out of bed and go stand around outside in the cold for about a half-hour, before being allowed back in. I’d gone to bed at 10, and was pretty well asleep at 11 when the alarm went off. And afterwards, I just couldn’t get back to sleep. So I didn’t have a lot of energy on Thanksgiving. About all I did was re-watch some episodes of Doctor Who and nap. I guess that’s a reasonable Thanksgiving, really.

I tried to get back to something closer to normal yesterday, Black Friday, but that was also a pretty low-energy day. I didn’t read anything on Thanksgiving, and read only one comic on Friday. I’m noticing that my resilience just isn’t what it used to be, physically (and sometimes, mentally). One bad day or night can screw me up for a few days afterward. I’m not sure how much of that is just normal for my age, vs. being something I should worry about.

Anyway, what I wanted to write about was really just some Black Friday stuff. I don’t buy a lot of physical stuff on Black Friday, typically, but I keep my eye on some digital deals on software, subscription services, and stuff like that.

  • Last year, around this time, I signed up for a free one-year subscription to Calm. The deal was for one free year, and a second year at half-price. I’ve been using it pretty consistently this year, so I was ready to let it renew for the second year. But Calm runs a Black Friday deal every year, where you can get a lifetime sub at 60% off. So I went ahead and took advantage of that, and paid $160 for a lifetime sub. That’s a fair amount of money, but I’ve stuck with my meditation habit pretty consistently this year, and I think I’ll keep it up for the foreseeable future. That was my big Black Friday purchase.
  • Another thing I did last year was to sign up for Hulu’s Black Friday deal, which was $2/month for their ad-supported tier, for a year. So that was coming to an end. For that, I decided to pause the subscription for a few months are reevaluate it later. There’s some good stuff on Hulu, but I feel like I’ve got too many streaming services going right now, and too much stuff to watch.
  • I also subscribed to Letterboxd Pro last year, on Black Friday, for $12/year. I’ve been using Letterboxd a lot this year, so I let that renew, and it looks like I’ll continue to get the discounted $12/year rate.
  • It’s also about time for me to do my yearly review as to whether or not Pluralsight is worth renewing. I’m currently on a “legacy” plan, which should renew in January at $179. If I cancel my account, then I won’t be able to get that old rate back. Though it seems like their current Black Friday deal would let me subscribe to their “standard” plan for $179, so maybe there’s a little wiggle room there. I haven’t actually used Pluralsight that much this year, so maybe it’s time to give up on that. I’ll have to decide on that before the end of the year.
  • Meanwhile, my Amazon Prime subscription renews on December 1, for the usual $119. I’m always a little unhappy about supporting Amazon to the extent that I do, but honestly, it’d be kind of hard to live without Amazon at this point, and dropping Amazon Prime would not affect Amazon’s fortunes in the slightest. So I’ll just let that one renew too.
  • I generally think about various hardware upgrades around the end of the year. I don’t really have anything pressing this year though. I looked at the Kindle deals at Amazon. I’m happy enough with my current Kindle, but the new Paperwhite is supposed to be really good. But I just don’t need it. I might want a new iPhone next year, but, again, I don’t really feel like I need one just yet, and there aren’t any really good deals on iPhones. So probably no new hardware this year.

So that’s about it. Nothing much exciting, but it kept me out of trouble for an hour. I need to try to get back in the swing of things today and tomorrow, so I can go back to work Monday and have a good productive day. I know there’s going to be a lot of work waiting for me on the first day back from vacation.

moving from ADAL to MSAL

I haven’t written a programming-related post in a while. I just had to rewrite some code that used ADAL to use MSAL, so I thought I’d write up a short post on that.

There’s a bunch of documentation around this on the Microsoft web site, but for the simple case I was interested in, it took some effort to track down.

Here are links to a couple of general articles:

What I needed was to rewrite a small block of code that calls a web API from a console app, with no user intervention. I have an Azure app registration set up to help with that, with a client ID and secret, and all that stuff. I have some links on how to set that up somewhere, but I’ll skip that for now.

The actual code I needed was something like the code here (to initialize MSAL) and here (to get a token). After I get the token, I just add it as a bearer token to the header for the request.

Here’s a bit of “before” and “after” code:

// before:
using Microsoft.IdentityModel.Clients.ActiveDirectory;

// get the parameters from a config file, or somewhere...
string clientId = ConfigurationManager.AppSettings["ClientId"];
string clientSecret = ConfigurationManager.AppSettings["ClientSecret"];
string authority = ConfigurationManager.AppSettings["Authority"];
string svcResourceId = ConfigurationManager.AppSettings["ServiceResourceId"];

AuthenticationContext authContext = null;
ClientCredential clientCredential = null;

authContext = new AuthenticationContext(authority);
clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = null;
try
{
	result = await authContext.AcquireTokenAsync(svcResourceId, clientCredential);
}
catch (AdalException ex)
{
	Console.WriteLine(String.Format(
		"An error occurred while acquiring a token\nTime: {0}\nError: {1}\n",
		DateTime.Now.ToString(), ex.ToString()));
	return;
}
//Console.WriteLine("Access Token: {0}", result.AccessToken);

client = new HttpClient();
client.BaseAddress = new Uri(BaseAddr);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Add the access token to the authorization header of the request.
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

// after: 
using Microsoft.Identity.Client;

IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
	.WithClientSecret(clientSecret)
	.WithAuthority(authority)
	.Build();

AuthenticationResult authResult = null;
try
{
	List<String> scopes = new List<String>() { svcResourceId + "/.default" };
	authResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();
	//string accessToken = authResult.AccessToken;
	//Console.WriteLine($"Access Token: {accessToken}");
}
catch (Exception ex)
{
	Console.WriteLine($"MSAL Error: {ex.Message}");
}

I’m not sure if anyone other than me will ever find this useful, but here it is, just in case.

Ephemeral Port Exhaustion

We’ve been having some trouble with our main web server at work over the last few months. It all boils down to ephemeral port exhaustion, which sounds kind of like a post-COVID side-effect, but is actually something that can happen to a Windows server if you’re opening too many ports and then not releasing them. The post linked above contains some useful troubleshooting information regarding this problem.

I actually think the best explanation of this issue is in a 2008 TechNet article titled Port Exhaustion and You. (That link goes to the original version of the article via archive.org. Here’s a link to it’s current location at Microsoft’s site.)

The basic issue is that you can run out of ports and then anything that relies on opening a new one fails, and you just need to reboot the server. So, not the end of the world, but not good for a production server. We’ve been working around it for awhile. We had it scheduled to reboot once a week, but upped that to twice a week when it seemed like once wasn’t enough. And now it’s gotten to the point where I really think we need to find the underlying issue and correct it.

In our case, the server is running a bunch of web services under IIS. There are more than a dozen separate services, written by various programmers, at various points in time. They’re all (probably) C# programs, but they’re written under various versions of .NET Framework and .NET Core. They’re grouped into three or four app pools.

The first thing that makes sense to look at here is how the individual programs are handling outgoing network connections. Normally, in C#, you’d use HttpClient for that. I wrote a blog post in 2018 about HttpClient and included a link to this article about how to properly use HttpClient without opening a bunch of unnecessary connections. I think I’ve got all of my own code using HttpClient correctly and efficiently, though I’m not sure about everyone else’s.

It can be hard to tell what’s going on behind the scenes, though, if you need to rely on closed-source third-party libraries that also open up HTTP connections. I’ve got a few of those, and I think they’re not causing problems, but I don’t really know.

To try to monitor and track down port exhaustion issues, there are a few tools you can use. A number of the articles I’ve linked above mention “netstat -anob” or some variation of that, and I’ve found that helpful. One issue with that, if you’re running a lot of web services, is that you can’t easily see which service is causing a problem.

My big breakthrough yesterday was realizing that I could use “appcmd list wp” to get a list of the PIDs and app pool names associated with the various IIS worker processes. From that, you can tie the netstat output back to a specific app pool at least. (Of course, if you have ten web services under one app pool, then you’ve still got some more work to do.) See here for some info on appcmd.

Anyway, we still haven’t quite got our problem solved, but we’re getting closer. For now, we’ll still just need to keep an eye on it and use the old IT Crowd solution: “Have you tried turning it off and on again?”

debugging

In a recent blog post, Mark Evanier included this quote from Maurice Wilkes, probably taken from his memoir:

By June 1949, people had begun to realize that it was not so easy to get a program right as had at one time appeared. It was on one of my journeys between the EDSAC room and the punching equipment that the realization came over me with full force that a good part of the remainder of my life was going to be spent in finding errors in my own programs.

(Emphasis mine.) Yep. Today, I spent too much time working on a bug that boiled down to something like this: I had a WHERE clause in some SQL that was originally “where X and Y.” I changed it to “where X and Y or Z.” It should have been “where X and (Y or Z).” Stupid parentheses.

Playing with Postman

Postman is a tool that I’ve been meaning to learn for years. I’m not sure when I first heard of it, but I’m pretty sure it was back when it was just a Chrome extension. So it might have been almost ten years ago. I didn’t really get serious about it until 2019, at which point I was doing enough REST API work that it seemed like I should take some time and see what all the fuss was about. At that time, I would have primarily been using Fiddler for API testing. Fiddler’s Composer tab is pretty good for basic API testing, but you can do a lot more with Postman.

Alas, when I tried setting up Postman on my development VM in 2019, I couldn’t get it to work. It would just hang every time I launched it. I went back and forth with support for a while, and tried a number of things, but I just couldn’t get it working. So I gave up and went back to Fiddler.

But I switched to a new VM a while back, so I thought I’d give Postman another try. I successfully installed it on my VM at some point last year, and poked around a bit, but never had time to actually learn it. So last week I had a bit of free time and decided to spend some of it figuring out Postman.

I started with this Postman 101 for Developers video on YouTube. The Postman YouTube channel has a bunch of useful videos. After that, I moved on to a couple of LinkedIn Learning videos:

  • Introducing Postman – This video is from Dave Fancher, and was created in 2019, so it’s a little out of date, but still useful. It’s about 90 minutes.
  • Postman Essential Training – This one is by Kristin Jackvony, and is from 2020, so it’s a little closer to up-to-date. It’s also about 90 minutes. It covers some more advanced testing stuff, like the collection runner and Newman.

Then, I moved on to a Pluralsight video: Postman Fundamentals, by Nate Taylor. That one is about 2.5 hours long, and gets a bit deeper into what you can do with JavaScript for testing API calls. I found it to be very useful for the kind of stuff I’m likely to be doing.

All three of these courses are old enough that they predate the new v8 Postman user interface, so it can occasionally be a little challenging to figure out where something is in the current version vs. where it was in 2019 or 2020. But it’s not too bad.

So I think I now have a pretty good grounding in the basics. Of course, now I’ve gotten busy again, and haven’t gotten back to Postman in the last few days. But I did at least set up a collection/workspace for one of the APIs that I work on, by importing the Swagger JSON for it. I need to clean it up a bit, but I can certainly use it for ad-hoc testing now.

Next, I need to find the time to maybe write some test scripts. My current “smoke tests” for the API are in C#. I have a number of console programs that exercise different aspects of the API, to test out different stuff. An I have a C# script that I run in LINQPad after every deployment that just does some quick non-destructive tests, to make sure the deployment didn’t break anything obvious. But I’d really like to have some more structured and exhaustive tests that I can run. I’m not 100% sure that I want to commit to Postman for that, since it does add some complexity. But it might be worth it. It was worth spending several hours learning about it, either way, and I think I’ll be using it for a lot of my ad-hoc testing now.

Online Learning Resources

I have a bunch of topics I’ve been meaning to blog about this year, and just haven’t had the time to get to too many of them. A lot of my blogging recently has been more “getting stuff off my chest” blogging or “clearing out my head” blogging. But I have a few topics to cover that might be mildly useful to other people. Today’s topic is going to be an overview of online learning resources. I had to write up some notes of this stuff for work recently, since we’re doing a review of the training resources we make available in the IT department. So this post is basically repurposed from an email I sent to my boss.

I get a fair amount of use out of Pluralsight. I have my own subscription, but we also have a department subscription at work. Pluralsight is really good for .NET stuff and other Microsoft-specific programming topics. It does cover topics outside of the Microsoft ecosystem, but not as well. It’s all video training (no books).

We also have a Percipio account at work, and I’ve poked around in it a bit, but haven’t gotten much out of it. There are a lot of books and videos available, and it covers a much wider set of subjects than Pluralsight. There’s probably a lot of useful stuff in there, but it’s not that useful for me. (Percipio seems to be a rebranding of Skillsoft, which I also have access to via my ACM membership.)

I’ve also tried out LinkedIn Learning, which we have access to at work. This platform has a much wider breadth of material than Pluralsight or Percipio, and includes a lot of non-IT oriented stuff. I’m looking at the home page now, and I’m seeing stuff like “Life Mastery: Achieving Happiness and Success”. Basically, a lot of “soft skill” stuff. There’s plenty of content for programmers too though. Like Pluralsight, it’s all video (no books). From what I’ve watched, I’d say that the quality of stuff on this platform is pretty mixed. Some of it is really good, and some of it is more on the level of what you’d get from random YouTube videos. (And LinkedIn Learning is a rebranding of Lynda, which I still (I think) have access to via the Somerset County Library.)

Through my membership in ACM, I have access to the O’Reilly learning platform (formerly Safari), which is, I think, the best one out there for programming topics. They have basically every programming-related book that gets published by any of the major publishers (and some minor ones). It used to be just an ebook platform, but they’ve adding a lot of video content too. And the ACM access used to be to just a subset of the full Safari library, but it’s now the full library, which is awesome. (See previous mention here.)

Outside of paid learning platforms, there’s a lot of free stuff out there now. Microsoft has a lot of stuff at Microsoft Learn and Channel 9. And all of their conferences went virtual (and free) in 2020. Both Build and Ignite had some good content last year. Ignite is already scheduled for March 2-4 this year, and will be free and virtual.

In terms of my own current online learning, I’m trying to finish a course on ASP.NET Core Fundamentals on Pluralsight. I’ve been really busy at work though, and haven’t watched any of it in more than a week. (And yes, I know, I could watch it at night or on the weekend, but I’ve been either tired and/or busy on weeknights and weekends lately too. But that’s a subject for an entirely different blog post.)