CodeSOD: Quite a Distance from the Right Solution
Fritz's team needed to see if one point was within a certain distance from a center point. You or I would likely try and answer this question using a simple distance calculation, since that's the question we're trying to answer. But what if you didn't understand distances at all? Then you could write this little piece of genius.
private bool IsWithin(Point point, int radius) { for (int x = -radius; x < radius + 1; x++) { for (int y = -radius; y < radius + 1; y++) { if ((PositionX == point.X + x) && (PositionY == point.Y + y)) return true; } } return false; }This limits us to integer point values, which itself is fine. This iterates across every coordinate from (x-radius,y-radius) to (x+radius,y+radius) and checks if any of them are equal to our center point, (PositionX,PositionY). Notably, this means we're not checking a radius, at least not in a traditional metric, we're checking a box (or using the taxicab metric). Which if we wanted to check a box, we actually have even easier math than the distance equation- we could do that with pure bounds checking.
I've never thought to try and brute force distance checking, and that feels like a failure of my own creativity. Fritz's team mate at least was able to WTF in a way I wouldn't have considered.
Representative Line: Unique Testing
Nikolai M's team had a flaky CI/CD build. About 0.5% of the time, one of their tests would fail: frequent enough to be annoying, but not so frequent that it motivated management to assign anybody to investigate. Nikolai eventually dug in, taking the initiative.
Microsoft's implementation of UUIDs calls them GUIDs. The GUID is, per the docs, really just a wrapper around UUID algorithms, and supports a variety of different UUID variants. I'm sure at some historical point, this wasnt't true, and Microsoft had some weird internal algorithm. That's not really here nor there, but the test that was failing was failing because of an assertion relating to GUIDs.
Assert.DoesNotContain("000", transactionId.ToString());transactionId is a GUID. This line converts it to a string and checks if it contains "000". They're attempting to check if it's an empty UUID, and they've assumed that three sequential zeroes in the output would be an impossible event. This is untrue. Nikolai measured it, and found it happens 0.5527% of the time- 1-in-181.
So this is a bad test, and could be made better with a more thorough check. Or it could leverage the built in constant GUID.Empty, which is a UUID where every bit is zero.
And it's that which really bugs me, honestly. Even if you didn't know about the constant, the idea of constructing an empty GUID seems like the obvious choice. Though I suppose you'd have to check the docs to find out how to construct a GUID directly, and if you're already reading the docs, the chances of you seeing the built-in constant are probably higher than 0.5527%.
[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.It's All Part 2 of the Process
Our submitter Tim C. is back as his bureaucratic nightmare continues. Read Part 1 here.
After I'd thrown the WTF Exchange (WTFX) for a loop, they managed to eventually right the ship. But then I faced another hurdle: I had run out of disk space. The software was not live, we were still developing the customer-specific customisations, but I was a bit stuck without a few more gigabytes.
The exchange went something like this:
"Hey Margritte, I need more disk space."
"Oh dear, what's happening?"
"Nothing is happening. I'm just developing the software. I need more disk space, not much. Can you get me another 20Gb? Wait, make that 40Gb."
"We can't just get you more disk space. These things need to be planned!"
"I just need a tiny amount, really. No need for any meetings. Just tell your IT guys to give me another NFS drive or expand one of the existing ones."
"Tim, that's not how things work here. We need to discuss your needs and it's important to us to know what needs you might have in future."
"Um, okay ... ?"
"I'll try to call an urgent meeting."
"Really? For 40Gb?"
"Well, how do we know 40Gb is going to be enough?"
"Because it's double what I really think I need."
"Why on Earth didn't you tell us before that you needed this disk space?"
"I'm telling you now!"
"Why didn't you do projections months ago?"
"At no stage did anyone ask me how much disk space we need! We obviously need some disk space! I have literally never given anyone an estimate! We are not even at the point yet to even estimate how much we'll need on project go-live."
"Well, I'll try to get an urgent meeting going. But it won't be today."
So at lunchtime, I went to the local computer store and bought an external hard drive. I asked Margritte to give it to the IT guys to stick somewhere in their computer room, or at least somewhere on their network.
That just seemed to make things worse.
"We can't take this! We have procurement processes!"
"Take it! This is a freebie from me to you. It'll help unstick a blockage in the project."
"It's not our standard hardware vendor. God knows what stuff is on it. We can't take the risk."
"I have literally not opened the plastic sealed wrapper on the outside of the box."
"Yes, but you could have re-sealed it. When we go via our trusted hardware partners we don't have to worry about things like that."
"You realise I'm making software every day which is running in the heart of your systems?! I work on this laptop here, and at the hotel, creating software, both C++ source code and Windows executables, that run inside your systems!"
"But it's not in the asset register. We need to record the warranty details every time we install hardware. What happens if it fails after you've left?"
"Well, how about we just agree I'll take it with me when I go?"
"Okay, I'll see if I can make the meeting happen tomorrow. To get you more disk space. Using our preferred hardware. Not that ... thing." (Said with disgust.)
Well, I finally got my disk space ... after a couple of days.
I was shocked to learn months later what a small proportion of the overall project budget our company earned. What seemed like an enormous multi-million-dollar contract to us was dwarfed by what WTFX spent on ex-pat "Anderson Androids", all immaculately groomed with expensive suits, who spent weeks twiddling their thumbs in expensive hotel rooms waiting for us to finish our software development, because they couldn't start until we had finished.
So sayeth the Gantt chart! Amen.
Error'd: Said Different
I know it's not nice to mock mistranslations, but would you believe we're laughing with them, not at them? In response to recent criticism of the difficulty of identifying precisely what is at issue, two of our readers have chosen to highlight the offending element. Thanks for saving me the trouble!"
"Bad Dutch translation, or is't?" asketh Mike V. "I visited the Dutch Dell site https://www.dell.com/nl-nl and noticed the bad translation on the "Accept All" cookies button. Then I visited the UK page on https://www.dell.com/en-uk and was not so sure anymore about the bad translation. Funny thing is that the text above the buttons refer to the buttons with a completely different text (which is an Error'd in itself)" If I squint I can almost see how this kind of translation could happen.
"Going under german" meint Matthias J.. "Trying to add info to my Euro-Tunnel France to Britain booking, seems LeShuttle does not care for my (old) german language setting anymore." Mox nix, as Kilroy said.
"Do you understand?!" rzants sztmbr "Am I a liar when clicking "Understand" under this cryptic message in Polish Glovo app?"
The Beast in Black gets two extra points for the Johnny Five meme here but minus two for the Mr Miyagi. Easy come, easy go. Beast says "I'm floored! Apparently I can improve my floor so the Roborock robot vacuum cleaner can better it. How, though? Pre-clean? Wax on wax off? Need input, Stephanie!"
"This one made me laugh today. Please provide your psychic address." Yes, humour is a strong physic! TheRealSteveJudge "Really funny. This was found at German Ebay. A Chinese seller of Makita compatible stuff. Please provide your psychic address. Please see in the last line before the orange bar. What is my psychic address? I still have to find out."
CodeSOD: Historical Pads
Tim inherited a fairly antique Visual Basic application some time back. Yes, Visual Basic, not VB .Net. The application is old enough that we might consider it "vintage" or "historical"; it certainly dates from before the millennium. But it has its own unique approach to handling historical dates:
mnYear% = CInt(Year(vntDate)) If mnYear% < 1000 Then msYear$ = "0" & Trim$(CStr(mnYear%)) Else msYear$ = Trim$(CStr(mnYear%)) End IfInsert obligatory complaints about Hungarian notation. What even is vnt.
Now, one important thing about this program: it didn't have to handle dates back into the middle ages, or honestly, historical dates at all. But someone decided they wanted to make sure that if someone wanted to track the founding of the Tang Dynasty in here, you could make sure it was padded out to four digits.
Unfortunately, if you wanted to track, say, the death of Caesar Augustus in 14AD, that'll only pad out to "014", which raises the question: what even is the point of this padding? And how many pre-1000 dates did the program handle? The answer to both questions is "none". Later code reformatted the date anyway, using the built in date types, even.
It's all Part 1 of the Process
Our submitter Tim C. shares his tale of an especially brutal culture clash:
I had co-founded a software startup doing stock market surveillance. In the early 2000s, the WTF Exchange (WTFX) was a major customer of ours. We were one part of a major project of theirs, a complete overhaul of their trading engine and IT systems.
Compared to our other clients, WTFX was quite bureaucratic. There seemed to be a strong focus on the process, rather than the mission or the result. They were even explicit about this, telling us, "The process is the deliverable."
I believe they made that explicit in talking to us because our culture was quite agile and thereby contrasted, or clashed, with the culture of everyone else working on the project.
"Predictability" was another buzzword they used to admonish us. We were required and expected to abide by the grand "waterfall" design and project plan and project timeline. The grand project was controlled via an enormous Gantt chart and they hated having to make any change to the Gantt chart.
There was one report which was central to the Exchange's surveillance department. The occasional opportunities I got to talk directly to end-users, they always stressed the importance of this report, and the importance of it updating immediately to changes in the filtering criteria, and the importance of the fine details such as colour-coding cells based on certain rules.
It became clear to me that we could not handle this report using a report definition file fed into our general report-writing module. It called for a custom piece of software. So over the course of the next weekend, in my hotel room, I whipped up a solution for these users: a whole new module to be added to our suite. It was maybe not perfect, but it was largely complete by the time I proudly unveiled my creation the next Monday.
However, instead of a pat on the back, I was greeted by looks of horror. This module had a whole new name, was not mentioned anywhere in the giant Gantt chart, had never been mentioned before as a concept in any specification document.
I had failed at "predictability." I had unleashed on the poor customer an urgent requirement to do a whirlwind set of meetings and priority changes and updates to the specification documentation and the giant Gantt chart.
In short, I had thrown the whole project into chaos.
This wasn't Tim's last brush with bureaucracy gone mad! Stay tuned for Part 2.
CodeSOD: The John Cage Variable
David C sends us a true confession.
For my job, I write C++ code as if it was a scripting language (long story) to produce programmatic animations for videos. Given that these are "write-and-run-once-and-never-look-at-it-again" programs, I tend to not try as hard to make my code good. But once I wrote this line of code, I had to take a step back and reevaluate my life choices.
fade_out(scene, length4->range(0, 3), length4_3_3, length4[4], length4[5]->range(0, 3));This is a natural consequence of passing parameters as arrays, it seems; instead of having meaningful named values in a struct or similar, we have all these things packed into arrays. We can see a long ago attempt at using variable, badly, in the John Cage variable: length4_3_3. John Cage's (in)famous composition, "4:33" calls into question what precisely even is music, just like this variable calls into question what even is a variable name- because this is clearly a range expression of size 1. length4->range(3,1), or more reasonably, probably length4[3].
Where a normal variable name might give us some indication about what its contents mean, this gives us none of that, just a statement of how we could acquire those contents if we ever wanted them again.
For disposable code, this is hardly the worst thing I've ever seen, but is any code truly disposable? You always find yourself wanting to pull some piece of it forward into the next project, inevitably. Read the third chapter of Structure and Interpretation of Computer Programs and consider yourself absolved. Go forth and sin no more.
The Weakest Leg
On July 10 2026, a life insurance company called TruStage had a "cybersecurity incident". According to an update posted five days later, when they revealed the incident, they are "continuing to work carefully and urgently to understand the facts, and the company will communicate appropriately as its understanding of the situation evolves."
This work, according to their outage page (last updated on September 11th, at the time of this writing), is continuing. Every major business function is listed as "partially available". They have some text explaining that they can't simply turn the system back on, because safety.
They don't know what data was compromised. They don't know when they'll have everything back up. As of mid-August they had gotten so far as setting up a "clean" environment that they could start migrating services to. The only good news is that some of their services are supplied to credit unions for banking, and those were on different systems, so only their insurance packages were compromised. I also suspect the fact that they serve credit unions is the only thing keeping the business afloat, because failing to handle transactions for months seems like a terminal event for most businesses.
Obviously, this also means they're embroiled in an ever growing number of lawsuits from a variety of parties. It also highlights something about how insurance works: it's middlemen all the way down.
TruStage will issue policies, but their connection to the actual policy holder will likely be routed through a number of different partners. Like, for example, Ethos.
Ethos uses "AI" to "democratize" insurance. Feed your data into their AI, and in minutes they'll pair you up with a policy. Which, while using machine learning to do things like assess insurance risk seems like a pretty reasonable idea, in this age of AI hype, one has to wonder how this actually is implemented and how it works in practice.
But Ethos isn't the end of the chain! Other companies live downstream from Ethos, for example, Family First Life. A customer might reach out to FFL to get a policy, FFL reaches out to Ethos (or one of many other partners), and Ethos reaches out to one of its partners (TruStage being one of their largest), and boom: an insurance happens. Like I said, it's middlemen all the way down.
But now, here's the problem: TruStage issued a bunch of policies, and then stopped being able to do business. They stopped being able to do even vital things, like process payments. And you know what happens when payments aren't made on a life insurance policy? It lapses. It goes away. You no longer have insurance. And the agents who sell those policies are often paid a commission based on the value of the first few months of the policy's life (since life insurance is expected to be a long term investment). If the policy lapses, those commissions vanish, companies like Ethos (or downstream partners like FFL) get chargebacks on the value of those failed policies.
This makes some people, like FFL's President, Shawn Meaike, very unhappy.
What we have here is a pretty egregious failure of cybersecurity and disaster recovery on the part of TruStage. It's an embarrassing and potentially terminal event for them. It's awful for the customers who bought insurance policies to give themselves peace of mind, only to discover their policies lapse because TruStage can't take their money. It's creating mild chaos in the entire industry. That's all interesting, but not why I wanted to write an article about this. I wanted to write about the incredible cringe.
In this clip from an insurance industry conference, Meaike brings up all of their partners for a "carrier panel". He then proceeds to line them up from stage right to stage left, ranking them as "weakest" to "strongest". Ethos, who sells TruStage policies, is labeled as the weakest.
"You have the weakest leg," he says, pointing at the reps from Ethos, to laughter from the audience, and then indicates the other side of the line represents "the strongest leg." Meaike hands the microphone to an Ethos employee, Dylan Cummings, "We'll start with you."
"I want to start out by thanking everyone," Cummings says.
Meaike cuts him off. "Hey, hey, hey, hey. I'm gonna help you. Why don't you start by saying you're sorry."
The crowd yells out, "Yes!" and applauds this idea.
"Say 'We have a carrier that's shut down'," Meakie continues, "'and I'm mother-freaking sorry.'"
Yes, he said "mother-freaking", on stage.
So let's recap. An insurance company gets pwned so hard they can't run operations for months. People's policies lapse, simply because they can't give their money to the insurance company. Which 100% sucks for those customers, though at least with life insurance we expect very few of them are missing their payout- hopefully! This chain cascades through the network of partners, until it culminates in the president of one company publicly humiliating the senior account manager at a middleman company, during an industry conference.
Nobody comes off looking particularly good here, obviously. Meakie is a jerk. TruStage is institutionally incompetent. Ethos is in a bit of a rock-and-a-hard-place situation, and Cummings is trying to do his best to tap dance his way out of oblivion, but it seems like the AI driven insurance startup just isn't a good partner even before their main issuer died.
All in all, it feels like a Tim Robinson sketch, or perhaps a Nathan Fielder bit. We're one step away from Meakie calling Cummings a "Wizard of Loneliness."
[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!
Error'd: Ai Dios Mio
It seems like only yesterday we were mocking the AIs for their limits, and tomorrow they're going to be mocking ours. In the meantime, here's a look back at some humorous moments from the last year or so.
"Mathing is hard" wrote Timothy W. "I for one welcome our new AI overlords."
"AI is replicating" quipped a clever anon who styled themselves AInonymous. "I don't want AI features on my phone. And yet, I'm now getting not just one, but two AI items on my context menu."
"AI is for the birds" chirped The Beast in Black. "Looks like someone's AI tool for image classification needs less of the A and more of the I."
"Get me the ruler" demanded Marius B.. "(note it's a bit old screenshot) Bing giving great suggestions for all the bear owners out there."
"AI not so I" spake Olivier. "This translates roughly to "I'm ready to help. Can you give me the title and description of the article ?""
CodeSOD: Vintage 2013
Today we have more of a representative comment, from Watson. This comes from some GPL licensed code published by everybody's favorite evil empire, Oracle.
/* * Copyright (c) 2011, 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; version 2 of the * License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */Line breaks added to really show the swamp.
What was the last year this file was released, I wonder? Clearly some sort of autogeneration gone wrong, but it doesn't exactly give me a lot of faith in the rest of the code in this file.
CodeSOD: Extremely One Line
Autoformatting your code is a standard thing to do these days. And in those days past, if we're being honest. There's no excuse to not use some kind of autoformatter. Whether you configure your editor to do it or are a weirdo like me who runs a formatter from the CLI as a build step, you've got an easy way to format your code so it looks neat and readable. And some IDEs, like Visual Studio, are pretty insistent about doing this for you. Which makes today's code sample a bit more perplexing. This comes from an ancient ASP .Net application that Austin has the misfortune to work with:
protected void Page_PreInit(object sender, EventArgs e){if (Request.ServerVariables["http_user_agent"].IndexOf("Safari", StringComparison.CurrentCultureIgnoreCase) != -1)Page.ClientTarget = "uplevel";} protected void Page_Load(object sender, EventArgs e) { Logic(); }Which function is Logic() called from? The fact that I'm asking probably is enough to get you to scroll over. The entire Page_PreInit function is on a single line, followed by the declaration of the Page_Load function. A confusing and annoying choice. The real bonus is that if the browser has "Safari" in its user agent, we set a field to a mysterious "uplevel" value. A mix of user agent sniffing, strings as enums/flags, and wonderfully unclear names.
And yes, this particular pattern appears in more than one page in Austin's application. Someone thought this was not just a good idea, but good enough to do over and over again.
[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.CodeSOD: An Odd Sort
Let's say we wanted to query Active Directory and print out a report of all of our users, and their last logon time. That seems like a pretty normal task for a Powershell script. It'd probably be short and easy to read, at least if it were written by a normal person.
Alice sends us one that wasn't. She's already done us a favor, as she writes: "Code cleaned up and indented for the whitespace-missing-impaired."
##################################### # lists accounts and selected attributes alphabetically ##################################### foreach( $letter in "a", "b", "c"......"z") { $strfilter = $letter + "*" $objdomain = New-object System.DirectoryServices.DirectoryEntry $objSearcher = New-object System.DirectoryServices.DirectorySearcher $objSearcher.SearchRoot = $objdomain $objSearcher.Filter = $strFilter $objSearcher.PropertiesToLoad.Add("name"); $colResults = $objSearcher.FindAll() foreach($result in $colResults) { $name = $result.Properties.Name $searcher = New-Object DirectoryServices.DirectorySearcher([adsi]"") $searcher.filter "(&(objectCategory=User)(sAMAccountName=$name))" $users = searcher.FindAll() foreach($user in $users) { Write-Output $user.properties.item("name") + "," + $user.properties.item("lastLogon") } } }This accomplishes sorting alphabetically by iterating across the alphabet. Which, I suspect, isn't going to actually get them in alphabetical order; it makes sure that albert and alice appear before bob, but doesn't enforce that albert must come before alice.
In any case, we iterate across the alphabet, and then create a searcher that finds a*, then b*, etc. We explicitly tell the searcher that the only property we care about is the name field, so that we don't load unnecessary fields, like the ones we want to report on.
We then iterate across the list of names, construct a new searcher, and search for the account with the username we fetched. That lets us get all of the fields we need, including the ones we aren't going to use.
Now, we search for a username, so we expect there to only be one result, but since searcher.FindAll() returns an array, we "need" to write a loop to iterate across the array of one, which is clearly a better choice than using the FindOne function.
As it usually goes with these sorts of things, one of the managers absolutely adores the fact that they have an easy way to generate a CSV file that they can manipulate in Excel, so this terrible script is "mission critical".
.comment {border: none;} [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.CodeSOD: I Exist
In addition to using an ancient development environment, with terrible UX, Greta also has the misfortune of working in Pascal.
Recently, she was diagnosing a bug. The program was reporting that files didn't exist when they definitely existed. She traced the problem down into the system library. Let's see if you can spot what's wrong:
{ Delphi / Kylix Cross-Platform Runtime Library } { System Utilities Unit } { } { Copyright (c) 1995-2001 Borland Softwrare Corporation } ... function FileAge(const FileName: string): Integer; {$IFDEF MSWINDOWS} var Handle: THandle; FindData: TWin32FindData; LocalFileTime: TFileTime; begin Handle := FindFirstFile(PChar(FileName), FindData); if Handle <> INVALID_HANDLE_VALUE then begin Windows.FindClose(Handle); if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then begin FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime); if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi, LongRec(Result).Lo) then Exit; end; end; Result := -1; end; {$ENDIF} function FileExists(const FileName: string): Boolean; {$IFDEF MSWINDOWS} begin Result := FileAge(FileName) <> -1; end; {$ENDIF}The first function here is FileAge, which returns the last modified timestamp on a file. Note the use of FileTimeToDosDateTime, which is a Windows API function. It converts LocalFileTime and stores the date part in the first output parameter (LongRec(Result).Hi) and the time part in the second output parameter (LongRec(Result).Lo). Result, in this case, is our return value. If anything goes wrong, we return -1.
The FileExists function then, simply calls FileAge. If it doesn't return a -1, there must be a file there.
That's an awkward, weird solution to the problem. There has to be a system call that can answer that question more obviously. But it doesn't seem like it should be blowing up- it looks like it should work.
But note that FileTimeToDosDateTime also returns a boolean value. If it succeeds, great, but if it fails, it returns false and sets an error code you can check. An error code that definitely isn't being checked.
And this brings us to the root cause of Greta's bug: the process that's writing the files isn't setting the "last write time", so while the file exists, the attempt to check its age fails, so FileExists believes that the file doesn't exist, just because it doesn't have a valid timestamp.
This is the kind of high quality softwrare that sometimes infects our system libraries.
Error'd: Unrewarding
Some new and some old entries this week, from people who find zero profoundly unrewarding.
"A scary blue button from The North Face" warns Dmitry K.. "Northface has just proudly rewarded me with nothing. I am not sure whether I want to try adding such a non-positive amount to my wallet. I am afraid I can trigger a chain reaction that will crash the financial system of the whole world..."
"0% Steam discount" advises Renan "As much as I love Final Fantasy, I guess I'll pass on this 0% discount."
"Give me my money around zero" demands Reinier B. "The Nederlandse Spoorwegen (Dutch Railways) owe me €2.25, but apparently I need to go back in time 2026 years to get my money ("we'll transfer the amount to your bank account around 0"). Or maybe it's not zero AD but something else?"
"My relationship with Office Depot is very unrewarding," complains Philip. "Office Depot doesn't exist in my country anymore (someone signed up with my email address), but it's good to know I have $0 waiting for me if they come back."
"Too late for that!" exclaims an Anonymous "Me hard earned zero award points on this ride app will expire at the Unix epoch which is apparently coming up soon."
"Rewards point conversion math is hard" figures Emily. "Will I have -0.01 next month? The suspense is killing me."
A Bit of DNS
I'm not a DNS person, in that I appreciate that it exists but am not up on the inner workings. It solves a lot of problems with dark magic I don't fully understand, and fortunately don't need to.
But Lucio noticed something that I do think is interesting, within the scope of the CAA record type.
The CAA record started with RFC6844, which was obsoleted by RFC8659. Both RFCs lay out the same core idea: you can add a CAA record to your DNS entries to say, "hey, this domain over here is allowed to issue certificates for me". That's the sort of thing that enables LetsEncrypt to hand out certs, and is an important part of why we can run HTTPS everywhere these days.
Now, RFC6844 has this in it:
Issuer Critical: If set to '1', indicates that the corresponding property tag MUST be understood if the semantics of the CAA record are to be correctly interpreted by an issuer. Issuers MUST NOT issue certificates for a domain if the relevant CAA Resource Record set contains unknown property tags that have the Critical bit set.The issuer critical flag means that the certificate issuer needs to validate your CAA record before it issues a certificate for you. There's more in the RFC about what exactly that means, but we don't care about those details for right now. The rule here is "set a flag to 1".
A little later in the RFC, the flag is described in more detail- as a bitmask. Specifically, bit 0 is the issuer critical flag. Bits 1-7 are reserved for future use.
Now, here's where we get into trouble, because programmers don't understand bits, and because the CAA record expects you to put an integer in this field. So, if you want issuer critical enabled, what value to you put in this field?
128, obviously. That's 10000000.
Except, if you don't understand bits, that's not obvious. A lot of people read this and decided that the documentation meant they needed to put 1 in the field- aka 00000001. This is wrong.
The updated RFC tries to explain it a bit more clearly:
Bit 0, Issuer Critical Flag: If the value is set to "1", the Property is critical. A CA MUST NOT issue certificates for any FQDN if the Relevant RRset for that FQDN contains a CAA critical Property for an unknown or unsupported Property Tag. Note that according to the conventions set out in [RFC1035], bit 0 is the Most Significant Bit and bit 7 is the Least Significant Bit. Thus, according to those conventions, the Flags value 1 means that bit 7 is set, while a value of 128 means that bit 0 is set.Now, pop quiz: what percentage of the people using this field have actually read the RFC? Not many. Probably a number that rounds down to zero, if we're being honest.
But now, let's say you're LetsEncrypt. You're supposed to be validating the CAA records of your customers if the bit is set, but a substantial portion of your customers are using it wrong. Do you: stand by the specification and tell them that they're wrong? Or say, "well, it's a reserved bit anyway, we'll (ab)use it and accept bad data".
Of course they'll accept bad data.
// filterCAA processes a set of CAA resource records and picks out the only bits // we care about. It returns two slices of CAA records, representing the issue // records and the issuewild records respectively, and a boolean indicating // whether any unrecognized records had the critical bit set. func filterCAA(rrs []*dns.CAA) ([]*dns.CAA, []*dns.CAA, bool) { var issue, issuewild []*dns.CAA var criticalUnknown bool for _, caaRecord := range rrs { switch strings.ToLower(caaRecord.Tag) { case "issue": issue = append(issue, caaRecord) case "issuewild": issuewild = append(issuewild, caaRecord) case "iodef": // We support the iodef property tag insofar as we recognize it, but we // never choose to send notifications to the specified addresses. So we // do not store the contents of the property tag, but also avoid setting // the criticalUnknown bit if there are critical iodef tags. continue case "issuemail", "issuevmc": // We support these property tags insofar as we recognize them and // therefore do not bail out if someone has one marked critical. But // of course we do not do any further processing, as we do not issue // S/MIME or VMC certificates. continue default: // The critical flag is the bit with significance 128. However, many CAA // record users have misinterpreted the RFC and concluded that the bit // with significance 1 is the critical bit. This is sufficiently // widespread that that bit must reasonably be considered an alias for // the critical bit. The remaining bits are 0/ignore as proscribed by the // RFC. if (caaRecord.Flag & (128 | 1)) != 0 { criticalUnknown = true } } } return issue, issuewild, criticalUnknown }Lucio writes:
Now I assume we all agree about the high wisdom of using bitmasks these days. Do we really need to save those bits at the price of a totally screwed up readability?
Now, I do like bitmasks, because I like the ability to trivially combine a bunch of values together with simple boolean operations, but I recognize that people can, and do screw it up. All the time. Am I going to say the DNS people were wrong for using a bitmask in their networking specification? No, I wouldn't go that far. But it certainly caused issues, and I do have to wonder: if you're treating 7 of 8 bits as reserved, maybe you should just have made it a flag?
CodeSOD: Asynchronous Directories
Eri has a mix of a "true confession" and a "wait, really?" today.
The programming language Vala bills itself as a C# like language that compiles into something pretty close to C performance, designed specifically for writing code against Gnome and its associated libraries.
One of the C#-isms in brings in is async/await type semantics. You can yield someAsyncFunction(), which returns control to the caller, allowing it to proceed until the yielded function returns an actual value.
Because it has asynchronous functions, many library functions for handling I/O are already async. So you can make_directory_async, which yields control so you can keep executing while waiting for the filesystem to make your directory.
There are also synchronous versions of those methods. And then there's create_directory_with_parents, which will create a chain of directories for you. That's the synchronous version, and Vala's core library has decided not to provide an asynchronous version of it, which is my "wait, really?" I suspect it's really about the race conditions involved and the risks of things going wrong while doing it asynchronously; all solvable problems, but tricky ones to solve.
But it's the problem Eri had, and this is their solution:
/// Note: does not throw if target already exists async void create_directory_with_parents_async(File file, Cancellable? cancellable = null) throws Error { var to_create = new File[0]; var? current_target = file; while(current_target != null) { try { yield current_target.make_directory_async(Priority.DEFAULT, cancellable); } catch(IOError.NOT_FOUND e) { to_create += current_target; current_target = current_target.get_parent(); continue; } catch(IOError.EXISTS e) { break; } break; } for (int i = to_create.length - 1; i >= 0; --i) { try { yield to_create[i].make_directory_async(Priority.DEFAULT, cancellable); } catch(IOError.EXISTS e) { // Created by another process } } }If I'm reading this correctly, we start by trying to create the full path to our leaf node. If there's a not found error, we go up one level and try and create that one. We keep trying that until we either run out of parent nodes to try against, or we hit a directory that already exists, or we successfully create a directory. All along the way, we keep appending the current_target to our to_create array.
Once we've gotten that baseline, we then iterate across our to_create array, backwards, creating the shortest non-existent paths first.
This works, but it's ugly as sin. Mostly, it's ugly because we're using exceptions for flow control instead of doing things like checking for file existence, though I suppose those checks may also break our goal of doing all our I/O operations in an async context. I don't know enough about Vala to know the better way of doing this.
Eri writes:
The function works as intended, but trying to trace control flow through the first loop is not pleasant. Ironically, the C mechanism Vala wraps is slightly advanced error codes, which would be nicer to work with in this case
Eri also provides a slightly re-worked version of the main loop, that is at least a bit easier to follow, but still an ugly approach:
while(current_target != null) { try { yield current_target.make_directory_async(Priority.DEFAULT, cancellable); break; } catch(IOError.EXISTS e) { break; } catch(IOError.NOT_FOUND e) { to_create += current_target; current_target = current_target.get_parent(); } }Still, since this is an attempt to patch over a missing core library method and solve a tricky problem about how to handle race conditions, I think absolution is reasonable. It's ugly, it's weird, but it does the job. Go hide it in a box, and never touch its implementation again- except to make it go away.
A Mortal Blow
From our anonymous submitter:
Having reached the end of the road at a company increasingly swallowed up companies further east which you'd never believe were still afloat, I found myself headhunted for certain specialty software skills. I was reaching the final few years of my expected working span, so I jumped at the chance. The money was (to me at that time) spectacularly good, so I jumped into it.
It started when my first day was spent by me being sent home for the weeks it was still going to take to onboard me. Not bad, engaged to wait, as it were, and the first 6 months was thus and so.
The man who had interviewed me, call him Fred, was intelligent and urbane, and was a joy to meet. He and I clearly hit it off, and lo and behold I was in. It was he who gave me my first assignment, which was mathematical analysis of their core milk-cow program because they needed to find out what it did, and how it did it, so they could perhaps implement it in a more contemporary language.
So I did that, and was just about to publish my findings with him, when Fred inconveniently dropped dead suddenly. In the what-are-we-going-to-do-now-our-key-man-is-no-more confusion, we contractors were forgotten.
For the next 18 months or so (may have been more, may have been less) I was more or less ignored. I spent the time writing a development environment to work on any part of the program conveniently, all the while sitting next to a man who was constantly, forcefully and repetitiously speaking ill of the managers in his line structure. The ridiculously garrulous boss who inherited me thought little of me, and handed me the little work that came my way with active hostility. One or two good guys, but mostly a cabal of elderly men trying to preserve their little money-spinner as long as they could, and a johnny-come-lately trying to increase (and even introduce) automatic processes was less than welcome. During that time I spent quite some time on TDWTF, submitting a gem or two here and there.
No surprise when they finally kicked my arse away. No love lost there. Now working my last couple of years to retirement as a postie.
No punchline here. I want you to know that dropping dead from work is all too real and can happen to anyone.
Best of…: Classic WTF: A Dumbain Specific Language
I’ve had to write a few domain-specific-languages in the past. As per Remy’s Law of Requirements Gathering, it’s been mostly because the users needed an Excel-like formula language. The danger of DSLs, of course, is that they’re often YAGNI in the extreme, or at least a sign that you don’t really understand your problem.
XML, coupled with schemas, is a tool for building data-focused DSLs. If you have some complex structure, you can convert each of its features into an XML attribute. For example, if you had a grammar that looked something like this:
The Source specification obeys the following syntax source = ( Feature1+Feature2+... ":" ) ? steps Feature1 = "local" | "global" Feature2 ="real" | "virtual" | "ComponentType.all" Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all" Feature4 = "first" | "last" | "DayAllocation.all" If features are specified, the order of features as given above has strictly to be followed. steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps oneOrMoreNameSteps = nameStep ( "." nameStep ) * zeroOrMoreNameSteps = ( nameStep "." ) * nameStep = "#" name name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum. componentSteps is a list of valid values, see below. Valid 'componentSteps' are: - GlobalValue - Product - Product.Brand - Product.Accommodation - Product.Accommodation.SellingAccom - Product.Accommodation.SellingAccom.Board - Product.Accommodation.SellingAccom.Unit - Product.Accommodation.SellingAccom.Unit.SellingUnit - Product.OnewayFlight - Product.OnewayFlight.BookingClass - Product.ReturnFlight - Product.ReturnFlight.BookingClass - Product.ReturnFlight.Inbound - Product.ReturnFlight.Outbound - Product.Addon - Product.Addon.Service - Product.Addon.ServiceFeature In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'. 'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.You could turn that grammar into an XML document by converting syntax elements to attributes and elements. You could do that, but Stella’s predecessor did not do that. That of course, would have been work, and they may have had to put some thought on how to relate their homebrew grammar to XSD rules, so instead they created an XML schema rule for SourceAttributeType that verifies that the data in the field is valid according to the grammar… using regular expressions. 1,310 characters of regular expressions.
<xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="(((Scope.)?(global|local|current)\+?)?((((ComponentType.)? (real|virtual))|ComponentType.all)\+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)\+?)? ((((DayAllocation.)?(first|last))|DayAllocation.all)\+?)?:)?(#[A-Za-z0-9\-_]+(\.(#[A-Za-z0-9\-_]+))*|(#[A-Za-z0- 9\-_]+\.)* (ThisComponent|GlobalValue|Product|Product\.Brand|Product\.Accommodation|Product\.Accommodation\.SellingAccom|Prod uct\.Accommodation\.SellingAccom\.Board|Product\.Accommodation\.SellingAccom\.Unit|Product\.Accommodation\.Selling Accom\.Unit\.SellingUnit|Product\.OnewayFlight|Product\.OnewayFlight\.BookingClass|Product\.ReturnFlight|Product\. ReturnFlight\.BookingClass|Product\.ReturnFlight\.Inbound|Product\.ReturnFlight\.Outbound|Product\.Addon|Product\. Addon\.Service|Product\.Addon\.ServiceFeature|Brand|Accommodation|Accommodation\.SellingAccom|Accommodation\.Selli ngAccom\.Board|Accommodation\.SellingAccom\.Unit|Accommodation\.SellingAccom\.Unit\.SellingUnit|OnewayFlight|Onewa yFlight\.BookingClass|ReturnFlight|ReturnFlight\.BookingClass|ReturnFlight\.Inbound|ReturnFlight\.Outbound|Addon|A ddon\.Service|Addon\.ServiceFeature|SellingAccom|SellingAccom\.Board|SellingAccom\.Unit|SellingAccom\.Unit\.Sellin gUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit\.SellingUnit|SellingUnit))"/> </xs:restriction> </xs:simpleType> </xs:union>There’s a bug in that regex that Stella needed to fix. As she put it: “Every time you evaluate it a few little kitties die because you shouldn’t use kitties to polish your car. I’m so, so sorry, little kitties…”
The full, unexcerpted code is below, so… at least it has documentation. In two languages!
<xs:simpleType name="SourceAttributeType"> <xs:annotation> <xs:documentation xml:lang="de"> Die Source Angabe folgt folgender Syntax source = ( Eigenschaft1+Eigenschaft2+... ":" ) ? steps Eigenschaft1 = "local" | "global" Eigenschaft2 ="real" | "virtual" | "ComponentType.all" Eigenschaft3 ="self" | "ancestors" | "descendants" | "Hierarchy.all" Eigenschaft4 = "first" | "last" | "DayAllocation.all" Falls Eigenschaften angegeben werden muss zwingend die oben angegebene Reihenfolge der Eigenschaften eingehalten werden. steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps oneOrMoreNameSteps = nameStep ( "." nameStep ) * zeroOrMoreNameSteps = ( nameStep "." ) * nameStep = "#" name name ist eine Folge von Zeichen aus der Menge "A"-"Z", "a"-"z", "0"-"9", "-" und "_". Keine Umlaute. Mindestens ein Zeichen componentSteps ist eine Liste gültiger Werte, siehe im folgenden Gültige 'componentSteps' sind zunächst: - GlobalValue - Product - Product.Brand - Product.Accommodation - Product.Accommodation.SellingAccom - Product.Accommodation.SellingAccom.Board - Product.Accommodation.SellingAccom.Unit - Product.Accommodation.SellingAccom.Unit.SellingUnit - Product.OnewayFlight - Product.OnewayFlight.BookingClass - Product.ReturnFlight - Product.ReturnFlight.BookingClass - Product.ReturnFlight.Inbound - Product.ReturnFlight.Outbound - Product.Addon - Product.Addon.Service - Product.Addon.ServiceFeature Desweiteren sind alle Unterschrittfolgen aus obigen Pfaden erlaubt, also 'Board', 'Accommodation.SellingAccom' oder 'SellingAccom.Unit.SellingUnit'. 'Accommodation.Unit' hingegen ist nicht erlaubt, da in diesem Fall einige Zwischenschritte fehlen. </xs:documentation> <xs:documentation xml:lang="en"> The Source specification obeys the following syntax source = ( Feature1+Feature2+... ":" ) ? steps Feature1 = "local" | "global" Feature2 ="real" | "virtual" | "ComponentType.all" Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all" Feature4 = "first" | "last" | "DayAllocation.all" If features are specified, the order of features as given above has strictly to be followed. steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps oneOrMoreNameSteps = nameStep ( "." nameStep ) * zeroOrMoreNameSteps = ( nameStep "." ) * nameStep = "#" name name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum. componentSteps is a list of valid values, see below. Valid 'componentSteps' are: - GlobalValue - Product - Product.Brand - Product.Accommodation - Product.Accommodation.SellingAccom - Product.Accommodation.SellingAccom.Board - Product.Accommodation.SellingAccom.Unit - Product.Accommodation.SellingAccom.Unit.SellingUnit - Product.OnewayFlight - Product.OnewayFlight.BookingClass - Product.ReturnFlight - Product.ReturnFlight.BookingClass - Product.ReturnFlight.Inbound - Product.ReturnFlight.Outbound - Product.Addon - Product.Addon.Service - Product.Addon.ServiceFeature In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'. 'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing. </xs:documentation> </xs:annotation> <xs:union> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="(((Scope.)?(global|local|current)\+?)?((((ComponentType.)?(real|virtual))|ComponentType.all)\+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)\+?)?((((DayAllocation.)?(first|last))|DayAllocation.all)\+?)?:)?(#[A-Za-z0-9\-_]+(\.(#[A-Za-z0-9\-_]+))*|(#[A-Za-z0-9\-_]+\.)*(ThisComponent|GlobalValue|Product|Product\.Brand|Product\.Accommodation|Product\.Accommodation\.SellingAccom|Product\.Accommodation\.SellingAccom\.Board|Product\.Accommodation\.SellingAccom\.Unit|Product\.Accommodation\.SellingAccom\.Unit\.SellingUnit|Product\.OnewayFlight|Product\.OnewayFlight\.BookingClass|Product\.ReturnFlight|Product\.ReturnFlight\.BookingClass|Product\.ReturnFlight\.Inbound|Product\.ReturnFlight\.Outbound|Product\.Addon|Product\.Addon\.Service|Product\.Addon\.ServiceFeature|Brand|Accommodation|Accommodation\.SellingAccom|Accommodation\.SellingAccom\.Board|Accommodation\.SellingAccom\.Unit|Accommodation\.SellingAccom\.Unit\.SellingUnit|OnewayFlight|OnewayFlight\.BookingClass|ReturnFlight|ReturnFlight\.BookingClass|ReturnFlight\.Inbound|ReturnFlight\.Outbound|Addon|Addon\.Service|Addon\.ServiceFeature|SellingAccom|SellingAccom\.Board|SellingAccom\.Unit|SellingAccom\.Unit\.SellingUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit\.SellingUnit|SellingUnit))"/> </xs:restriction> </xs:simpleType> </xs:union> </xs:simpleType> hljs.initHighlightingOnLoad();Error'd: Good Time
Astute readers noticed last week that this editor (that is to say, me) had his own error'd failure to remember what day it was. Thank you for pointing it out promptly, and then proceeding to send in a bunch of examples of other sites calendar failures. Misery loves company!
Traveler's travails, from C_Chell "Trying to complete the form on https://www.ihg.com to tell when I plan to arrive at the hotel, I can't complete the form because of this little time problem."
"You Have -1 Month(s) To Order!" announces dragoncoder047. "Ah, GradImages... the company that told all graduates that they'd get a free 5x7 but tried to charge me for it, then refused to honor my "unsubscribe" request and is *still* emailing me to this day... Can't do date math? Par for the course."
"Stansted Temporal UI design" shared by Michael R. "While waiting for a friend to arrive at Stansted I see this. I better fire up the DeLorean to pick her up at 00:06 tomorrow."
While he was hunting through the website, Michael R. also found that "The Stansted airport website seems to suffer from Directional Confusion."
Nothing wrong with the calendar here, but
Slaoput simply opposes mandatory existence.
"I was filling out a form that said the Birthdate
is optional, but when I hit submit I found out
it was required. (I guess technically you have to be
born to fill out the form.)"
NOT TO BE!
[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.
CodeSOD: Heating Up
A common option for retrofitting heating and cooling into older homes is a mini-split, frequently tied to a heat pump. They're (relatively) cheap to install, energy efficient, and can be added without substantial modifications to the home. They also, annoyingly, are mostly controlled via IR remotes, making them challenging to wire up to home automation or even a household thermostat.
People have made solutions, and today's code comes from one of those solutions. Which, I want to stress, this code comes from an open source project for home automation, so it's not the code that's wrong, here. At first I thought it was, and had a moment of, "I'm not going to pick on some hobby project," but then I realised the hobby project points at a deeper issue.
// temperature helper these are direct mappings based on the remote float toFahrenheit(float fromCelsius) { // Lookup table for specific mappings const std::map<float, int> lookupTable = { {16.0, 61}, {16.5, 62}, {17.0, 63}, {17.5, 64}, {18.0, 65}, {18.5, 66}, {19.0, 67}, {20.0, 68}, {21.0, 69}, {21.5, 70}, {22.0, 71}, {22.5, 72}, {23.0, 73}, {23.5, 74}, {24.0, 75}, {24.5, 76}, {25.0, 77}, {25.5, 78}, {26.0, 79}, {26.5, 80}, {27.0, 81}, {27.5, 82}, {28.0, 83}, {28.5, 84}, {29.0, 85}, {29.5, 86}, {30.0, 87}, {30.5, 88} }; // Check if the input is in the lookup table auto it = lookupTable.find(fromCelsius); if (it != lookupTable.end()) { return it->second; } // Default conversion and rounding to nearest integer return roundf(fromCelsius * 1.8 + 32.0); }Okay, I am going to pick on their code a little bit; using float as a key in a map is asking for trouble, because rounding errors are going to surprise you. But honestly, failing to find the key you're looking for is better than the opposite, since that actually does the correct thing. Because if you look carefully at the table, you'll see that it's wrong.
18C, for example, should be 64F. Well, 64.4F, but we're rounding to an integer. The choice here is to roughly map every 0.5C increase to a 1F increase, which is not the conversion factor. They try and correct- note how the table mostly steps by 0.5C, but skips 19.5C.
The opposite direction is similarly bad:
// temperature helper these are direct mappings based on the remote float toCelsius(float fromFahrenheit) { // Lookup table for specific mappings const std::map<int, float> lookupTable = { {61, 16.0}, {62, 16.5}, {63, 17.0}, {64, 17.5}, {65, 18.0}, {66, 18.5}, {67, 19.0}, {68, 20.0}, {69, 21.0}, {70, 21.5}, {71, 22.0}, {72, 22.5}, {73, 23.0}, {74, 23.5}, {75, 24.0}, {76, 24.5}, {77, 25.0}, {78, 25.5}, {79, 26.0}, {80, 26.5}, {81, 27.0}, {82, 27.5}, {83, 28.0}, {84, 28.5}, {85, 29.0}, {86, 29.5}, {87, 30.0}, {88, 30.5} }; // Check if the input is in the lookup table auto it = lookupTable.find(static_cast<int>(fromFahrenheit)); if (it != lookupTable.end()) { return it->second; } // Default conversion and rounding to nearest 0.5 return roundf((fromFahrenheit - 32.0) / 1.8 * 2) / 2.0; }Here, we can be off by as much as a 1C, which is certainly a noticeable feeling.
At first glance, I thought this was just a misguided attempt at optimizing the lookup. For common values, do a lookup instead of calculating because it's faster. Seems like the kind of mistake a hobby project might make, and definitely not a WTF. But it's the comment which corrects me: these are direct mappings based on the remote.
These remotes usually have a display. So when you see on the remote that you're trying to set the temperature to a comfortable 72F, the remote is actually sending 22.5C to the unit. That's the actual temperature being sent.
Now, why on Earth does the remote behave this way? Well, I haven't cracked one open to read off the part numbers, but I'm going to go out on a limb and guess that the microcontoller in the remote doesn't handle floating point operations all that well. So it almost certainly does use a lookup table to decide what signal to send, and the lookup table is populated by "good enough" approximations of temperature conversions. There aren't a lot of places that use Fahrenheit, so being "close enough" is a reasonable solution. If you want accurate temperatures, use SI units, not "freedom units".
In the end, I'd say that neither the hobby project, nor the remote control are the WTF here; locales that insist on using weird ass units are.
.comment { border: none }