|
The Daily WTF
The following are the titles of recent articles syndicated from The Daily WTF
Add this feed to your friends list for news aggregation, or view this feed's syndication information.
LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose.
[ << Previous 20 ]
| Friday, June 5th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Error'd: Bridge for Sale "Scammer offers to buy Google" is certainly a new twist on a very old New York con.
Jan B. explains
"Scammers have found a new way to steal money, scrap
LinkedIn profiles and then send out emails with fake offers
to buy people's companies. I'm guessing suddenly they need some
fees paid just before the deal is finalised. However, they
may need to improve their filtering before sending out their
scams, I don't even own Google!" I'm putting together a group of people
to buy it, do you want to get in the deal? I'll just need you to transfer
two million to this SWIFT account...

"But when?" queries
Hercules
"I've always had difficulty understanding phone billing and payment cycles.
My phone company seems intent on making that harder..." Strong, heroically good-looking... Bright?The gods don't require it.

"Next update: 25 years 11 months ago" is some kind of reverse Y2K bug.
Laurent boggles
"It's bad enough to have a power outage, but to
have to go back in time to get an update?"

"What is 30% of NaN?" asks
Geoff O. rhetorically. However, the answer is well-defined and explicit.

And finally, another "lost in translation" error from
Martin K.:
"Not only have the store not changed the generic cookie
bar text, they apparently don't have a fall back to
e.g. english, if the browser language isn't found."

 [Advertisement]
BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!
| | Thursday, June 4th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Build Up If there's one thing that seems to be a constant source of issues, it's people constructing SQL queries through string concatenation. Even if you're using parameters in the query, I'm opposed to handling raw SQL as strings in my programs. My solution is always "use a builder"- an API that constructs a syntax tree that it can then render to SQL as needed. (Yes, a builder, not an ORM, that's a whole other discussion, I'm not dogmatically anti-ORM, but it's a leaky abstraction at best.)
Many languages have such a thing, Java included. Lukasz's team was using Java, and they had a rule: "don't do SQL strings, use a builder". Unfortunately for Lukasz's team, their guideline didn't specify what kind of builder.
StringBuilder builder = new StringBuilder();
builder.append("where ID_BSNGP = ? ");
builder.append("and ID_ITM = ? ");
builder.append("for update");
SQLQuery query = new SQLQuery();
query.setQueryString(builder.toString());
A StringBuilder is a kind of builder. Technically correct and all that. It's just concatenation with extra steps, but it's a builder. Of course, the bonus point here is that this built query is… just wrong? SELECT FOR UPDATE field FROM table WHERE condition would make sense, but we're missing most of that syntax here.
That this code was running in production without anyone noticing means that whatever errors this was triggering were getting swallowed or ignored, and the fact that no good output ever came from it ended up not mattering. The real WTF is less the malicious compliance and more the fact that this obviously broken code wasn't so broken as to be noticed.
| | Wednesday, June 3rd, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Coerce the Truth Out of You Frank suspected something odd when he spotted a use of React's useMemo function in some JavaScript code. Now, there's nothing wrong with using that method, in and of itself. It watches some variables and recalculates a callback if they change for any reason. It's a great tool for when you want to avoid recalculating expensive things over and over again.
But in this case, the calculation in question was isAuthorized, which wasn't an expensive calculation; it was just checking if certain values are set. The code looked like this:
const isAuthorized = useMemo(() => {
return (session && token && !group) === false;
}, [session, token, group]);
session, token and group are all either going to be null, or be an object. To be authorized, all three must be set to non-null values. A rational person, knowing this, might choose to return session && token && group, and exploit JavaScript's truthiness. Or, if you really wanted to coerce it to a boolean, you could return !!(session && token && group).
So why on Earth are they negating group? How would this even work? If the check is "all three must be set" what is this doing?
Well, if you do a && b && c, JavaScript will return the last value you looked at. The && operator short circuits, so that means it either returns the first falsy value you encounter, or the very last value in the chain.
So in this scenario: (session && token && !group), if session or token is null, the expression evaluates to null. Otherwise, if group is null, then !group will evaluate to true. Because they use the === operator, JavaScript won't do any type coercion, and that means null === false is false, as is true === false.
I can't believe that this code works as intended. I mean, it works, it gives the correct output, but I think that's an accident. Happenstance of someone with no clue gradually throwing operators into an expression until it does what they want. Perhaps it's LLM generated code- who can even guess anymore? It certainly seems like it was generated through a stochastic process; whether that's a bumbling developer or a bunch of math, there's definitely no intelligence involved, artificial or otherwise.
| | Tuesday, June 2nd, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Blocked the Date Volodya sends us some bad date handling code in PHP. Which, I know, you're just reaching for the close tab and yawning when you hear that. You've seen it before. But bear with me, this one still has some fun bits to it.
$monthes = array(
1 => 'Января', 2 => 'Февраля', 3 => 'Марта', 4 => 'Апреля',
5 => 'Мая', 6 => 'Июня', 7 => 'Июля', 8 => 'Августа',
9 => 'Сентября', 10 => 'Октября', 11 => 'Ноября', 12 => 'Декабря'
);
This creates a list of months.
if ( $team->have_posts() ) :
while ( $team->have_posts() ) : $team->the_post();
Today, I have learned something about PHP. PHP has an alternate syntax for blocks. Instead of if { statements }, you can do: if : statements endif. Just one more quirk of PHP to make the language more confusing.
This block checks have_posts in an if, and then checks it again in a while, meaning we don't need the if at all, but so it goes. We haven't gotten to the date handling yet, so let's look at that.
$date = get_the_date();
$d1 = explode(".", $date);
if ($d1[1][0]=='0')
$m = $d1[1][1];
else
$m = $d1[1][0];
?><div class="date"><?php echo $d1[0]." ".$monthes[$m]." ".$d1[2]; ?></div>
We get the date as a string, and then split it out into date parts. This is, of course, highly locale specific, but clearly they know what locale they're in. Then they look at the array of date parts. The second element holds their "month" string, as two digits, so they look at the digits. If the month string starts with a 0, they grab the second character and put it in $m. Otherwise, they grab the first character and put it in $m. Then they use $m to look up the $monthes.
Unless there's some substring weirdness going on that I don't know about, this code… doesn't work? Right? Since they're grabbing only a single character out of $d1[1] every time, for months later in the year, $m is only ever going to hold 1, and thus we only output Января, meaning we get four months of January, which just seems cruel, honestly, at least in the Northern Hemisphere.
As with all bad date handling code, this could easily be fixed by just using the built in functions, even in PHP. What I'm going to take away from this though is that PHP's syntax lets you write in Visual Basic or Ruby if you're determined enough. And you can mix and match, so enjoy a codebase that has :/endif and {} scattered throughout.
[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!
| | Monday, June 1st, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Let's Be Facebook! The real WTF is that our long-time friend and submitter Argle failed to dissuade all three of his sons from pursuing IT careers of their own:
Back circa 2012, my three sons all got jobs at a company that had a brilliant web project. So brilliant that it had the support of a Disney VP, the mayor of the city, and other VIPs. At one point, my sons asked to borrow money to invest in the project. They are good boys (one is now a senior developer with Proctor & Gamble), so I backed them.
A year later, the project was released late, over budget, and not fully functional.

My boys convinced the CEO to bring me in to fix things. I fixed things. In that time, I found out they had taken bids on the project. Bids were nominally $15,000, some higher, some lower, of course. All but one group that had bid $5,000. Their plan? Hire some programmers in India for $8/hour and pocket the money without having to do work themselves.
Costs had shot well over $35,000 before I was brought in.
After I got the system working, I went to one of the weekly general standups for the company. The CEO walked in and said something like, "I just learned that Facebook was written in PHP. I think we should rewrite the whole project in PHP. That's what we really need to do."
And thus the decision was made.
A meeting was held the next day to discuss how long it would take to remake the project in PHP instead of C#. Bear in mind, a year and a half had been thrown into making the project thus far.
Going around the table, everyone said between 2 and 3 weeks. There was one other programmer in the company who had exactly 2 months of work experience; he simply parroted what the others had said before him. There was also the general contractor who leased the building to the company. He was involved with the project, and was second-to-last to speak. I fully expected this contractor to have more sense. He came in at 3 to 4 weeks.
My mouth dropped open.
It was my turn. You know those psych tests where you get someone who acts sensibly when alone, but conforms with the rest of the crowd when there's more than one? I'm simply not that guy. I said, "Those are absurd estimates! This will take a minimum of 5 months before it's in beta stages and not ready for public consumption for another couple more months."
The next day, I got a call telling me my services were no longer needed because "I wasn't forward-thinking enough for the company."
My boys stayed on another year, so I got regular reports on the "upgrade." Sure enough, just shy of 8 months later, the new system went live.
As they say, the most experienced person will be the one to accurately tell everyone that it will take longer and cost more than everyone else says.
Anyone else have their own intergenerational WTFs? Please share in the comments!
 [Advertisement]
Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready. Learn more.
| | Friday, May 29th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Error'd: Super SEO Strategies It's ironic -- this site gets absolutely inundated with blogspam from people trying to improve their SEO ranking, and yet the only requirement to get your website linked is one dumb little typo in the right menu.
Faithful
Michael R. is still job hunting, now even farther afield.
"I shall try the gigs in United Kingsom. https://electronicmusicopenmic.com/"

B.J.H. is getting hot undeh the collah.
"Weather.com is an endless source of WTF. Today the high
temperature will be 53F, unless you care about any hour
after 8:00 AM. (And why don't they have enough room
to spell out "hour"?)"

Jake W. isn't storming about like BJ. He just wants us to know there's an opening at Durmstrang. No stress.

Martin K. reveals
"The resignation of the Microsoft Denmark CEO broke more than news,
it also broke the date."

"confirmation.message.text" incoming from
Totty
"Snarky comment. Snarky comment. Snarky comment."

[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!
| | Thursday, May 28th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: What Condition is This Untodesu sends us this submission, with this comment:
Literally no idea what kind of drugs the guy was taking but nonetheless we've rewritten it to be just a two-liner
Well, that doesn't tell us a lot about what to expect from the code, but let's take a look.
QStringList TableViewAssembly::parametersFilter(ProbePart::Type type, int pos, QList<ProbePart> probeDesign) {
QString to, from;
if(pos == -1) {
if(probeDesign.length() == 0) {
to = "*";
from = "AutoJoint";
} else {
to = probeDesign.at(0).fromMounting();;
from = "AutoJoint";
}
} else if(pos == 0) {
if(probeDesign.length() == 1) {
if(probeDesign.at(pos).type() == ProbePart::Type::Stylus) {
to = probeDesign.at(pos).fromMounting();
from = "*";
} else {
to = "*";
from = probeDesign.at(pos).toMounting();
}
} else {
to = probeDesign.at(pos + 1).fromMounting();
from = probeDesign.at(pos).toMounting();
}
} else if(pos == probeDesign.length() - 1) {
if(probeDesign.at(pos).type() == ProbePart::Type::Stylus) {
if(probeDesign.length() <= 1) {
from = "*";
to = probeDesign.at(pos).fromMounting();
} else {
from = probeDesign.at(pos - 1).toMounting();
to = probeDesign.at(pos).fromMounting();
}
} else {
from = probeDesign.at(pos).toMounting();
to = "*";
}
} else {
from = probeDesign.at(pos).toMounting();
to = probeDesign.at(pos + 1).fromMounting();
}
return { to, from };
}
QStringList andQList tell me that this is a Qt-based application. The goal of this function seems to be to take some inputs about a "probe part" and construct a pair of strings. Let's trace through it.
Let's just walk through the conditions, quickly, without worrying too much about the inside. We look at pos, and check for three cases: either pos is -1, 0, or probeDesign.length() - 1.
Inside each of those branches, we also check the length of the list, testing if it contains no elements, exactly one elemnet, or more than one element. We also check if the part in question is a stylus.
With that in mind, let's see if we can summarize the conditions here. If pos == -1, we do some automatic stuff, using the first element in the list if there is one. If pos == 0 and there's exactly one element in the list, we grab the first element and link it to * (the to/from order depends on the stylus question). If there's more that one element in the list, we pair the current pos with pos+1; notably, in this branch, pos is definitely zero. If pos is the last element in the list, we follow the same logic, but pair with pos-1, with a side branch for checking against the length of the list.
It's all bounds checking. That's all this code is. Bounds checking that's gotten out of hand. The main branch here is actually the final else: that's where most of the code is going to pass through. All the other branches are just handling edge cases. Literal edge cases, as in "the edge of the list".
Untodesu didn't supply the two line version, but based on the fact such a version exists, I also suspect that many of these branches weren't actually used. Or, at least, based on the actual business rules, could be combined.
| | Wednesday, May 27th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Are There Files Yet? Are there any files to send? That's the question that Chris C's predecessor had. So they asked it. Again. And again. And again.
Chris writes:
I'm occasionally called upon to troubleshoot an ecommerce application that was built in the PHP 5.x days and has been running largely untroubled by maintenance or modernity (aside from the backported security patches to its binaries) ever since.
if(sizeof($files) > 0){
if(sizeof($files) > 0){
foreach($files as $file){
$mime->addAttachment($file);
}
}
}
Indentation as per the original.
If the files array contains items, then if the files array contains items, then we iterate across the files array, which hopefully contains items, and add them as an attachment to an email.
I feel like the way this got indented, the developer responsible knew, deep down, that this was wrong. They lacked the reading comprehension to understand why, but deep down in their spleen, something was screaming at them. And thus those stacked curly brackets at the end there.
Of course, none of the conditionals are needed: a foreach on an empty object just does nothing.
| | Tuesday, May 26th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Whales Ahoy! The waters are even more dangerous than we imagined. Have a look at some of the crazed whales our brave submitters and commenters have encountered in the wild.
First comes an Anonymous tale of woe:

Our company makes apps for businesses. We have 1 MAIN client whose CEO can make or break our company, and his wish is our command. He sent a priority email on a Friday night saying the app was slow and needed to be fixed.
The client CEO is so important that he works directly with our CEO, who decided to PM this huge issue.
All weekend, we were trying out tons of different things to optimize this "slow" app that "wasn't loading or refreshing." We deployed the app Monday night after a weekend of unpaid overtime (darn salary). On Tuesday, the account manager made a bug card to officially represent the work we did, and they posted a previously-unseen video of the slowness.
There is a refresh icon that spins when clicked. The video was of the refresh icon, and it was spinning for an extra second after the data loaded (and jumping 2 pixels from padding styling).
That is what was high priority.
I mean, we all hate the system, but sometimes the system is actually there to protect us.
Next, we have Daniel Orner's ongoing peril:
We do digital flyers/circulars/ads. Eight years ago, that meant we got PDFs from retailers and turned them into digital content. One huge retailer (hundreds of stores) wanted a dynamically-created flyer that would have up-to-date pricing twice a day. We didn't have time to build out a full digital solution (which would have made sense), so instead we spent six months banging together a solution with spit and duct tape which baked out hundreds of PDFs every morning and afternoon. This one retailer was responsible for about 40% of our processing power.
We're finally getting somewhat closer to phasing this out, but "it worked" for this long ...
Finally, let's be grateful Brian escaped with his life!
Worked for a company that was building a component of a high-profile weapons platform for one of the major military suppliers. We had taken over the project from another company that was under-performing, so we were already behind schedule from the minute the contract was signed. Of course this company saw fit to treat us more as a subsidiary than a subcontractor. Including, for a time, sending one of their own managers to sit in our lab and observe (read: babysit) us. On Saturdays. Then they demanded we start working shifts to make more use of the lab equipment, and I got the bad draw: 3 AM - noon. Never mind that I had just gotten married (they actually called to tell me this while I was on vacation the week after my wedding) and would like to actually spend some time with my wife ...
That experience soured me on the whole military-industrial complex for a long time. To this day I still get headhunters pinging me to work for that megacorp; I just chuckle and delete their messages.
Have these tales knocked loose any foul memories that your brain tried to repress? Send them to us!
 [Advertisement]
ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
| | Monday, May 25th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Classic WTF: One-and-a-Half-Tiered Application Design It's a holiday in the US today, so we're reaching back into the archives. What we really need is a single function that can do it all, and by "it" we mean "ruin your life." Original --Remy
There are several types of bad code; there's lazy code, frantic code, unaware-of-a-better-way code, and aware-of-a-better-way-but-too-apathetic-to-do-it code, to name a few. Then there're amalgamations of different types of bad code.
Môshe encountered such an amalgam when his company was trying out a new delivery service. Môshe spent some time evaluating the IE-only web interface, and was curious about some JavaScript errors he was getting. Strangely, he noticed variables named dateSQL, newSQLTag, and modeSQL.
Môshe dug a little deeper, probably thinking that his suspicions couldn't possibly be correct, only to find sendLinkVal() in the page's code:
function sendLinkVal(theDate,theStatus,MainTitle,PageTitle){
var dateSQL = " AND J.JBDeliveryDate=''" + theDate +
"''"
var status = ""
var newSQLTag =""
var PageTitle = PageTitle
var MainTitle = MainTitle
//alert(dateSQL)
switch (theStatus){
case "Confirmed":
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
" + theDate + "'' AND J.JBConfirmed=''Yes'' AND
J.MIStatusCode<>5" + modeSQL + " AND
(ISNULL(J.JBCancelled, 0) <> 1) ORDER BY
Convert(int, J.MIJobID)"
break;
case "Unconfirmed":
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
" + theDate + "'' AND J.JBConfirmed=''No''" +
modeSQL + " ORDER BY Convert(int, J.MIJobID)"
break;
case "Complete":
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
" + theDate + "'' AND J.MIStatusCode=5" +
modeSQL + " ORDER BY Convert(int, J.MIJobID)"
break;
case "Unconformed":
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
" + theDate + "'' AND (J.MIConformance IS NOT NULL
AND J.MIConformance<>'''') " + modeSQL + "
ORDER BY Convert(int, J.MIJobID)"
break;
case "NoDelDate":
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
dateSQL =" GlobalJobStatusView AS J WHERE J.JBDeliveryDate
IS NULL " + modeSQL + " ORDER BY Convert(int, J.MIJobID)
"
break;
case "Collections":
// the dateSQL is not required so set it to nothing so that it
// doesn't interfere with the sql being generated at the end of
// the function.
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
" + theDate + "''" + modeSQL + " ORDER BY
Convert(int, J.MIJobID)"
break;
case "Deliveries":
// the dateSQL is not required so set it to nothing so that it
// doesn't interfere with the sql being generated at the end of
// the function.
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE J.JBDeliveryDate=''
" + theDate + "''" + modeSQL + " ORDER BY
Convert(int, J.MIJobID)"
break;
case "ColAndDel":
// the dateSQL is not required so set it to nothing so that it
// doesn't interfere with the sql being generated at the end of
// the function.
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE ((J.JBDeliveryDate=''
" + theDate + "'') OR (J.JBCollectDate=''" +
theDate + "''))" + modeSQL + " ORDER BY
Convert(int, J.MIJobID)"
break;
case "Subcontractor":
// the dateSQL is not required so set it to nothing so that it
// doesn't interfere with the sql being generated at the end of
// the function.
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " JobAndLoadView AS J WHERE (J.JBDeliveryDate=''
" + theDate + "'') " + modeSQL + "
ORDER BY Convert(int, J.MIJobID)"
break;
case "Cancelled":
// the dateSQL is not required so set it to nothing so that it
// doesn't interfere with the sql being generated at the end of
// the function.
dateSQL= ""
var modeSQL = ""
modeSQL = " AND (J.JBCompanyID=31337) "
status = " GlobalJobStatusView AS J WHERE (J.JBCollectDate==''
" + theDate + "'') " + modeSQL + " AND
ISNULL(J.JBCancelled, 0) = 1 ORDER BY Convert(int, J.MIJobID)"
break;
default : status ="";
}
newSQLTag = dateSQL + status;
document.all.hiddenForm.linkVal.value = newSQLTag;
document.all.hiddenForm.PageTitle.value = PageTitle
document.all.hiddenForm.MainTitle.value = MainTitle
document.all.hiddenForm.submit();
//alert(newSQLTag)
}
Môshe could replace his customer ID with any other and access customer data, and for that matter, to modify or delete whatever he wanted. He could add or remove columns to tables. He could possibly even change permissions, add his own database user and deny all other users access.
Shocked, Môshe called the delivery service, who got him in touch with the developer of the system. This developer was equally shocked to learn that it was even possible to view a web page's JavaScript code, let alone that his architecture was open to SQL injection attacks from virtually any angle. He took immediate and decisive action; all queries were moved to the .NET backend.
Of course, the queries still didn't use parameters and are therefore still open to SQL injection, but now it takes slightly more effort to hack.
 [Advertisement]
Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
| | Friday, May 22nd, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Error'd: April is Special, and so are you "April is special," writes
Elwin. It is, but take heart May, every month is special at TDWTF.

"Admiral Ackbar is pinterested," punned
The Beast in Black

Manuel H. clocked something off on this website.
"Noon seems to be very late in Lithuania, or maybe
only in this hotel restaurant in Vilnius." 15H AM must be on some planet with a 32H day.

"Amazon can't make up its mind!" ranted an anon.
"Do I need to wait 2 business days or 3?
Make up your mind Amazon!"

Duston decided to close us out with a pun.
"Looks like they have a problem, but it's trivial." Well done.

 [Advertisement]
Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
| | Thursday, May 21st, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: In the Know Delilah works in a Python shop. Despite Python's "batteries included" design, that doesn't stop people from trying to make their own batteries from potatoes. For example, her co-worker wrote this function:
def key_exists(element, key):
if isinstance(element, dict):
try:
element = element[key]
except KeyError:
return False
return True
Python, of course, has an in operator. key in dictionary is an extremely common idiom. There's no reason to implement your own. Certainly, there's no reason to re-implement it by catching and throwing exceptions.
This is ugly, stupid, and bad. It gets worse, though, when you see how it gets used.
for key in old_yaml_data:
if key in new_yaml_data:
if old_yaml_data[key] != new_yaml_data[key]:
temp = new_yaml_data[key]
new_yaml_data[key] = merge(new_yaml_data[key], old_yaml_data[key])
if key_exists(new_yaml_data[key], 'image') and key_exists(old_yaml_data[key], 'image'):
new_yaml_data[key]['image'] = temp['image']
elif key == "databases":
revert_db_tags(new_yaml_data[key], temp)
This code is attempting to upgrade "old" YAML data with "new" data. So it's basically merging dictionaries, which is a great case for the in operator.
And they use the correct idiom on the second line there! This was written by one developer! They do the standard key in new_yaml_data check. And they also use key_exists. I can only assume that they had a stroke between starting and finishing this script, which I'll note is, in total, 48 lines long.
Here's the whole short script, which is just generally a mess. Slapped together Python code that's trying to be a "smarter" shell script, but is definitely written with the elegance of hacked-together-bash.
import sys
import yaml
from jsonmerge import merge
appHomePath = sys.argv[1]
oldValuesYAML = appHomePath + "values.yaml"
newValuesYAML = appHomePath + "/upgrade_version/values.yaml"
with open(newValuesYAML, 'r') as f:
new_yaml_data = yaml.load(f, Loader=yaml.loader.FullLoader)
with open(oldValuesYAML, 'r') as f:
old_yaml_data = yaml.load(f, Loader=yaml.loader.FullLoader)
def key_exists(element, key):
if isinstance(element, dict):
try:
element = element[key]
except KeyError:
return False
return True
def revert_db_tags(old_yaml_data, new_yaml_data):
dbList = ["mongoDB", "postgresDB"]
mongoDbTagsToRevert = ["mongoRestore"]
mongodbKeysToDelete = []
postgresDbTagsToRevert = []
for db in dbList:
old_yaml_data[db]['image'] = new_yaml_data[db]['image']
for mongoDbTag in mongoDbTagsToRevert:
old_yaml_data['mongoDB'][mongoDbTag]['image'] = new_yaml_data['mongoDB'][mongoDbTag]['image']
for mongoDbTag in mongoKeysToDelete:
del old_yaml_data['mongoDB'][mongoDbTag]
for postgresDbTag in postgresDbTagsToRevert:
old_yaml_data['postgresDB'][postgresDbTag]['image'] = new_yaml_data['postgresDB'][postgresDbTag]['image']
for key in old_yaml_data:
if key in new_yaml_data:
if old_yaml_data[key] != new_yaml_data[key]:
temp = new_yaml_data[key]
new_yaml_data[key] = merge(new_yaml_data[key], old_yaml_data[key])
if key_exists(new_yaml_data[key], 'image') and key_exists(old_yaml_data[key], 'image'):
new_yaml_data[key]['image'] = temp['image']
elif key == "databases":
revert_db_tags(new_yaml_data[key], temp)
with open(newValuesYAML, 'w') as f:
data = yaml.dump(new_yaml_data, f, sort_keys=False)
[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!
| | Wednesday, May 20th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Find a Bar for This One A depressing quantity of software is what I would call a "data pump". I have some data over here, and I need it over there. Maybe I'm integrating into a legacy app. Or into an ERP. Or into a 3rd party API. At the end of the day, I have data in one place, and I want it in another place.
Sally has a Java application written in the Quarkus framework, which has a nightly batch that works to keep a table of Bar entities in sync with a table of Foo entities. (This anonymization comes from Sally) These exist in the same database. There is also a Bar webservice, which provides information about the Bar entities. The workflow, such as it is, is that the software needs to find all of the Foo entities that do not currently have associated Bar entities, and then call the Bar webservice to get the required information to create those Bar entities.
Let's see how that works.
@Inject UserTransaction transaction
public List<FooData> getAllFoos() {
try{
return fooDataRepository.findAllFoos();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
We'll worry about that comment in a second, but this function returns a list of all of the Foo objects in the database. It does not return a list of all the Foo objects without associated Bar entities. It's just the whole giant list of everything. The underlying database is a standard relational database; it'd be trivially easy to write that query, even going through the ORM.
Well, that's bad, but it's all pretty minor. How does the actual update go?
Message updateBarsWithFoos() {
List<FooData> foos = getAllFoos();
if(!foos.isEmpty()){
foos.forEach(foo -> {
try{
transaction.begin();
if(barRepository.findByName(foo.getName()) == null){
if(barDataService.searchByName(foo.getName()) != null && barDataService.searchByName(foo.getName()).marker() != null){
barRepository.createBar(barDataService.searchByName(foo.getName()));
}
}
transaction.commit();
} catch (Exception e) {
try {
transaction.rollback();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
});
}
return new Message(MessageLevel.INFO, "Created bars")
};
Ah, the real WTF is that it's an Oracle database. That's always a WTF.
But let's trace through this code.
We get all of our Foo entities. We check for emptiness and then do a forEach, which seems to make the empty check superfluous: a forEach on an empty list would be a no-op anyway.
We start a transaction, then check the database: if there are no Bar objects that link to Foo, then we call into the barDataService to find data. If there is, we call into the service again, to see if the marker property is not null. If it is, we call into the service again to get the actual data we're putting into the database. Then we close the transaction. If anything goes wrong, we rollback the transaction and chuck an exception up the chain.
That is three web service calls inside of a database transaction. Three calls which could easily be one, and that call could easily also happen outside of a transaction if you're mindful about confirming your constraints. And of course, because they're not mindful at all, they need to manage the transaction directly, and can't use the @Transaction annotation provided by their framework, which would at least cut down on some of the boilerplate.
Now, I'm sure you'll be shocked - shocked - to learn that the webservice is actually a bit flaky, and thus times out from time to time. And this isn't the only batch job running, which means the long-lived transactions cause all sorts of contention and terrible performance across the various batches. And this app doesn't have its connection pool properly configured, so the entire software stack can exhaust all of its database connections surprisingly quickly, causing yet more failures.
The root of the WTF, of course, is doing this as a batch job. A well engineered application would do everything it could to not create data in the database that isn't referentially sound. There, Sally gives us the one bit of good news:
My current project will do away with the batch processing altogether, so we can say, "RIP, transactional wholesale triple caller!"
 [Advertisement]
Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready. Learn more.
| | Tuesday, May 19th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Three Digit Acronyms JB has a database table that, at first glance, looks like one of those data warehouse tables that exists to make queries performant. You know the sort, the table that contains every date between 1979 and 2050, or every number out to 1,000,000 or something. It looks dumb, but it helps make certain joins and queries performant.
The database table is called three_alpha_numerics. It has two columns: digit, which contains three characters, and is_numeric, which is a a single character: 'Y' or 'N'. It looks roughly like this:
+-------+------------+
| digit | is_numeric |
+-------+------------+
| 009 | Y |
+-------+------------+
| 00A | N |
+-------+------------+
So, for example, if you wanted all the possible numeric triples, you could SELECT digit FROM three_alpha_numerics WHERE is_numeric = 'Y', which is obviously the easiest thing one can imagine.
So what is this for? Well, it's used by a stored procedure that generates unique IDs. That stored procedure does a left join against another table to find all the unused digits. And here's the real gotcha: that stored procedure only ever uses the rows where is_numeric is Y, meaning the vast majority of the data in this table is never used.
Unique IDs, of course, are an incredibly difficult task for databases to do, so it absolutely makes sense that we create a system that allows us to only have 1,000 unique IDs. That's more than 640, which should be enough for anyone. Having many thousands of unusable alphanumeric triplets is just the cost we have to pay.
 [Advertisement]
BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!
| | Monday, May 18th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Representative Line: Dating Backwards Another representative line, and this one comes from an Excel spreadsheet. But, per Remy's Law of Requirements gathering ("No matter what the requirements doc says, what your users wanted was Excel"), this one was actually written by a developer. A developer who didn't understand how Excel works, but more important, didn't understand how dates worked either.
This comes from Ulysse J.
=CONCATENER(SI(MOIS($A18)>9;ANNEE($A18)-2000;(ANNEE($A18)-2000)*10);SI(JOUR($A18)>9;MOIS($A18);MOIS($A18)*10);JOUR($A18))
Now, the first thing: Excel function names are locale specific. This was written in France, so the functions are French. CONCATENER is "concatenate", SI is "if", MOIS is "month", and so on.
The purpose of this function is to convert a field (cell A18) in DD/MM/YYYY into YYMMDD. So how does it do this?
Well, we check the month. If it's greater than 9, we output the year minus 2000. If it's less than 9, then, we output the year minus 2000, multiplied by 10. That is to say, August, 2026 would start by outputting 260. We repeat this logic for the days: if the day is larger than 9, we output the month, otherwise we output the month times 10. Finally, we output the day.
This is attempting to do padding. There's just a problem. Imagine February 1st, 2009- an actual date in the document. We convert the year into 90, the month into 20, rendering the date as 90210. That's incorrect. And once we get to 2100, if there is still an Excel in 2100 (I joke: of course Excel will still exist in 2100. Humanity won't, but the robots will use Excel), this will also break. Not that it matters- I mean, YYMMDD doesn't make sense by that point.
Obviously, the correct solution is to use Excel's rich, built-in formatting functions to convert between date formats. It's easy! But Ulysse raises another point:
Extra points: even if you do not know how to do proper [formatting], the input format is guaranteed to have correct padding. I would just concatenate parts of it (treating dates as text is bad, but still less bad than treating them as integer triplets).
I will say this: I know a software developer wrote this, because your average Excel user could easily write bad formulas, but never bad in this kind of convoluted way. You need a real expert to do something this bad.
 [Advertisement]
ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
| | Friday, May 15th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Error'd: Balmenach Bad Gateway Single Malt "Winner ad placement!" snarked our
Peter G.

Errors on this website are always a shoo-in for the weekly column.
An anonymous reader wrote
"I got error 500 when I tried to submit an
Error'd. Please make the file uploader check if the attached
file is within the file upload limit, which I think
is less than 4 MB." They shared an audio error'd which may be coming along next week.

"Give us feedback - wait, did it work at all?" confused poor
I_Absolutely_Want_To_Give F.
"As every good service management company, ServiceNow wants feedback, above
all."

"0 minutes does not equal 0 seconds..." sagely summarized
Daniel D.
"Claude like floors. I mean floor. But maybe ceil would
be better applicable to this calculation, right?"

Finally, this one is a real novelty,
from
Adam R.
Is the label actually 27
years old? It certainly could be; Error 502 is a good bit older. But I think this would be our oldest Error'd yet. Adam explained:
"This appears to be a real auction for a whiskey
bottle whose label does, in fact, say Error 502 Bad
Gateway on it. The winning bid: £130. Source: https://www.scotchwhiskyauctions.com/auctions/228-the-179th-auction/876095-balmenach-1998-27-year-old-error-502-bad-gateway-thompson-bros/"

| | Thursday, May 14th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
The Pride Goeth Janči, a master's student of bioinformatics, was seated near the back of a large classroom. This was a simple compulsory elective course geared toward biologists. The professor was currently walking the class through their latest assignment. "We'll need to connect to some Linux servers," he announced.
The other students seated nearby traded blank stares. They were all Mac and Windows users with no IT background. Meanwhile Janči, a veteran Linux user, started feeling a little smug. An easy A was at hand.

"First," the professor continued, "you'll need a private key."
After the professor had explained a few details, the first WTF came in the form of a bulk email sent to the entire class. The private key was attached. The username was the email address it was sent to.
What do you call the exact opposite of a private key? Janči wondered, bemused.
"You'll also need to download an application to help you log in," the professor said. "I recommend MobaXterm."
As he detailed the process of visiting the SSH client website to download the software, Janči tuned out. He didn't need such hand-holding. He accessed OpenSSH, tried connecting ...
... and failed.
Meanwhile, everyone around him was logging in no problem.
Janči's face burned with embarrassment at this second WTF. His first instinct was to blame the deprecated cryptography of the server. He spent most of the remaining lecture time searching for a way to allow his SSH to use SSH-DSS. (It turned out to be supported the whole time, despite the warnings he received.)
Janči then tried to re-download the "private" key and adjust the SSH config file several times. He cycled through different possible usernames associated with his university email account.
No dice.
He was the only person in the class who hadn't yet logged into the server. Not even the professor was able to help him, since he was using Linux.
Embarrassment and frustration mounted. An hour later, out of ideas, Janči fell back to downloading MobaXterm and running it inside Wine.
It didn't work.
The professor offered him a spare Windows box. "Here, try this one."
Janči booted it up, copied the "private" key to the new machine ... and still couldn't sign in.
Now, this was getting suspicious.
The lecture ended. A friend of Janči's hung back while the rest of the students filed out. "Why don't you try logging in with my credentials instead of yours?" she asked.
Janči was up for anything at that point.
It worked. On his own machine, on the Windows box, everywhere.
With that lead in mind, Janči opened the server's /etc/passwd file to look at all the usernames. He noticed that, unlike everyone else, his username and email address didn't match.
His university used Microsoft emails. Everyone had several address aliases, and they could also use whatever email address they liked in the system, even a personal one.
Janči had chosen to use a school email in the form of <number>@uni.uni. Unfortunately, the Ubuntu server didn't like the idea of user being named just <number>, so it had renamed it to user<number>. Some script for generating SSH configuration had probably failed from there, because Janči also discovered that his user home directory was missing a .ssh directory and known_hosts file.
Unfortunately, due to restricted access, he wasn't able to copy them from any of his classmates. In the end, he could connect to the server as any of his classmates, but not as himself.
| | Wednesday, May 13th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
CodeSOD: Over and Under Reaction Today's anonymous submitter sends us two blocks. The first is a perfectly normal line of React code:
const [width, setWidth] = useState(false)
This creates a width variable, defaulting it to false, and a setWidth function, which lets React detect when you change the variable, and trigger a re-render. Importantly, this mutation only happens on the next render, which means if you call setWidth and then check width, you won't see your change happen.
As I said, this is perfectly normal React code. Well, almost. First, I have to ask: why on Earth is width being set to a boolean value? "How wide are you?" "Yes." It's possible that there's a good reason for this, though I suspect that it's unlikely.
The second issue, however, is that the linter complained that the setter was never actually used. That was odd, because if our submitter grepped the codebase, there were two calls to setWidth. Let's see what that looked like:
const show = (show) => {
setWidth(show)
setWidth(!show)
}
We create a function show, where we expect a boolean value, and then we setWidth with that value, and then with the negation of that value. So show(true) will set width to be false. To make matters more confusing, we set width both ways, and I assume this is someone trying to get around React's state management. React won't trigger a re-render if you set the state to a value it already has. So I suspect they're twiddling to try and force it to re-render, and I also suspect that this might not work? Even if it does, this isn't how you should be using React. As I said, I'm no React expert, but as the saying goes: "I don't have to be a helicopter pilot to know that when I see a helicopter hanging upside down from a tree someone messed up."
Our submitter writes:
Got hired to cleanup a mission critical website for a company that had just learned that offshore teams might not be worth the cost saving measures.
"Pay me now or pay me later."
 [Advertisement]
ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
| | Tuesday, May 12th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Representative Line: Underscore Its Unimportance Frequent submitter Argle (previously), sends us a short little representative line. The good news is that this line of code came across Argle's screen during a code review: it was being removed. The bad news is that it was sitting in the code base for ages.
_ = len / 8.0f;
Argle writes:
In a code review today. A co-worker wisely removed the line. Dunno the logic that made anyone write it in the first place.
This is C#, though it could be basically any language. Using _ is one of those little conventions that we use to tell the linter to ignore the fact that this variable isn't used. And this variable was not being used. Of course, in addition to being unused, it's also a puzzle: where does the 8.0f come from? No one knows. Why would we even want the length divided by eight? No one knows. There's nothing about this code that gives any indication that it was a meaningful operation at any point.
No one knows what it does, or why it was there in the first place, but someone put the time into making sure the linter didn't complain about its uselessness by using _ as the variable.
 [Advertisement]
Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready. Learn more.
| | Monday, May 11th, 2026 | | LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose. |
| 6:30 am |
Representative Line: A Solid Reference Today's anonymous submitter works for a large company. It's one of those sorts of companies which has piles, and piles, and piles of paperwork and bureaucracy. It also means that much of their portfolio of software is basic CRUD applications. "Here's a database for managing invoices." "Here's a database for managing desk assignments." "Here's a pile of databases which link our legacy applications to our new ERP system."
Which brings us to our representative line. It is not a representative line of code, but a representative line of the design specification. This is the design specification for yet another database-driven application.
7.7 REFERENTIAL INTEGRITY CONSTRAINTS
Referential integrity constraints are not applicable for [REDACTED] Application.
Upon seeing this, our submitter predicted that they'd be having a lot of TDWTF submissions in their future.
The worst part? This isn't the only time this has been included in the design spec. Several database driven applications have had this line in their spec. No one is able to explain exactly why referential integrity constraints are not applicable. At best, there are a few batch jobs that don't define a schema themselves, though they need to comply with it. Maybe someone is just copying and pasting from an old design spec and hoping no one notices or cares?
Good news: it's likely that no one will notice, or care. At least not until something breaks in production.
 [Advertisement]
ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
|
[ << Previous 20 ]
LJ.Rossia.org makes no claim to the content supplied through this journal account. Articles are retrieved via a public feed supplied by the site for this purpose.
|