Reading view

There are new articles available, click to refresh the page.

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() ) :
    // Start the Loop.
    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 Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

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.

Facebook dislike

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.

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/"

43d63150fa7d48d3a7998e14e111c211

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"?)"

561594f875db486085450afbb4f65a4e

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

8eefa2a1182146b3b595a3fbbfef5012

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

73c8b26e71ed4518bbdfeacc9850629f

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

d0e6feb93e324e509946643027ddbc5e

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: 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.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

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.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

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:

Killer whales (Orcinus orca) spyhopping to locate a crabeater seal (Lobodon carcinophaga) on an ice floe in Antarctica.

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'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.

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.

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.

ef33dacc82c1495bbc2c68cf30461f3c

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

0b5ff0ba77cc480cb3c0a6ca91ef10b6

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.

18d8b28ac37243708f1f4711be97cebf

"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!"

abc72aa0987b4e84816906e2b598dc11

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

a821a18e000c4152a327d79dd2a05744

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

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 Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: 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
// If this is annotated with @Transaction the usage in the Message function down below will have some Thread exception
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?

// Can't be annotated with @Transaction because Oracle DB can handle the given Amount of dataEntities in one Transaction '\._./'
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.

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!

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.

Error'd: Balmenach Bad Gateway Single Malt

"Winner ad placement!" snarked our Peter G.

cc79d61a927848f48b2a41988ebf8c5d

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.

fec797b9fc3642a5b940675292dc764e

"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."

3d6b7629ef5a4c90bafa3f2e6a21f663

"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?"

76dd1834b8394e5ebca32229fa87fb7e

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/"

1f77b40a37f24eabbac33a8de3aee9a7

