Error'd: Null Null Null
The single most common category of entries for this column is failed handling of NaN, null and undefined. Almost exclusively from javascript in web pages, sometimes in node servers, and almost never any other languages or frameworks. They're getting a bit repetitive but it's our solemn duty to call out failure where we find it. So if you send us one of these, make sure it identifies the source!
"If you want something you've never had, do something you've never done" exhorted Ben.
"Dashed Hope for Jennifer Null," titled an entry from some guy[sic]. "As recently linked from TDWTF article "Not for Nullthing", not only names can break computer systems, but also article content." Stretching, but we'll allow it.
"Where does Batman go on holiday?" asked Morgan. "Nananananana... Nowhere!"
"UBER is ready for driverless vehicles..." Bruce C. "Uber is getting so big, they can't even keep track of their driver's names."
"Well at least the reason wasn't null or NaN," wrote Steve W. regarding CenturyLink. "I've been trying for weeks to use their web page to change my (incorrect billing address). Such progress."
Additional entries on the topic from
Dan
: "we're fresh out of null"
Henrik
: "What is this null music streaming service"
Mike
: "Name: undefined"
Laks
: "In this app, every new user defaults to a nullptr."
and
Jim
: "Think I'll buy $NaCar with this refund!"
and many others were all appreciated and noted.
CodeSOD: Failing to Fail
Russell F (previously) sends us a small one today. It's not just a representative line, it's a representative comment. More than that, it's a true confession. Russell wrote some code, you see, and the logic was confusing. So, a co-worker added a comment to explain what the code was doing:
'This is *supposed* to fail. If it fails to fail, it throws a failure messageRussell writes:
I have to confess that this one is my fault. The comment was added by one of my coworkers to clarify what I was doing, and made me realize how stupid I'd been.
"Failing to plan is planning to fail" becomes "failing to fail is failure message".
CodeSOD: Please Find, Rewind
As previously discussed, C++ took a surprisingly long time to get a "starts with" function for strings. It took even longer to get a function called "contains". In part, that's simply because string::find solves that problem.
Nancy sends us a… different approach to solving this problem.
bool substringInString(string str, string::iterator &it) { string tmp; bool result = false; int size = str.length(); int count = 0; while (count < size) { tmp += *it; it++; count++; if (tmp.find(str) != string::npos) { result = true; it -= size; break; } } if ( !result) { it -= size; } return result; }This function iterates across a string, character by character. In this iteration, we copy one character at a time into tmp. Then we see if tmp contains our search str. If it does, we break out of the loop after rewinding the iterator. Outside of the loop, we check if we found the substring, and if we did, we rewind the iterator. Then we return true or false based on whether on not we found the substring.
So wait a second. str is our search string. it is where we're searching. And we copy from it up to our search string's length into a temporary string. We then do a find in that temporary string- hey! This is just a startsWith check written in the most insane way possible.
Why even bother with the while loop? While tmp is shorter than the search string, the answer is always "no, we haven't found it". And the developers knew that- that's why they always rewind size characters on the iterator. They're always searching exactly that many characters. Of course, since we always rewind the same amount, we can also just move the it -= size statement out of the loop and out of the if statement and do it once.
Nancy calls this "a little gem" in a "large codebase". Yeah, a real gem.
[Advertisement] Plan Your .NET 9 Migration with ConfidenceYour journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
CodeSOD: Not for Nullthing
Today's anonymous submitter sends us some code that just makes your mind go… blank when you look at it.
public static boolean isNull(String value) { return StringUtils.isBlank(value); }StringUtils.isBlank comes from the Apache Commons library. It's a helper function for Java which returns true if a string is, well, blank. "Blank" in this case is: empty, null, or only whitespace. So it's important to note that isBlank may return true on a null, but it isn't truly a null-check, so wrapping it in isNull is just confusing.
But imagine I've got another problem. Let's say I have a database that's been poorly normalized and maintained. And so I have a bunch of fields that maybe are null, but some also maybe contain the string "null". What am I going to do then? I need another function.
public static boolean isNullAndNull(String value) { return isNull(value) && "null".equalsIgnoreCase(value); }Ah yes, isNullAndNull, the clearest and easiest name I could imagine for this. It tells me exactly what the function is checking: is it null, and is it also null? We add a second check to our isNull call- we check if the input value matches the string "null". Except we're &&ing the conditions together. So this function will always return false. It can't both be blank and contain the string "null".
Which means Jennifer Null, who is a real person, can breathe easy. This version of a null check won't think she's nothing.
[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.Empty Pockets
If you've seen one developer recounting how their AI agent deleted production, you've seen them all. They're mostly not interesting stories. It's like watching someone speeding through traffic on a motorcycle without a helmet: the eventual tragedy is sad, but it's unsurprising and not an interesting story to tell. It's not even interesting as a warning: the kind of person who speeds on a motorcycle without a helmet isn't doing so because they don't understand the danger. They've just decided it doesn't apply to them.
But the founder of PocketOS, Jer, recently shared how- whoopsie!- their AI agent deleted production. There's a lot of ingredients that go into this particular disaster, which I think makes it interesting, because the use of a poorly supervised AI agent is only one ingredient in this absolute trainwreck of a story.
PocketOS is a small company that makes software for rental companies to manage reservations. Car rentals are a big customer, but the tool is more general than that. They manage all of their infrastructure via a service called Railway. Railway is a pretty-looking GUI tool for automating your deployments and the target environments.
PocketOS also is heavily adopting Cursor wrapping around the Claude model. They've paid big bucks for the top-end model offered. Many of their components, like Railway, offer MCP services so that their LLM can do useful things. They're using the Claude LLM to automate as much as they can.
So far, this is all a pretty typical setup. They pointed Claude at their code and gave it a "routine" task, and sent it to work. It toddled through the problem and encountered a credential issue. It "decided" that the fix for this issue was to delete a storage volume and recreate it. It scanned through the code to find a file containing an API key, found it, and then sent a POST request via cURL to delete the volume in question.
Jer writes:
To execute the deletion, the agent went looking for an API token. It found one in a file completely unrelated to the task it was working on. That token had been created for one purpose: to add and remove custom domains via the Railway CLI for our services. We had no idea — and Railway's token-creation flow gave us no warning — that the same token had blanket authority across the entire Railway GraphQL API, including destructive operations like volumeDelete. Had we known a CLI token created for routine domain operations could also delete production volumes, we would never have stored it.
Wait, the tokens you create in Railway all have god-level privileges? That sounds like a terrible idea. And you were storing the token in your code? We'll come back to this in a moment, but sure, this is bad, but you can just restore from backup, right?
The volume was deleted. Because Railway stores volume-level backups in the same volume — a fact buried in their own documentation that says "wiping a volume deletes all backups" — those went with it. Our most recent recoverable backup was three months old.
Oh. Oh no.
Now, I don't think it's literally true that Railway is storing your backups literally in the same volume as the thing they're backing up. I certainly hope not. But they do apparently delete your backups when you delete the volume associated with them. Which is a choice, certainly. A bad one. And one that they documented, according to Jer. It was, in his words, "buried" in the docs.
But let's go back to the tokens for a moment. I am not a Railway user, but I checked out the tool and went through the process of creating a project token. And while no, Railway does not give you big red flags warning you "Hey, this token can do ABSOLUTELY ANYTHING", it also never gives you an opportunity to scope the token. Which, I don't know about you, but the first thing I do when I create an authentication entity is try and figure out how to control its authorizations, because I assume at the start it doesn't have any. That'd be sane.
The scoping happens when you create the token, depending on what context you're in when you do it. It's only a handful of scopes, and no fine grained permissions on API keys at all. The lowest level is "Project" which can do anything to a single environment- which does mean that even if you, like Jer's team, wanted to have a script that changed some DNS settings in production, that same key could be used to delete volumes in production. Which means you really really want to take care of that key, and you certainly don't want to leave it where some junior developer or bumbling AI agent can find it.
Jer also complains that Railway shouldn't allow an API call to take destructive actions without more protections, like forcing someone to type in the name of the thing being deleted or sending a confirmation email, or something. This, I'm more skeptical of. Most cloud providers don't offer anything like this in their APIs, at least that I've seen, because on a certain level, if you're invoking the API with the proper credentials, that's a big enough hill to climb that we can assume you've intended your action. The correct way to protect against this is properly scoped keys and keeping those keys secure and not just lying around in plain text. There's a certain aspect of understanding that you're using a potentially dangerous tool and need to take the responsibility for safety into your own hands; while a table saw can easily take some fingers off, it's perfectly safe when used correctly.
This is all bad, but how can we make it worse? Well, Jer demanded that Claude "explain itself". In a section called "The Agent's Confession", Jer highlights that the agent is able to identify the explict rules that it failed to follow.
Read that again. The agent itself enumerates the safety rules it was given and admits to violating every one. This is not me speculating about agent failure modes. This is the agent on the record, in writing.
No, it is not the agent on record. I see this kind of thing a lot when people talk about LLMs. An LLM cannot explain its reasoning. It cannot go on "the record". It cannot confess to anything. While what it plops out when asked might be interesting, it is not an explanation. The only explanation is that it's a powerful statistical model trying to create a plausible string of tokens! It's simply looking at its context window and your prompt and trying to predict what it should say. It can tell you what rules it violated not because it understands the rules or knows it violated any rules, but because those rules are in its context window. If you ask it right, it'll confess to killing JFK and framing Oswald for the crime.
Jer then tries to ensure that Cursor takes some of the blame, pointing to Cursor's "guardrails" documentation. Except, here, the documentation is actually quite explicit about what those guardrails guarantee. If you're using a first-party tool, it will prohibit unsafe operations. When using 3rd party MCPs, like Railway's, the only guardrail is that it requires human approval for every action- unless you update your allowlist for that MCP. If you put them in your allowlist, the guardrails go away. Jer argues that tools should enforce more protection against LLM behaviors, but the problem with that is people- like the PocketOS team- turn those protections off. And like a lot of safety mistakes, they can get away with it all the way up until the point where they can't.
Jer follows this by listing off a pile of other times using Cursor has caused disasters, which isn't making the argument he thinks it is: yes, Cursor is dangerous, but those dangers are well known. It makes the choice to turn Cursor loose without strict supervision seem even more foolish.
Jer writes:
For now I want this incident understood on its own terms: as a Cursor failure, a Railway failure, and a backup-architecture failure that all happened to one company in one Friday afternoon.
It's also a PocketOS failure. It's a failure to properly assess the tools and environments you chose to use for your product. A failure to read and understand the docs for vital features, like *backups*. A failure to employ even the most basic safeguards. A failure to put a second's thought into key management- even if that key was only for DNS entries, you still shouldn't chuck it in source control. A failure to have a competent backup strategy. It's worth noting that they did restore from a three month old backup, which means they were at one point taking backups outside of Railway's volume setup. That was a wise decision. That they stopped is a failure.
The first rule of disaster retrospectives is that it's never one piece that's the failure. It's never one person's fault, one tool's fault, one vendor's fault. It's a systemic failure. Railway's keys should be finer grained. But also, you shouldn't leave keys lying around. Deleting backups when you delete the volume is a terrible idea, but having only one service for backups (that's also your primary site) is a terrible idea. Claude's ability to enforce its own guardrails should be better, but LLMs are notoriously dangerous about this: you should know better, and by your own words you did.
This is not an anti-AI post, or even a "get a load of this asshole" post. It is a "understand the damn tools you're using" post. Be critical of them. Don't trust them. Ever. Especially LLMs, because the worst part of an LLM is that it takes away the one thing computers used to be good at: predictable, deterministic behavior. But not just LLMs: don't trust your cloud provider, don't trust your infrastructure manager. Dig into them and understand how they work, and if they seem to complicated to understand, than they may be too complicated to trust.
Update: As pointed out in the featured comment below, Railway did finally get a backup restored. So they got their data back. Yay? From the post, Jer remains committed to making this a Railway issue and not a PocketOS issue.
[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.Error'd: Parametric Projection
"AWS Spellcheck Fail!" alerts Peter "If only someone at AWS knew the correct paramters to activate the spellcheck."
"How long is too long for a job to be open? " wonders Lincoln K. "I didn't even know LinkedIn existed 61 years ago, let alone was accepting postings... Though only 81 applicants in that time is hardly an impressive turn-out." For a "Vice President Operations and Quality Control", no less.
An anonymous Richard reports "This came through my door. On a card that, in order to get to my door, had my full address printed on it, including my ."
Oenophile Abroad Michael R. shares "My Macbook broke after being "exposed" to red wine. As a German in London it pleases me so see that the repair shop offers this time granularity."
CodeSOD: Cancel Catch
"This WTF is in Matlab" almost feels like cheating. At one place I worked, somebody's job was struggling through a mountain of Matlab code and porting it into C. "This Matlab code looks like it was written by an alien," also doesn't really get much traction- all Matlab code looks like it was written by an alien. This falls into the realm of "Researchers use Matlab, researchers may be very smart about their domain, but generally don't know the first thing about writing maintainable code, because that's not their job."
But let's take a look at some MatLab Carl W found:
try if (~isempty(fieldnames(bigStruct)) && isfield(bigStruct,'pathName')) [FileName, PathName] = uigetfile(bigStruct.pathName); else [FileName, PathName] = uigetfile(lastPath); %lastPath holds previous path end catch bigStruct = struct; endThe uigetfile function opens a file dialog box. When the user selects a file, FileName holds the filename, PathName holds the containing path. If the user doesn't select a valid file, or clicks "Cancel", both of those variables get set to 0. It's then up to the caller to check the return value and decide what happens next.
Which is not what happens here, obviously. The developer responsible seems to believe that it maybe throws an exception? And they can just catch it? Carl's best guess is that this is a "weird" way to catch the cancel button. But it does mean that FileName and PathName get set to 0, and those zeros propagate until something finally tries to open those files, at which point everything blows up and the user doesn't know why.
[Advertisement] Plan Your .NET 9 Migration with ConfidenceYour journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
A Whale of a Problem
From our Anonymous submitter:
Our company creates graphs to visualize data. We have many small fish customers, but we have one whale who uses our product that is 90% of company revenue. (WTF number 1.)
So if he is not happy, it's all-hands-on deck-mode.
He complained that our APIs and charts are loading slowly for him. For 3 weeks, we've tried a TON of optimizations, including WTF 2: spinning up a special server he alone can hit.
Today, we found out that he's always complaining when he's in his car, driving from home to the office. But since he "totally has the best wifi money can buy," that isn't worth investigating.
WTF 3: thinking wifi and data are always 100% reliable in a car driving around.
Our submitter highlights one of the major pitfalls of the so-called whale client: if they're a bad client, you're in for an extra-bad time.
As I lean harder into freelancing, I'm learning to scan the waters ahead of me for potential whales. My goal is to build up multiple small, diverse income streams, because I've had my own dangerous encounters with whales in the past.
At one employer of mine, there was Facebook, who acted as if they were our new owners rather than a new customer. They'd already produced flashy marketing videos of the sorts of solutions they planned to implement with our software, showing people delighted with the results. In meetings, these things were talked up as amazing game-changers. Meanwhile, I found all the things Facebook wanted to do horribly creepy and invasive.
Even worse, Facebook began dictating how our award-winning technical support should change to accommodate their whims, up to and including having a dedicated toady—er, support rep—who did nothing but field Facebook-related tickets, similar to a technical account manager (TAM).
That was the last straw for me. I left that company before I was forced to deal with any of Facebook's crap.
My second whale sighting occurred at a startup that'd landed Porsche, far and away their biggest client ever. All of a sudden, our timeline for adding new features and fixing bugs became Porsche's honey-do list. All of a sudden, the platform frequently crashed and became unusable for everyone because it couldn't handle the amount of traffic Porsche (and their clients) hurled at it.
On the other hand, there were several times in that startup's existence when a big wad of promised funding failed to materialize. Porsche kept the business afloat and literally kept my lights on.
I find it less than ideal to be at any company's mercy. I want a world that would neither spawn whales nor millions of startups named Sploink, Dink, and Twangle that promise to bring the power of AI to your dinner fork.
Have your own epic whaling adventures? Share with us in the comments!
CodeSOD: Lint Brush Off
A few years back, C# added the concept of "primary constructors". Instead of declaring the storage for class members and then initializing them in the constructor, you can annotate the class itself with the required fields, and C# automatically generates a constructor for you. It's all very TypeScript and very Microsoft, and certainly cuts down on some boilerplate.
Esben B's team isn't really using them in many places, but they are using a linter which is opinionated about them. So this in-line constructor causes the linter to complain:
public DocumentNetworkController(ILookupClient service)The linter wants you to switch this to a primary constructor. Esben didn't want to do that, and didn't want to change the global linter configuration, and so added a pragma to disable that particular warning:
#pragma warning disable IDE0290 // Use primary constructor public DocumentNetworkController(ILookupClient service) #pragma warning restore IDE0290The linter didn't like this. It threw a new warning: that this suppression wasn't needed. Which was news to Esben, as clearly the suppression was needed if you wanted to make the warnings go away. The obvious solution was to disable the warning that you didn't need to disable the warning:
#pragma warning disable IDE0079, IDE0290 // Use primary constructor public DocumentNetworkController(ILookupClient service) #pragma warning restore IDE0290, IDE0079Except this doesn't work. These pragmas take effect on the next line, which means you can't disable IDE0079 on the same line as IDE0290 and expect it to work. Which means the final version of the code looked like this:
#pragma warning disable IDE0079 // Disable warning about not needed supression #pragma warning disable IDE0290 // Use primary constructor public DocumentNetworkController(ILookupClient service) #pragma warning restore IDE0290, IDE0079Esben writes:
So the nice recommendation to use a primary ctor ended up with 3 lines of annoying boilerplate code. Good times \o/
While yes, this is frustrating, I will say there's an element of "when the table saw keeps taking fingers off, that may be more of a you problem." I don't know the details, so I can't say, "just change the linter config or adopt its recommendation" and claim that the problem goes away, but when the tool hurts you, it's a definite sign of one of two things: it's either the wrong tool, or you're using it wrong.
CodeSOD: The JSON Template
We rip on PHP a lot, but I am willing to admit that the language and ecosystem have evolved over the years. What started as an ugly templating language is now just an ugly regular language.
But what happens when you still really want to do things with templates? Allison has inherited a Python-based, WSGI application which rejects any sort of formal routing or basic web development best practices. Their way of routing requests is simply long chains of "if condition then invokeA elif otherCondition then invokeB". Sometimes, those conditions will directly set the MIME type on the HTTP response.
They do use a templating library called Mako for generating their responses. They use it for their HTML responses, obviously. They also use it for their JSON responses, generating code like this:
{ "success": true, "items": { %for item in items_available.keys(): "${item}": ${items_available[item]}${',' if not loop.last else ''} %endfor } }The %for and matching %endfor mark the Python code off, which generates JSON via string-munging, complete with the check to make sure we're not on the last iteration of the loop.
Like so much bad code, this offers a degree of fractal wrongness. Instead of iterating over the keys and fetching the items inside the loop, you could iterate for key,value in items_available.items()- and according to the Mako docs, that for is just a regular Python for loop. That we're just outputting the contents of the dictionary is itself potentially a problem- sure, if we know the types of the dictionary, we'll know that whatever it is can be output in the body of a JSON document, but do we really think this code is using type annotations? I don't. And for a RESTful web service, I'm always going to feel weird about using a success field when ideally the HTTP status code could convey most of that information (and yes, I know there are reasons to still put status in the body, I just hate it).
Of course, the real issue is just: Python's built in JSON serialization is actually pretty advanced. And performant! You don't need any of this, you could just do something like:
return json.dumps({"success": true, "items": items_available})No templates. No formatting. No worries about how the data gets represented. Well, still worries, because JSON serialier will throw exceptions if it doesn't know what to do with a type. But then at least you get that exception on the server side and aren't sending the client a malformed document.
In any case, this is a good demonstration that you can write bad PHP in any language.
Error'd: April Showers
"RFC 1738 (and 3986) disagree" and so does Daniel D. "Reddit API has some weird app creation going on with lots of recently migrated and undocumented stuff. But having redirect URL set to localhost (or 127.0.0.1) usually works. Well, if you don't disagree with Sir Tim Berners-Lee about what URL is. Which Reddit does. hostnumber = digits "." digits "." digits "." digits". I'd file this one with all the websites that try to perform validation on email addresses, and get it wrong.
"Why aren't we getting any resumes?" wondered Fred G. "This is a snippet from a job posting. I'm sure it worked perfectly when HR tested it."
"Service required..." was Chris H.'s title for this gem. "My 2022 Chevrolet has been at the dealer for recall service for two weeks now, "waiting for parts". That doesn't stop GM from emailing every few days with a reminder that the car needs the recall service, and inviting me to schedule it at a dealer (that isn't actually a dealer) located a convenient 2500 mile drive from my home (about 200 times the distance to the dealer where the car currently sits), and providing a non-existent placeholder phone number to contact them at to schedule the recall service."
"How to subtly tell your customers that you don't wish to be contacted" explains Yuri. "The bank's staff must be wondering why no one wants to talk to them...Is it their suit's brand that is throwing everyone off? Can they blame it on COVID?"
"Bad money formatting by tax software" Adam R. complained. "I'm ashamed to admit it, but yes, I did pay Intuit money to file my taxes. This should really be a free service provided by the government, but, y'know, *lobbying*. You'd think that a business focused on tax preparation software would know how to properly format currency values, but in this case they failed to set the proper number of decimal points."
CodeSOD: Tune Out the Static
Henrik H (previously) sends us a simple representative C# line:
static void GenerateCommercilaInvoice()This is a static method which takes no parameters and returns nothing. Henrik didn't share the implementation, but this static function likely does something that involves side effects, maybe manipulating the database (to generate that invoice?). Or, possibly worse, it could be doing something with some global or static state. It's all side effects and no meaningful controls, so enjoy debugging that when things go wrong. Heck, good luck testing it. Our best case possibility is that it's just a wrapper around a call to a stored procedure.
This method signature is basically a commercila for refactoring.
Representative Line: Comment Overflow
Today, we look at a representative comment, sent to us by Nona. This particular comment was in a pile of code delivered by an offshore team.
// https://stackoverflow.com/questions/46744740/lodash-mongoose-object-id-difference/46745169"Wait," you say, "what's the WTF about a comment pointing to a Stack Overflow page. I do that all the time?"
In this case, it's because this particular comment wasn't given any further explanation. It also wasn't in a block of code that was doing anything with either lodash, Mongoose, or set differences. It was, however, repeated multiple times throughout the codebase, because the entire codebase was a pile of copy-pasta glued together with the bare minimum code to make it work.
In at least one place, the comment was probably correct and helpful. But it got swept up as part of a broader copy/paste exercise, and now is scattered through the code without any true purpose.
Turning Thirty
Eric O worked for a medical device company. The medical device industry moves slowly, relative to other technical industries. Medical science and safety have their own cadence, and at a certain point, iterating faster doesn't matter much.
Eric was working on a new feature on a system that had been in use for thirteen years. This new feature interacted with a database which stored information about racks of test tubes, and Eric's tests meant creating several entries for racks of test tubes. And that's when Eric discovered that the database only allowed thirty racks. Add any more, it would just roll right back over to one.
This was odd. The database was small- less than 40MB, even in production- and there were automatic tasks to purge old data for compliance purposes. Why a hard limit of thirty?
Eric had only been at the company for a year, so he asked one of the more senior team members, Lester. "Oh yeah, that was before my time. You should probably ask Carl."
Later that day, Eric happened to bump into Carl around the coffee maker, and asked the question. "Oh, yeah, I do vaguely remember something about that. It was in the requirements for the product. I thought it was weird, but didn't think too much about it. You should probably ask Elise, she's been here like twenty years."
Well, now it was getting curious. Eric went over to the "old building", as it was named, the original office for the company on the other side of the parking lot. Most of the offices had moved to the new building a decade earlier, and it mostly served as fabrication and storage, but a few offices remained.
Elise was on the third floor, down a poorly lit hallway, sitting in an office with water-stained acoustical tile in its ceiling. "Oh, yeah, I put that into the requirements document. It's funny, I thought it was weird too, but the system you're working on was a replacement for an older system. Our requirements were derived from those. Let me think… Irving worked on that, but he's dead, god rest him. Penny is retired. Oh, you know, Humbert is still around. He didn't work on that, but he worked on some of the systems that came before that. He's upstairs and on the other side of the building."
Eric went upstairs and to the other side of the building. The fourth floor had been last remodeled circa 1985, and the ugly industrial paint on the wall was made even uglier by the fact that someone had replaced most of the flourescent tubes with LEDs. Most. The mismatched color temperature started Eric down the path of a headache.
Humbert was in an office similar to Elise's. On his desk was a plaque commemerating 40 years of service with the company. Eric asked about the limitation, and Humbert laughed.
"You're working on the latest version of a product that initially started on an old PDP-11 running MUMPS. I mean, the first versions, anyway. We ran to desktop computers as fast as we could. I wrote a version for DOS in… oh… '86? I knew none of the facilities we worked with had more than ten or fifteen racks of tubes, and I needed somehow to limit the size of the database so it all fit on a single 5 1/4" floppy disk. I picked thirty, because it seemed like a good round number. Honestly, I'm shocked that the limit still exists."
So was Eric. There had been several ground-up-rewrites since 1986, before the one Eric maintained had been released thirteen years ago. Each one of them had chosen to maintain the same limitation, without ever considering why it existed. The rule had simply been copied, mindlessly, for 40 years.
"I'm kind of impressed," Eric said to Humbert, "in a horrified way."
"Me too, kid, me too."
CodeSOD: Good Etiquette
"Here, you're a programmer, take this over. It's business critical."
That's what Felicity's boss told her when he pointed her to a network drive containing an Excel spreadsheet. The Excel spreadsheet contained a pile of macros. The person who wrote it had left, and nobody knew how to make it work, but the macros in question were absolutely business vital.
Also, it's in French.
We'll take this one in chunks. The indentation is as in the original.
Public Sub ExporToutVersBaseDonnées(ClasseurEnCours As Workbook) Call AffectionVariables(ToutesLesCellulesNommées) Call AffectationBaseDonnées(BaseDonnées) BaseDonnées.ActivateThe procedures AffectionVariables and AffectationBaseDonnées populate a pile of global variables. "base de données" is French for database, but don't let the name fool you- anything referencing "base de données" is referencing another Excel file located on a shared server. There are, in total, four Excel files that must live on a shared server, and two more which must be in a hard-coded path on the user's computer.
Oh, and the shared server is referenced not by a hostname, but by IP address- which is why the macros were breaking on everyone's computer; the IP address changed.
Let's continue.
'Vérifier si la ligne existe déjà. If ClasseurEnCours.Sheets("DATA").Range("Num_Fichier") = 0 Then Num_Fichier = BaseDonnées.Sheets(1).Range("Dernier_Fichier").Value + 1 Insérer_Ligne: '(étiquette Goto) insérer une ligne Application.GoTo Reference:="Dernière_Ligne" Selection.EntireRow.Insert 'Copie les cellules (colonne A à colonne FI) de la ligne au-dessus de la ligne insérée. With ActiveCell .Offset(-1, 0).Range("A1:FM1").Copy 'Colle le format de la cellule précédemment copiée à la cellule active puis libère les données du presse papier .PasteSpecial .Range("A1:FM1").Value = "" 'Se repositionne au début de la ligne insérée. .Range("A1").Select End With Application.CutCopyMode = FalseUh oh, Insérer_Ligne is a label for a Goto target. Not to be confused by the Application.GoTo call on the next line- that just selects a range in the spreadsheet.
After that little landmine, we copy/paste some data around in the sheet.
That's the If side of the conditional, let's look at the else clause:
Else Cherche_Numéro_Fichier: ' Chercher la ligne ou le numéro de fichier est égale à NumFichier. While ActiveCell.Value <> Num_Fichier If ActiveCell.Row = Range("Etiquettes").Row Then GoTo Insérer_Ligne End If ActiveCell.Offset(-1, 0).Range("a1:a1").Select Wend 'Vérifier le numéro d'indice de la ligne active. If Cells(ActiveCell.Row, 165).Value <> ClasseurEnCours.Sheets("DATA").Range("Dernier_Indice") Then ActiveCell.Offset(-1, 0).Range("A1:A1").Select GoTo Cherche_Numéro_Fichier End If ActiveCell.Offset(0, 0).Range("A1:FM1").Value = "" End IfWe start with another label, and… then we have a Goto. A Goto which jumps us back into the If side of the conditional. A Goto inside of a while loop, a while loop that's marching around the spreadsheet to search for certain values in the cell.
After the loop, we have another Goto which will possibly jump us up to the start of the else block.
The procedure ends with some cleanup:
'----- ' Do some stuff on the active cell and the following cells on the column .----- BaseDonnées.Close True Set BaseDonnées = Nothing End SubI do not know what this function does, and the fact that the code is largely in a language I don't speak isn't the obstacle. I have no idea what the loops and the gotos are trying to do. I'm not even a "never use Goto ever ever ever" person; in a language like VBA, it's sometimes the best way to handle errors. But this bizarre time-traveling flow control boggles me.
"Etiquettes" is French for "labels", and it may be bad etiquette but I've got some four letter labels for this code.
Error'd: Having a Beastly Time
It's time again for a reader special, and once again it's all The Beast In Black (there must be a story to that nick, no?).
"MySQL is not better than your SQL," he pontificated, "especially when it comes to the Workbench Migration Wizard"
"Sadly," says he, "Not even gmail/chromium either."
"Updated software is available, but there are no updates!" he puzzled. "Clicking Install Now just throws that dialog right back in my face. I'm re-cursing." Zero, one, does it really make a difference?
"Questions" The Beast in Black "I do, in fact, have a question..."
One of the foundational guides to my [lyle, not bib] engineering career
was John Bentley's Programming Pearls. These are not those.
"Veni, vidi: vc. No pearls of wisdom here, just litter." says
The Beast.
[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
CodeSOD: We'll Hire Better Contractors Next Time, We Promise
Nona writes: "this is the beginning of a 2100 line function."
That's bad. Nona didn't send us the entire JavaScript function, but sent us just the three early lines, which definitely raise concerns:
if (res.length > 0) { await (function () { return new Promise((resolve, reject) => {We await a synchronous function which retuns a promise, passing a function to the promise. As a general rule, you don't construct promises directly, you let asynchronous code generate them and pass them around (or await them). It's not a thing you never do, but it's certainly suspicious. It gets more problematic when Nona adds:
This function happens to contain multiple code repetition snippets, including these three lines.
That's right, this little block appears multiple times in the function, inside of anonymous function getting passed to the Promise.
No, the code does not work in its current state. It's unclear what the 2100 line function was supposed to do. And yes, this was written by lowest-bidder third-party contractors.
Nona adds:
I am numb at this point and know I gotta fix it or we lose contracts
Management made the choice to "save money" by hiring third parties, and now Nona's team gets saddled with all the crunch to fix the problems created by the "savings".
[Advertisement] Plan Your .NET 9 Migration with ConfidenceYour journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
CodeSOD: Three Letter Acronyms, Four Letter Words
Candice (previously) has another WTF to share for us.
We're going to start by just looking at one fragment of a class defined in this C++ code: TLAflaList.
Every type and variable has a three-letter-acronym buried in its name. The specific meaning of most of the acronyms are mostly lost to time, so "TLA" is as good as any other three random letters. No one knows what "fla" is.
What drew Candice's attention was that there was a type called "list", which implies they're maybe not using the standard library and have reinvented a wheel. Another data point arguing in favor of that is that the class had a method called getNumElements, instead of something more conventional like size.
Let's look at that function:
size_t TLAflaList::getNumElements() { return mv_FLAarray.size(); }In addition to the meaningless three-letter-acronyms which start every type and variable, we're also adding on a lovely bit of hungarian notation, throwing mv_ on the front for a member variable. The variable is called "array", but is it? Let's look at that definition.
class TLAflaList { … private: TLAflaArray_t mv_FLAarray; … }Okay, that gives me a lot more nonsense letters but I still have no idea what that variable is. Where's that type defined? The good news, it's in the same header.
typedef std::vector<INtabCRMprdinvusage_t*> TLAflaArray_t;So it's not a list or an array, it's a vector. A vector of bare pointers, which definitely makes me worry about inevitable use-after-free errors or memory leaks. Who owns the memory that those pointers are referencing?
"IN" in the type name is an old company, good ol' Initrode, which got acquired a decade ago. "tab" tells us that it's meant to be a database table. We can guess at the rest.
This isn't a codebase, it's a bad Scrabble hand. It's also a trainwreck. Confusing, disorganized, and all of that made worse by piles of typedefs that hide what you're actually doing and endless acronyms that make it impossible to read.
One last detail, which I'll let Candice explain:
I started scrolling down the class definition - it took longer than it should have, given that the company coding style is to double-space the overwhelming majority of lines. (Seriously; I've seen single character braces sandwiched by two lines of nothing.) On the upside, this was one of the classes with just one public block and one private block - some classes like to ping-pong back and forth a half-dozen times.
A Hole in Your Plan
Theresa works for a company that handles a fair bit of personally identifiable information that can be tied to health care data, so for them, security matters. They need to comply with security practices laid out by a variety of standards bodies and be able to demonstrate that compliance.
There's a dirty secret about standards compliance, though. Most of these standards are trying to avoid being overly technically prescriptive. So frequently, they may have something like, "a process must exist for securely destroying storage devices before they are disposed of." Maybe it will include some examples of what you could do to meet this standard, but the important thing is that you have to have a process. This means that if you whip up a Word document called "Secure Data Destruction Process" and tell people they should follow it, you can check off that box on your compliance. Sometimes, you need to validate the process; sometimes you need to have other processes which ensure this process is being followed. What you need to do and to what complexity depends on the compliance structure you're beholden to. Some of them are surprisingly flexible, which is a polite way of saying "mostly meaningless".
Theresa's company has a process for safely destroying hard drives. They even validated it, shortly after its introduction. They even have someone who checks that the process has been followed. The process is this: in the basement, someone set up a cheap drill press, and attached a wooden jig to it. You slap the hard drive in the jig, turn on the drill, and brrrrzzzzzz- poke a hole through the platters making the drive unreadable.
There's just one problem with that process: the company recently switched to using SSDs. The SSDs are in a carrier which makes them share the same form factor as old-style spinning disk drives, but that's just a thin plastic shell. The actual electronics package where the data is stored is quite small. Small enough, and located in a position where the little jig attached to the drill guarantees that the drill won't even touch the SSD at all.
For months now, whenever a drive got decommissioned, the IT drone responsible for punching a hole through it has just been drilling through plastic, and nothing else. An unknown quantity of hard drives have been sent out for recycling with PII and health data on them. But it's okay, because the process was followed.
The compliance team at the company will update the process, probably after six months of meetings and planning and approvals from all of the stakeholders. Though it may take longer to glue together a new jig for the SSDs.
CodeSOD: Non-cogito Ergo c_str
Tim (previously) supports a relatively ancient C++ application. And that creates some interesting conundrums, as the way you wrote C++ in 2003 is not the way you would write it even a few years later. The standard matured quickly.
Way back in 2003, it was still common to use C-style strings, instead of the C++ std::string type. It seems silly, but people had Strong Opinions™ about using standard library types, and much of your C++ code was probably interacting with C libraries, so yeah, C-strings stuck around for a long time.
For Tim's company, however, the migration away from C-strings was in 2007.
So they wrote this:
if( ! strncmp( pdf->symTabName().c_str(), prefix.c_str(), strlen( prefix.c_str() ) ) ) { // do stuff }This is doing a "starts with" check. strncmp, strlen are both functions which operate on C-strings. So we compare the symTabName against the prefix, but only look at as many characters as are in the prefix. As is common, strncmp returns 0 if the two strings are equal, so we negate that to say "if the symTabName starts with prefix, do stuff".
In C code, this is very much how you would do this, though you might contemplate turning it into a function. Though maybe not.
In C++, in 2007, you do not have a built-in starts_with function- you have to wait until the C++20 standard for that- but you have some string handling functions which could make this more clear. As Tim points out, the "correct" answer is: if(pdf->symTabName().find(prefix) != 0UL). It's more readable, it doesn't involve poking around with char*s, and also isn't spamming that extra whitespace between every parenthesis and operator.
Tim writes: "String handling in C++ is pretty terrible, but it doesn't have to be this terrible."