Tuesday, January 14, 2025

Easing into retirement

I posted the following message into the ozdotnet mailing list in October 2024.


Hello everyone, I have an announcement and tale that might interest you.

I’m easing into retirement.

Companies I’ve been working for are being sold, retired or are no longer developing new software. I only have a few hours of ad-hoc maintenance work each week. Running out of legacy work would drive a regular dev to seek new work, but in my case, I declined to create a LinkedIn page, or send out feelers through contacts for new work, because… I’m burnt out.

Why? That’s what pleases me to imagine might interest you.

I learned to code in 1975 and became an official programmer in 1981. I wrote FORTRAN, ALGOL, COBOL, assemblers and various JCLs and scripting languages on Honeywell, FACOM and IBM mainframes. Things were simpler back then of course because you moved inside the ecosystem of a particular manufacturer and had high-level support and voluminous and accurate documentation. If you wanted to solve a problem or do something edgy, then an answer was nearby. It was a different simpler world, but … everything worked.

Now, well into the 21st century of IT, everything doesn’t work. My wife often hears me shout from the other end of the house “Everything f***ing doesn’t work”. I also only semi-jokingly say I’ll have these words carved into my gravestone: “Everything f***ing doesn’t work all the f***ing time”.

Overall, what has burnt me out is complexity and instability. I’ll break those topics down a bit.

Everything in modern IT is complicated and fragile. Every new toolkit, platform, pattern, library, package, upgrade, etc is unlikely to install and work first time. I seem to spend more time getting things working and updated than I do actually writing software. In a typical working month I might have to juggle Windows, Linux, Android, iOS, macOS, Google, Amazon, Azure, .NET, Python, PowerShell and C++, and they all have different styles and cultures. Software engineering has fractured into so many overlapping pieces that I’m tired of trying to maintain competence in them all.

That leads naturally to the problem of dependencies. Just having so many moving parts with so many different versions available produces dependencies more complex than abstract algebra. How many times have you hit some kind of compile or runtime version conflict and spent hours trying to dig your way out of it? (A special salute to Mr Newtonsoft there!) Or you install A, but it needs B, which needs C, and so on.

I often hit incomprehensible blocker problems for which web searches produce absurd and conflicting suggestions which don’t work anyway. All I can do is futz around and change things randomly until things work again. I don’t know what went wrong and I don’t know what went right.

The Web — Browsers, HTML, CSS, JavaScript, the HTTP protocol, JSON and REST can all burn for eternity in fusing hellfire. About ten years ago I told my customers I refused to write any more web UI apps. However, I was forced to do so a few times and I’m still scarred by the horror. It’s just over 30 years since the web became public and we’re still attempting to render serious business apps using dumb HTML. HTML5 is the joke of the century (so far). I still lament the loss of Silverlight.

Git — Someone is lucky I don’t own a gun.

Fads — An exercise for the reader: name all the platforms, kits, patterns and frameworks that you know were once the coolest thing and now might only be found in history articles. An advanced exercise is to speculate on which currently cool things will be gone soon.

Finally, here is a list of typical things that give me the shits, just as they pop out of my head.

  • Attempting to compile projects that have been idle for a year or more will usually fail due to changed dependencies or deprecations and it can take hours to get them going again.
  • I develop and test something with great care, then deploy it and it crashes. This is part of the general “it works on my machine” disease.
  • I can stop successful work on Friday night, then resume on Monday morning and everything utterly fails.
  • My USB microscope and music recording both stopped working recently, and it took me a week to discover that it was a block by Windows 11 app security (I thought it was hardware or incompatibility problem).
  • Security! Walls, barriers and hurdles of security everywhere to crash through. Yes, I know we need security everywhere to stop the black hats, but it’s also stopping developers. Lord knows how many times I’ve hit run or debug on my own PC and I get “Access denied” and hours of research will be required. I’m also fed-up with ceaseless 2FA requests via email or SMS.
  • Everything about mobile devices. The ludicrous variety of devices and brands makes app development a nightmare. Then you must struggle through the variety of publishing processes.
  • My final entry is simply the tiny “thousand cuts” that torture you during development: version mismatches, inconsistent behaviour, strange errors, editor quirks, missing files, etc. All the little personal problems that slip between the cracks of bigger issues I’ve previous mentioned. Your mileage may vary.

In summary, being a software engineer is now so exhausting that after 40+ years of a generally enjoyable career immersed in programming and computer technology I’ve reached a point I never thought would arrive… I’m burnt out. Even working on my hobby projects has become a burden because they suffer from many of the impediments previously mentioned.

I still plan to attend some upcoming conventions and Meetups, and I’ll be watching the forum, but my posts will diminish because I’m probably out trying to prevent the garden and house disintegrating back into the earth from whence they came.

Greg Keogh

Saturday, January 11, 2025

Web API Status Codes and Errors