[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.

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.

Roman key (FindID 519853)

"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.

[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: 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.

CodeSOD: Please Find, Rewind

As previously discussed, C++ took a surprisingly long time to get a "starts with" function for strings. It took even longer to get a function called "contains". In part, that's simply because string::find solves that problem.

Nancy sends us a… different approach to solving this problem.

bool substringInString(string str, string::iterator &it)
{
  string tmp;
  bool result = false;
  int size = str.length();

  int count = 0;
  while (count < size)
  {
    tmp += *it;
    it++;
    count++;
    if (tmp.find(str) != string::npos)
    {
      result = true;
      it -= size;
      break;
    }
  }

  if ( !result)
  {
    it -= size;
  }

  return result;
}

This function iterates across a string, character by character. In this iteration, we copy one character at a time into tmp. Then we see if tmp contains our search str. If it does, we break out of the loop after rewinding the iterator. Outside of the loop, we check if we found the substring, and if we did, we rewind the iterator. Then we return true or false based on whether on not we found the substring.

So wait a second. str is our search string. it is where we're searching. And we copy from it up to our search string's length into a temporary string. We then do a find in that temporary string- hey! This is just a startsWith check written in the most insane way possible.

Why even bother with the while loop? While tmp is shorter than the search string, the answer is always "no, we haven't found it". And the developers knew that- that's why they always rewind size characters on the iterator. They're always searching exactly that many characters. Of course, since we always rewind the same amount, we can also just move the it -= size statement out of the loop and out of the if statement and do it once.

Nancy calls this "a little gem" in a "large codebase". Yeah, a real gem.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Not for Nullthing

Today's anonymous submitter sends us some code that just makes your mind go… blank when you look at it.

	public static boolean isNull(String value) {
		return StringUtils.isBlank(value);
	}

StringUtils.isBlank comes from the Apache Commons library. It's a helper function for Java which returns true if a string is, well, blank. "Blank" in this case is: empty, null, or only whitespace. So it's important to note that isBlank may return true on a null, but it isn't truly a null-check, so wrapping it in isNull is just confusing.

But imagine I've got another problem. Let's say I have a database that's been poorly normalized and maintained. And so I have a bunch of fields that maybe are null, but some also maybe contain the string "null". What am I going to do then? I need another function.

	public static boolean isNullAndNull(String value) {
		return isNull(value) && "null".equalsIgnoreCase(value);
	}

Ah yes, isNullAndNull, the clearest and easiest name I could imagine for this. It tells me exactly what the function is checking: is it null, and is it also null? We add a second check to our isNull call- we check if the input value matches the string "null". Except we're &&ing the conditions together. So this function will always return false. It can't both be blank and contain the string "null".

Which means Jennifer Null, who is a real person, can breathe easy. This version of a null check won't think she's nothing.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Empty Pockets

If you've seen one developer recounting how their AI agent deleted production, you've seen them all. They're mostly not interesting stories. It's like watching someone speeding through traffic on a motorcycle without a helmet: the eventual tragedy is sad, but it's unsurprising and not an interesting story to tell. It's not even interesting as a warning: the kind of person who speeds on a motorcycle without a helmet isn't doing so because they don't understand the danger. They've just decided it doesn't apply to them.

But the founder of PocketOS, Jer, recently shared how- whoopsie!- their AI agent deleted production. There's a lot of ingredients that go into this particular disaster, which I think makes it interesting, because the use of a poorly supervised AI agent is only one ingredient in this absolute trainwreck of a story.

PocketOS is a small company that makes software for rental companies to manage reservations. Car rentals are a big customer, but the tool is more general than that. They manage all of their infrastructure via a service called Railway. Railway is a pretty-looking GUI tool for automating your deployments and the target environments.

PocketOS also is heavily adopting Cursor wrapping around the Claude model. They've paid big bucks for the top-end model offered. Many of their components, like Railway, offer MCP services so that their LLM can do useful things. They're using the Claude LLM to automate as much as they can.

So far, this is all a pretty typical setup. They pointed Claude at their code and gave it a "routine" task, and sent it to work. It toddled through the problem and encountered a credential issue. It "decided" that the fix for this issue was to delete a storage volume and recreate it. It scanned through the code to find a file containing an API key, found it, and then sent a POST request via cURL to delete the volume in question.

Jer writes:

To execute the deletion, the agent went looking for an API token. It found one in a file completely unrelated to the task it was working on. That token had been created for one purpose: to add and remove custom domains via the Railway CLI for our services. We had no idea — and Railway's token-creation flow gave us no warning — that the same token had blanket authority across the entire Railway GraphQL API, including destructive operations like volumeDelete. Had we known a CLI token created for routine domain operations could also delete production volumes, we would never have stored it.

Wait, the tokens you create in Railway all have god-level privileges? That sounds like a terrible idea. And you were storing the token in your code? We'll come back to this in a moment, but sure, this is bad, but you can just restore from backup, right?

The volume was deleted. Because Railway stores volume-level backups in the same volume — a fact buried in their own documentation that says "wiping a volume deletes all backups" — those went with it. Our most recent recoverable backup was three months old.

Oh. Oh no.

Now, I don't think it's literally true that Railway is storing your backups literally in the same volume as the thing they're backing up. I certainly hope not. But they do apparently delete your backups when you delete the volume associated with them. Which is a choice, certainly. A bad one. And one that they documented, according to Jer. It was, in his words, "buried" in the docs.

But let's go back to the tokens for a moment. I am not a Railway user, but I checked out the tool and went through the process of creating a project token. And while no, Railway does not give you big red flags warning you "Hey, this token can do ABSOLUTELY ANYTHING", it also never gives you an opportunity to scope the token. Which, I don't know about you, but the first thing I do when I create an authentication entity is try and figure out how to control its authorizations, because I assume at the start it doesn't have any. That'd be sane.

The scoping happens when you create the token, depending on what context you're in when you do it. It's only a handful of scopes, and no fine grained permissions on API keys at all. The lowest level is "Project" which can do anything to a single environment- which does mean that even if you, like Jer's team, wanted to have a script that changed some DNS settings in production, that same key could be used to delete volumes in production. Which means you really really want to take care of that key, and you certainly don't want to leave it where some junior developer or bumbling AI agent can find it.

Jer also complains that Railway shouldn't allow an API call to take destructive actions without more protections, like forcing someone to type in the name of the thing being deleted or sending a confirmation email, or something. This, I'm more skeptical of. Most cloud providers don't offer anything like this in their APIs, at least that I've seen, because on a certain level, if you're invoking the API with the proper credentials, that's a big enough hill to climb that we can assume you've intended your action. The correct way to protect against this is properly scoped keys and keeping those keys secure and not just lying around in plain text. There's a certain aspect of understanding that you're using a potentially dangerous tool and need to take the responsibility for safety into your own hands; while a table saw can easily take some fingers off, it's perfectly safe when used correctly.

This is all bad, but how can we make it worse? Well, Jer demanded that Claude "explain itself". In a section called "The Agent's Confession", Jer highlights that the agent is able to identify the explict rules that it failed to follow.

Read that again. The agent itself enumerates the safety rules it was given and admits to violating every one. This is not me speculating about agent failure modes. This is the agent on the record, in writing.

No, it is not the agent on record. I see this kind of thing a lot when people talk about LLMs. An LLM cannot explain its reasoning. It cannot go on "the record". It cannot confess to anything. While what it plops out when asked might be interesting, it is not an explanation. The only explanation is that it's a powerful statistical model trying to create a plausible string of tokens! It's simply looking at its context window and your prompt and trying to predict what it should say. It can tell you what rules it violated not because it understands the rules or knows it violated any rules, but because those rules are in its context window. If you ask it right, it'll confess to killing JFK and framing Oswald for the crime.

Jer then tries to ensure that Cursor takes some of the blame, pointing to Cursor's "guardrails" documentation. Except, here, the documentation is actually quite explicit about what those guardrails guarantee. If you're using a first-party tool, it will prohibit unsafe operations. When using 3rd party MCPs, like Railway's, the only guardrail is that it requires human approval for every action- unless you update your allowlist for that MCP. If you put them in your allowlist, the guardrails go away. Jer argues that tools should enforce more protection against LLM behaviors, but the problem with that is people- like the PocketOS team- turn those protections off. And like a lot of safety mistakes, they can get away with it all the way up until the point where they can't.

Jer follows this by listing off a pile of other times using Cursor has caused disasters, which isn't making the argument he thinks it is: yes, Cursor is dangerous, but those dangers are well known. It makes the choice to turn Cursor loose without strict supervision seem even more foolish.

Jer writes:

For now I want this incident understood on its own terms: as a Cursor failure, a Railway failure, and a backup-architecture failure that all happened to one company in one Friday afternoon.

It's also a PocketOS failure. It's a failure to properly assess the tools and environments you chose to use for your product. A failure to read and understand the docs for vital features, like *backups*. A failure to employ even the most basic safeguards. A failure to put a second's thought into key management- even if that key was only for DNS entries, you still shouldn't chuck it in source control. A failure to have a competent backup strategy. It's worth noting that they did restore from a three month old backup, which means they were at one point taking backups outside of Railway's volume setup. That was a wise decision. That they stopped is a failure.

The first rule of disaster retrospectives is that it's never one piece that's the failure. It's never one person's fault, one tool's fault, one vendor's fault. It's a systemic failure. Railway's keys should be finer grained. But also, you shouldn't leave keys lying around. Deleting backups when you delete the volume is a terrible idea, but having only one service for backups (that's also your primary site) is a terrible idea. Claude's ability to enforce its own guardrails should be better, but LLMs are notoriously dangerous about this: you should know better, and by your own words you did.

This is not an anti-AI post, or even a "get a load of this asshole" post. It is a "understand the damn tools you're using" post. Be critical of them. Don't trust them. Ever. Especially LLMs, because the worst part of an LLM is that it takes away the one thing computers used to be good at: predictable, deterministic behavior. But not just LLMs: don't trust your cloud provider, don't trust your infrastructure manager. Dig into them and understand how they work, and if they seem to complicated to understand, than they may be too complicated to trust.

Update: As pointed out in the featured comment below, Railway did finally get a backup restored. So they got their data back. Yay? From the post, Jer remains committed to making this a Railway issue and not a PocketOS issue.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Parametric Projection

Roger C. gets on second base with an unforced error. "Not only is the content too large, the error message informing us of this is also too large to fit the visible space. A layered, double WTF."

782b1790d9d549d6a8acf4045669d7a6

"AWS Spellcheck Fail!" alerts Peter "If only someone at AWS knew the correct paramters to activate the spellcheck."

ee85e87fd7cb4cc2ac3038cb9f97ccf8

"How long is too long for a job to be open? " wonders Lincoln K. "I didn't even know LinkedIn existed 61 years ago, let alone was accepting postings... Though only 81 applicants in that time is hardly an impressive turn-out." For a "Vice President Operations and Quality Control", no less.

1c3d4b06a37e4119b62dc39bad29b9a3

An anonymous Richard reports "This came through my door. On a card that, in order to get to my door, had my full address printed on it, including my ."

9df5e07d210846f08dc925105f19b64b

Oenophile Abroad Michael R. shares "My Macbook broke after being "exposed" to red wine. As a German in London it pleases me so see that the repair shop offers this time granularity."

a9f634b888de4927babb91d7d2920579

[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.

CodeSOD: Cancel Catch

"This WTF is in Matlab" almost feels like cheating. At one place I worked, somebody's job was struggling through a mountain of Matlab code and porting it into C. "This Matlab code looks like it was written by an alien," also doesn't really get much traction- all Matlab code looks like it was written by an alien. This falls into the realm of "Researchers use Matlab, researchers may be very smart about their domain, but generally don't know the first thing about writing maintainable code, because that's not their job."

But let's take a look at some MatLab Carl W found:

    try
        if (~isempty(fieldnames(bigStruct)) && isfield(bigStruct,'pathName'))
            [FileName, PathName] = uigetfile(bigStruct.pathName);
        else
            [FileName, PathName] = uigetfile(lastPath); %lastPath holds previous path
        end
    catch
        bigStruct = struct;
    end

The uigetfile function opens a file dialog box. When the user selects a file, FileName holds the filename, PathName holds the containing path. If the user doesn't select a valid file, or clicks "Cancel", both of those variables get set to 0. It's then up to the caller to check the return value and decide what happens next.

Which is not what happens here, obviously. The developer responsible seems to believe that it maybe throws an exception? And they can just catch it? Carl's best guess is that this is a "weird" way to catch the cancel button. But it does mean that FileName and PathName get set to 0, and those zeros propagate until something finally tries to open those files, at which point everything blows up and the user doesn't know why.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

A Whale of a Problem

From our Anonymous submitter:

Our company creates graphs to visualize data. We have many small fish customers, but we have one whale who uses our product that is 90% of company revenue. (WTF number 1.)

So if he is not happy, it's all-hands-on deck-mode.

He complained that our APIs and charts are loading slowly for him. For 3 weeks, we've tried a TON of optimizations, including WTF 2: spinning up a special server he alone can hit.

Today, we found out that he's always complaining when he's in his car, driving from home to the office. But since he "totally has the best wifi money can buy," that isn't worth investigating.

WTF 3: thinking wifi and data are always 100% reliable in a car driving around.

Humpback whale breaching in Ballena Marine National Park

Our submitter highlights one of the major pitfalls of the so-called whale client: if they're a bad client, you're in for an extra-bad time.

As I lean harder into freelancing, I'm learning to scan the waters ahead of me for potential whales. My goal is to build up multiple small, diverse income streams, because I've had my own dangerous encounters with whales in the past.

At one employer of mine, there was Facebook, who acted as if they were our new owners rather than a new customer. They'd already produced flashy marketing videos of the sorts of solutions they planned to implement with our software, showing people delighted with the results. In meetings, these things were talked up as amazing game-changers. Meanwhile, I found all the things Facebook wanted to do horribly creepy and invasive.

Even worse, Facebook began dictating how our award-winning technical support should change to accommodate their whims, up to and including having a dedicated toady—er, support rep—who did nothing but field Facebook-related tickets, similar to a technical account manager (TAM).

That was the last straw for me. I left that company before I was forced to deal with any of Facebook's crap.

My second whale sighting occurred at a startup that'd landed Porsche, far and away their biggest client ever. All of a sudden, our timeline for adding new features and fixing bugs became Porsche's honey-do list. All of a sudden, the platform frequently crashed and became unusable for everyone because it couldn't handle the amount of traffic Porsche (and their clients) hurled at it.

On the other hand, there were several times in that startup's existence when a big wad of promised funding failed to materialize. Porsche kept the business afloat and literally kept my lights on.

I find it less than ideal to be at any company's mercy. I want a world that would neither spawn whales nor millions of startups named Sploink, Dink, and Twangle that promise to bring the power of AI to your dinner fork.

Have your own epic whaling adventures? Share with us in the comments!

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Lint Brush Off

A few years back, C# added the concept of "primary constructors". Instead of declaring the storage for class members and then initializing them in the constructor, you can annotate the class itself with the required fields, and C# automatically generates a constructor for you. It's all very TypeScript and very Microsoft, and certainly cuts down on some boilerplate.

Esben B's team isn't really using them in many places, but they are using a linter which is opinionated about them. So this in-line constructor causes the linter to complain:

    public DocumentNetworkController(ILookupClient service)

The linter wants you to switch this to a primary constructor. Esben didn't want to do that, and didn't want to change the global linter configuration, and so added a pragma to disable that particular warning:

#pragma warning disable IDE0290 // Use primary constructor
    public DocumentNetworkController(ILookupClient service)
#pragma warning restore IDE0290

The linter didn't like this. It threw a new warning: that this suppression wasn't needed. Which was news to Esben, as clearly the suppression was needed if you wanted to make the warnings go away. The obvious solution was to disable the warning that you didn't need to disable the warning:

#pragma warning disable IDE0079, IDE0290 // Use primary constructor
    public DocumentNetworkController(ILookupClient service)
#pragma warning restore IDE0290, IDE0079

Except this doesn't work. These pragmas take effect on the next line, which means you can't disable IDE0079 on the same line as IDE0290 and expect it to work. Which means the final version of the code looked like this:

#pragma warning disable IDE0079 // Disable warning about not needed supression
#pragma warning disable IDE0290 // Use primary constructor
    public DocumentNetworkController(ILookupClient service)
#pragma warning restore IDE0290, IDE0079

Esben writes:

So the nice recommendation to use a primary ctor ended up with 3 lines of annoying boilerplate code. Good times \o/

While yes, this is frustrating, I will say there's an element of "when the table saw keeps taking fingers off, that may be more of a you problem." I don't know the details, so I can't say, "just change the linter config or adopt its recommendation" and claim that the problem goes away, but when the tool hurts you, it's a definite sign of one of two things: it's either the wrong tool, or you're using it wrong.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: The JSON Template

We rip on PHP a lot, but I am willing to admit that the language and ecosystem have evolved over the years. What started as an ugly templating language is now just an ugly regular language.

But what happens when you still really want to do things with templates? Allison has inherited a Python-based, WSGI application which rejects any sort of formal routing or basic web development best practices. Their way of routing requests is simply long chains of "if condition then invokeA elif otherCondition then invokeB". Sometimes, those conditions will directly set the MIME type on the HTTP response.

They do use a templating library called Mako for generating their responses. They use it for their HTML responses, obviously. They also use it for their JSON responses, generating code like this:

{
    "success": true,
    "items": {
        %for item in items_available.keys():
        "${item}": ${items_available[item]}${',' if not loop.last else ''} 
        %endfor
        }   
}

The %for and matching %endfor mark the Python code off, which generates JSON via string-munging, complete with the check to make sure we're not on the last iteration of the loop.

Like so much bad code, this offers a degree of fractal wrongness. Instead of iterating over the keys and fetching the items inside the loop, you could iterate for key,value in items_available.items()- and according to the Mako docs, that for is just a regular Python for loop. That we're just outputting the contents of the dictionary is itself potentially a problem- sure, if we know the types of the dictionary, we'll know that whatever it is can be output in the body of a JSON document, but do we really think this code is using type annotations? I don't. And for a RESTful web service, I'm always going to feel weird about using a success field when ideally the HTTP status code could convey most of that information (and yes, I know there are reasons to still put status in the body, I just hate it).

Of course, the real issue is just: Python's built in JSON serialization is actually pretty advanced. And performant! You don't need any of this, you could just do something like:

return json.dumps({"success": true, "items": items_available})

No templates. No formatting. No worries about how the data gets represented. Well, still worries, because JSON serialier will throw exceptions if it doesn't know what to do with a type. But then at least you get that exception on the server side and aren't sending the client a malformed document.

In any case, this is a good demonstration that you can write bad PHP in any language.

[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.

Error'd: April Showers

"RFC 1738 (and 3986) disagree" and so does Daniel D. "Reddit API has some weird app creation going on with lots of recently migrated and undocumented stuff. But having redirect URL set to localhost (or 127.0.0.1) usually works. Well, if you don't disagree with Sir Tim Berners-Lee about what URL is. Which Reddit does. hostnumber = digits "." digits "." digits "." digits". I'd file this one with all the websites that try to perform validation on email addresses, and get it wrong.

ad5bfafde9a74b7a8c38d429a364be48

"Why aren't we getting any resumes?" wondered Fred G. "This is a snippet from a job posting. I'm sure it worked perfectly when HR tested it."

2c21d5766e724b9095103c6c537adfa3

"Service required..." was Chris H.'s title for this gem. "My 2022 Chevrolet has been at the dealer for recall service for two weeks now, "waiting for parts". That doesn't stop GM from emailing every few days with a reminder that the car needs the recall service, and inviting me to schedule it at a dealer (that isn't actually a dealer) located a convenient 2500 mile drive from my home (about 200 times the distance to the dealer where the car currently sits), and providing a non-existent placeholder phone number to contact them at to schedule the recall service."

78cac2590ecf4996a2f4ee79e0b38b49

"How to subtly tell your customers that you don't wish to be contacted" explains Yuri. "The bank's staff must be wondering why no one wants to talk to them...Is it their suit's brand that is throwing everyone off? Can they blame it on COVID?"

81b84743c3a9405f8ed25c9c18b86029

"Bad money formatting by tax software" Adam R. complained. "I'm ashamed to admit it, but yes, I did pay Intuit money to file my taxes. This should really be a free service provided by the government, but, y'know, *lobbying*. You'd think that a business focused on tax preparation software would know how to properly format currency values, but in this case they failed to set the proper number of decimal points."

a9085ecfb2d2403ebd3d856e0c2a1179

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Tune Out the Static

Henrik H (previously) sends us a simple representative C# line:

static void GenerateCommercilaInvoice()

This is a static method which takes no parameters and returns nothing. Henrik didn't share the implementation, but this static function likely does something that involves side effects, maybe manipulating the database (to generate that invoice?). Or, possibly worse, it could be doing something with some global or static state. It's all side effects and no meaningful controls, so enjoy debugging that when things go wrong. Heck, good luck testing it. Our best case possibility is that it's just a wrapper around a call to a stored procedure.

This method signature is basically a commercila for refactoring.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Representative Line: Comment Overflow

Today, we look at a representative comment, sent to us by Nona. This particular comment was in a pile of code delivered by an offshore team.

// https://stackoverflow.com/questions/46744740/lodash-mongoose-object-id-difference/46745169

"Wait," you say, "what's the WTF about a comment pointing to a Stack Overflow page. I do that all the time?"

In this case, it's because this particular comment wasn't given any further explanation. It also wasn't in a block of code that was doing anything with either lodash, Mongoose, or set differences. It was, however, repeated multiple times throughout the codebase, because the entire codebase was a pile of copy-pasta glued together with the bare minimum code to make it work.

In at least one place, the comment was probably correct and helpful. But it got swept up as part of a broader copy/paste exercise, and now is scattered through the code without any true purpose.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Turning Thirty

Eric O worked for a medical device company. The medical device industry moves slowly, relative to other technical industries. Medical science and safety have their own cadence, and at a certain point, iterating faster doesn't matter much.

Eric was working on a new feature on a system that had been in use for thirteen years. This new feature interacted with a database which stored information about racks of test tubes, and Eric's tests meant creating several entries for racks of test tubes. And that's when Eric discovered that the database only allowed thirty racks. Add any more, it would just roll right back over to one.

This was odd. The database was small- less than 40MB, even in production- and there were automatic tasks to purge old data for compliance purposes. Why a hard limit of thirty?

Eric had only been at the company for a year, so he asked one of the more senior team members, Lester. "Oh yeah, that was before my time. You should probably ask Carl."

Later that day, Eric happened to bump into Carl around the coffee maker, and asked the question. "Oh, yeah, I do vaguely remember something about that. It was in the requirements for the product. I thought it was weird, but didn't think too much about it. You should probably ask Elise, she's been here like twenty years."

Well, now it was getting curious. Eric went over to the "old building", as it was named, the original office for the company on the other side of the parking lot. Most of the offices had moved to the new building a decade earlier, and it mostly served as fabrication and storage, but a few offices remained.

Elise was on the third floor, down a poorly lit hallway, sitting in an office with water-stained acoustical tile in its ceiling. "Oh, yeah, I put that into the requirements document. It's funny, I thought it was weird too, but the system you're working on was a replacement for an older system. Our requirements were derived from those. Let me think… Irving worked on that, but he's dead, god rest him. Penny is retired. Oh, you know, Humbert is still around. He didn't work on that, but he worked on some of the systems that came before that. He's upstairs and on the other side of the building."

Eric went upstairs and to the other side of the building. The fourth floor had been last remodeled circa 1985, and the ugly industrial paint on the wall was made even uglier by the fact that someone had replaced most of the flourescent tubes with LEDs. Most. The mismatched color temperature started Eric down the path of a headache.

Humbert was in an office similar to Elise's. On his desk was a plaque commemerating 40 years of service with the company. Eric asked about the limitation, and Humbert laughed.

"You're working on the latest version of a product that initially started on an old PDP-11 running MUMPS. I mean, the first versions, anyway. We ran to desktop computers as fast as we could. I wrote a version for DOS in… oh… '86? I knew none of the facilities we worked with had more than ten or fifteen racks of tubes, and I needed somehow to limit the size of the database so it all fit on a single 5 1/4" floppy disk. I picked thirty, because it seemed like a good round number. Honestly, I'm shocked that the limit still exists."

So was Eric. There had been several ground-up-rewrites since 1986, before the one Eric maintained had been released thirteen years ago. Each one of them had chosen to maintain the same limitation, without ever considering why it existed. The rule had simply been copied, mindlessly, for 40 years.

"I'm kind of impressed," Eric said to Humbert, "in a horrified way."

"Me too, kid, me too."

[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.

CodeSOD: Good Etiquette

"Here, you're a programmer, take this over. It's business critical."

That's what Felicity's boss told her when he pointed her to a network drive containing an Excel spreadsheet. The Excel spreadsheet contained a pile of macros. The person who wrote it had left, and nobody knew how to make it work, but the macros in question were absolutely business vital.

Also, it's in French.

We'll take this one in chunks. The indentation is as in the original.

Public Sub ExporToutVersBaseDonnées(ClasseurEnCours As Workbook)
Call AffectionVariables(ToutesLesCellulesNommées)
Call AffectationBaseDonnées(BaseDonnées)
BaseDonnées.Activate

The procedures AffectionVariables and AffectationBaseDonnées populate a pile of global variables. "base de données" is French for database, but don't let the name fool you- anything referencing "base de données" is referencing another Excel file located on a shared server. There are, in total, four Excel files that must live on a shared server, and two more which must be in a hard-coded path on the user's computer.

Oh, and the shared server is referenced not by a hostname, but by IP address- which is why the macros were breaking on everyone's computer; the IP address changed.

Let's continue.

'Vérifier si la ligne existe déjà.
        If ClasseurEnCours.Sheets("DATA").Range("Num_Fichier") = 0 Then
        Num_Fichier = BaseDonnées.Sheets(1).Range("Dernier_Fichier").Value + 1
Insérer_Ligne: '(étiquette Goto) insérer une ligne
    Application.GoTo Reference:="Dernière_Ligne"
            Selection.EntireRow.Insert
'Copie les cellules (colonne A à colonne FI) de la ligne au-dessus de la ligne insérée.
            With ActiveCell
                    .Offset(-1, 0).Range("A1:FM1").Copy
'Colle le format de la cellule précédemment copiée à la cellule active puis libère les données du presse papier
                    .PasteSpecial
                    .Range("A1:FM1").Value = ""
'Se repositionne au début de la ligne insérée.
                    .Range("A1").Select
            End With
            Application.CutCopyMode = False

Uh oh, Insérer_Ligne is a label for a Goto target. Not to be confused by the Application.GoTo call on the next line- that just selects a range in the spreadsheet.

After that little landmine, we copy/paste some data around in the sheet.

That's the If side of the conditional, let's look at the else clause:

        Else
Cherche_Numéro_Fichier: ' Chercher la ligne ou le numéro de fichier est égale à NumFichier.
                        While ActiveCell.Value <> Num_Fichier
                If ActiveCell.Row = Range("Etiquettes").Row Then
                    GoTo Insérer_Ligne
                End If
                ActiveCell.Offset(-1, 0).Range("a1:a1").Select
            Wend
            'Vérifier le numéro d'indice de la ligne active.
                If Cells(ActiveCell.Row, 165).Value <> ClasseurEnCours.Sheets("DATA").Range("Dernier_Indice") Then
                    ActiveCell.Offset(-1, 0).Range("A1:A1").Select
                    GoTo Cherche_Numéro_Fichier
                End If
            ActiveCell.Offset(0, 0).Range("A1:FM1").Value = ""
        End If

We start with another label, and… then we have a Goto. A Goto which jumps us back into the If side of the conditional. A Goto inside of a while loop, a while loop that's marching around the spreadsheet to search for certain values in the cell.

After the loop, we have another Goto which will possibly jump us up to the start of the else block.

The procedure ends with some cleanup:

'----- 
' Do some stuff on the active cell and the following cells on the column
.-----
BaseDonnées.Close True
Set BaseDonnées = Nothing
End Sub

I do not know what this function does, and the fact that the code is largely in a language I don't speak isn't the obstacle. I have no idea what the loops and the gotos are trying to do. I'm not even a "never use Goto ever ever ever" person; in a language like VBA, it's sometimes the best way to handle errors. But this bizarre time-traveling flow control boggles me.

"Etiquettes" is French for "labels", and it may be bad etiquette but I've got some four letter labels for this code.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Having a Beastly Time

It's time again for a reader special, and once again it's all The Beast In Black (there must be a story to that nick, no?).

"MySQL is not better than your SQL," he pontificated, "especially when it comes to the Workbench Migration Wizard"

7369002fc20e41b89b64ed7f32ef3641

"Sadly," says he, "Not even gmail/chromium either."

149c9109443f4521b1a38b91bd0bcc22

"Updated software is available, but there are no updates!" he puzzled. "Clicking Install Now just throws that dialog right back in my face. I'm re-cursing." Zero, one, does it really make a difference?

e9ea57c886984dc8a106df503a2fd923

"Questions" The Beast in Black "I do, in fact, have a question..."

f5c83f7bc02644a895f1f9aa5ec368a1

One of the foundational guides to my [lyle, not bib] engineering career was John Bentley's Programming Pearls. These are not those.
"Veni, vidi: vc. No pearls of wisdom here, just litter." says The Beast.

4e13a188deb94473abcc6148d106458c

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: We'll Hire Better Contractors Next Time, We Promise

Nona writes: "this is the beginning of a 2100 line function."

That's bad. Nona didn't send us the entire JavaScript function, but sent us just the three early lines, which definitely raise concerns:

if (res.length > 0) {
  await (function () {
    return new Promise((resolve, reject) => {

We await a synchronous function which retuns a promise, passing a function to the promise. As a general rule, you don't construct promises directly, you let asynchronous code generate them and pass them around (or await them). It's not a thing you never do, but it's certainly suspicious. It gets more problematic when Nona adds:

This function happens to contain multiple code repetition snippets, including these three lines.

That's right, this little block appears multiple times in the function, inside of anonymous function getting passed to the Promise.

No, the code does not work in its current state. It's unclear what the 2100 line function was supposed to do. And yes, this was written by lowest-bidder third-party contractors.

Nona adds:

I am numb at this point and know I gotta fix it or we lose contracts

Management made the choice to "save money" by hiring third parties, and now Nona's team gets saddled with all the crunch to fix the problems created by the "savings".

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Awaiting A Reaction

Today's Anonymous submitter sends us some React code. We'll look at the code and then talk about the WTF:

// inside a function for updating checkboxes on a page
if (!e.target.checked) {
  const removeIndex = await checkedlist.findIndex(
    (sel) => sel.Id == selected.Id,
  )
  const removeRowIndex = await RowValue.findIndex(
    (sel) => sel == Index,
  )

// checkedlist and RowValue are both useState instances.... they should never be modified directly
  await checkedlist.splice(removeIndex, 1)
  await RowValue.splice(removeRowIndex, 1)

// so instead of doing above logic in the set state, they dont
  setCheckedlist(checkedlist)
  setRow(RowValue)
} else {
  if (checkedlist.findIndex((sel) => sel.Id == selected.Id) == -1) {
    await checkedlist.push(selected)
  }
// same, instead of just doing a set state call, we do awaits and self updates
  await RowValue.push(Index)
  setCheckedlist(checkedlist)
  setRow(RowValue)
}

Comments were added by our submitter.

This code works. It's the wrong approach for doing things in React: modifying objects controlled by react, instead of using the provided methods, it's doing asynchronous push calls. Without the broader context, it's hard to point out all the other ways to do this, but honestly, that's not the interesting part.

I'll let our submitter explain:

This code is black magic, because if I update it, it breaks everything. Somehow, this is working in perfect tandem with the rest of the horrible page, but if I clean it up, it breaks the checkboxes; they're no longer able to be clicked. Its forcing React somehow to update asynchronously so it can use these updated values correctly, but thats the neat part, they aren't even being used anywhere else, but somehow the re-rendering page only accepts awaits. I've tried refactoring it 5 different ways to no avail

That's what makes truly bad code. Code so bad that you can't even fix it without breaking a thousand other things. Code that you have to carefully, slowly, pick through and gently refactor, discovering all sorts of random side-effects that are hidden. The code so bad that you actually have to live with it, at least for awhile.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: All Docked Up

Aankhen has a peer who loves writing Python scripts to automate repetitive tasks. We'll call this person Ernest.

Ernest was pretty proud of some helpers he wrote to help him manage his Docker containers. For example, when he wanted to stop and remove all his running Docker containers, he wrote this script:

#!/usr/bin/env python
import subprocess

subprocess.run("docker kill $(docker ps -q)", shell=True)
subprocess.run("docker rm $(docker ps -a -q)", shell=True)

He aliased this script to docker-stop, so that with one command he could… run two.

"Ernest," Aankhen asked, "couldn't this just be a bash script?"

"I don't really know bash," Ernest replied. "If I just do it in bash, if the first command fails, the second command doesn't run."

Aankhen pointed out that you could make bash not do that, but Ernest replied: "Yeah, but I always forget to. This way, it handles errors!"

"It explicitly doesn't handle errors," Aankhen said.

"Exactly! I don't need to know when there are no containers to kill or remove."

"Okay, but why not use the Docker library for Python?"

"What, and make the software more complicated? This has no dependencies!"

Aankhen was left with a sinking feeling: Ernest was either the worst developer he was working with, or one of the best.

[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.

CodeSOD: To Shutdown You Must First Shutdown

Every once in awhile, we get a bit of terrible code, and our submitter also shares, "this isn't called anywhere," which is good, but also bad. Ernesto sends us a function which is called in only one place:

///
/// Shutdown server
///
private void shutdownServer()
{
    shutdownServer();
}

The "one place", obviously, is within itself. This is the Google Search definition of recursion, where each recursive call is just the original call, over and over again.

This is part of a C# service, and this method shuts down the server, presumably by triggering a stack overflow. Unless C# has added tail calls, anyway.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Anti-Simplification

Our anonymous submitter relates a tale of simplification gone bad. As this nightmare unfolds, imagine the scenario of a new developer coming aboard at this company. Imagine being the one who has to explain this setup to said newcomer.

Imagine being the newcomer who inherits it.

A "Storm P machine" - the Danish equivalent of a Rube Goldberg machine.

David's job should have been an easy one. His company's sales data was stored in a database, and every day the reporting system would query a SQL view to get the numbers for the daily key performance indicators (KPIs). Until the company's CTO, who was proudly self-taught, decided that SQL views are hard to maintain, and the system should get the data from one of those new-fangled APIs instead.

But how does one call an API? The reporting system didn't have that option, so the logical choice was Azure Data Factory to call the API, then output the data to a file that the reporting system could read. The only issue was that nobody on the team spoke Azure Data Factory, or for that matter SQL. But no problem, one of David's colleagues assured, they could do all the work in the best and most multifunctional language ever: C#.

But you can't just write C# in a data factory directly, that would be silly. What you can do is have the data factory pipeline call an Azure function, which calls a DLL that contains the bytecode from C#. Oh, and a scheduler outside of the data factory to run the pipeline. To read multiple tables, the pipeline calls a separate function for each table. Each function would be based on a separate source project in C#, with 3 classes each for the HTTP header, content, and response; and a separate factory class for each of the actual classes.

After all, each table had a different set of columns, so you can't just re-use classes for that.

There was one little issue: the reporting system required an XML file, whereas the API would export data in JSON. It would be silly to expect a data factory, of all things, to convert this. So the CTO's solution was to have another C# program (in a DLL called by a function from a pipeline from an external scheduler) that reads the JSON document saved by the earlier program, uses foreach to go over each element, then saves the result as XML. A distinct program for each table, of course, requiring distinct classes for header, content, response, and factories thereof.

Now here's the genius part: to the C# class representing the output data, David's colleague decided to attach one different object for each input table required. The data class would use reflection to iterate over the attached objects, and for each object, use a big switch block to decide which source file to read. This allows the data class to perform joins and calculations before saving to XML.

To make testing easier, each calculation would be a separate function call. For example, calculating a customer's age was a function taking struct CustomerWithBirthDate as input, use a foreach loop to copy all the data except replacing one field, and return a CustomerWithAge struct to pass to the next function. The code performed a bit slowly, but that was an issue for a later year.

So basically, the scheduler calls the data factory, which calls a set of Azure functions, which call a C# function, which calls a set of factory classes to call the API and write the data to a text file. Then, the second scheduler calls a data factory, which calls Azure functions, which call C#, which calls reflection to check attachment classes, which read the text files, then call a series of functions for each join or calculation, then call another set of factory classes to write the data to an XML file, then call the reporting system to update.

Easy as pie, right? So where David's job could have been maintaining a couple hundred lines of SQL views, he instead inherited some 50,000 lines of heavily-duplicated C# code, where adding a new table to the process would easily take a month.

Or as the song goes, Somebody Told Me the User Provider should use an Adaptor to Proxy the Query Factory Builder ...

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: That's What I Want

First up with the money quote, Peter G. remarks "Hi first_name euro euro euro, look how professional our marketing services are! "

1

 

"It takes real talent to mispell error" jokes Mike S. They must have done it on purpose.

0

 

I long wondered where the TikTok profits came from, and now I know. It's Daniel D. "I had issues with some incorrectly documented TikTok Commercial Content API endpoints. So I reached out to the support. I was delighted to know that it worked and my reference number was . PS: 7 days later I still have not been contacted by anyone from TikTok. You can see their support is also . "

2

 

Fortune favors the prepared, and Michael R. is very fortunate. "I know us Germans are known for planning ahead so enjoy the training on Friday, February 2nd 2029. "

3

 

Someone other than dragoncoder047 might have shared this earlier, but this time dragoncoder047 definitely did. "Digital Extremes (the developers of Warframe) were making many announcements of problems with the new update that rolled out today [February 11]. They didn’t mention this one!"

4

 

[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: Qaudruple Negative

We mostly don't pick on bad SQL queries here, because mostly the query optimizer is going to fix whatever is wrong, and the sad reality is that databases are hard to change once they're running; especially legacy databases. But sometimes the code is just so hamster-bowling-backwards that it's worth looking into.

Jim J has been working on a codebase for about 18 months. It's a big, sprawling, messy project, and it has code like this:

AND CASE WHEN @c_usergroup = 50 AND NOT EXISTS(SELECT 1 FROM l_appl_client lac WHERE lac.f_application = fa.f_application AND lac.c_linktype = 840 AND lac.stat = 0 AND CASE WHEN ISNULL(lac.f_client,0) <> @f_client_user AND ISNULL(lac.f_c_f_client,0) <> @f_client_user THEN 0 ELSE 1 END = 1 ) THEN 0 ELSE 1 END = 1 -- 07.09.2022

We'll come back to what it's doing, but let's start with a little backstory.

This code is part of a two-tier application: all the logic lives in SQL Server stored procedures, and the UI is a PowerBuilder application. It's been under development for a long time, and in that time has accrued about a million lines of code between the front end and back end, and has never had more than 5 developers working on it at any given time. The backlog of feature requests is nearly as long as the backlog of bugs.

You may notice the little date comment in the code above. That's because until Jim joined the company, they used Visual Source Safe for version control. Visual Source Safe went out of support in 2005, and let's be honest: even when it was in support it barely worked as a source control system. And that's just the Power Builder side- the database side just didn't use source control. The source of truth was the database itself. When going from development to test to prod, you'd manually export object definitions and run the scripts in the target environment. Manually. Yes, even in production. And yes, environments did drift and assumptions made in the scripts would frequently break things.

You may also notice the fields above use a lot of Hungarian notation. Hungarian, in the best case, makes it harder to read and reason about your code. In this case, it's honestly fully obfuscatory. c_ stands for a codetable, f_ for entities. l_ is for a many-to-many linking table. z_ is for temporary tables. So is x_. And t_. Except not all of those "temporary" tables are truly temporary, a lesson Jim learned when trying to clean up some "junk" tables which were not actually junk.

I'll let Jim add some more detail around these prefixes:

an "application" may have a link to a "client", so there is an f_client field; but also it references an "agent" (which is also in the f_client table, surpise!) - this is how you get an f_c_f_client field. I have no clue why the prefix is f_c_ - but I also found c_c_c_channel and fc4_contact columns. The latter was a shorthand for f_c_f_c_f_c_f_contact, I guess.

"f_c_f_c_f_c_f_c" is also the sound I'd make if I saw this in a codebase I was responsible for. It certainly makes me want to change the c_c_c_channel.

With all this context, let's turn it back over to Jim to explain the code above:

And now, with all this background in mind, let's have a look at the logic in this condition. On the deepest level we check that both f_client and f_c_f_client are NOT equal to @f_client_user, and if this is the case, we return 0 which is NOT equal to 1 so it's effectively a negation of the condition. Then we check that records matching this condition do NOT EXIST, and when this is true - also return 0 negating the condition once more.

Honestly, the logic couldn't be clearer, when you put it that way. I jest, I've read that twelve times and I still don't understand what this is for or why it's here. I just want to know who we can prosecute for this disaster. The whole thing is a quadruple negative and frankly, I can't handle that kind of negativity.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Repeating Your Existence

Today's snippet from Rich D is short and sweet, and admittedly, not the most TFs of WTFs out there. But it made me chuckle, and sometimes that's all we need. This Java snippet shows us how to delete a file:

if (Files.exists(filePath)) {
    Files.deleteIfExists(filePath);
}

If the file exists, then if it exists, delete it.

This commit was clearly submitted by the Department of Redundancy Department. One might be tempted to hypothesize that there's some race condition or something that they're trying to route around, but if they are, this isn't the way to do it, per the docs: "Consequently this method may not be atomic with respect to other file system operations." But also, I fail to see how this would do that anyway.

The only thing we can say for certain about using deleteIfExists instead of delete is that deleteIfExists will never throw a NoSuchFileException.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Blocked Up

Agatha has inherited some Windows Forms code. This particular batch of such code falls into that delightful category of code that's wrong in multiple ways, multiple times. The task here is to disable a few panels worth of controls, based on a condition. Or, since this is in Spanish, "bloquear controles". Let's see how they did it.

private void BloquearControles()
{
	bool bolBloquear = SomeConditionTM; // SomeConditionTM = a bunch of stuff. Replaced for clarity.

	// Some code. Removed for clarity.
	
	// private System.Windows.Forms.Panel pnlPrincipal;
	foreach (Control C in this.pnlPrincipal.Controls)
	{
		if (C.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C.Enabled = bolBloquear;
		}
	}
	
	// private System.Windows.Forms.GroupBox grpProveedor;
	foreach (Control C1 in this.grpProveedor.Controls)
	{
		if (C1.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C1.Enabled = bolBloquear;
		}
	}

	// private System.Windows.Forms.GroupBox grpDescuentoGeneral;
	foreach (Control C2 in this.grpDescuentoGeneral.Controls)
	{
		if (C2.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C2.Enabled = bolBloquear;
		}
	}

	// Some more code. Removed for clarity.
}

This manages two group boxes and a panel. It checks a condition, then iterates across every control beneath it, and sets their enabled property on the control. In order to do this, it checks the type of the control for some reason.

Now, a few things: every control inherits from the base Control class, which has an Enabled property, so we're not doing this check to make sure the property exists. And every built-in container control automatically passes its enabled/disabled state to its child controls. So there's a four line version of this function where we just set the enabled property on each container.

This leaves us with two possible explanations. The first, and most likely, is that the developer responsible just didn't understand how these controls worked, and how inheritance worked, and wrote this abomination as an expression of that ignorance. This is extremely plausible, extremely likely, and honestly, our best case scenario.

Because our worse case scenario is that this code's job isn't to disable all of the controls. The reason they're doing type checking is that there are some controls used in these containers that don't match the types listed. The purpose of this code, then, is to disable some of the controls, leaving others enabled. Doing this by type would be a terrible way to manage that, and is endlessly confusing. Worse, I can't imagine how this behavior is interpreted by the end users; the enabling/disabling of controls following no intuitive pattern, just filtered based on the kind of control in use.

The good news is that Agatha can point us towards the first option. She adds:

They decided to not only disable the child controls one by one but to check their type and only disable those five types, some of which aren't event present in the containers. And to make sure this was WTF-worthy the didn't even bother to use else-if so every type is checked for every child control

She also adds:

At this point I'm not going to bother commenting on the use of GetType() == typeof() instead of is to do the type checking.

Bad news, Agatha: you did bother commenting. And even if you didn't, don't worry, someone would have.

[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: Popping Off

Python is (in)famous for its "batteries included" approach to a standard library, but it's not that notable that it has plenty of standard data structures, like dicts. Nor is in surprising that dicts have all sorts of useful methods, like pop, which removes a key from the dict and returns its value.

Because you're here, reading this site, you'll also be unsurprised that this doesn't stop developers from re-implementing that built-in function, badly. Karen sends us this:

def parse_message(message):
    def pop(key):
        if key in data:
            result = data[key]
            del data[key]
            return result
        return ''

    data = json.loads(message)
    some_value = pop("some_key")
    # <snip>...multiple uses of pop()...</snip>

Here, they create an inner method, and they exploit variable hoisting. While pop appears in the code before data is declared, all variable declarations are "hoisted" to the top. When pop references data, it's getting that from the enclosing scope. Which while this isn't a global variable, it's still letting a variable cross between two scopes, which is always messy.

Also, this pop returns a default value, which is also something the built-in method can do. It's just the built-in version requires you to explicitly pass the value, e.g.: some_value = data.pop("some_key", "")

Karen briefly wondered if this was a result of the Python 2 to 3 conversion, but no, pop has been part of dict for a long time. I wondered if this was just an exercise in code golf, writing a shorthand function, but even then- you could just wrap the built-in pop with your shorthand version (not that I'd recommend such a thing). No, I think the developer responsible simply didn't know the function was there, and just reimplemented a built-in method badly, as so often happens.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Perverse Perseveration

Pike pike pike pike Pike pike pike.

Lincoln KC repeated "I never knew Bank of America Bank of America Bank of America was among the major partners of Bank of America."

4

 

"Extra tokens, or just a stutter?" asks Joel "An errant alt-tab caused a needless google search, but thankfully Gemini's AI summary got straight-to-the-point(less) info. It is nice to see the world's supply of Oxford commas all in once place. "

0

 

Alessandro M. isn't the first one to call us out on our WTFs. "It’s adorable how the site proudly supports GitHub OAuth right up until the moment you actually try to use it. It’s like a door with a ‘Welcome’ sign that opens onto a brick wall." Meep meep.

1

 

Float follies found Daniel W. doubly-precise. "Had to go check on something in M365 Admin Center, and when I was on the OneDrive tab, I noticed Microsoft was calculating back past the bit. We're in quantum space at this point."

2

 

Weinliebhaber Michael R. sagt "Our German linguists here will spot the WTF immediately where my local wine shop has not. Weiẞer != WEIBER. Those words mean really different things." Is that 20 euro per kilo, or per the piece?

3

 

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: The Counting Machine

Industrial machines are generally accompanied by "Human Machine Interfaces", HMIs. This is industrial slang for a little computerized box you use to control the industrial machine. All the key logic and core functionality and especially the safety functionality is handled at a deeper computer layer in the system. The HMI is just buttons users can push to interact with the machine.

Purchasers of those pieces of industrial equipment often want to customize that user interface. They want to guide users away from functions they don't need, or make their specific workflow clear, or even just brand the UI. This means that the vendor needs to publish an API for their HMI.

Which brings us to Wendy. She works for a manufacturing company which wants to customize the HMI on a piece of industrial equipment in a factory. That means Wendy has been reading the docs and poking at the open-sourced portions of the code, and these raise more questions than they answer.

For example, the HMI's API provides its own set of collection types, in C#. We can wonder why they'd do such a thing, which is certainly a WTF in itself, but this representative line raises even more questions than that:

Int32 Count { get; set; }

What happens if you use the public set operation on the count of items in a collection? I don't know. Wendy doesn't either, as she writes:

I'm really tempted to set the count but I fear the consequences.

All I can hear in my head when I think about "setting the Count" is: "One! One null reference exception! Two! TWO null reference exceptions! HA HA HA HA!"

Count von Count kneeling.png
By http://muppet.wikia.com/wiki/Count_von_Count

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Safegaurd Your Comments

I've had the misfortune of working in places which did source-control via comments. Like one place which required that, with each section of code changed, you needed to add a comment with your name, the ticket number, and the reason the change was made. You know, the kind of thing you can just get from your source control service.

In their defense, that policy was invented for mainframe developers and then extended to everyone else, and their source control system was in Visual Source Safe. VSS was a) terrible, and b) a perennial destroyer of history, so maybe they weren't entirely wrong and VSS was the real WTF. I still hated it.

In any case, Alice's team uses more modern source control than that, which is why she's able to explain to us the story of this function:

public function calculateMassGrossPay(array $employees, Payroll $payroll): array
{
    // it shouldn't enter here, but if it does by any change, do nth
    return [];
}

Once upon a time, this function actually contained logic, a big pile of fairly complicated logic. Eventually, a different method was created which streamlined the functionality, but had a different signature and logic. All the callers were updated to use that method instead- by commenting out the line which called this one. This function had a comment added to the top: // it shouldn't enter here.

Then, the body of this function got commented out, and the return was turned into an empty array. The comment was expanded to what you see above. Then, eventually, the commented-out callers were all deleted. Years after that, the commented out body of this function was also deleted, leaving behind the skeleton you see here.

This function is not referenced anywhere else, not even in a comment. It's truly impossible for code to "enter here".

Alice writes: "Version control by commented out code does not work very well."

Indeed, it does not.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Representative Line: Years Go By

Henrik H's employer thought they could save money by hiring offshore, and save even more money by hiring offshore junior developers, and save even more money by basically not supervising them at all.

Henrik sends us just one representative line:

if (System.DateTime.Now.AddDays(-365) <= f.ReleaseDate) // 365 days means one year 

I appreciate the comment, that certainly "helps" explain the magic number. There's of course, just one little problem: It's wrong. I mean, ~75% of the time, it works every time, but it happily disregards leap years. Which may or may not be a problem in this case, but if they got so far as learning about the AddDays method, they were inches from using AddYears.

I guess it's true what they say: you can lead a dev to docs, but you can't make them think.

[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.

WTF: Home Edition

The utility closet Ellis had inherited and lived with for 17 years had been a cesspool of hazards to life and limb, a collection of tangible WTFs that had everyone asking an uncaring god, "What were they thinking?"

Every contractor who'd ever had to perform any amount of work in there had come away appalled. Many had even called over their buddies to come and see the stunning mess for themselves:

INTERIOR OF UTILITY ROOM SHOWING STORAGE CLOSET AT PHOTO CENTER LEFT AND HOT WATER HEATER CLOSET AT PHOTO CENTER RIGHT. VIEW TO EAST. - Bishop Creek Hydroelectric System, HAER CAL,14-BISH.V,7A-28

  • All of the electrical components, dating from the 1980s, were scarily underpowered for what they were supposed to be powering.
  • To get to the circuit breaker box—which was unlabeled, of course—one had to contort themselves around a water heater almost as tall as Ellis herself.
  • As the house had no basement, the utility closet was on the first floor in an open house plan. A serious failure with said water heater would've sent 40 gallons (150 liters) of scalding-hot tsunami surging through the living room and kitchen.
  • The furnace's return air vent had been screwed into crumbling drywall, and only prayers held it in place. Should it have fallen off, it would never have been replaceable. And Ellis' cat would've darted right in there for the adventure of a lifetime.
  • To replace the furnace filter, Ellis had to put on work gloves, unscrew a sharp sheet-metal panel from the side of the furnace, pull the old filter out from behind a brick (the only thing holding it in place), manipulate the filter around a mess of water and natural gas pipes to get it out, thread the new filter in the same way, and then secure it in place with the brick before screwing the panel back on. Ellis always pretended to be an art thief in a museum, slipping priceless paintings around security-system lasers.
  • Between the water tank, furnace, water conditioning unit, fiber optical network terminal, and router, there was barely room to breathe, much less enough air to power ignition for the gas appliances. Some genius had solved this by cutting random holes in several walls to admit air from outside. One of these holes was at floor-level. Once, Ellis opened the closet door to find a huge puddle on the floor, making her fear her hot water heater was leaking. As it turned out, a power-washing service had come over earlier that day. When they'd power-washed the exterior of her home, some of that water shot straight through one of those holes she hadn't known about, giving her utility closet a bonus bath.
  • If air intake was a problem, venting the appliances' exhaust was an even worse issue. The sheet-metal vents had calcified and rusted over time. If left unaddressed, holes could've formed that would've leaked carbon monoxide into Ellis' house.

Considering all the above, plus the fact that the furnace and air conditioner were coming up on 20 years of service, Ellis couldn't put off corrective action any longer. Last week, over a span of 3 days, contractors came in to exorcise the demons:

  • Upgrading electricals that hadn't already been dealt with.
  • Replacing the hot water tank with a wall-mounted tankless heater.
  • Replacing the furnace and AC with a heat pump and backup furnace, controlled by a new thermostat.
  • Creating new pipes for intake and venting (no more reliance on indoor air for ignition).
  • Replacing the furnace return air vent with a sturdier one.
  • Putting a special hinged door on the side of the furnace, allowing the filter to be replaced in a matter of seconds (RIP furnace brick).

With that much work to be done, there were bound to be hiccups. For instance, when the Internet router was moved, an outage occurred: for no good reason, the optical network terminal refused to talk to Ellis' Wifi router after powering back up. A technician came out a couple days later, reset the Internet router, and everything was fine again.

All in all, it was an amazing and welcome transformation. As each new update came online, Ellis was gratefully satisfied. It seemed as though the demons were finally gone.

Unbeknownst to them all, there was one last vengeful spirit to quell, one final WTF that it was hell-bent on doling out.

It was late Friday afternoon. Despite the installers' best efforts, the new thermostat still wasn't communicating with the new heat pump. Given the timing, they couldn't contact the company rep to troubleshoot. However, the thermostat was properly communicating with the furnace. And so, Ellis was left with the furnace for the weekend. She was told not to mess with the thermostat at all except to adjust the set point as desired. They would follow back up with her on Monday.

For Ellis, that was perfectly fine. With the historically cold winter they'd been enduring in her neck of the woods, heat was all she cared about. She asked whom to contact in case of any issues, and was told to call the main number. With all that squared away, she looked forward to a couple of quiet, stress-free days before diving back into HVAC troubleshooting.

Everything was fine, until it wasn't. Around 11AM on Saturday, Ellis noticed that the thermostat displayed the word "Heating" while the furnace wasn't actually running. Maybe it was about to turn on? 15 minutes went by, then half an hour. Nothing had changed except for the temperature in her house steadily decreasing.

Panic set in at the thought of losing heat in her home indefinitely. That fell on top of a psyche that was already stressed out and emotionally exhausted from the last several days' effort. Struggling for calm, Ellis first tried to call that main number line for help as directed. She noticed right away that it wasn't a real person on the other end asking for her personal information, but an AI agent. The agent informed her that the on-call technician had no availablity that weekend. It would pencil her in for a service appointment on Monday. How did that sound?

"Not good enough!" Ellis cried. "I wanna speak to a representative!"

"I understand!" replied the blithe chatbot. "Hold on, let me transfer you!"

For a moment, Ellis was buoyed with hope. She'd gotten past the automated system. Soon, she'd be talking with a live person who might even be able to walk her through troubleshooting over the phone.

The new agent answered. Ellis began pouring her heart out—then stopped dead when she realized it was another AI agent, this time with a male voice instead of a female one. This one proceeded through nearly the same spiel as the first. It also scheduled her for a Monday service appointment even though the other chatbot had already claimed to have done so.

This was the first time an AI had ever pulled such a trick on Ellis. It was not a good time for it. Ellis hung up and called the only other person she could think to contact: her sales rep. When he didn't answer, she left a voicemail and texts: no heat all weekend was unacceptable. She would really appreciate a call back.

While playing the horrible waiting game, Ellis tried to think about what she could do to fix this. They had told her not to mess with the thermostat. Well, from what she could see, the thermostat was sending a signal to the furnace that the furnace wasn't responding to for whatever reason. It was time to look at the docs. Fortunately, the new furnace's manual was resting right on top of it. She spread it open on her kitchen table.

OK, Ellis thought, this newfangled furnace has an LED display which displays status codes. Her old furnace had lacked such a thing. Lemme find that.

Inside her newly remodeled utility closet, she located the blinking display, knelt, and spied the code: 1dL. Looking that up in the doc's troubleshooting section, she found ... Normal Operation. No action.

The furnace was OK, then? Now what?

Aside from documentation, another thing Ellis knew pretty well was tech support. She decided to break out the ol' turn-it-off-and-on-again. She shut off power to both the furnace and thermostat, waited a few minutes, then switched everything back on, crossing her fingers.

No change. The indoor temperature kept dropping.

Her phone rang: the sales rep. He connected her with the on-call technician for that weekend, who fortunately was able to arrive at her house within the hour.

One tiny thermostat adjustment later, and Ellis was enjoying a warm house once more.

What had happened?

This is where an understanding of heat pumps comes into play. In this configuration, the heat pump is used for cooling and for heating, unless the outside temperature gets very cold. At that point, the furnace kicks in, which is more efficient. (Technology Connections has some cool videos about this if you're curious.)

Everything had been running fine for Ellis while the temperatures had remained below freezing. The problem came when, for the first time in approximately 12 years, the temperature rose above 40F (4C). At that point, the new thermostat decided, without telling Ellis, I'm gonna tell the HEAT PUMP to heat the joint!

... which couldn't do anything just then.

Workaround: the on-call technician switched the thermostat to an emergency heat mode that used the furnace no matter what.

Ellis had been told not to goof around with the thermostat. Even if she had, as a heat pump neophyte, she wouldn't have known to go looking for such a setting. She might've dug it up in a manual. Someone could've walked her through it over the phone. Oh, well. There is heat again, which is all that matters.

They will attempt to bring the heat pump online soon. We shall see if the story ends here, or if this becomes The WTF That Wouldn't Die.

P.S. When Ellis explained the AI answering service's deceptive behavior, she was told that the agent had been universally complained about ever since they switched to it. Fed up, they told Ellis they're getting rid of it. She feels pretty chuffed about more people seeing the light concerning garbage AI that creates far more problems than it solves.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Three Blinded Mice

...sent us five wtfs. And so on anon.

Item the first, an anon is "definitely not qualified" for this job. "These years of experience requirements are getting ridiculous."

0

 

Item the second unearthed by a farmanon has a loco logo. "After reading about the high quality spam emails which are indistinguishable from the company's emails, I got one from the spammer just starting his first day."

1

 

In thrid place, anon has only good things to say: "I'm liking their newsletter recommendations so far."

2

 

"A choice so noice, they gave it twoice," quipped somebody.

3

 

And foinally, a tdwtfer asks "I've seen this mixmastered calendering on several web sites. Is there an OSS package that is doing this? Or is it a Wordpress plugin?" I have a sneaking suspicion I posted this before. Call me on it.

4

 

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Error'd: A Horse With No Name

Scared Stanley stammered "I'm afraid of how to explain to the tax authority that I received $NaN."

1

 

Our anonymous friend Anon E. Mous wrote "I went to look up some employee benefits stuff up and ... This isn't a good sign."

0

 

Regular Michael R. is not actually operating under an alias, but this (allegedly scamming?) site doesn't know.

2

 

Graham F. gloated "I'm glad my child 's school have followed our naming convention for their form groups as well!"

3

 

Adam R. is taking his anonymous children on a roadtrip to look for America. "I'm planning a trip to St. Louis. While trying to buy tickets for the Gateway Arch, I noticed that their ticketing website apparently doesn't know how to define adults or children (or any of the other categories of tickets, for that matter)."

4

 

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Pawn Pawn in in Game Game of of Life Life

It feels like ages ago, when document databases like Mongo were all the rage. That isn't to say that they haven't stuck around and don't deliver value, but gone is the faddish "RDBMSes are dead, bro." The "advantage" they offer is that they turn data management problems into serialization problems.

And that's where today's anonymous submission takes us. Our submitter has a long list of bugs around managing lists of usernames. These bugs largely exist because the contract developer who wrote the code didn't write anything, and instead "vibe coded too close to the sun", according to our submitter.

Here's the offending C# code:

   [JsonPropertyName("invitedTraders")]
   [BsonElement("invitedTraders")]
   [BsonIgnoreIfNull]
   public InvitedTradersV2? InvitedTraders { get; set; }

   [JsonPropertyName("invitedTradersV2")]
   [BsonElement("invitedTradersV2")]
   [BsonIgnoreIfNull]
   public List<string>? InvitedTradersV2 { get; set; }

Let's start with the type InvitedTradersV2. This type contains a list of strings which represent usernames. The field InvitedTradersV2 is a list of strings which represent usernames. Half of our submitter's bugs exist simply because these two lists get out of sync- they should contain the same data, but without someone enforcing that correctly, problems accrue.

This is made more frustrating by the MongoDB attribute, BsonIgnoreIfNull, which simply means that the serialized object won't contain the key if the value is null. But that means the consuming application doesn't know which key it should check.

For the final bonus fun, note the use of JsonPropertyName. This comes from the built-in class library, which tells .NET how to serialize the object to JSON. The problem here is that this application doesn't use the built-in serializer, and instead uses Newtonsoft.JSON, a popular third-party library for solving the problem. While Newtonsoft does recognize some built-in attributes for serialization, JsonPropertyName is not among them. This means that property does nothing in this example, aside from add some confusion to the code base.

I suspect the developer responsible, if they even read this code, decided that the duplicated data was okay, because isn't that just a normal consequence of denormalization? And document databases are all about denormalization. It makes your queries faster, bro. Just one more shard, bro.

[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.

The Thanksgiving Shakedown

On Thanksgiving Day, Ellis had cuddled up with her sleeping cat on the couch to send holiday greetings to friends. There in her inbox, lurking between several well wishes, was an email from an unrecognized sender with the subject line, Final Account Statement. Upon opening it, she read the following:

1880s stock delivery form agreement

Dear Ellis,

Your final account statement dated -1 has been sent to you. Please log into your portal and review your balance due totaling #TOTAL_CHARGES#.

Payment must be received within 30 days of this notice to avoid collection. You may submit payment online via [Payment Portal Link] or by mail to:

Chamberlin Apartments
123 Main Street
Anytown US 12345

If you believe there is an error on your account, please contact us immediately at 212-555-1212.

Thank you for your prompt attention to this matter.

Chamberlin Apartments

Ellis had indeed rented an apartment managed by this company, but had moved out 16 years earlier. She'd never been late with a payment for anything in her life. What a time to receive such a thing, at the start of a long holiday weekend when no one would be able to do anything about it for the next 4 days!

She truly had so much to be grateful for that Thanksgiving, and here was yet more for her list: her broad technical knowledge, her experience working in multiple IT domains, and her many years of writing up just these sorts of stories for The Daily WTF. All of this added up to her laughing instead of panicking. She could just imagine the poor intern who'd hit "Send" by mistake. She also imagined she wasn't the only person who'd received this message. Rightfully scared and angry callers would soon be hammering that phone number, and Ellis was further grateful that she wasn't the one who had to pick up.

"I'll wait for the apology email!" she said out loud with a knowing smile on her face, closing out the browser tab.

Ellis moved on physically and mentally, going forward with her planned Thanksgiving festivities without giving it another thought. The next morning, she checked her inbox with curious anticipation. Had there been a retraction, a please disregard?

No. Instead, there were still more emails from the same sender. The second, sent 7 hours after the first, bore the subject line Second Notice - Outstanding Final Balance:

Dear Ellis,

Our records show that your final balance of #TOTAL_CHARGES# from your residency at your previous residence remains unpaid.

This is your second notice. Please remit payment in full or contact us to discuss the balance to prevent your account from being sent to collections.

Failure to resolve the balance within the next 15 days may result in your account being referred to a third-party collections agency, which could impact your credit rating.

To make payment or discuss your account, please contact us at 212-555-1212 or accounting@chamapts.com.

Sincerely,

Chamberlin Apartments

The third, sent 6 and a half hours later, threatened Final Notice - Account Will Be Sent to Collections.

Dear Ellis,

Despite previous notices, your final account balance remains unpaid.

This email serves as final notice before your account is forwarded to a third-party collections agency for recovery. Once transferred, we will no longer be able to accept payment directly or discuss the account.

To prevent this, payment of #TOTAL_CHARGES# must be paid in full by #CRITICALDATE#.

Please submit payment immediately. Please contact 212-555-1212 to confirm your payment.

Sincerely,

Chamberlin Apartments

It was almost certainly a mistake, but still rather spooky to someone who'd never been in such a situation. There was solace in the thought that, if they really did try to force Ellis to pay #TOTAL_CHARGES# on the basis of these messages, anyone would find it absurd that all 3 notices were sent mere hours apart, on a holiday no less. The first two had also mentioned 30 and 15 days to pay up, respectively.

Suddenly remembering that she probably wasn't the only recipient of these obvious form emails, Ellis thought to check her local subreddit. Sure enough, there was already a post revealing the range of panic and bewilderment they had wrought among hundreds, if not thousands. Current and more recent former tenants had actually seen #TOTAL_CHARGES# populated with the correct amount of monthly rent. People feared everything from phishing attempts to security breaches.

It wasn't until later that afternoon that Ellis finally received the anticipated mea culpa:

We are reaching out to sincerely apologize for the incorrect collection emails you received. These messages were sent in error due to a system malfunction that released draft messages to our entire database.

Please be assured of the following:
The recent emails do not reflect your actual account status.
If your account does have an outstanding balance, that status has not changed, and you would have already received direct and accurate communication from our office.
Please disregard all three messages sent in error. They do not require any action from you.

We understand that receiving these messages, especially over a holiday, was upsetting and confusing, and we are truly sorry for the stress this caused. The issue has now been fully resolved, and our team has worked with our software provider to stop all queued messages and ensure this does not happen again.

If you have any questions or concerns, please feel free to email leasing@chamapts.com. Thank you for your patience and understanding.

All's well that ends well. Ellis thanked the software provider's "system malfunction," whoever or whatever it may've been, that had granted the rest of us a bit of holiday magic to take forward for all time.

[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: The Destination Dir

Darren is supporting a Delphi application in the current decade. Which is certainly a situation to be in. He writes:

I keep trying to get out of doing maintenance on legacy Delphi applications, but they keep pulling me back in.

The bit of code Darren sends us isn't the largest WTF, but it's a funny mistake, and it's a funny mistake that's been sitting in the codebase for decades at this point. And as we all know, jokes only get funnier with age.

FileName := DestDir + ExtractFileName(FileName);
if FileExists(DestDir + ExtractFileName(FileName)) then
begin
  ...
end;

This code is inside of a module that copies a file from a remote server to the local host. It starts by sanitizing the FileName, using ExtractFileName to strip off any path components, and replace them with DestDir, storing the result in the FileName variable.

And they liked doing that so much, they go ahead and do it again in the if statement, repeating the exact same process.

Darren writes:

As Homer Simpson said "Lather, rinse, and repeat. Always repeat."

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Formula Length

Remy's Law of Requirements Gathering states "No matter what the requirements document says, what your users really wanted was Excel." This has a corrolary: "Any sufficiently advanced Excel file is indistingushable from software."

Given enough time, any Excel file whipped up by any user can transition from "useful" to "mission critical software" before anyone notices. That's why Nemecsek was tasked with taking a pile of Excel spreadsheets and converting them into "real" software, which could be maintained and supported by software engineers.

Nemecsek writes:

This is just one of the formulas they asked me to work on, and not the longest one.

Nemecsek says this is a "formula", but I suspect it's a VBA macro. In reality, it doesn't matter.

InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).
InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).Losses = 
calcLossesInPart(InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).RatedFrequency, InitechNeoDTMachineDevice.
InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).
InitechNeoDTActivePartPart(iPart).RadialPositionToMainDuct, InitechNeoDTMachineDevice.
InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).
InitechNeoDTActivePartPart(iPart).InitechNeoDTActivePartPartSectionContainer(0).
InitechNeoDTActivePartPartSection(0).InitechNeoDTActivePartPartConductorComposition(0).IsTransposed, 
InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).
InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).ParallelRadialCount, InitechNeoDTMachineDevice.
InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).
InitechNeoDTActivePartPart(iPart).InitechNeoDTActivePartPartSectionContainer(0).
InitechNeoDTActivePartPartSection(0).InitechNeoDTActivePartPartConductorComposition(0).
ParallelAxialCount, InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).InitechNeoDTActivePartPartConductor(0).Type, 
InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).
InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).InitechNeoDTActivePartPartConductor(0).
DimensionRadialElectric, InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).InitechNeoDTActivePartPartConductor(0).
DimensionAxialElectric + InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).InitechNeoDTActivePartPartConductor(0).InsulThickness, 
getElectricConductivityAtTemperatureT1(InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).InitechNeoDTActivePartPartConductor(0).
InitechNeoDTActivePartPartConductorRawMaterial(0).ElectricConductivityT0, InitechNeoDTMachineDevice.
InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).
InitechNeoDTActivePartPart(iPart).InitechNeoDTActivePartPartSectionContainer(0).
InitechNeoDTActivePartPartSection(0).InitechNeoDTActivePartPartConductorComposition(0).
InitechNeoDTActivePartPartConductor(0).InitechNeoDTActivePartPartConductorRawMaterial(0).MaterialFactor, 
InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).InitechNeoDTActivePart(0).
InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartSectionContainer(0).InitechNeoDTActivePartPartSection(0).
InitechNeoDTActivePartPartConductorComposition(0).InitechNeoDTActivePartPartConductor(0).
InitechNeoDTActivePartPartConductorRawMaterial(0).ReferenceTemperatureT0, InitechNeoDTMachineDevice.
ReferenceTemperature), LayerNumberRatedVoltage, InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).InitechNeoDTActivePartPartContainer(0).InitechNeoDTActivePartPart(iPart).
InitechNeoDTActivePartPartLayerContainer(0),InitechNeoDTMachineDevice.InitechNeoDTActivePartContainer(0).
InitechNeoDTActivePart(0).RFactor)

Line breaks added to try and keep horizontal scrolling sane. This arguably hurts readability, in the same way that beating a dead horse arguably hurts the horse.

This may not be the longest one, but it's certainly painful. I do not know exactly what this is doing, and frankly, I do not want to.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Error'd: On the Dark Side

...matter of fact, it's all dark.

Gitter Hubber checks in on the holidays: "This is the spirit of the Black Friday on GitHub. That's because I'm using dark mode. Otherwise, it would have a different name… You know what? Let's just call it Error Friday!"

1

 

"Best get typing!" self-admonishes. Jason G. Suffering a surfeit of snark, he proposes "Not sure my battery will last long enough.
Finally, quantum resistant security.
I can't remember my number after the 5000th digit. " Any of those will do just fine.

2

 

Don't count Calle L. out. "This is for a calorie tracking app, on Thanksgiving. Offer was so delicious it wasn't even a number any more! Sadly it did not slim the price down more than expected."

0

 

"Snow and rain and rain and snow!" exclaims Paul N. "Weather so astounding, they just had to trigger three separate notifications at the same time."

3

 

It's not a holiday for everyone though, is it? Certainly not for Michael R. , who is back with a customer service complaint about custom deliveries. "I am unlucky with my deliveries. This time it's DPD. "

4

 

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