Overview

This post is an update to one I made in 2018 which complained about the clumsy way REST style web services use status codes to report success or failure for various types of requests. The key point I'm going to make is that the convention of using status codes like 201 (Created), 204 (NoContent), 400 (BadRequest), etc, is inappropriate for expressing the results from a typical business service.

The list of HTTP Status Codes contains a bewlidering set of values that are mostly related to networking, or web server internals or other esoteric conditions. There is no sensible mapping of these status codes to typical business processing results. You could argue that a 400 (BadRequest) might indicate a bad parameter in a request, but the definition of 400 is far broader than that. If you get a 404 (NotFound), then exactly what is "not found"? Is it some file or database row, or the whole uri? Many other status codes are event trickier to assign some sort of business meaning.

The worst thing about the zoo of possible response codes is that the client has the burden of switching code paths to handle them all, and hoping they haven't missed any. Polite service authors will publish OpenAPI to document all their responses, but it can be time-consuming to safely turn large amounts of documentation into code (although there are various tools that convert OpenAPI into client-side code).

Only 200 (OK)

I eventually got fed-up with thinking about status codes and decided to return only 200 (OK) from my services. This indicates that the request succeeded without any kind of external problem. It does not indicate if the request succeeded in the business logic sense, as some extra standard information in the response body provides that information (explained shortly).

Any response code other than 200 indicates something went seriously wrong unrelated to the service logic, probably a network or web server failure. In this case the client app would probably show a pink screen or similar to indicate a serious problem.

If your service only returns 200, then how do you indicate if the business logic of the request succeeded or not?

I think the simplest way of returning business processing result information is to have some standard properties present in every response. Here is part of a typical error response from one of my services:

{
  "code": 2,
  "title": "Customer create failed",
  "detail": "Customer with key '806000123' name 'Contoso Pty Ltd' already exists.",
  // See below for more details of what could be here...
  "data": null  // Success data would go here
}

Exactly what standard properties to place in the response is your choice, and there are many articles that argue around this matter. In recent years there have been attempts to standardise error reporting properties, such as RFC 7807. The full RFC error response recommendations may be overkill for most business scenarios, but it's worth considering following some of the naming conventions.

Coding Details

What follows is specific to the C# language, but it can easily be applied to any other modern language.

There are two ways to return standard response properties. Firstly, define a base class with the properties and derive all responses from the base class. This does cause the standard properties to merge into the response properties at the root level, which might look a bit confusing. Secondly (my preference), is to have a generic response class like this:

public class ResponseWrap<T>
{
  public ResponseWrap(T data)
  {
    Data = data;
  }
  public ResponseWrap(int code, string? title, string? detail = null)
  {
    Code = code;
    Title = title;
    Detail = detail;
  }
  public string? Title { get; set; }
  public string? Detail { get; set; }
  public bool HasError => Code != null;
  public int? Code { get; set; }
  public T Data { get; set; }
}

Note how the standard properties are at root level, and so is a Data generic property which is expected to contain any data that is in a success response. This results in a simply shaped JSON document common to all service responses. Clients can inspect the HasError property to determine if the business logic of the requests suceeded or not. The exact code is flexible and can be adjusted according to coding preferences, but the important fact is that there are some root standard properties and one of them indicates success or failure. In case of success, the Data property will contain the return data and the other root properties will be null, and in case of failure the reverse is true.

Returning values from service methods will be easier as there is no need to construct different response status codes and types. The pair of constructors of the response class simplify service code to look like this:

if (cust == null)
  return new ResponseWrap<Customer?>(2, $"Customer {id} not found", null);
else
  return new ResponseWrap<Customer?>(cust)

.NET Web API Global Errors

Unhandled errors in a .NET Web API controller will result in a 500 (InternalServerError) and a response body that doesn't contain any useful diagnostic information. In .NET Core services you can use a global exception handler to trap unexpected errors and convert them into the standard response so that clients always receive the same shaped JSON response bodies.

This is entirely optional, as letting the error propagate back to the client as a status 500 will clearly indicate to them that something went seriously wrong, and the response body might be irrelevant anyway. The service should have internally logged the detailed error details so that developers can diagnose the problem.

Summary

By reducing the REST responses down to a single status code and putting standard properties in the response, it could be argued that I'm hijacking the REST conventions and turning them into a toy protocol. I can't argue with that, but as a developer I really need a simple protocol to return only success or failure information without the mental bother of applying status codes to my business logic (where that's meaningful). It also simplifies the coding on the service and client sides.

Ironically, the .NET SOAP protocol I used back in the 2000s was actually similar to what I'm doing now. I'm conforted to know that I'm not the only person who has considered ignoring status codes, as I recently used the API of a parcel shipping company who only returned status 200 and they had a root property named code in all their responses to indicate success or failure. In their case, curiously, code=300 indicated success.