Classic WTF: Teleported Release

It's a holiday in the US today, one where we give thanks. And today, we give thanks to not have this boss. Original. --Remy

Matt works at an accounting firm, as a data engineer. He makes reports for people who don’t read said reports. Accounting firms specialize in different areas of accountancy, and Matt’s firm is a general firm with mid-size clients.

The CEO of the firm is a legacy from the last century. The most advanced technology on his desk is a business calculator and a pencil sharpener. He still doesn’t use a cellphone. But he does have a son, who is “tech savvy”, which gives the CEO a horrible idea of how things work.

Usually, this is pretty light, in that it’s sorting Excel files or sorting the output of an existing report. Sometimes the requests are bizarre or utter nonsense. And, because the boss doesn’t know what the technical folks are doing, some of the IT staff may be a bit lazy about following best practices.

This means that most of Matt’s morning is spent doing what is essentially Tier 1 support before he gets into doing his real job. Recently, there was a worse crunch, as actual support person Lucinda was out for materinity leave, and Jackie, the one other developer, was off on vacation on a foreign island with no Internet. Matt was in the middle of eating a delicious lunch of take-out lo mein when his phone rang. He sighed when he saw the number.

“Matt!” the CEO exclaimed. “Matt! We need to do a build of the flagship app! And a deploy!”

The app was rather large, and a build could take upwards of 45 minutes, depending on the day and how the IT gods were feeling. But the process was automated, the latest changes all got built and deployed each night. Anything approved was released within 24 hours. With everyone out of the office, there hadn’t been any approved changes for a few weeks.

Matt checked the Github to see if something went wrong with the automated build. Everything was fine.

“Okay, so I’m seeing that everything built on GitHub and everything is available in production,” Matt said.

“I want you to do a manual build, like you used to.”

“If I were to compile right now, it could take quite awhile, and redeploying runs the risk of taking our clients offline, and nothing would be any different.”

“Yes, but I want a build that has the changes which Jackie was working on before she left for vacation.”

Matt checked the commit history, and sure enough, Jackie hadn’t committed any changes since two weeks before leaving on vacation. “It doesn’t looked like she pushed those changes to Github.”

“Githoob? I thought everything was automated. You told me the process was automated,” the CEO said.

“It’s kind of like…” Matt paused to think of an analogy that could explain this to a golden retriever. “Your dishwasher, you could put a timer on it to run it every night, but if you don’t load the dishwasher first, nothing gets cleaned.”

There was a long pause as the CEO failed to understand this. “I want Jackie’s front-page changes to be in the demo I’m about to do. This is for Initech, and there’s millions of dollars riding on their account.”

“Well,” Matt said, “Jackie hasn’t pushed- hasn’t loaded her metaphorical dishes into the dishwasher, so I can’t really build them.”

“I don’t understand, it’s on her computer. I thought these computers were on the cloud. Why am I spending all this money on clouds?”

“If Jackie doesn’t put it on the cloud, it’s not there. It’s uh… like a fax machine, and she hasn’t sent us the fax.”

“Can’t you get it off her laptop?”

“I think she took it home with her,” Matt said.

“So?”

“Have you ever seen Star Trek? Unless Scotty can teleport us to Jackie’s laptop, we can’t get at her files.”

The CEO locked up on that metaphor. “Can’t you just hack into it? I thought the NSA could do that.”

“No-” Matt paused. Maybe Matt could try and recreate the changes quickly? “How long before this meeting?” he asked.

“Twenty minutes.”

“Just to be clear, you want me to do a local build with files I don’t have by hacking them from a computer which may or may not be on and connected to the Internet, and then complete a build process which usually takes 45 minutes- at least- deploy to production, so you can do a demo in twenty minutes?”

“Why is that so difficult?” the CEO demanded.

“I can call Jackie, and if she answers, maybe we can figure something out.”

The CEO sighed. “Fine.”

Matt called Jackie. She didn’t answer. Matt left a voicemail and then went back to eating his now-cold lo mein.

[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.

Announcements: We Want Your Holiday Horrors

As we enter into the latter portion of the year, folks are traveling to visit family, logging off of work in hopes that everything can look after itself for a month, and somewhere, someone, is going to make the choice "yes, I can push to prod on Christmas Eve, and it'll totally work out for me!"

Over the next few weeks, I'm hoping to get a chance to get some holiday support horrors up on the site, in keeping with the season. Whether it's the absurd challenges of providing family tech support, the last minute pushes to production, the five alarm fires caused by a pointy-haired-bosses's incompetence, we want your tales of holiday IT woe.

So hit that submit button on the side bar, and tell us who's on Santa's naughty list this year.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Tales from the Interview: Interview Smack-Talk

In today's Tales from the Interview, our Anonymous submitter relates their experience with an anonymous company:

I had made it through the onsite, but along the way I had picked up some toxic work environment red flags. Since I had been laid off a couple months prior, I figured I wasn't in a position to be picky, so I decided I would still give it my best shot and take the job if I got it, but I'd continue looking for something better.

Then they brought me back onsite a second time for one final interview with 2 senior managers. I went in and they were each holding a printout of my resume. They proceeded to go through everything on it. First they asked why I chose the university I went to, then the same for grad school, which was fine.

WWF SmackDown Logo (1999-2001)

Then they got to my first internship. I believe the conversation went something like this:

Manager: "How did you like it?"

Me: "Oh, I loved it!"

Manager: "Were there any negatives?"

Me: "No, not that I can think of."

Manager: "So it was 100% positive?"

Me: "Yep!"

And then they got to my first full-time job, where the same manager repeated the same line of questioning but pushed even harder for me to say something negative, at one point saying "Well, you left for (2nd company on my resume), so there must have been something negative."

I knew better than to bad-mouth a previous employer in an interview, it's like going into a first date and talking smack about your ex. But what do you do when your date relentlessly asks you to talk smack about all your exes and refuses to let the subject turn to anything else? This not only confirmed my suspicions of a toxic work environment, I also figured *they* probably knew it was toxic and were relentlessly testing every candidate to make sure they wouldn't blow the whistle on them.

That was the most excruciatingly awkward interview I've ever had. I didn't get the job, but at that point I didn't care anymore, because I was very, very sure I didn't want to work there in the long term.

I'm glad Subby dodged that bullet, and I hope they're in a better place now.

It seems like this might be some stupid new trend. I recently bombed an interview where I could tell I wasn't giving the person the answer on their checklist, no matter how many times I tried. It was a question about how I handled it when someone opposed what I was doing at work or gave me negative feedback. It felt like they wanted me to admit to more fur-flying drama and fireworks than had ever actually occurred.

I actively ask for and welcome critique on my writing, it makes my work so much better. And if my work is incorrect and needs to be redone, or someone has objections to a project I'm part of, I seek clarification and (A) implement the requested changes, (B) explain why things are as they are and offer alternate suggestions/solutions, (C) seek compromise, depending on the situation. I don't get personal about it.

So, why this trend? Subby believed it was a way to test whether the candidate would someday badmouth the employer. That's certainly feasible, though if that were the goal, you'd think Subby would've passed their ordeal with flying colors. I'm not sure myself, but I have a sneaking suspicion that the nefarious combination of AI and techbro startup culture have something to do with it.

So perhaps I also dodged a bullet: one of the many things I'm grateful for this Thanksgiving.

Feel free to share your ideas, and any and all bullets you have dodged, in the comments.

[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: The Map to Your Confession

Today, Reginald approaches us for a confession.

He writes:

I've no idea where I "copied" this code from five years ago. The purpose of this code was to filter out Maps and Collections Maybe the intention was to avoid a recursive implementation by an endless loop? I am shocked that I wrote such code.

Well, that doesn't bode well, Reginald. Let's take a look at this Java snippet:

/**
 * 
 * @param input
 * @return
 */
protected Map rearrangeMap(Map input) {
	Map retMap = new HashMap();

	if (input != null && !input.isEmpty()) {

		Iterator it = input.keySet().iterator();
		while (true) {
			String key;
			Object obj;
			do {
				do {
					if (!it.hasNext()) {
					}
					key = (String) it.next();

				} while (input.get(key) instanceof Map);

				obj = input.get(key);

			} while (obj instanceof Boolean && ((Boolean) obj).equals(Boolean.FALSE));

			if (obj != null) {
				retMap.put(key, obj);
				return retMap;
			}
		}
	} else {
		return retMap;
	}
}

The first thing that leaps out is that this is a non-generic Map, which is always a code smell, but I suspect that's the least of our problems.

We start by verifying that the input Map exists and contains data. If the input is null or empty, we return it. In our main branch, we create an iterator across the keys, before ethering a while(true) loop. So far so bad

Then we enter a pair of nested do loops. Which definitely hints that we've gone off the edge of the map here. In the inner most loop, we do a check- if there isn't a next element in the iterator, we… do absolutely nothing. Whether there is or isn't an element, we advance to the next element, risking a NoSuchElementException. We do this while the key points to an instance of Map. As always, an instanceof check is a nauseating code stench.

Okay, so the inner loop skips across any keys that point to maps, and throws an exception when it gets to the end of the list.

The surrounding loop skips over every key that is a boolean value that is also false.

If we find anything which isn't a Map and isn't a false Boolean and isn't null, we put it in our retMap and return it.

This function finds the first key that points to a non-map, non-false value and creates a new map that contains only that key/value. Which it's a hard thing to understand why I'd want that, especially since some Map implementations make no guarantee about order. And even if I did want that, I definitely wouldn't want to do that this way. A single for loop could have solved this problem.

Reginald, I don't think there's any absolution for this. Instead, my advice would be to install a carbon monoxide detector in your office, because I have some serious concerns about whether or not your brain is getting enough oxygen.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Copied Homework

Part of the "fun" of JavaScript is dealing with code which comes from before sensible features existed. For example, if you wanted to clone an object in JavaScript, circa 2013, that was a wheel you needed to invent for yourself, as this StackOverflow thread highlights.

There are now better options, and you'd think that people would use them. However, the only thing more "fun" than dealing with code that hasn't caught up with the times is dealing with developers who haven't, and still insist on writing their own versions of standard methods.

  const objectReplace = (oldObject, newObject) => {
    let keys = Object.keys(newObject)
    try {
      for (let key of keys) {
        oldObject[key] = newObject[key]
      }
    } catch (err) {
      console.log(err, oldObject)
    }     

    return oldObject
  }

It's worth noting that Object.entries returns an array containing both the keys and values, which would be a more sensible for this operation, but then again, if we're talking about using correct functions, Object.assign would replace this function.

There's no need to handle errors here, as nothing about this assignment should throw an exception.

The thing that really irks me about this though is that it pretends to be functional (in the programming idiom sense) by returning the newly modified value, but it's also just changing that value in place because it's a reference. So it has side effects, in a technical sense (changing the value of its input parameters) while pretending not to. Now, I probably shouldn't get too hung up on that, because that's also exactly how Object.assign behaves, but dammit, I'm going to be bothered by it anyway. If you're going to reinvent the wheel, either make one that's substantially worse, or fix the problems with the existing wheel.

In any case, the real WTF here is that this function is buried deep in a 15,000 line file, written by an offshore contract team, and there are at least 5 other versions of this function, all with slightly different names, but all basically doing the same thing, because everyone on the team is just copy/pasting until they get enough code to submit a pull request.

Our submitter wonders, "Is there a way to train an AI to not let people type this?"

No, there isn't. You can try rolling that boulder up a hill, but it'll always roll right back down. Always and forever, people are going to write bad code.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Error'd: Untimely

Sometimes, it's hard to know just when you are. This morning, I woke up to a Macbook that thinks it's in Paris, four hours ago. Pining for pain chocolate. A bevy of anonyms have had similar difficulties.

First up, an unarabian anonym observes "They say that visiting Oman feels like traveling back in time to before the rapid modernization of the Arab states. I just think their eVisa application system is taking this "time travel" thing a bit too far... "

0

 

Snecod, an unretired (anteretired?) anonym finds it hard to plan when the calendar is unfixed. "The company's retirement plan was having a rough time prior to Second June." Looks like the first wtf was second March.

2

 

And an unamerican anonym sent us this (uh, back in first March) "Was looking to change the cable package I have from them. Apparently my discounts are all good until 9th October 1930, and a second one looking good until 9th January 2024."

3

 

On a different theme, researcher Jennifer E. exclaimed "Those must have been BIG divorces! Guy was so baller Wikipedia couldn’t figure out when he divorced either of these women." Or so awful they divorced him continuously.

4

 

Finally, parsimonious Greg L. saved this for us. "I don't remember much about #Error!, but I guess it was an interesting day."

1

 

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Invalid Route and Invalid Route

Someone wanted to make sure that invalid routes logged an error in their Go web application. Artem found this when looking at production code.

if (requestUriPath != "/config:system") &&
    (requestUriPath != "/config:system/ntp") &&
    (requestUriPath != "/config:system/ntp/servers") &&
    (requestUriPath != "/config:system/ntp/servers/server") &&
    (requestUriPath != "/config:system/ntp/servers/server/config") &&
    (requestUriPath != "/config:system/ntp/servers/server/config/address") &&
    (requestUriPath != "/config:system/ntp/servers/server/config/key-id") &&
    (requestUriPath != "/config:system/ntp/servers/server/config/minpoll") &&
    (requestUriPath != "/config:system/ntp/servers/server/config/maxpoll") &&
    (requestUriPath != "/config:system/ntp/servers/server/config/version") &&
    (requestUriPath != "/config:system/ntp/servers/server/state") &&
    (requestUriPath != "/config:system/ntp/servers/server/state/address") &&
    (requestUriPath != "/config:system/ntp/servers/server/state/key-id") &&
    (requestUriPath != "/config:system/ntp/servers/server/state/minpoll") &&
    (requestUriPath != "/config:system/ntp/servers/server/state/maxpoll") &&
    (requestUriPath != "/config:system/ntp/servers/server/state/version") {
    log.Info("ProcessGetNtpServer: no return of ntp server state for ", requestUriPath)
    return nil
}

The most disturbing part of this, for Artem, isn't that someone wrote this code and pushed it to production. It's that, according to git blame, two people wrote this code, because the first developer didn't include all the cases.

For the record, the application does have an actual router module, which can trigger logging on invalid routes.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Are You Mocking Me?

Today's representative line comes from Capybara James (most recently previously). It's representative, not just of the code base, but of Goodhart's Law: when a measure becomes a target, it ceases to be a good measure. Or, "you get what you measure".

If, for example, you decide that code coverage metrics are how you're going to judge developers, then your developers are going to ensure that the code coverage looks great. If you measure code coverage, then you will get code coverage- and nothing else.

That's how you get tests like this:

Mockito.verify(exportRequest, VerificationModeFactory.atLeast(0)).failedRequest(any(), any(), any());

This test passes if the function exportRequest.failedRequest is called at least zero times, with any input parameters.

Which, as you might imagine, is a somewhat useless thing to test. But what's important is that there is a test. The standards for code coverage are met, the metric is satisfied, and Goodhart marks up another win on the board.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Using an ADE: Ancient Development Environment

One of the things that makes legacy code legacy is that code, over time, rots. Some of that rot comes from the gradual accumulation of fixes, hacks, and kruft. But much of the rot also comes from the tooling going unsupported or entirely out of support.

For example, many years ago, I worked in a Visual Basic 6 shop. The VB6 IDE went out of support in April, 2008, but we continued to use it well into the next decade. This made it challenging to support the existing software, as the IDE frequently broke in response to OS updates. Even when we started running it inside of a VM running an antique version of Windows 2000, we kept running into endless issues getting projects to compile and build.

A fun side effect of that: the VB6 runtime remains supported. So you can run VB6 software on modern Windows. You just can't modify that software.

Greta has inherited an even more antique tech stack. She writes, "I often wonder if I'm the last person on Earth encumbered with this particular stack." She adds, "The IDE is long-deprecated from a vendor that no longer exists- since 2002." Given the project started in the mid 2010s, it may have been a bad choice to use that tech-stack.

It's not as bad as it sounds- while the technology and tooling is crumbling ruins, the team culture is healthy and the C-suite has given Greta wide leeway to solve problems. But that doesn't mean that the tooling isn't a cause of anguish, and even worse than the tooling- the code itself.

"Some things," Greta writes, "are 'typical bad'" and some things "are 'delightfully unique' bad."

For example, the IDE has a concept of "designer" files, for the UI, and "code behind" files, for the logic powering the UI. The IDE frequently corrupts its own internal state, and loses the ability to properly update the designer files. When this happens, if you attempt to open, save, or close a designer file, the IDE pops up a modal dialog box complaining about the corruption, with a "Yes" and "No" option. If you click "No", the modal box goes away- and then reappears because you're seeing this message because you're on a broken designer file. If you click "Yes", the IDE "helpfully" deletes pretty much everything in your designer file.

Nothing about the error message indicates that this might happen.

The language used is a dialect of C++. I say "dialect" because the vendor-supplied compiler implements some cursed feature set between C++98 and C++11 standards, but doesn't fully conform to either. It's only capable of outputting 32-bit x86 code up to a Pentium Pro. Using certain C++ classes, like std::fstream, causes the resulting executable to throw a memory protection fault on exit.

Worse, the vendor supplied class library is C++ wrappers on top of an even more antique Pascal library. The "class" library is less an object-oriented wrapper and more a collection of macros and weird syntax hacks. No source for the Pascal library exists, so forget about ever updating that.

Because the last release of the IDE was circa 2002, running it on any vaguely modern environment is prone to failures, but it also doesn't play nicely inside of a VM. At this point, the IDE works for one session. If you exit it, reboot your computer, or try to close and re-open the project, it breaks. The only fix is to reinstall it. But the reinstall requires you to know which set of magic options actually lets the install proceed. If you make a mistake and accidentally install, say, CORBA support, attempting to open the project in the IDE leads to a cascade of modal error boxes, including one that simply says, "ABSTRACT ERROR" ("My favourite", writes Greta). And these errors don't limit themselves to the IDE; attempting to run the compiler directly also fails.

But, if anything, it's the code that makes the whole thing really challenging to work with. While the UI is made up of many forms, the "main" form is 18,000 lines of code, with absolutely no separation of concerns. Actually, the individual forms don't have a lot of separation of concerns; data is shared between forms via global variables declared in one master file, and then externed into other places. Even better, the various sub-forms are never destroyed, just hidden and shown, which means they remember their state whether you want that or not. And since much of the state is global, you have to be cautious about which parts of the state you reset.

Greta adds:

There are two files called main.cpp, a Station.cpp, and a Station1.cpp. If you were to guess which one owns the software's entry point, you would probably be wrong.

But, as stated, it's not all as bad as it sounds. Greta writes: "I'm genuinely happy to be here, which is perhaps odd given how terrible the software is." It's honestly not that odd; a good culture can go a long way to making wrangling a difficult tech stack happy work.

Finally, Greta has this to say:

We are actively working on a .NET replacement. A nostalgic, perhaps masochistic part of me will miss the old stack and its daily delights.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Will You Still Need Me?

... when I'm eight thousand and three? Doesn't quite scan.

Old soul jeffphi hummed "It's comforting to know that I'll have health insurance coverage through my 8,030th birthday!"

1

 

Scififan Steve muttered "I asked Copilot if Tom Baker and Lalla Ward were the same age. Apparently, they have been traveling on different timelines, so Copilot claims they are the same age despite having birthdays 17 years apart. Who knew?" It's a timey-wimey thing.

2

 

An anonymous Aussie announced "I was trying to look up a weather forecast for several weeks in advance as I'm attending an event on Saturday 22nd November. Apparently the Weather Channel has invented its own calendar (or decided there are too many days in the year), as while the 1st of November was a Saturday and 22nd of November occur on a Saturday, the Weather Channel has decided those dates fall on a Friday.
Rosanna is in Melbourne, Australia. Temperatures are displayed in Celsius. " Rosanna is rated the 99th most liveable suburb of Melbourne, probably due to the aberrant calendar.

0

 

Beatrix W. wants to relocate to a more pedestrian-friendly nabe. "I was bored and looked for houses on a german real estate website. When I tried to have the distance to a house calculated I got the lovely result from the screenshot. The 99 minutes are okay when using the car but the rest is -1 minute."

3

 

Taxpayer Marlin D. is making sure he gets everything lined up for year-end. Checking his witholdings, he found that the IRS try very hard to be precise with their language. "This is from the IRS online Tax Withholding Estimator tool. I guess they really do mean *between*."

4

 

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Lucky Thirteen

Wolferitza sends us a large chunk of a C# class. We'll take it in chunks because there's a lot here, but let's start with the obvious problem:

    private int iID0;
    private int iID1;
    private int iID2;
    private int iID3;
    private int iID4;
    private int iID5;
    private int iID6;
    private int iID7;
    private int iID8;
    private int iID9;
    private int iID10;
    private int iID11;
    private int iID12;
    private int iID13;

If you say, "Maybe they should use an array," you're missing the real problem here: Hungarian notation. But sure, yes, they should probably use arrays. And you might think, "Hey, they should use arrays," would be an easy fix. Not for this developer, who used an ArrayList.

private void Basculer(DataTable dtFrom, DataTable dtTo)
{
    ArrayList arrRows = new ArrayList();

    int index;

    DataRow drNew1 = dtTo.NewRow();
    DataRow drNew2 = dtTo.NewRow();
    DataRow drNew3 = dtTo.NewRow();
    DataRow drNew4 = dtTo.NewRow();
    DataRow drNew5 = dtTo.NewRow();
    DataRow drNew6 = dtTo.NewRow();
    DataRow drNew7 = dtTo.NewRow();
    DataRow drNew8 = dtTo.NewRow();
    DataRow drNew9 = dtTo.NewRow();
    DataRow drNew10 = dtTo.NewRow();
    DataRow drNew11 = dtTo.NewRow();
    DataRow drNew12 = dtTo.NewRow();
    DataRow drNew13 = dtTo.NewRow();
    DataRow drNew14 = dtTo.NewRow();
    DataRow drNew15 = dtTo.NewRow();

    arrRows.Add(drNew1);
    arrRows.Add(drNew2);
    arrRows.Add(drNew3);
    arrRows.Add(drNew4);
    arrRows.Add(drNew5);
    arrRows.Add(drNew6);
    arrRows.Add(drNew7);
    arrRows.Add(drNew8);
    arrRows.Add(drNew9);
    arrRows.Add(drNew10);
    arrRows.Add(drNew11);
    arrRows.Add(drNew12);
    arrRows.Add(drNew13);
    arrRows.Add(drNew14);
    arrRows.Add(drNew15);
    // more to come…

Someone clearly told them, "Hey, you should use an array or an array list", and they said, "Sure." There's just one problem: arrRows is never used again. So they used an ArrayList, but also, they didn't use an ArrayList.

But don't worry, they do use some arrays in just a moment. Don't say I didn't warn you.

    if (m_MTTC)
    {
        if (m_dtAAfficher.Columns.Contains("MTTCRUB" + dr[0].ToString()))
        {
            arrMappingNames.Add("MTTCRUB" + dr[0].ToString());
            arrHeadersTexte.Add(dr[4]);
            arrColumnsFormat.Add("");
            arrColumnsAlign.Add("1");

Ah, they're splitting up the values in their data table across multiple arrays; the "we have object oriented programming at home" style of building objects.

And that's all the setup. Now we can get into the real WTF here.

            if (iCompt == Convert.ToInt16(0))
            {
                iID0 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(1))
            {
                iID1 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(2))
            {
                iID2 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(3))
            {
                iID3 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(4))
            {
                iID4 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(5))
            {
                iID5 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(6))
            {
                iID6 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(7))
            {
                iID7 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(8))
            {
                iID8 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(9))
            {
                iID9 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(10))
            {
                iID10 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(11))
            {
                iID11 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(12))
            {
                iID12 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(13))
            {
                iID13 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
        }
    }

Remember those private iID* values? Here's how they get populated. We check a member variable called iCompt and pull the first value out of a dr variable (a data reader, also a member variable). You may have looked at the method signature and assumed dtFrom and dtTo would be used, but no- they have to purpose in this method at all.

And if you liked what happened in this branch of the if, you'll love the else:

    else
    {
        if (m_dtAAfficher.Columns.Contains("MTTHRUB" + dr[0].ToString()))
        {
            arrMappingNames.Add("MTTHRUB" + dr[0].ToString());
            arrHeadersTexte.Add(dr[4]);
            arrColumnsFormat.Add("");
            arrColumnsAlign.Add("1");

            if (iCompt == Convert.ToInt16(0))
            {
                iID0 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(1))
            {
                iID1 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(2))
            {
                iID2 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(3))
            {
                iID3 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(4))
            {
                iID4 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(5))
            {
                iID5 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(6))
            {
                iID6 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(7))
            {
                iID7 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(8))
            {
                iID8 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(9))
            {
                iID9 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(10))
            {
                iID10 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(11))
            {
                iID11 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(12))
            {
                iID12 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
            else if (iCompt == Convert.ToInt16(13))
            {
                iID13 = Convert.ToInt32(dr[0]);
                iCompt = iCompt + 1;
            }
        }
    }
}

I can only assume that this function is called inside of a loop, though I have to wonder about how that loop exits? Maybe I'm being too generous, this might not be called inside of a loop, and the whole class gets to read 13 IDs out before it's populated. Does iCompt maybe get reset somewhere? No idea.

Honestly, does this even work? Wolferitza didn't tell us what it's actually supposed to do, but found this code because there's a bug in there somewhere that needed to be fixed. To my mind, "basically working" is the worst case scenario- if the code were fundamentally broken, it could just be thrown away. If it mostly works except for some bugs (and terrible maintainability) no boss is going to be willing to throw it away. It'll just fester in there forever.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Historical Dates

Handling non-existent values always presents special challenges. We've (mostly) agreed that NULL is, in some fashion, the right way to do it, though it's still common to see some sort of sentinel value that exists outside of the expected range- like a function returning a negative value when an error occurred, and a zero (or positive) value when the operation completes.

Javier found this function, which has a… very French(?) way of handling invalid dates.

 Private Function CheckOraDate(ByVal sDate As String) As String
        Dim OraOValDate As New DAL.PostGre.DataQuery()
        Dim tdate As Date
        If IsDate(sDate) Then
            Return IIf(OraOValDate.IsOracle, CustomOracleDate(Convert.ToDateTime(sDate).ToString("MM-dd-yyyy")), "'" & sDate & "'")
        Else
            '~~~ No Date Flag of Bastille Day
            Return CustomOracleDate(Convert.ToDateTime("07/14/1789").ToString("MM-dd-yyyy"))
        End If

    End Function

Given a date string, we check if it is a valid date string using IsDate. If it is, we check if our data access layer thinks the IsOracle flag is set, and if it is, we do some sort of conversion to a `CustomOracleDate", otherwise we just return the input wrapped in quotes.

All that is sketchy- any function that takes dates as a string input and then returns the date in a new format as a string always gets my hackles up. It implies loads of stringly typed operations.

But the WTF is how we handle a bad input date: we return Bastille Day.

In practice, this meant that their database system was reporting customers' birthdays as Bastille Day. And let me tell you, those customers don't look a day over 200, let alone 236.

For an extra bonus WTF, while the "happy path" checks if we should use the custom oracle formatting, the Bastille Day path does not, and just does whatever the Oracle step is every time.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Losing a Digit

Alicia recently moved to a new country and took a job with a small company willing to pay well and help with relocation costs. Overall, the code base was pretty solid. Despite the overall strong code base, one recurring complaint was that running the test suite was painfully long.

While Alicia doesn't specify what the core business is, but says: "in this company's core business, random numbers were the base of everything."

As such, they did take generating random numbers fairly seriously, and mostly used strong tools for doing that. However, whoever wrote their test suite was maybe a bit less concerned, and wrote this function:

public static Long generateRandomNumberOf(int length) {
    while (true) {
            long numb = (long)(Math.random() * 100000000 * 1000000); // had to use this as int's are to small for a 13 digit number.
            if (String.valueOf(numb).length() == length)
                return numb;		
        }		
}

They want many digits of random number. So they generate a random floating point, and then multiply it a few times to get a large number. If the length of the resulting number, in characters, is the desired length, we return it. Otherwise, we try again.

The joy here, of course, is that this function is never guaranteed to exit. In fact, if you request more than 15 digits, it definitely won't exit. In practice, most of the time, the function is able to hit the target length in a relative handful of iterations, but there's no guarantee for that.

Alicia was tracking down a bug in a test which called this function. So she went ahead and fixed this function so that it use a sane way to generate the appropriate amount of entropy that actually guaranteed a result. She included that change in her pull request, nobody had any comments, and it got merged in.

The unit tests aren't vastly faster than they were, but they are faster. Who knows what other surprises the test suite has in store?

[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.

CodeSOD: High Temperature

Brian (previously)found himself contracting for an IoT company, shipping thermostats and other home automation tools, along with mobile apps to control them.

Brian was hired because the previous contractor had hung around long enough for the product to launch, cashed the check, and vanished, never to be heard from again.

And let's just say that Brian's predecessor had a unique communication style.

    private class NoOpHandler extends AsyncCharisticHandler {
        public NoOpHandler(Consumer<BluetoothGattCharacteristic> consumer) {
            super(null, consumer);
        }

        @Override
        public void Execute() {
            // Don't do any actual BLE Communication here, and just go straight to the callback, This
            // handler is just to allow people to get a callback after after a bunch of Async IO Operations
            // have happened, without throwing all the completion logic into the "last" async callback of your batch
            // since the "last" one changes.
            InvokeCallback();
            // After this callback has been handled, recheck the queue to run any subsequent Async IO Operations
            OnBLEAsyncOperationCompleted();
            // I'm aware this is recursive. If you get a stack overflow here, you're using it wrong.
            // You're not supposed to queue thousands of NoOp handlers one after the other, Stop doing it!
            // If you need to have code executed sequentially just, er... write a fu*king function there is
            // nothing special about this callback, or the thread it is called on, and you don't need to use
            // it for anything except getting a callback after doing a batch of async IO, and then, it runs 
            // in the context of the last IO Completion callback, which shouldn't take ages. If you use 
            // AsyncRunWhenCompleted() to create more of these within the callback of AsyncRunWhenCompleted
            // it just keeps the IO completion thread busy, which also breaks shit. 
            // Basically, you shouldn't be using AsyncRunWhenCompleted() at all if you're not me.
        }
    }

Who said bad programmers don't write comments? This bad programmer wrote a ton of comments. What's funny about this is that, despite the wealth of comments, I'm not 100% certain I actually know what I'm supposed to do, aside from not use AsyncRunWhenCompleted.

The block where we initialize the Bluetooth system offers more insight into this programmer's style.

    @SuppressLint("MissingPermission")
    private void initializeBluetooth() {
        _bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        _bluetoothAdapter = _bluetoothManager != null ? _bluetoothManager.getAdapter() : null;
        if (_bluetoothAdapter != null && _bluetoothAdapter.isEnabled()) {
            /* #TODO: I don't know if cancelDiscovery does anything... either good, or bad. It seems to make BLE discovery faster after 
            *         the service is restarted by android, but I don't know if it screws anything else up in the process. Someone should check into that */
            _bluetoothAdapter.cancelDiscovery();
            _bluetoothScanner = _bluetoothAdapter.getBluetoothLeScanner();
            _scanFilters = Collections.singletonList(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(BLE_LIVELINK_UUID)).build());
            CreateScanCallback();
        } else {
            // #TODO: Handle Bluetooth not available or not enabled
            stopSelf();
        }
    }

This is a clear example of "hacked together till it works". What does cancelDiscovery do? No idea, but we call it anyway because it seems to be faster. Should we look it up? Because yes, it sounds like calling it is correct, based on the docs. Which took me 15 seconds to find. "Someone should check into that," and apparently I am that someone.

Similarly, the second TODO seems like an important missing feature. At least a notification which says, "Hey, you need bluetooth on to talk to bluetooth devices," would go a long way.

All this is in service of an IoT control app, which seems to double as a network scanner. It grabs the name of every Bluetooth and WiFi device it finds, and sends them and a location back to a web service. That web service logs them in a database, which nobody at the company ever looks at. No one wants to delete the database, because it's "valuable", though no one can ever specify exactly how they'd get value from it. At best, they claim, "Well, every other app does it." Mind you, I think we all know how they'd get value: sell all this juicy data to someone else. It's just no one at the company is willing to say that out loud.

[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.

Error'd: What Goes Up

As I was traveling this week (just home today), conveyances of all sorts were on my mind.

Llarry A. warned "This intersection is right near my house. Looks like it's going to be inconvenient for a while..." Keeping this in mind, I chose to take the train rather than drive.

1

 

Unfortunately US trains are restricted to plodding sublight speeds, but Mate has it better. "I love how the Swiss Federal Railways keep investing in new rolling stock... like this one that can teleport from one side of the country to the other in 0 minutes?! "

0

 

And Michael R. 's TfL seems to operate between parallel universes. "I was happy to see that the "not fitted" Northern Line train actually rolled in 2 mins later."

2

 

Daniel D. 's elevator went up but the ubiquitous screen went down. Daniel even slipped a bit of selfie into his submission. Sneaky. "This display usually features some looping video. This time it featured only desktop with bunch of scripts / *.bat files. I guess it's better when elevator's display crashes than when an actual elevator crashes?"

4

 

Joel C. 's elevator had a different fault. "The screen in my hotel's elevator is not sending quite the message they probably want."

3

 

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

Secure to Great Lengths

Our submitter, Gearhead, was embarking on STEM-related research. This required him to pursue funding from a governmental agency that we’ll call the Ministry of Silly Walks. In order to start a grant application and track its status, Gearhead had to create an account on the Ministry website.

The registration page asked for a lot of personal information first. Then Gearhead had to create his own username and password. He used his password generator to create a random string: D\h.|wAi=&:;^t9ZyoO

Silly Walk Gait

Upon clicking Save, he received an error.

Your password must be a minimum eight characters long, with no spaces. It must include at least three of the following character types: uppercase letter, lowercase letter, number, special character (e.g., !, $, % , ?).

Perplexed, Gearhead emailed the Ministry’s web support, asking why his registration failed. The reply:

Hello,
The site rejects password generators as hacking attempts. You will need to manually select a password.
Ex. GHott*01

Thank you,

Support

So a long sequence of random characters was an active threat, but a 1990s-era AOL username was just fine. What developer had this insane idea and convinced other people of it? How on earth did they determine what was a "manually selected" string versus a randomly-generated one?

It seems the deciding factor is nothing more than length. If you go to the Ministry’s registration page now, their password guidelines have changed (emphasis theirs):

Must be 8-10 characters long, must contain at least one special character ( ! @ # $ % ^ & * ( ) + = { } | < > \ _ - [ ] / ? ) and no spaces, may contain numbers (0-9), lower and upper case letters (a-z, A-Z). Please note that your password is case sensitive.

Only good can come of forcing tiny passwords.

The more a company or government needs secure practices, the less good they are at secure practices. Is that a law yet? It should be.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

Future Documentation

Dotan was digging through vendor supplied documentation to understand how to use an API. To his delight, he found a specific function which solved exactly the problem he had, complete with examples of how it was to be used. Fantastic!

He copied one of the examples, and hit compile, and reviewed the list of errors. Mostly, the errors were around "the function you're calling doesn't exist". He went back to the documentation, checked it, went back to the code, didn't find any mistakes, and scratched his head.

Now, it's worth noting the route Dotan took to find the function. He navigated there from a different documentation page, which sent him to an anchor in the middle of a larger documentation page- vendorsite.com/docs/product/specific-api#specific-function.

This meant that as the page loaded, his browser scrolled directly down to the specific-function section of the page. Thus, Dotan missed the gigantic banner at the top of the page for that API, which said this:

/!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!
This doc was written to help flesh out a user API. The features described here are all hypothetical and do not actually exist yet, don't assume anything you see on this page works in any version /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\ NOTE /!\

On one hand, I think providing this kind of documentation is invaluable, both to your end users and for your own development team. It's a great roadmap, a "documentation driven development" process. And I can see that they made an attempt to be extremely clear about it being incomplete and unimplemented- but they didn't think about how people actually used their documentation site. A banner at the top of the page only works if you read the page from top to bottom, but documentation pages you will frequently skip to specific sections of the page.

But there was a deeper issue with the way this particular approach was executed: while the page announced that one shouldn't assume anything works, many of the functions on the page did work. Many did not. There was no rhyme or reason, to version information or other indicators to help a developer understand what was and was not actually implemented.

So while the idea of a documentation-oriented roadmap specifying features that are coming is good, the execution here verged into WTF territory. It was a roadmap, but with all the landmarks erased, so you had no idea where you actually were along the length of that road. And the one warning sign that would help you was hidden behind a bush.

Dotan asks: "WTF is that page doing on the official documentation wiki?"

And I'd say, I understand why it's there, but boy it should have been more clear about what it actually was.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Undefined Tasks

Years ago, Brian had a problem: their C# application would crash sometimes. What was difficult to understand was why it was crashing, because it wouldn't crash in response to a user action, or really, any easily observable action.

The basic flow was that the users used a desktop application. Many operations that the users wanted to perform were time consuming, so the application spun up background tasks to do them, thus allowing the user to do other things within the application. And sometimes, the application would just crash, both when the user hadn't done anything, and when all background jobs should have been completed.

The way the background task was launched was this:

seeder.RunSeeder();

It didn't take too much head scratching to realize what was running every time the application crashed: the garbage collector.

RunSeeder returned a Task object, but since Brian's application treated the task as "fire and forget", they didn't worry about the value itself. But C# did- the garbage collector had to clean up that memory.

And this was running under .Net 4.0. This particular version of the .Net framework was a special, quantum realm, at least when it came to tasks. You see, if a Task raises an exception, nothing happens. At least, not right away. No one is notified of the exception unless they inspect the Task object directly. There's a cat in the box, and no one knows the state of the cat unless they open the box.

The application wasn't checking the Task result. The cat remained in a superposition of "exception" and "no exception". But the garbage collector looked at the task. And, in .Net 4.0, Microsoft made a choice about what to do there: when they opened the box and saw an exception (instead of a cat), they chose to crash.

Microsoft's logic here wasn't entirely bad; an uncaught exception means something has gone wrong and hasn't been handled. There's no way to be certain the application is in a safe state to continue. Treating it akin to undefined behavior and letting the application crash was a pretty sane choice.

The fix for Brian's team was simple: observe the exception, and choose not to do anything with it. They truly didn't care- these tasks were fire-and-forget, and failure was acceptable.

seeder.RunSeeder().ContinueWith(t => { var e = t.IsFaulted ? t.Exception : null; }); // Observe exceptions to prevent quantum crashes

This code merely opens the box and sees if there's an exception in there. It does nothing with it.

Now, I'd say as a matter of programming practice, Microsoft was right here. Ignoring exceptions blindly is a definite code smell, even for a fire-and-forget task. Writing the tasks in such a way as they catch and handle any exceptions that bubble up is better, as is checking the results.

But I, and Microsoft, were clearly on the outside in this argument. Starting with .Net 4.5 and moving forward, uncaught exceptions in background tasks were no longer considered show-stoppers. Whether there was a cat or an exception in the box, when the garbage collector observed it, it got thrown away either way.

In the end, this reminds me of my own failing using background tasks in .Net.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Solve a Captcha to Continue

The first time Z hit the captcha on his company's site, he didn't think much of it. And to be honest, the second time he wasn't paying that much attention. So it wasn't until the third time that he realized that the captcha had showed him the same image every single time- a "5" with lines scribbled all over it.

That led Z to dig out the source and see how the captcha was implemneted.

<Center>Click a number below to proceed to the next page. <br>Some browsers do not like this feature and will try to get around it. If you are having trouble<br> seeing the image, empty your internet cache and temporary internet files may help. <br>Please ensure you have no refresher add-ons installed on your browser.<br />
<table border=1><Tr><td colspan='3' align='center'>
<font style='font-size:36px;'><img width='150' title='5' alt='5' src='valimages/5.gif'> </font></td></tr>
<Tr>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=1'>1</a></font></td>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=2'>2</a></font></td>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=3'>3</a></font></td>
</tr>
<Tr>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=4'>4</a></font></td>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=5'>5</a></font></td>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=6'>6</a></font></td>
</tr>
<tr>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=7'>7</a></font></td>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=8'>8</a></font></td>
<Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=9'>9</a></font></td>
</tr></table>
</Center>

Look, I know there's a joke about how hard it is to center things in CSS, but I think we've gone a little overboard with our attempt here.

Now, the PHP driving this page could have easily been implemented to randomly select an image from the valimages directory, and there was some commented out code to that effect. But it appears that whoever wrote it couldn't quite understand how to link the selected image to the behavior in valid.php, so they just opted to hard code in five as the correct answer.

The bonus, of course, is that the image for five is named 5.gif, which means if anyone really wanted to bypass the captcha, it'd be trivial to do so by scraping the code. I mean, not more trivial than just realizing "it's the same answer every time", but still, trivial.

Of course, out here in the real world, captchas have never been about keeping bots out of sites, and instead are just a way to trick the world into training AI. Pretty soon we'll roll out the Voight-Kampf test, but again, the secret purpose won't be to find the replicants, but instead gather data so that the next generation of replicants can pass the test.

[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.

Error'd: Once Is Never Enough

"Getting ready to!" anticipated richard h. but then this happened. "All I want are the CLI options to mark the stupid TOS box so I can install this using our Chef automation. "What are the options" is too much to ask, apparently. But this is Microsoft. Are stupid errors like this really that unexpected?"

1

 

Followed immediately by richard's report: "Following up my error'd submission a few minutes ago, I clicked the "Accept TOS" box, and the "Something unexpected happened" box lit up, so I clicked the button to let the unexpected do what the something wanted to do. Now I have successfully Something unexpected happened. smh Microsoft. "

0

 

An anonymous griper snickered "It's a made up word, but I just wanted to check the spelling before writing it in a slack comment as a joke referencing the show("that's a nice tnetennba"), but the first thing I saw was the AI preview with the first sentence incorrectly claiming it's "basketball" spelled backwards(which it's clearly not, backwards it would be "abnnetent" which is also not a word). " I have to differ, though. Spelled backwards it would be llabteksab.

Silly monkey, backwards it would be ti

 

And a different anonymous griper (I assume they're different, but they're anonymous so who can really know?) needed some help doing a quite trivial computation. "On which planet?" we all wonder together. 3

 

Finally, a recurring theme from a recurring reader, B.J.H. keeps buying stuff. "This screen shot was captured the morning of 26 October. I'm not sure what bothers me more, that the package was picked up twice (once in the future), or that "Standard Transit" (when the package should be expected) is a day before the pick-up. Or maybe they just lie about the pickup to cover for not meeting the standard delivery date. "

4

 

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

The Ghost Cursor

Everyone's got workplace woes. The clueless manager; the disruptive coworker; the cube walls that loom ever higher as the years pass, trapping whatever's left of your soul.

But sometimes, Satan really leaves his mark on a joint. I worked Tech Support there. You may remember The C-Level Ticket. I'm Anonymous. This is my story.


Between 2 Buildings (Montreal) - Flickr - MassiveKontent

Night after night, my dreams are full of me trying and failing at absolutely everything. Catch a bus? I'm already running late and won't make it. Dial a phone number to get help? I can't recall the memorized sequence, and the keypad's busted anyway. Drive outta danger? The car won't start. Run from a threat? My legs are frozen.

Then I wake up in my bed in total darkness, scared out of my skull, and I can't move for real. Not one muscle works. Even if I could move, I'd stay still because I'm convinced the smallest twitch will give me away to the monster lurking nearby, looking to do me in.

The alarm nags me before the sun's even seen fit to show itself. What day is it? Tuesday? An invisible, overwhelming dread pins me in place under the covers. I can't do it. Not again.

The thing is, hunger, thirst, and cold are even more nagging than the alarm. Dead tired, I force myself up anyway to do the whole thing over.


The office joe that morning was so over-brewed as to be sour. I tossed down the last swig in my mug, checking my computer one more time to make sure no Tech Support fires were raging by instant message or email. Then I threw on my coat and hat and quit my cube, taking the stairs to ground level.

I pushed open a heavy fire-escape door and stepped out into the narrow alley between two massive office buildings. Brisk autumn air and the din of urban motor traffic rushed to greet me. The dull gray sky above threatened rain. Leaning against the far brick wall were Toby and Reynaldo, a couple of network admins, hugging themselves as they nursed smoldering cigarettes. They nodded hello.

I tipped my hat in greeting, slipping toward the usual spot, a patch of asphalt I'd all but worn grooves in by that point. I lit my own cigarette and took in a deep, warming draw.

"Make it last another year," Toby spoke in a mocking tone, tapping ash onto the pavement. "I swear, that jerk can squeeze a nickel until Jefferson poops!"

An ambulance siren blared through the alley for a minute. The rig was no doubt racing toward the hospital down the street.

Reynaldo smirked. "You think Morty finally did it?"

Toby smirked as well.

I raised an eyebrow. "Did what?"

"Morty always says he's gonna run out into traffic one of these days so they can take him to the hospital and he won't have to be here," Reynaldo explained.

I frowned at the morbid suggestion. "Hell of a way to catch a break."

"Well, it's not like we can ask for time off," Toby replied bitterly. "They always find some way to rope us back in."

I nodded in sympathy. "You have it worse than we do. But my sleep's still been jacked plenty of times by 3AM escalated nonsense that shoulda been handled by a different part of the globe."

Reynaldo's eyes lit up fiercely. "They have all the same access and training, but it never falls on them! Yeah, been there."

The door swung open again, admitting a young woman with the weight of the world on her shoulders. This was Megan, a junior developer and recent hire. I tipped my hat while helping myself to another drag.

She hastened my way, pulling a pack of cigarettes from her handbag. With shaking hands, she fumbled to select a single coffin nail. "I quit these things!" she lamented. After returning the pack to her bag, she rummaged through it fruitlessly. "Dammit, where are those matches?!" She glanced up at me with a pleading expression.

I pulled the lighter from my coat pocket. "You sure?"

She nodded like she hadn't been more sure about anything in her entire life.

I lit it for her. She took a lung-filling pull, then exhaled a huge cloud of smoke.

"Goin' that well, huh?" I asked.

Megan also hugged herself, her expression pained. "Every major player in the industry uses our platform, and I have no idea how it hasn't all come crashing down. There are thousands of bugs in the code base. Thousands! It breaks all the time. Most of the senior devs have no clue what they're doing. And now we're about to lose the only guy who understands the scheduling algorithm, the most important thing!"

"That's tough." I had no idea what else to say. Maybe it was enough that I listened.

Megan glanced up nervously at the brewing storm overhead. "I just know that algorithm's gonna get dumped in my lap."

"The curse of competence." I'd seen it plenty of times.

"Ain't that the truth!" She focused on me again with a look of apology. "How've you been?"

I shrugged. "Same old, same old." I figured a fresh war story might help. "Had to image and set up the tech for this new manager's onboarding. Her face is stuck in this permanent glare. Every time she opens her mouth, it's to bawl someone out."

"Ugh."

"The crazy thing is, the walls of her office are completely covered with crucifixes, and all these posters plastered with flowers and hearts and sap like Choose Kindness." I leaned in and lowered my voice. "You know what I think? I think she’s an ancient Roman whose spite has kept her alive for over two thousand years. Those crosses are a threat!"

That teased a small laugh out of Megan. For a moment, the amusement reached her eyes. Then it was gone, overwhelmed by worry. She took to pacing through the narrow alley.


Back at my cube, I found a new urgent ticket at the top of my case load. Patricia Dracora, a senior project manager, had put in a call claiming her computer had been hacked. Her mouse cursor was moving around and clicking things all on its own.

It was too early in the morning for a case like this. That old dread began sneaking up on me again. The name put me on edge as well. Over the years, our paths had never crossed, but her nickname throughout Tech Support, Dracula, betrayed what everyone else made of her.

"Make like a leaf and blow!"

The boss barked his stern command over my shoulder. I stood and turned from my computer to find him at my cubicle threshold with arms folded, blocking my egress.

I couldn't blow, so I shrugged. "Can't be as bad as The Crucifier."

"Dracula's worse than The Crucifier," the boss replied under his breath in a warning tone. "For your own good, don't keep her waiting!" He tossed a thumb over his shoulder for good measure.

When he finally backed out of the way, I made tracks outta there. A few of my peers made eye contact as I passed, looking wary on my behalf.

The ticket pegged Dracora's office in a subfloor I'd never set foot in before. Descending the stairs, I had too much time to think. Of course I didn't expect a real hacking attempt. Peripheral hardware on the fritz, some software glitch: there'd be a simple explanation. What fresh hell would I have to endure to reach that point? That was what my tired brain couldn't let go of. The stimulants hadn't kicked in yet. With the strength of a kitten, I was stepping into a lion's den. A lion who might make me wish for crucifixion by the time it was all over.

From the stairwell, I entered a dank, deserted corridor. Old florescent lighting fixtures hummed and flickered overhead. That, combined with the overwhelming stench of paint fumes, set the stage for a ripping headache. There were no numbers on the walls to lead me to the right place. They must've taken them down to paint and never replaced them. I inched down worn, stained carpeting, peeking into each open gap I found to either side of me. Nothing but darkness, dust, and cobwebs at first. Eventually, I spotted light blaring from one of the open doors ahead of me. I jogged the rest of the way, eager to see any living being by that point.

The room I'd stumbled onto was almost closet-sized. It contained a desk and chair, a laptop docking station, and a stack of cardboard boxes on the floor. Behind the desk was a woman of short stature, a large purse slung over one shoulder. Her arms were folded as she paced back and forth in the space behind her chair. When I appeared, she stopped and looked to me wide-eyed, maybe just as relieved as I was. "Are you Tech Support?"

"Yes, ma'am." I entered the room. "What's—?"

"I don't know how it happened!" Dracora returned to pacing, both hands making tight fists around the straps of the purse she was apparently too wired and distracted to set down. "They made me move here from the fourth floor. I just brought everything down and set up my computer, and now someone has control of the mouse. Look, look!" She stopped and pointed at the monitor.

I rounded the desk. By the time I got there, whatever she'd seen had vanished. Onscreen, the mouse cursor sat still against a backdrop of open browsers and folders. Nothing unusual.

"It was moving, I swear!" Anguished, Dracora pleaded with me to believe her.

It seemed like she wasn't hostile at all, just stressed out and scared. I could handle that. "I'm sure we can figure this out, ma'am. Lemme have a look here."

I sat down at the desk and tried the wireless mouse first. It didn't work at all to move the cursor.

"The hacker's locked us out!" Dracora returned to pacing behind me.

As I sat there, not touching a thing, the mouse cursor shuttled across the screen like it was possessed.

"There! You see?"

Suddenly, somehow, my brain smashed everything together. "Ma'am, I have an idea. Could you please stand still?"

Dracora stopped.

I swiveled around in the chair to face her. "Ma'am, you said you were moving in down here. What's in your purse right now?"

Her visible confusion deepened. "What?"

"The mouse cursor only moves around when you do," I explained.

Her eyes widened. She dug deeply into her purse. A moment later, she pulled out a second wireless mouse. Then she looked to me like she couldn't believe it. "That's it?!"

"That's it!" I replied.

"Oh, lord!" Dracora replaced the dud sitting on her mousepad with the mouse that was actually connected to her machine, wilting over the desk as she did so. "I don't know whether to laugh or cry."

I knew the feeling. But the moment of triumph, I gotta admit, felt pretty swell. "Anything else I can help with, ma'am?"

"No, no! I've wasted enough of your time. Thank you so much!"

I had even more questions on the way back upstairs. With this huge, spacious office building, who was forcing Dracora to be in that pit? How had she garnered such a threatening reputation? Why had my experience been so different from everyone else's? I didn't mention it to the boss or my peers. I broke it all down to Megan in the alley a few days later.

"She even put in a good word for me when she closed the ticket," I told her. "The boss says I'm on the fast track for another promotion." I took a drag from my cigarette, full of bemusement. "I'm already as senior as it gets. The only way up from here is management." I shook my head. "That ain't my thing. Look how well it's gone for Dracora."

Megan lowered her gaze, eyes narrowed. "You said it yourself: the only reward for good work is more work."

And then they buried you ... in a basement, or a box.

I remembered being at the start of my career, like Megan. I remembered feeling horrified by all the decades standing between me and the day when I wouldn't or couldn't ever work again. A couple decades in, some part of me that I'd repressed had resurfaced. What the hell is this? What have I been doing?

Stop caring, a different part replied. Just stop caring. Take things day by day, case by case.

I'd obeyed for so long. Where had it gotten me?

Under my breath, I risked airing my wildest wish for the future. "Someday, I wanna break outta this joint."

Megan blinked up at me. I had her attention. "How?"

"I dunno," I admitted. "I gotta figure it out ... before I go nuts."

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: A Basic Mistake

Way back in 1964, people were starting to recgonize that computers were going to have a large impact on the world. There was not, at the time, very much prepackaged software, which meant if you were going to use a computer to do work, you were likely going to have to write your own programs. The tools to do that weren't friendly to non-mathematicians.

Thus, in 1964, was BASIC created, a language derived from experiments with languages like DOPE (The Dartmouth Oversimplified Programming Experiment). The goal was to be something easy, something that anyone could use.

In 1977, the TRS-80, the Commodore PET, and the Apple II all launched, putting BASIC into the hands of end users. But it's important to note that BASIC had already been seeing wide use for a decade on "big iron" systems, or more hobbyist systems, like the Altair 8800.

Today's submitter, Coyne, was but a humble student in 1977, and despite studying at a decent university, brand spanking new computers were a bit out of reach. Coyne was working with professors to write code to support papers, and using some dialect of BASIC on some minicomputer.

One of Coyne's peers had written a pile of code, and one simple segment didn't work. As it was just a loop to print out a series of numbers, it seemed like it should work, and work quite easily. But the programmer writing it couldn't get it to work. They passed it around to other folks in the department, and those folks also couldn't get it to work. What could possibly be wrong with this code?

3010 O = 45
3020 FOR K = 1 TO O
3030   PRINT K
3040 NEXT K

Now, it's worth noting, this particular dialect of BASIC didn't support long variable names- you could use a single letter, or you could use a letter and a number, and that was it. So the short variable names are not explicitly a problem here- that's just the stone tools which were available to programmers at the time.

For days, people kept staring at this block, trying to figure out what was wrong. Finally, Coyne took a glance, and in a moment was able to spot the problem.

I've done something nasty here, because I posted the correct block first. What the programmer had actually written was this:

3010 O = 45
3020 FOR K = 1 TO 0
3030   PRINT K
3040 NEXT K

The difference is subtle, especially when you're staring at a blurry CRT late at night in the computer lab, with too little coffee and too much overhead lighting. I don't know what device they were using for display; most terminals made sure to make O look different from 0, but I couldn't be bold enough to say all of them did. And, in this era, you'd frequently review code on printed paper, so who knows how it was getting printed out?

But that, in the end, was the problem- the programmer accidentally typed a zero where they meant the letter "O". And that one typo was enough to send an entire computer science department spinning for days when no one could figure it out.

In any case, it's interesting to see how an "easy" to use language once restricted variable names to such deep inscrutability.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: A Truly Bad Comparison

For C programmers of a certain age (antique), booleans represent a frustrating challenge. But with the addition of stdbool.h, we exited the world of needing to work hard to interact with boolean values. While some gotchas are still in there, your boolean code has the opportunity to be simple.

Mark's predecessor saw how simple it made things, and decided that wouldn't do. So that person went and wrote their own special way of comparing boolean values. It starts with an enum:

typedef enum exop_t {
    EXOP_NONE,
    EXOP_AND,
    EXOP_OR,
    EXOP_EQUAL,
    EXOP_NOTEQUAL,
    EXOP_LT,
    EXOP_GT,
    EXOP_LEQUAL,
    EXOP_GEQUAL,
    EXOP_ADD,
    EXOP_SUBTRACT,
    EXOP_MULTIPLY,
    EXOP_DIV,
    EXOP_MOD,
    EXOP_NEGATE,
    EXOP_UNION,
    EXOP_FILTER1,
    EXOP_FILTER2
};

Yes, they did write an enum to compare booleans. They also wrote not one, but two functions. Let's start with the almost sane one.

static bool compare_booleans (bool bool1,
                              bool bool2,
                              exop_t  exop)
{

    int32_t  cmpresult;

    if ((bool1 && bool2) || (!bool1 && !bool2)) {
        cmpresult = 0;
    } else if (bool1) {
        cmpresult = 1;
    } else {
        cmpresult = -1;
    }

    return convert_compare_result(cmpresult, exop);

}

This function takes two boolean values, and a comparison we wish to perform. Then, we test if they're equal, though the way we do that is by and-ing them together, then or-ing that with the and of their negations. If they're equal, cmpresult is set to zero. If they're not equal, and the first boolean is true, we set cmpresult to one, and finally to negative one.

Thus, we're just invented strcmp for booleans.

But then we call another function, which is super helpful, because it turns that integer into a more normal boolean value.

static boolean
    convert_compare_result (int32_t cmpresult,
                            exop_t exop)
{
    switch (exop) {
    case EXOP_EQUAL:
        return (cmpresult) ? FALSE : TRUE;
    case EXOP_NOTEQUAL:
        return (cmpresult) ? TRUE : FALSE;
    case EXOP_LT:
        return (cmpresult < 0) ? TRUE : FALSE;
    case EXOP_GT:
        return (cmpresult > 0) ? TRUE : FALSE;
    case EXOP_LEQUAL:
        return (cmpresult <= 0) ? TRUE : FALSE;
    case EXOP_GEQUAL:
        return (cmpresult >= 0) ? TRUE : FALSE;
    default:
        printf( "ERR_INTERNAL_VAL\n" );
        return TRUE;
    }
} 

We switch based on the requested operation, and each case is its own little ternary. For equality comparisons, it requires a little bit of backwards logic- if cmpresult is non-zero (thus true), we need to return FALSE. Also note how our expression enum has many more options than convert_compare_result supports, making it very easy to call it wrong- and worse, it returns TRUE if you call it wrong.

At least they made booleans hard again. Who doesn't want to be confused about how to correctly check if two boolean values are the same?

It's worth noting that, for all this code, the rest of the code base never used anything but EXOP_EQUAL and EXOP_NOTEQUAL, because why would you do anything else on booleans? Every instance of compare_booleans could have been replaced with a much clearer == or != operator. Though what should really have been replaced was whoever wrote this code, preferably before they wrote it.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

A Government Data Center

Back in the antediluvian times, when I was in college, people still used floppy disks to work on their papers. This was a pretty untenable arrangement, because floppy disks lost data all the time, and few students had the wherewithal to make multiple copies. Half my time spent working helldesk was breaking out Norton Diskutils to try and rescue people's term papers. To avoid this, the IT department offered network shares where students could store documents. The network share was backed up, tracked versions, and could be accessed from any computer on campus, including the VAX system (in fact, it was stored on the VAX).

I bring this up because we have known for quite some time that companies and governments need to store documents in centrally accessible locations so that you're not reliant on end users correctly managing their files. And if you are a national government, you have to make a choice: either you contract out to a private sector company, or you do it yourself.

South Korea made the choice to do it themselves, with their G-Drive system (short for Government Drive, no relation to Google Drive), a government file store hosted primarily out of a datacenter in Daejeon. Unfortunately, "primarily" is a bit too apropos- last month, a fire in that datacenter destroyed data.

The Interior Ministry explained that while most systems at the Daejeon data center are backed up daily to separate equipment within the same center and to a physically remote backup facility, the G-Drive’s structure did not allow for external backups. This vulnerability ultimately left it unprotected.

Someone, somehow, designed a data storage system that was structurally incapable of doing backups? And then told 750,000 government employees that they should put all their files there?

Even outside of that backup failure, while other services had backups, they did not have a failover site, so when the datacenter went down, the government went down with it.

In total, it looks like about 858TB of data got torched. 647 distinct services were knocked out. At least 90 of them were reported to be unrecoverable (that last link is from a company selling Lithium Ion safety products, but is a good recap). A full recovery was, shortly after the accident, predicted to take a month, but as of October 22, only 60% of services had been restored.

Now, any kind of failure of this scale means heads must roll, and police investigations have gone down the path of illegal subcontracting. The claim is that the government hired a contractor broke the law by subcontracting the work, and that those subcontractors were unqualified for the work they were doing- that while they were qualified to install or remove a li-ion battery, they were not qualified to move one, which is what they were doing and resulted in the fire.

I know too little about Korean laws about government contracting and too little about li-ion battery management to weigh in on this. Certainly, high-storage batteries are basically bombs, and need to be handled with great care and protected well. Though, if one knows how to install and uninstall a battery, moving a battery seems covered in those steps.

But if I were doing a root cause analysis here, while that could be the root cause of the fire, it is not the root cause of the outage. If you build a giant datacenter but can't replicate services to another location, you haven't built a reliable cloud storage system- you've just built an expensive floppy disk that is one trip too close to a fridge magnet away from losing all of your work. In this case, the fridge magnet was made of fire, but the result is the same.

I'm not going to say this problem would have be easy to avoid; actually building resilient infrastructure that fails gracefully under extreme stress is hard. But while it's a hard problem, it's also a well-understood problem. There are best practices, and clearly not one of them was followed.

[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.

CodeSOD: Contracting Space

A ticket came in marked urgent. When users were entering data in the header field, the spaces they were putting in kept getting mangled. This was in production, and had been in production for sometime.

Mike P picked up the ticket, and was able to track down the problem to a file called Strings.java. Yes, at some point, someone wrote a bunch of string helper functions and jammed them into a package. Of course, many of the functions were re-implementations of existing functions: reinvented wheels, now available in square.

For example, the trim function.

    /**
     * @param str
     * @return The trimmed string, or null if the string is null or an empty string.
     */
    public static String trim(String str) {
        if (str == null) {
            return null;
        }

        String ret = str.trim();

        int len = ret.length();
        char last = '\u0021';    // choose a character that will not be interpreted as whitespace
        char c;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < len; i++) {
            c = ret.charAt(i);
            if (c > '\u0020') {
                if (last <= '\u0020') {
                    sb.append(' ');
                }
                sb.append(c);
            }
            last = c;
        }
        ret = sb.toString();

        if ("".equals(ret)) {
            return null;
        } else {
            return ret;
        }
    }

Now, Mike's complaint is that this function could have been replaced with a regular expression. While that would likely be much smaller, regexes are expensive- in performance and frequently in cognitive overhead- and I actually have no objections to people scanning strings.

But let's dig into what we're doing here.

They start with a null check, which sure. Then they trim the string; never a good sign when your homemade trim method calls the built-in.

Then, they iterate across the string, copying characters into a StringBuffer. If the current character is above unicode character 20- the realm of printable characters- and if the last character was a whitespace character, we copy a whitespace character into the output, and then the printable character into the output.

What this function does is simply replace runs of whitespace with single whitespace characters.

"This        string"
becomes
"This string"

Badly I should add. Because there are plenty of whitespace characters which appear above \u0020- like the non-breaking space (\u00A0), and many other control characters. While you might be willing to believe your users will never figure out how to type those, you can't guarantee that they'll never copy/paste them.

For me, however, this function does something far worse than being bad at removing extraneous whitespace. Because it has that check at the end- if I handed it a perfectly good string that is only whitespace, it hands me back a null.

I can see the argument- it's a bad input, so just give me back an objectively bad result. No IsNullOrEmpty check, just a simple null check. But I still hate it- turning an actual value into a null just bothers me, and seems like an easy way to cause problems.

In any case, the root problem with this bug was simply developer invented requirements: the users never wanted stray spaces to be automatically removed in the middle of the string. Trimmed yes, gutted no.

No one tried to use multiple spaces for most of the history of the application, thus no one noticed the problem. No one expected it to not work. Hence the ticket and the panic by users who didn't understand what was going on.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Pickup Sticklers

An Anonymous quality analyst and audiophile accounted "As a returning customer at napalmrecords.com I was forced to update my Billing Address. Fine. Sure. But what if my *House number* is a very big number? More than 10 "symbols"? Fortunately, 0xDEADBEEF for House number and J****** for First Name both passed validation."

3

And then he proved it, by screenshot:

4

 

Richard P. found a flubstitution failure mocking "I'm always on the lookout for new and interesting Lego sets. I definitely don't have {{product.name}} in my collection!"

2

 

"I guess short-named siblings aren't allowed for this security question," pointed out Mark T.

0

 

Finally, my favorite category of Error'd -- the security snafu. Tim R. reported this one, saying "Sainsbury/Argos in the UK doesn't want just anybody picking up the item I've ordered online and paid for, so they require not one, not two, but 3 pieces of information when I come to collect it. There's surely no way any interloper could possibly find out all 3, unless they were all sent in the same email obviously." Personally, my threat model for my grocery pickups is pretty permissive, but Tim cares.

1

 

[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.

Coded Smorgasbord: High Strung

Most languages these days have some variation of "is string null or empty" as a convenience function. Certainly, C#, the language we're looking at today does. Let's look at a few example of how this can go wrong, from different developers.

We start with an example from Jason, which is useless, but not a true WTF:

/// <summary>
/// Does the given string contain any characters?
/// </summary>
/// <param name="strToCheck">String to check</param>
/// <returns>
/// true - String contains some characters.
/// false - String is null or empty.
/// </returns>
public static bool StringValid(string strToCheck)
{
        if ((strToCheck == null) ||
                (strToCheck == string.Empty))
                return false;

        return true;
}

Obviously, a better solution here would be to simply return the boolean expression instead of using a conditional, but equally obvious, the even better solution would be to use the built-in. But as implementations go, this doesn't completely lose the plot. It's bad, it shouldn't exist, but it's barely a WTF. How can we make this worse?

Well, Derek sends us an example line, which is scattered through the codebase.

if (Port==null || "".Equals(Port)) { /* do stuff */}

Yes, it's frequently done as a one-liner, like this, with the do stuff jammed all together. And yes, the variable is frequently different- it's likely the developer responsible saved this bit of code as a snippet so they could easily drop it in anywhere. And they dropped it in everywhere. Any place a string got touched in the code, this pattern reared its head.

I especially like the "".Equals call, which is certainly valid, but inverted from how most people would think about doing the check. It echos Python's string join function, which is invoked on the join character (and not the string being joined), which makes me wonder if that's where this developer started out?

I'll never know.

Finally, let's poke at one from Malfist. We jump over to Java for this one. Malfist saw a function called checkNull and foolishly assumed that it returned a boolean if a string was null.

public static final String checkNull(String str, String defaultStr)
{
    if (str == null)
        return defaultStr ;
    else
        return str.trim() ;
}

No, it's not actually a check. It's a coalesce function. Okay, misleading names aside, what is wrong with it? Well, for my money, the fact that the non-null input string gets trimmed, but the default string does not. With the bonus points that this does nothing to verify that the default string isn't null, which means this could easily still propagate null reference exceptions in unexpected places.

I've said it before, and I'll say it again: strings were a mistake. We should just abolish them. No more text, everybody, we're done.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Across the 4th Dimension

We're going to start with the code, and then talk about it. You've seen it before, you know the chorus: bad date handling:

C_DATE($1)
C_STRING(7;$0)
C_STRING(3;$currentMonth)
C_STRING(2;$currentDay;$currentYear)
C_INTEGER($month)

$currentDay:=String(Day of($1))
$currentDay:=Change string("00";$currentDay;3-Length($currentDay))
$month:=Month of($1)
Case of

: ($month=1)
$currentMonth:="JAN"

: ($month=2)
$currentMonth:="FEB"

: ($month=3)
$currentMonth:="MAR"

: ($month=4)
$currentMonth:="APR"

: ($month=5)
$currentMonth:="MAY"

: ($month=6)
$currentMonth:="JUN"

: ($month=7)
$currentMonth:="JUL"

: ($month=8)
$currentMonth:="AUG"

: ($month=9)
$currentMonth:="SEP"

: ($month=10)
$currentMonth:="OCT"

: ($month=11)
$currentMonth:="NOV"

: ($month=12)
$currentMonth:="DEC"

End case

$currentYear:=Substring(String(Year of($1));3;2)

$0:=$currentDay+$currentMonth+$currentYear

At this point, most of you are asking "what the hell is that?" Well, that's Brewster's contribution to the site, and be ready to be shocked: the code you're looking at isn't the WTF in this story.

Let's rewind to 1984. Every public space was covered with a thin layer of tobacco tar. The Ground Round restaurant chain would sell children's meals based on the weight of the child and have magicians going from table to table during the meal. And nobody quite figured out exactly how relational databases were going to factor into the future, especially because in 1984, the future was on the desktop, not the big iron "server side".

Thus was born "Silver Surfer", which changed its name to "4th Dimension", or 4D. 4D was an RDBMS, an IDE, and a custom programming language. That language is what you see above. Originally, they developed on Apple hardware, and were almost published directly by Apple, but "other vendors" (like FileMaker) were concerned that Apple having a "brand" database would hurt their businesses, and pressured Apple- who at the time was very dependent on its software vendors to keep its ecosystem viable. In 1993, 4D added a server/client deployment. In 1995, it went cross platform and started working on Windows. By 1997 it supported building web applications.

All in all, 4D seems to always have been a step or two behind. It released a few years after FileMaker, which served a similar niche. It moved to Windows a few years after Access was released. It added web support a few years after tools like Cold Fusion (yes, I know) and PHP (I absolutely know) started to make building data-driven web apps more accessible. It started supporting Service Oriented Architectures in 2004, which is probably as close to "on time" as it ever got for shipping a feature based on market demands.

4D still sees infrequent releases. It supports SQL (as of 2008), and PHP (as of 2010). The company behind it still exists. It still ships, and people- like Brewster- still ship applications using it. Which brings us all the way back around to the terrible date handling code.

4D does have a "date display" function, which formats dates. But it only supports a handful of output formats, at least in the version Brewster is using. Which means if you want DD-MMM-YYYY (24-SEP-2025) you have to build it yourself.

Which is what we see above. The rare case where bad date handling isn't inherently the WTF; the absence of good date handling in the available tooling is.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: One Last ID

Chris's company has an unusual deployment. They had a MySQL database hosted on Cloud Provider A. They hired a web development company, which wanted to host their website on Cloud Provider B. Someone said, "Yeah, this makes sense," and wrote the web dev company a sizable check. They app was built, tested, and released, and everyone was happy.

Everyone was happy until the first bills came in. They expected the data load for the entire month to be in the gigabytes range, based on their userbase and expected workloads. But for some reason, the data transfer was many terabytes, blowing up their operational budget for the year in a single month.

Chris fired up a traffic monitor and saw that, yes, huge piles of data were getting shipped around with every request. Well, not every request. Every insert operation ended up retrieving a huge pile of data. A little more research was able to find the culprit:

SELECT last_insert_id() FROM some_table_name

The last_insert_id function is a useful one- it returns the last autogenerated ID in your transaction. So you can INSERT, and then check what ID was assigned to the inserted record. Great. But the way it's meant to be used is like so: SELECT last_insert_id(). Note the lack of a FROM clause.

By adding the FROM, what the developers were actually saying were "grab all rows from this table, and select the last_insert_id once for each one of them". The value of last_insert_id() just got repeated once for each row, and there were a lot of rows. Many millions. So every time a user inserted a row into most tables, the database sent back a single number, repeated millions and millions of times. Each INSERT operation caused a 30MB reply. And when you have high enough traffic, that adds up quickly.

On a technical level, it was an easy fix. On a practical one, it took six weeks to coordinate with the web dev company and their hosting setup to make the change, test the change, and deploy the change. Two of those weeks were simply spent convincing the company that yes, this was in fact happening, and yes, it was in fact their fault.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Identify a Nap

Guy picked up a bug ticket. There was a Hiesenbug; sometimes, saving a new entry in the application resulted in a duplicate primary key error, which should never happen.

The error was in the message-bus implementation someone else at the company had inner-platformed together, and it didn't take long to understand why it failed.

/**
 * This generator is used to generate message ids.
 * This implementation merely returns the current timestamp as long.
 *
 * We are, thus, limited to insert 1000 new messages per second.
 * That throughput seems reasonable in regard with the overall
 * processing of a ticket.
 *
 * Might have to re-consider that if needed.
 *
 */
public class IdGenerator implements IdentifierGenerator
{

        long previousId;
       
        @Override
        public synchronized Long generate (SessionImplementor session, Object parent) throws HibernateException {
                long newId = new Date().getTime();
                if (newId == previousId) {
                        try { Thread.sleep(1); } catch (InterruptedException ignore) {}
                        newId = new Date().getTime();
                }
                return newId;
        }
}

This generates IDs based off of the current timestamp. If too many requests come in and we start seeing repeating IDs, we sleep for a second and then try again.

This… this is just an autoincrementing counter with extra steps. Which most, but I suppose not all databases supply natively. It does save you the trouble of storing the current counter value outside of a running program, I guess, but at the cost of having your application take a break when it's under heavier than average load.

One thing you might note is absent here: generate doesn't update previousId. Which does, at least, mean we won't ever sleep for a second. But it also means we're not doing anything to avoid collisions here. But that, as it turns out, isn't really that much of a problem. Why?

Because this application doesn't just run on a single server. It's distributed across a handful of nodes, both for load balancing and resiliency. Which means even if the code properly updated previousId, this still wouldn't prevent collisions across multiple nodes, unless they suddenly start syncing previousId amongst each other.

I guess the fix might be to combine a timestamp with something unique to each machine, like… I don't know… hmmm… maybe the MAC address on one of their network interfaces? Oh! Or maybe you could use a sufficiently large random number, like really large. 128-bits or something. Or, if you're getting really fancy, combine the timestamp with some randomness. I dunno, something like that really sounds like it could get you to some kind of universally unique value.

Then again, since the throughput is well under 1,000 messages per second, you could probably also just let your database handle it, and maybe not generate the IDs in code.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Error'd: You Talkin' to Me?

The Beast In Black is back with a simple but silly factual error on the part of the gateway to all (most) human knowledge.

0

 

B.J.H. "The old saying is "if you don't like the weather wait five minutes". Weather.com found a time saver." The trick here is to notice that the "now" temperature is not the same as the headline temperature, also presumably now.

1

 

"That's some funny math you got there. Be a shame if it was right," says Jason . "The S3 bucket has 10 files in it. Picking any two (or more) causes the Download button to go disabled with this message when moused over. All I could think of is that this S3 bucket must be in the same universe as https://thedailywtf.com/articles/free-birds " Alas, we are all in the same universe as https://thedailywtf.com/articles/free-birds .

2

 

"For others, the markets go up and down, but me, I get real dividends!" gloats my new best friend Mr. TA .

3

 

David B. is waiting patiently. "Somewhere in the USPS a package awaits delivery. Either rain, nor snow, nor gloom of night shall prevent the carrier on their appointed rounds. When these rounds will occur are not the USPS's problem." We may not know the day, but we know the hour!

4

 

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: An Echo In Here in here

Tobbi sends us a true confession: they wrote this code.

The code we're about to look at is the kind of code that mixes JavaScript and PHP together, using PHP to generate JavaScript code. That's already a terrible anti-pattern, but Tobbi adds another layer to the whole thing.


if (AJAX)
{
    <?php
        echo "AJAX.open(\"POST\", '/timesheets/v2/rapports/FactBCDetail/getDateDebutPeriode.php', true);";
            
    ?>
    
    AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    AJAX.onreadystatechange = callback_getDateDebutPeriode;
    AJAX.send(strPostRequest);
}

if (AJAX2)
{
    <?php
        echo "AJAX2.open(\"POST\", '/timesheets/v2/rapports/FactBCDetail/getDateFinPeriode.php', true);";
    ?>
    AJAX2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    AJAX2.onreadystatechange = callback_getDateFinPeriode;
    AJAX2.send(strPostRequest);
}

So, this uses server side code to… output string literals which could have just been written directly into the JavaScript without the PHP step.

"What was I thinking when I wrote that?" Tobbi wonders. Likely, you weren't thinking, Tobbi. Have another cup of coffee, I think you need it.

All in all, this code is pretty harmless, but is a malodorous brain-fart. As for absolution: this is why we have code reviews. Either your org doesn't do them, or it doesn't do them well. Anyone can make this kind of mistake, but only organizational failures get this code merged.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Representative Line: Brace Yourself

Today's representative line is almost too short to be a full line. But I haven't got a category for representative characters, so we'll roll with it. First, though, we need the setup.

Brody inherited a massive project for a government organization. It was the kind of code base that had thousands of lines per file, and frequently thousands of lines per function. Almost none of those lines were comments. Almost.

In the middle of one of the shorter functions (closer to 500 lines), Brody found this:

//    }

This was the only comment in the entire file. And it's a beautiful one, because it tells us so much. Specifically, it tells us the developer responsible messed up the brace count (because clearly a long function has loads of braces in it), and discovered their code didn't compile. So they went around commenting out extra braces until they found the offender. Code compiled, and voila- on to the next bug, leaving the comment behind.

Now, I don't know for certain that's why a single closing brace is commented out. But also, I know for certain that's what happened, because I've seen developers do exactly that.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Representative Line: Reduced to a Union

The code Clemens M supported worked just fine for ages. And then one day, it broke. It didn't break after a deployment, which implied some other sort of bug. So Clemens dug in, playing the game of "what specific data rows are breaking the UI, and why?"

One of the organizational elements of their system was the idea of "zones". I don't know the specifics of the application as a whole, but we can broadly describe it thus:

The application oversaw the making of widgets. Widgets could be assigned to one or more zones. A finished product requires a set of widgets. Thus, the finished product has a number of zones that's the union of all of the zones of its component widgets.

Which someone decided to handle this way:

zones.reduce((accumulator, currentValue) => accumulator = _.union(currentValue))

So, we reduce across zones (which is an array of arrays, where the innermost arrays contain zone names, like zone-0, zone-1). In each step we union it with… nothing. The LoDash union function expects an array of arrays, and returns an array that's the union of all its inputs. This isn't how that function is meant to be used, but the behavior from this incorrect usage was that accumulator would end up holding the last element in zones. Which actually worked until recently, because until recently no one was splitting products across zones. When all the inputs were in the same zone, grabbing the last one was just fine.

The code had been like this for years. It was only just recently, as the company expanded, that it became problematic. The fix, at least, was easy- drop the reduce and just union correctly.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Functionally, a Date

Dates are messy things, full of complicated edge cases and surprising ways for our assumptions to fail. They lack the pure mathematical beauty of other data types, like integers. But that absence doesn't mean we can't apply the beautiful, concise, and simple tools of functional programming to handling dates.

I mean, you or I could. J Banana's co-worker seems to struggle a bit with it.

/**
 * compare two dates, rounding them to the day
 */
private static int compareDates( LocalDateTime date1, LocalDateTime date2 ) {
    List<BiFunction<LocalDateTime,LocalDateTime,Integer>> criterias = Arrays.asList(
            (d1,d2) -> d1.getYear() - d2.getYear(),
            (d1,d2) -> d1.getMonthValue() - d2.getMonthValue(),
            (d1,d2) -> d1.getDayOfMonth() - d2.getDayOfMonth()
        );
    return criterias.stream()
        .map( f -> f.apply(date1, date2) )
        .filter( r -> r != 0 )
        .findFirst()
        .orElse( 0 );
}

This Java code creates a list containing three Java functions. Each function will take two dates and returns an integer. It then streams that list, applying each function in turn to a pair of dates. It then filters through the list of resulting integers for the first non-zero value, and failing that, returns just zero.

Why three functions? Well, because we have to check the year, the month, and the day. Obviously. The goal here is to return a negative value if date1 preceeds date2, zero if they're equal, and positive if date1 is later. And on this metric… it does work. That it works is what makes me hate it, honestly. This not only shouldn't work, but it should make the compiler so angry that the computer gets up and walks away until you've thought about what you've done.

Our submitter replaced all of this with a simple:

return date1.toLocalDate().compareTo( date2.toLocalDate() );
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Free Birds

"These results are incomprensible," Brian wrote testily. "The developers at SkillCertPro must use math derived from an entirely different universe than ours. I can boast a world record number of answered questions in one hour and fifteen minutes somewhere."

0

 

"How I Reached Inbox -1," Maia titled her Tickity Tock. "Apparently I've read my messages so thoroughly that my email client (Mailspring) time traveled into the future and read a message before it was even sent."

1

 

... which taught Jason how to use Mailspring to order timely tunes. "Apparently, someone invented a time machine and is able to send us vinyls from the future..."

4

 

"Yes, we have no bananas," sang out Peter G. , rapping "... or email addresses or phone numbers, but we're going to block your post just the same (and this is better than the previous result of "Whoops something went wrong", because you'd never be able to tell something had gone wrong without that helpful message)."

2

 

Finally, our favorite cardsharp Adam R. might have unsharp eyes but sharp browser skills. "While reading an online bridge magazine, I tried to zoom out a bit but was dismayed to find I couldn't zoom out. Once it zooms in to NaN%, you're stuck there."

3

 

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: The Getter Setter Getter

Today's Java snippet comes from Capybara James.

The first sign something was wrong was this:

private Map<String, String> getExtractedDataMap(PayloadDto payload) {
    return setExtractedDataToMap(payload);
}

Java conventions tell us that a get method retrieves a value, and a set method mutates the value. So a getter that calls a setter is… confusing. But neither of these are truly getters nor setters.

setExtractedDataToMap converts the PayloadDto to a Map<String, String>. getExtractedMap just calls that, which is just one extra layer of indirection that nobody needed, but whatever. At its core, this is just two badly named methods where there should be one.

But that distracts from the true WTF in here. Why on Earth are we converting an actual Java object to a Map<String,String>? That is a definite code smell, a sign that someone isn't entirely comfortable with object-oriented programming. You can't even say, "Well, maybe for serialization to JSON or something?" because Java has serializers that just do this transparently. And that's just the purpose of a DTO in the first place- to be a bucket that holds data for easy serialization.

We're left wondering what the point of all of this code is, and we're not alone. James writes:

I found this gem of a code snippet while trying to understand a workflow for data flow documentation purpose. I was not quite sure what the original developer was trying to achieve and at this point I just gave up

[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: Upsert Yours

Henrik H sends us a short snippet, for a relative value of short.

We've all seen this method before, but this is a particularly good version of it:

public class CustomerController
{
    public void MyAction(Customer customer)
    {
        // snip 125 lines

        if (customer.someProperty)
            _customerService.UpsertSomething(customer.Id, 
            customer.Code, customer.Name, customer.Address1, 
            customer.Address2, customer.Zip, customer.City, 
            customer.Country, null, null, null, null, null, 
            null, null, null, null, null, null, null, null, 
            null, false, false, null, null, null, null, null, 
            null, null, null, null, null, null, null, false, 
            false, false, false, true, false, null, null, null,
            false, true, false, true, true, 0, false, false, 
            false, false, customer.TemplateId, false, false, false, 
            false, false, string.Empty, true, false, false, false, 
            false, false, false, false, false, true, false, false, 
            true, false, false, MiscEnum.Standard, false, false, 
            false, true, null, null, null);
        else
            _customerService.UpsertSomething(customer.Id, 
            customer.Code, customer.Name, customer.Address1, 
            customer.Address2, customer.Zip, customer.City, 
            customer.Country, null, null, null, null, null, 
            null, null, null, null, null, null, null, null, 
            null, false, false, null, null, null, null, null, 
            null, null, null, null, null, null, null, false, 
            false, false, false, true, false, null, null, null, 
            false, false, false, true, true, 0, false, false, 
            false, false, customer.TemplateId, false, false, false, 
            false, false, string.Empty, true, false, false, false, 
            false, false, false, false, true, true, false, false, 
            true, false, false, MiscEnum.Standard, false, false, 
            false, true, null, null, null);

        // snip 52 lines
    }
}

Welcome to the world's most annoying "spot the difference" puzzle. I've added line breaks (as each UpsertSomething was all on one line in the original) to help you find it. Here's a hint: it's one of the boolean values. I'm sure that narrows it down for you. It means the original developed didn't need the if/else and instead could have simply passed customer.someProperty as a parameter.

Henrick writes:

While on a simple assignment to help a customer migrate from .NET Framework to .NET core, I encountered this code. The 3 lines are unfortunately pretty representative for the codebase

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Myopic Focus

Chops was a developer for Initrode. Early on a Monday, they were summoned to their manager Gary's office before the caffeine had even hit their brain.

Gary glowered up from his office chair as Chops entered. This wasn't looking good. "We need to talk about the latest commit for Taskmaster."

Taskmaster was a large application that'd been around for decades, far longer than Chops had been an employee. Thousands of internal and external customers relied upon it. Refinements over time had led to remarkable stability, its typical uptime now measured in years. However, just last week, their local installation had unexpectedly suffered a significant crash. Chops had been assigned to troubleshooting and repair.

Looker Studio Marketing Dashboard Overview

"What's wrong?" Chops asked.

"Your latest commit decreased the number of unit tests!" Gary replied as if Chops had slashed the tires on his BMW.

Within Taskmaster, some objects that were periodically generated were given a unique ID from a pool. The pool was of limited size and required scanning to find a spare ID. Each time a value was needed, a search began where the last search ended. IDs returned to the pool as objects were destroyed would only be reused when the search wrapped back around to the start.

Chops had discovered a bug in the wrap-around logic that would inevitably produce a crash if Taskmaster ran long enough. They also found that if the number of objects created exceeded the size of the pool, this would trigger an infinite loop.

Rather than attempt to patch any of this, Chops had nuked the whole thing and replaced it with code that assigned each object a universally unique identifier (UUID) from a trusted library UUID generator within its constructor. Gone was the bad code, along with its associated unit tests.

Knowing they would probably only get in a handful of words, Chops wonderered how on earth to explain all this in a way that would appease their manager. "Well—"

"That number must NEVER go down!" Gary snapped.

"But—"

"This is non-negotiable! Roll it back and come up with something better!"

And so Chops had no choice but to remove their solution, put all the janky code back in place, and patch over it with kludge. Every comment left to future engineers contained a tone of apology.

Taskmaster became less stable. Time and expensive developer hours were wasted. Risk to internal and external customers increased. But Gary could rest assured, knowing that his favored metric never faltered on his watch.

[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.

The Modern Job Hunt: Part 1

Ellis knew she needed a walk after she hurried off of Zoom at the end of the meeting to avoid sobbing in front of the group.

She'd just been attending a free online seminar regarding safe job hunting on the Internet. Having been searching since the end of January, Ellis had already picked up plenty of first-hand experience with the modern job market, one rejection at a time. She thought she'd attend the seminar just to see if there were any additional things she wasn't aware of. The seminar had gone well, good information presented in a clear and engaging way. But by the end of it, Ellis was feeling bleak. Goodness gracious, she'd already been slogging through months of this. Hundreds of job applications with nothing to show for it. All of the scams out there, all of the bad actors preying on people desperate for their and their loved ones' survival!

Whiteboard - Job Search Process - 27124941129

Ellis' childhood had been plagued with anxiety and depression. It was only as an adult that she'd learned any tricks for coping with them. These tricks had helped her avoid spiraling into full-on depression for the past several years. One such trick was to stop and notice whenever those first feelings hit. Recognize them, feel them, and then respond constructively.

First, a walk. Going out where there were trees and sunshine: Ellis considered this "garbage collection" for her brain. So she stepped out the front door and started down a tree-lined path near her house, holding on to that bleak feeling. She was well aware that if she didn't address it, it would take root and grow into hopelessness, self-loathing, fear of the future. It would paralyze her, leave her curled up on the couch doing nothing. And it would all happen without any words issuing from her inner voice. That was the most insidious thing. It happened way down deep in a place where there were no words at all.

Once she returned home, Ellis forced herself to sit down with a notebook and pencil and think very hard about what was bothering her. She wrote down each sentiment:

  • This job search is a hopeless, unending slog!
  • No one wants to hire me. There must be something wrong with me!
  • This is the most brutal job search environment I've ever dealt with. There are new scams every day. Then add AI to every aspect until I want to vomit.

This was the first step of a reframing technique she'd just read about in the book Right Kind of Wrong by Amy Edmonson. With the words out, it was possible to look at each statement and determine whether it was rational or irrational, constructive or harmful. Each statement could be replaced with something better.

Ellis proceeded step by step through the list.

  • Yes, this will end. Everything ends.
  • There's nothing wrong with me. Most businesses are swamped with applications. There's a good chance mine aren't even being looked at before they're being auto-rejected. Remember the growth mindset you learned from Carol Dweck. Each application and interview is giving me experience and making me a better candidate.
  • This job market is a novel context that changes every day. That means failure is not only inevitable, it's the only way forward.

Ellis realized that her job hunt was very much like a search algorithm trying to find a path through a maze. When the algorithm encountered a dead end, did it deserve blame? Was it an occasion for shame, embarrassment, and despair? Of course not. Simply backtrack and keep going with the knowledge gained.

Yes, there was truth to the fact that this was the toughest job market Ellis had ever experienced. Therefore, taking a note from Viktor Frankl, she spent a moment reimagining the struggle in a way that made it meaningful to her. Ellis began viewing her job hunt in this dangerous market, her gradual accumulation of survival information, as an act of resistance against it. She now hoped to write all about her experience once she was on the other side, in case her advice might help even one other person in her situation save time and frustration.

While unemployed, she also had the opportunity to employ the search algorithm against entirely new mazes. Could Ellis expand her freelance writing into a sustainable gig, for instance? That would mean exploring all the different ways to be a freelance writer, something Ellis was now curious and excited to explore.

[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.

Best of…: Classic WTF: We Are Not Meatbots!

Today's Labor Day in the US, a day where we celebrate workers. Well, some of us. This story from the archives is one of the exceptions. Original. --Remy

Sales, as everyone knows, is the mortal enemy of Development.

Their goals are opposite, their people are opposite, their tactics are opposite. Even their credos - developers "Make a good product" but sales will "Do anything to get that money" - are at complete odds.

The company Jordan worked for made a pseudo-enterprise product responsible for everything e-commerce: contacts, inventory, website, shipping, payment...everything. His responsibility included the inventory package, overseeing the development team, designing APIs, integration testing, and coordination with the DBAs and sysadmins...you know, everything. One of his team members implemented a website CMS into the product, letting the website design team ignore the content and focus on making it look good.

Care to guess who was responsible for the site content? If you guessed the VP of Sales, congratulations! You win a noprize.

A couple months passed by without incident. Everything's peachy in fact...that is, until one fateful day when the forty-person stock-and-shipping department are clustered in the parking lot when Jordan shows up.

Jordan parked, crossed the asphalt, and asked one of the less threatening looking warehouse guys, "What's the problem?"

The reply was swift as the entire group unanimously shouted "YOUR F***ING WEBSITE!" Another worker added, "You guys in EYE TEE are so far removed from real life out here. We do REAL WORK, what you guys do from behind your desks?"

Jordan was dumbfounded. What brought this on? For a moment he considered defending his and his team's honor but decided it wouldn't accomplish much besides get his face rearranged and instead replied with a meek "Sure, just let me check into this..." before quickly diving into the nearest entry door.

It didn't take much long after for Jordan to ascertain that the issue wasn't that the website was down, but that the content of one page in particular , the "About Us" page, had upset the hardworking staff who accomplished what the company actually promised: stock and ship the products that they sold on their clients' websites.

After an hour of mediation, it was discovered that the VP of Sales, in a strikingly-insensitive-even-for-him moment, had referred to the warehouse staff as "meatbots." The lively folk who staffed the shipping and stocking departments naturally felt disrespected by being reduced to some stupid sci-fi cloning trope nomenclature. The VP's excuse was simply that he had drunk a couple of beers while he wrote the page text for the website. Oops!

Remarkably, the company (which Jordan left some time later for unrelated reasons) eventually caught up to the backlog of orders to go out. It took a complete warehouse staff replacement, but they did catch up. Naturally, the VP of Sales is still there, with an even more impressive title.


photo credit: RTD Photography via photopin cc

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Error'd: Scamproof

Gordon S. is smarter than the machines. "I can only presume the "Fix Now with AI" button adds some mistakes in order to fix the lack of needed fixes."

0

 

"Sorry, repost with the link https://www.daybreaker.com/alive/," wrote Michael R.

3

 

And yet again from Michael R., following up with a package mistracker. "Poor DHL driver. I hope he will get a break within those 2 days. And why does the van look like he's driving away from me."

1

 

Morgan airs some dirty laundry. "After navigating this washing machine app on holiday and validating my credit card against another app I am greeted by this less than helpful message each time. So is OK okay? Or is the Error in error?
Washing machine worked though."

2

 

And finally, scamproof Stuart wondered "Maybe the filter saw the word "scam" and immediately filed it into the scam bucket. All scams include the word "scam" in them, right?"

4

 

[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.

Representative Line: Springs are Optional

Optional types are an attempt to patch the "billion dollar mistake". When you don't know if you have a value or not, you wrap it in an Optional, which ensures that there is a value (the Optional itself), thus avoiding null reference exceptions. Then you can query the Optional to see if there is a real value or not.

This is all fine and good, and can cut down on some bugs. Good implementations are loaded with convenience methods which make it easy to work on the optionals.

But then, you get code like Burgers found. Which just leaves us scratching our heads:

private static final Optional<Boolean> TRUE = Optional.of(Boolean.TRUE);
private static final Optional<Boolean> FALSE = Optional.of(Boolean.FALSE);

Look, any time you're making constants for TRUE or FALSE, something has gone wrong, and yes, I'm including pre-1999 versions of C in this. It's especially telling when you do it in a language that already has such constants, though- at its core- these lines are saying TRUE = TRUE. Yes, we're wrapping the whole thing in an Optional here, which potentially is useful, but if it is useful, something else has gone wrong.

Burgers works for a large insurance company, and writes this about the code:

I was trying to track down a certain piece of code in a Spring web API application when I noticed something curious. It looked like there was a chunk of code implementing an application-specific request filter in business logic, totally ignoring the filter functions offered by the framework itself and while it was not related to the task I was working on, I followed the filter apply call to its declaration. While I cannot supply the entire custom request filter implementation, take these two static declarations as a demonstration of how awful the rest of the class is.

Ah, of course- deep down, someone saw a perfectly functional wheel and said, "I could make one of those myself!" and these lines are representative of the result.

[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.

CodeSOD: The HTML Print Value

Matt was handed a pile of VB .Net code, and told, "This is yours now. I'm sorry."

As often happens, previous company leadership said, "Why should I pay top dollar for experienced software engineers when I can hire three kids out of college for the same price?" The experiment ended poorly, and the result was a pile of bad VB code, which Matt now owned.

Here's a little taste:

// SET IN SESSION AND REDIRECT TO PRINT PAGE
Session["PrintValue"] = GenerateHTMLOfItem();
Response.Redirect("PrintItem.aspx", true);

The function name here is accurate. GenerateHTMLOfItem takes an item ID, generates the HTML output we want to use to render the item, and stores it in a session variable. It then forces the browser to redirect to a different page, where that HTML can then be output.

You may note, of course, that GenerateHTMLOfItem doesn't actually take parameters. That's because the item ID got stored in the session variable elsewhere.

Of course, it's the redirect that gets all the attention here. This is a client side redirect, so we generate all the HTML, shove it into a session object, and then send a message to the web browser: "Go look over here". The browser sends a fresh HTTP request for the new page, at which point we render it for them.

The Microsoft documentation also has this to add about the use of Response.Redirect(String, Boolean), as well:

Calling Redirect(String) is equivalent to calling Redirect(String, Boolean) with the second parameter set to true. Redirect calls End which throws a ThreadAbortException exception upon completion. This exception has a detrimental effect on Web application performance. Therefore, we recommend that instead of this overload you use the HttpResponse.Redirect(String, Boolean) overload and pass false for the endResponse parameter, and then call the CompleteRequest method. For more information, see the End method.

I love it when I see the developers do a bonus wrong.

Matt had enough fires to put out that fixing this particular disaster wasn't highest on his priority list. For the time being, he could only add this comment:

// SET IN SESSION AND REDIRECT TO PRINT PAGE
// FOR THE LOVE OF GOD, WHY?!?
Session["PrintValue"] = GenerateHTMLOfItem();
Response.Redirect("PrintItem.aspx", true);
[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.

Representative Line: Not What They Meant By Watching "AndOr"

Today's awfulness comes from Tim H, and while it's technically more than one line, it's so representative of the code, and so short that I'm going to call this a representative line. Before we get to the code, we need to talk a little history.

Tim's project is roughly three decades old. It's a C++ tool used for a variety of research projects, and this means that 90% of the people who have worked on it are PhD candidates in computer science programs. We all know the rule of CompSci PhDs and programming: they're terrible at it. It's like the old joke about the farmer who, when unable to find an engineer to build him a cow conveyer, asked a physicist. After months of work, the physicist introduced the result: "First, we assume a perfectly spherical cow in a vacuum…"

Now, this particularly function has been anonymized, but it's easy to understand what the intent was:

bool isFooOrBar() {
  return isFoo() && isBar();
}

The obvious problem here is the mismatch between the function name and the actual function behavior- it promises an or operation, but does an and, which the astute reader may note are different things.

I think this offers another problem, though. Even if the function name were correct, given the brevity of the body, I'd argue that it actually makes the code less clear. Maybe it's just me, but isFoo() && isBar() is more clear in its intent than isFooAndBar(). There's a cognitive overhead to adding more symbols that would make me reluctant to add such a function.

There may be an argument about code-reuse, but it's worth noting- this function is only ever called in one place.

This particular function is not itself, all that new. Tim writes:

This was committed as new code in 2010 (i.e., not a refactor). I'm not sure if the author changed their mind in the middle of writing the function or just forgot which buttons on the keyboard to press.

More likely, Tim, is that they initially wrote it as an "or" operation and then discovered that they were wrong and it needed to be an "and". Despite the fact that the function was only called in one place, they opted to change the body without changing the name, because they didn't want to "track down all the places it's used". Besides, isn't the point of a function to encapsulate the behavior?

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

The C-Level Ticket

Everyone's got workplace woes. The clueless manager; the disruptive coworker; the cube walls that loom ever higher as the years pass, trapping whatever's left of your soul.

But sometimes, Satan really leaves his mark on a joint. I worked Tech Support there. This is my story. Who am I? Just call me Anonymous.


It starts at the top. A call came in from Lawrence Gibbs, the CEO himself, telling us that a conference room printer was, quote, "leaking." He didn't explain it, he just hung up. The boss ordered me out immediately, told me to step on it. I ignored the elevator, racing up the staircase floor after floor until I reached the dizzying summit of C-Town.

The Big Combo (1955)

There's less oxygen up there, I'm sure of it. My lungs ached and my head spun as I struggled to catch my breath. The fancy tile and high ceilings made a workaday schmuck like me feel daunted, unwelcome. All the same, I gathered myself and pushed on, if only to learn what on earth "leaking" meant in relation to a printer.

I followed the signs on the wall to the specified conference room. In there, the thermostat had been kicked down into the negatives. The cold cut through every layer of mandated business attire, straight to bone. The scene was thick with milling bystanders who hugged themselves and traded the occasional nervous glance. Gibbs was nowhere to be found.

Remembering my duty, I summoned my nerve. "Tech Support. Where's the printer?" I asked.

Several pointing fingers showed me the way. The large printer/scanner was situated against the far wall, flanking an even more enormous conference table. Upon rounding the table, I was greeted with a grim sight: dozens of sheets of paper strewn about the floor like blood spatter. Everyone was keeping their distance; no one paid me any mind as I knelt to gather the pages. There were 30 in all. Each one was blank on one side, and sported some kind of large, blotchy ring on the other. Lord knew I drank enough java to recognize a coffee mug stain when I saw one, but these weren't actual stains. They were printouts of stains.

The printer was plugged in. No sign of foul play. As I knelt there, unseen and unheeded, I clutched the ruined papers to my chest. Someone had wasted a tree and a good bit of toner, and for what? How'd it go down? Surely Gibbs knew more than he'd let on. The thought of seeking him out, demanding answers, set my heart to pounding. It was no good, I knew. He'd play coy all day and hand me my pink slip if I pushed too hard. As much as I wanted the truth, I had a stack of unpaid bills at home almost as thick as the one in my arms. I had to come up with something else.

There had to be witnesses among the bystanders. I stood up and glanced among them, seeking out any who would return eye contact. There: a woman who looked every bit as polished as everyone else. But for once, I got the feeling that what lay beneath the facade wasn't rotten.

With my eyes, I pleaded for answers.

Not here, her gaze pleaded back.

I was getting somewhere, I just had to arrange for some privacy. I hurried around the table again and weaved through bystanders toward the exit, hoping to beat it out of that icebox unnoticed. When I reached the threshold, I spotted Gibbs charging up the corridor, smoldering with entitlement. "Where the hell is Tech Support?!"

I froze a good distance away from the oncoming executive, whose voice I recognized from a thousand corporate presentations. Instead of putting me to sleep this time, it jolted down my spine like lightning. I had to think fast, or I was gonna lose my lead, if not my life.

"I'm right here, sir!" I said. "Be right back! I, uh, just need to find a folder for these papers."

"I've got one in my office."

A woman's voice issued calmly only a few feet behind me. I spun around, and it was her, all right, her demeanor as cool as our surroundings. She nodded my way. "Follow me."

My spirits soared. At that moment, I would've followed her into hell. Turning around, I had the pleasure of seeing Gibbs stop short with a glare of contempt. Then he waved us out of his sight.

Once we were out in the corridor, she took the lead, guiding me through the halls as I marveled at my luck. Eventually, she used her key card on one of the massive oak doors, and in we went.

You could've fit my entire apartment into that office. The place was spotless. Mini-fridge, espresso machine, even couches: none of it looked used. There were a couple of cardboard boxes piled up near her desk, which sat in front of a massive floor-to-ceiling window admitting ample sunlight.

She motioned toward one of the couches, inviting me to sit. I shook my head in reply. I was dying for a cigarette by that point, but I didn't dare light up within this sanctuary. Not sure what to expect next, I played it cautious, hovering close to the exit. "Thanks for the help back there, ma'am."

"Don't mention it." She walked back to her desk, opened up a drawer, and pulled out a brand-new manila folder. Then she returned to conversational distance and proffered it my way. "You're from Tech Support?"

There was pure curiosity in her voice, no disparagement, which was encouraging. I accepted the folder and stuffed the ruined pages inside. "That's right, ma'am."

She shook her head. "Please call me Leila. I started a few weeks ago. I'm the new head of HR."

Human Resources. That acronym, which usually put me on edge, somehow failed to raise my hackles. I'd have to keep vigilant, of course, but so far she seemed surprisingly OK. "Welcome aboard, Leila. I wish we were meeting in better circumstances." Duty beckoned. I hefted the folder. "Printers don't just leak."

"No." Leila glanced askance, grave.

"Tell me what you saw."

"Well ..." She shrugged helplessly. "Whenever Mr. Gibbs gets excited during a meeting, he tends to lean against the printer and rest his coffee mug on top of it. Today, he must've hit the Scan button with his elbow. I saw the scanner go off. It was so bright ..." She trailed off with a pained glance downward.

"I know this is hard," I told her when the silence stretched too long. "Please, continue."

Leila summoned her mettle. "After he leaned on the controls, those pages spilled out of the printer. And then ... then somehow, I have no idea, I swear! Somehow, all those pages were also emailed to me, Mr. Gibbs' assistant, and the entire board of directors!"

The shock hit me first. My eyes went wide and my jaw fell. But then I reminded myself, I'd seen just as crazy and worse as the result of a cat jumping on a keyboard. A feline doesn't know any better. A top-level executive, on the other hand, should know better.

"Sounds to me like the printer's just fine," I spoke with conviction. "What we have here is a CEO who thinks it's OK to treat an expensive piece of office equipment like his own personal fainting couch."

"It's terrible!" Leila's gaze burned with purpose. "I promise, I'll do everything I possibly can to make sure something like this never happens again!"

I smiled a gallows smile. "Not sure what anyone can do to fix this joint, but the offer's appreciated. Thanks again for your help."

Now that I'd seen this glimpse of better things, I selfishly wanted to linger. But it was high time I got outta there. I didn't wanna make her late for some meeting or waste her time. I backed up toward the door on feet that were reluctant to move.

Leila watched me with a look of concern. "Mr. Gibbs was the one who called Tech Support. I can't close your ticket for you; you'll have to get him to do it. What are you going to do?"

She cared. That made leaving even harder. "I dunno yet. I'll think of something."

I turned around, opened the massive door, and put myself on the other side of it in a hurry, using wall signs to backtrack to the conference room. Would our paths ever cross again? Unlikely. Someone like her was sure to get fired, or quit out of frustration, or get corrupted over time.

It was too painful to think about, so I forced myself to focus on the folder of wasted pages in my arms instead. It felt like a mile-long rap sheet. I was dealing with an alleged leader who went so far as to blame the material world around him rather than accept personal responsibility. I'd have to appeal to one or more of the things he actually cared about: himself, his bottom line, his sense of power.

By the time I returned to the conference room to face the CEO, I knew what to tell him. "You're right, sir, there's something very wrong with this printer. We're gonna take it out here and give it a thorough work-up."

That was how I was able to get the printer out of that conference room for good. Once it underwent "inspection" and "testing," it received a new home in a previously unused closet. Whenever Gibbs got to jawing in future meetings, all he could do was lean against the wall. Ticket closed.

Gibbs remained at the top, doing accursed things that trickled down to the roots of his accursed company. But at least from then on, every onboarding slideshow included a photo of one of the coffee ring printouts, with the title Respect the Equipment.

Thanks, Leila. I can live with that.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: 8 Days a Week

"What word can spell with the letters housucops?" asks Mark R. "Sometimes AI hallucinations can be hard to find. Other times, they just kind of stand out..."

1

 

"Do I need more disks?" wonders Gordon "I'm replacing a machine which has only 2 GB of HDD. New one has 2 TB, but that may not be enough. Unless Thunar is lying." It's being replaced by an LLM.

0

 

"Greenmobility UX is a nightmare" complains an anonymous reader. "Just like last week's submission, do you want to cancel? Cancel or Leave?" This is not quite as bad as last week's.

2

 

Cinephile jeffphi rated this film two thumbs down. "This was a very boring preview, cannot recommend."

4

 

Malingering Manuel H. muses "Who doesn't like long weekends? Sometimes, one Sunday per week is just not enough, so just put a second one right after the first." I don't want to wait until Oktober for a second Sunday; hope we get one søøn.

3

 

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

A Countable

Once upon a time, when the Web was young, if you wanted to be a cool kid, you absolutely needed two things on your website: a guestbook for people to sign, and a hit counter showing how many people had visited your Geocities page hosting your Star Trek fan fiction.

These days, we don't see them as often, but companies still like to track the information, especially when it comes to counting downloads. So when Justin started on a new team and saw a download count in their analytics, he didn't think much of it at all. Nor did he think much about it when he saw the download count displayed on the download page.

Another thing that Justin didn't think much about was big piles of commits getting merged in overnight, at least not at first. But each morning, Justin needed to pull in a long litany of changes from a user named "MrStinky". For the first few weeks, Justin was too preoccupied with getting his feet under him, so he didn't think about it too much.

But eventually, he couldn't ignore what he saw in the git logs.

docs: update download count to 51741
docs: update download count to 51740
docs: update download count to 51738

And each commit was exactly what the name implied, a diff like:

- 51740
+ 51741

Each time a user clicked the download link, a ping was sent to their analytics system. Throughout the day, the bot "MrStinky" would query the analytics tool, and create new commits that updated the counter. Overnight, it would bundle those commits into a merge request, approve the request, merge the changes, and then redeploy what was at the tip of main.

"But, WHY?" Justin asked his peers.

One of them just shrugged. "It seemed like the easiest and fastest way at the time?"

"I wanted to wire Mr Stinky up to our content management system's database, but just never got around to it. And this works fine," said another.

Much like the rest of the team, Justin found that there were bigger issues to tackle.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Copy of a Copy of a

Jessica recently started at a company still using Windows Forms.

Well, that was a short article. Oh, you want more WTF than that? Sure, we can do that.

As you might imagine, a company that's still using Windows Forms isn't going to upgrade any time soon; they've been using an API that's been in maintenance mode for a decade, clearly they're happy with it.

But they're not too happy- Jessica was asked to track down a badly performing report. This of course meant wading through a thicket of spaghetti code, pointless singletons, and the general sloppiness that is the code base. Some of the code was written using Entity Framework for database access, much of it is not.

While it wasn't the report that Jessica was sent to debug, this method caught her eye:

private Dictionary<long, decimal> GetReportDiscounts(ReportCriteria criteria)
{
    Dictionary<long, decimal> rows = new Dictionary<long, decimal>();

    string query = @"select  ii.IID,
        SUM(CASE WHEN ii.AdjustedTotal IS NULL THEN 
        (ii.UnitPrice * ii.Units)  ELSE
            ii.AdjustedTotal END) as 'Costs'
            from ii
                where ItemType = 3
            group by ii.IID
            ";

    string connectionString = string.Empty;
    using (DataContext db = DataContextFactory.GetInstance<DataContext>())
    {
        connectionString = db.Database.Connection.ConnectionString;
    }

    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        using (SqlCommand command = new SqlCommand(query, connection))
        {
            command.Parameters.AddWithValue("@DateStart", criteria.Period.Value.Min.Value.Date);
            command.Parameters.AddWithValue("@DateEnd", criteria.Period.Value.Max.Value.Date.AddDays(1));
            command.Connection.Open();

            using (SqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    decimal discount = (decimal)reader["Costs"];
                    long IID = (long)reader["IID"];

                    if (rows.ContainsKey(IID))
                    {
                        rows[IID] += discount;
                    }
                    else
                    {
                        rows.Add(IID, discount);
                    }
                }
            }
        }
    }

    return rows;
}

This code constructs a query, opens a connection, runs the query, and iterates across the results, building a dictionary as its result set. The first thing which leaps out is that, in code, they're doing a summary (iterating across the results and grouping by IID), which is also what they did in the query.

It's also notable that the table they're querying is called ii, which is not a result of anonymization, and actually what they called it. Then there's the fact that they set parameters on the query, for DateStart and DateEnd, but the query doesn't use those. And then there's that magic number 3 in the query, which is its own set of questions.

Then, right beneath that method was one called GetReportTotals. I won't share it, because it's identical to what's above, with one difference:

            string query = @"
select   ii.IID,
                SUM(CASE WHEN ii.AdjustedTotal IS NULL THEN 
                (ii.UnitPrice * ii.Units)  ELSE
                 ii.AdjustedTotal END)  as 'Costs' from ii
				  where  itemtype = 0 
				 group by iid
";

The magic number is now zero.

So, clearly we're in the world of copy/paste programming, but this raises the question: which came first, the 0 or the 3? The answer is neither. GetCancelledInvoices came first.

private List<ReportDataRow> GetCancelledInvoices(ReportCriteria criteria, Dictionary<long, string> dictOfInfo)
{
    List<ReportDataRow> rows = new List<ReportDataRow>();

    string fCriteriaName = "All";

    string query = @"select 
        A long query that could easily be done in EF, or at worst a stored procedure or view. Does actually use the associated parameters";


    string connectionString = string.Empty;
    using (DataContext db = DataContextFactory.GetInstance<DataContext>())
    {
        connectionString = db.Database.Connection.ConnectionString;
    }

    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        using (SqlCommand command = new SqlCommand(query, connection))
        {
            command.Parameters.AddWithValue("@DateStart", criteria.Period.Value.Min.Value.Date);
            command.Parameters.AddWithValue("@DateEnd", criteria.Period.Value.Max.Value.Date.AddDays(1));
            command.Connection.Open();

            using (SqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    long ID = (long)reader["ID"];
                    decimal costs = (decimal)reader["Costs"];
                    string mNumber = (string)reader["MNumber"];
                    string mName = (string)reader["MName"];
                    DateTime idate = (DateTime)reader["IDate"];
                    DateTime lastUpdatedOn = (DateTime)reader["LastUpdatedOn"];
                    string iNumber = reader["INumber"] is DBNull ? string.Empty : (string)reader["INumber"];
                    long fId = (long)reader["FID"];
                    string empName = (string)reader["EmpName"];
                    string empNumber = reader["EmpNumber"] is DBNull ? string.Empty : (string)reader["empNumber"];
                    long mId = (long)reader["MID"];

                    string cName = dictOfInfo[matterId];

                    if (criteria.EmployeeID.HasValue && fId != criteria.EmployeeID.Value)
                    {
                        continue;
                    }

                    rows.Add(new ReportDataRow()
                    {
                        CName = cName,
                        IID = ID,
                        Costs = costs * -1, //Cancelled i - minus PC
                        TimedValue = 0,
                        MNumber = mNumber,
                        MName = mName,
                        BillDate = lastUpdatedOn,
                        BillNumber = iNumber + "A",
                        FID = fId,
                        EmployeeName = empName,
                        EmployeeNumber = empNumber
                    });
                }
            }
        }
    }


    return rows;
}

This is the original version of the method. We can infer this because it actually uses the parameters of DateStart and DateEnd. Everything else just copy/pasted this method and stripped out bits until it worked. There are more children of this method, each an ugly baby of its own, but all alike in their ugliness.

It's also worth noting, the original version is doing filtering after getting data from the database, instead of putting that criteria in the WHERE clause.

As for Jessica's poor performing report, it wasn't one of these methods. It was, however, another variation on "run a query, then filter, sort, and summarize in C#". By simply rewriting it as a SQL query in a stored procedure that leveraged indexes, performance improved significantly.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
❌