The Daily WTF
Error'd: Mike's Job Search Job
Underqualified Mike S. is suffering a job hunt. "I could handle uD83D and uDC77 well enough, but I am a little short of uD83C and the all important uDFFE requirement."
Frank forecasts frustration. "The weather app I'm using seems to be a bit confused on my location as I'm on vacation right now." It would be a simple matter for the app to simply identify each location, if it can't meaningfully choose only one.
Marc Würth is making me hungry. Says Marc "I was looking through my Evernote notes for "transactional" (email service). It didn't find anything. Evernote, though, tried to be helpful and thought I was looking for some basil (German "Basilikum")."
"To be from or not from be," muses Michael R. Indeed, that is the question at Stansted Shakespeare airport.
That is not the King," Brendan commented. "I posted this on Discord, and my friend responded with "They have succeeded in alignment. Their AI is truly gender blind." Not only gender-blind but apparently also existence-blind as well. I think the Bard might have something quotable here as well but it escapes me. Comment section is open.
...and angels sing thee to thy rest. [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: A Trying Block
Mark sends us a very simple Java function which has the job of parsing an integer from a string. Now, you might say, "But Java has a built in for that, Integer.parseInt," and have I got good news for you: they actually used it. It's just everything else they did wrong.
private int makeInteger(String s) { int i=0; try { Integer.parseInt(s); } catch (NumberFormatException e) { i=0; return i; } i=Integer.parseInt(s); return i; }This function is really the story of variable i, the most useless variable ever. It's doing its best, but there's just nothing for it to do here.
We start by setting i to zero. Then we attempt to parse the integer, and do nothing with the result. If it fails, we set i to zero again, just for fun, and then return i. Why not just return 0? Because then what would poor i get to do?
Assuming we didn't throw an exception, we parse the input again, storing its result in i, and then return i. Again, we treat i like a child who wants to help paint the living room: we give it a dry brush and a section of wall we're not planning to paint and let it go to town. Nothing it does matters, but it feels like a participant.
Now, Mark went ahead and refactored this function basically right away, into a more terse and clear version:
private int makeInteger(String s) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return 0; } }He went about his development work, and then a few days later came across makeInteger reverted back to its original version. For a moment, he wanted to be mad at someone for reverting his change, but no- this was in an entirely different class. With that information, Mark went and did a search for makeInteger in the code, only to find 39 copies of this function, with minor variations.
There are an unknown number of copies of the function where the name is slightly different than makeInteger, but a search for Integer.parseInt implies that there may be many more.
[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: Buff Reading
Frank inherited some code that reads URLs from a file, and puts them into a collection. This is a delightfully simple task. What could go wrong?
static String[] readFile(String filename) { String record = null; Vector vURLs = new Vector(); int recCnt = 0; try { FileReader fr = new FileReader(filename); BufferedReader br = new BufferedReader(fr); record = new String(); while ((record = br.readLine()) != null) { vURLs.add(new String(record)); //System.out.println(recCnt + ": " + vURLs.get(recCnt)); recCnt++; } } catch (IOException e) { // catch possible io errors from readLine() System.out.println("IOException error reading " + filename + " in readURLs()!\n"); e.printStackTrace(); } System.out.println("Reading URLs ...\n"); int arrCnt = 0; String[] sURLs = new String[vURLs.size()]; Enumeration eURLs = vURLs.elements(); for (Enumeration e = vURLs.elements() ; e.hasMoreElements() ;) { sURLs[arrCnt] = (String)e.nextElement(); System.out.println(arrCnt + ": " + sURLs[arrCnt]); arrCnt++; } if (recCnt != arrCnt++) { System.out.println("WARNING: The number of URLs in the input file does not match the number of URLs in the array!\n\n"); } return sURLs; } // end of readFile()So, we start by using a FileReader and a BufferedReader, which is the basic pattern any Java tutorial on file handling will tell you to do.
What I see here is that the developer responsible didn't fully understand how strings work in Java. They initialize record to a new String() only to immediately discard that reference in their while loop. They also copy the record by doing a new String which is utterly unnecessary.
As they load the Vector of strings, they also increment a recCount variable, which is superfluous since the collection can tell you how many elements are in it.
Once the Vector is populated, they need to copy all this data into a String[]. Instead of using the toArray function, which is built in and does that, they iterate across the Vector and put each element into the array.
As they build the array, they increment an arrCnt variable. Then, they do a check: if (recCnt != arrCnt++). Look at that line. Look at the post-increment on arrCnt, despite never using arrCnt again. Why is that there? Just for fun, apparently. Why is this check even there?
The only way it's possible for the counts to not match is if somehow an exception was thrown after vURLs.add(new String(record)); but before recCount++, which doesn't seem likely. Certainly, if it happens, there's something worse going on.
Now, I'm going to be generous and assume that this code predates Java 8- it just looks old. But it's worth noting that in Java 8, the BufferedReader class got a lines() function which returns a Stream<String> that can be converted directly toArray, making all of this code superfluous, but also, so much of this code is just superfluous anyway.
Anyway, for a fun game, start making the last use of every variable be a post-increment before it goes out of scope. See how many code reviews you can sneak it through!
[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!Representative Line: What the FFFFFFFF
Combining Java with lower-level bit manipulations is asking for trouble- not because the language is inadequate to the task, but because so many of the developers who work in Java are so used to working at a high level they might not quite "get" what they need to do.
Victor inherited one such project, which used bitmasks and bitwise operations a great deal, based on the network protocol it implemented. Here's how the developers responsible created their bitmasks:
private static long FFFFFFFF = Long.parseLong("FFFFFFFF", 16);So, the first thing that's important to note, is that Java does support hex literals, so 0xFFFFFFFF is a perfectly valid literal. So we don't need to create a string and parse it. But we also don't need to make a constant simply named FFFFFFFF, which is just the old twenty = 20 constant pattern: technically you've made a constant but you haven't actually made the magic number go away.
Of course, this also isn't actually a constant, so it's entirely possible that FFFFFFFF could hold a value which isn't 0xFFFFFFFF.
[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.Representative Line: Identifying the Representative
Kate inherited a system where Java code generates JavaScript (by good old fashioned string concatenation) and embeds it into an output template. The Java code was written by someone who didn't fully understand Java, but JavaScript was also a language they didn't understand, and the resulting unholy mess was buggy and difficult to maintain.
Why trying to debug the JavaScript, Kate had to dig through the generated code, which led to this little representative line:
dojo.byId('html;------sites------fileadmin------en------fileadmin------index.html;;12').setAttribute('isLocked','true');The byId function is an alias to the browser's document.getElementById function. The ID on display here is clearly generated by the Java code, resulting in an absolutely cursed ID for an element in the page. The semicolons are field separators, which means you can parse the ID to get other information about it. I have no idea what the 12 means, but it clearly means something. Then there's that long kebab-looking string. It seems like maybe some sort of hierarchy information? But maybe not, because fileadmin appears twice? Why are there so many dashes? If I got an answer to that question, would I survive it? Would I be able to navigate the world if I understood the dark secret of those dashes? Or would I have to give myself over to our Dark Lords and dedicate my life to bringing about the end of all things?
Like all good representative lines, this one hints at darker, deeper evils in the codebase- the code that generates (or parses) this ID must be especially cursed.
The only element which needs to have its isLocked attribute set to true is the developer responsible for this: they must be locked away before they harm the rest of us.
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.Error'd: Teamwork
Whatever would we do without teamwork.
David doesn't know. "Microsoft Teams seems to have lost count (it wasn't a very big copy/paste)"
A follow-up from an anonymous doesn't know either. "Teams doing its best impression of a ransom note just to say you signed out. At least it still remembers how to suggest closing your browser. Small victories."
Bob F. just wants to make memes. "I've been setting my picture widths in this document to 7.5" for weeks, and suddenly after the latest MS Word update, Microsoft thinks 7.5 is not between -22.0 and 22.0. They must be using AI math to determine this."
Ewan W. wonders "a social life: priceless...?". Ewan has some brand confusion but after the Boom Battle Bar I bet I know why.
Big spender Bob B. maybe misunderstands NaN. He gleefully exclaims "I'm very happy to get 15% off - Here's hoping the total ends up as NaN and I get it all free." Yikes. 191.78-NaN is indeed NaN, but that just means you're going to end up owing them NaN. Don't put that on a credit card!
[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
CodeSOD: A Jammed Up Session
Andre has inherited a rather antique ASP .Net WebForms application. It's a large one, with many pages in it, but they all follow a certain pattern. Let's see if you can spot it.
protected void btnSearch_Click(object sender, EventArgs e) { ArrayList paramsRel = new ArrayList(); paramsRel["Name"] = txtNome.Text; paramsRel["Date"] = txtDate.Text; Session["paramsRel"] = paramsRel; List<Client> clients = Controller.FindClients(); //Some other code }Now, at first glance, this doesn't look terrible. Using an ArrayList as a dictionary and frankly, storing a dictionary in the Session object is weird, but it's not an automatic red flag. But wait, why is it called paramsRel? They couldn't be… no, they wouldn't…
public List<Client> FindClients() { ArrayList paramsRel = (ArrayList)Session["paramsRel"]; string name = (string)paramsRel["Name"]; string dateStr = (string)paramsRel["Date"]; DateTime date = DateTime.Parse(dateStr); //More code... }Now there's the red flag. paramsRel is how they pass parameters to functions. They stuff it into the Session, then call a function which retrieves it from that Session.
This pattern is used everywhere in the application. You can see that there's a vague gesture in the direction of trying to implement some kind of Model-View-Controller pattern (as FindClients is a member of the Controller object), but that modularization gets undercut by everything depending on Session as a pseudoglobal for passing state information around.
The only good news is that the Session object is synchronized so there's no thread safety issue here, though not for want of trying.
.comment { border: none; } [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: itouhhh…
Frequently in programming, we can make a tradeoff: use less (or more) CPU in exchange for using more (or less) memory. Lookup tables are a great example: use a big pile of memory to turn complicated calculations into O(1) operations.
So, for example, implementing itoa, the C library function for turning an integer into a character array (aka, a string), you could maybe make it more efficient using a lookup table.
I say "maybe", because Helen inherited some C code that, well, even if it were more efficient, it doesn't help because it's wrong.
Let's start with the lookup table:
char an[1000][3] = { {'0','0','0'},{'0','0','1'},{'0','0','2'},{'0','0','3'},{'0','0','4'},{'0','0','5'},{'0','0','6'},{'0','0','7'},{'0','0','8'},{'0','0','9'}, {'0','1','0'},{'0','1','1'},{'0','1','2'},{'0','1','3'},{'0','1','4'},{'0','1','5'},{'0','1','6'},{'0','1','7'},{'0','1','8'},{'0','1','9'}, …I'm abbreviating the lookup table for now. This lookup table is meant to be use to convert every number from 0…999 into a string representation.
Let's take a look at how it's used.
int ll = f->cfg.len_len; long dl = f->data_len; // Prepare length if ( NULL == dst ) { dst_len = f->data_len + ll + 1 ; dst = (char*) malloc ( dst_len ); } else //if( dst_len < ll + dl ) if( dst_len < (unsigned) (ll + dl) ) { // TO DOO - error should be processed break; } long i2; switch ( f->cfg.len_fmt) { case ASCII_FORM: { if ( ll < 2 ) { dst[0]=an[dl][2]; } else if ( ll < 3 ) { dst[0]=an[dl][1]; dst[1]=an[dl][2]; } else if ( ll < 4 ) { dst[0]=an[dl][0]; dst[1]=an[dl][1]; dst[2]=an[dl][2]; } else if ( ll < 5 ) { i2 = dl / 1000; dst[0]=an[i2][2]; i2 = dl % 1000; dst[3]=an[i2][2]; dst[2]=an[i2][1]; dst[1]=an[i2][0]; } else if ( ll < 6 ) { i2 = dl / 1000; dst[0]=an[i2][1]; dst[1]=an[i2][2]; i2 = dl % 1000; dst[4]=an[i2][2]; dst[3]=an[i2][1]; dst[2]=an[i2][0]; } else { // General case for ( int k = ll ; k > 0 ; k-- ) { dst[k-1] ='0' + dl % 10; dl/=10; } } dst[dl]=0; break; } }Okay, we start with some reasonable bounds checking. I have no idea what to make of a struct member called len_len- the length of the length? I'm lacking some context here.
Then we get into the switch statement. For all values less than 4 digits, everything makes sense, more or less. I'm not sure what the point of using a 2D array for you lookup table is if you're also copying one character at a time, but for such a small number of copies I'm sure it's fine.
But then we get into the len_lens longer than 3, and we start dividing my 1000 so that our lookup table continues to work. Which, again, I guess is fine, but I'm still left wondering why we're doing this, why this specific chain of optimizations is what we need to do. And frankly, why we couldn't just use itoa or a similar library function which already does this and is probably more optimized than anything I'm going to write.
When we have an output longer than 5 characters, we just use a naive for-loop and some modulus as our "general" case.
So no, I don't like this code. It reeks of premature optimization, and it also has the vibe of someone starting to optimize without fully understanding the problem they were optimizing, and trying to change course midstream without changing their solution.
But there's a punchline to all of this. Because, you see, I skipped most of the lookup table. Would you like to see how it ends? Of course you do:
{'9','8','0'},{'9','8','1'},{'9','8','2'},{'9','8','3'},{'9','8','4'},{'9','8','5'},{'9','8','6'},{'9','8','7'},{'9','8','8'},{'9','8','9'} };The lookup table doesn't work for values from 990 to 999. There are just no entries there. All this effort to optimize converting integers to text and we end up here: with a function that doesn't work for 1% of the possible values it could receive. And, given that the result is an out-of-bounds array access, it fails with everyone's favorite problem: undefined behavior. Usually it'll segfault, but who knows! Maybe it returns whatever bytes it finds? Maybe it sends the nasal demons after you. The compiler is allowed to do anything.
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.CodeSOD: Exactly a Date
Alexandar sends us some C# date handling code. The best thing one can say is that they didn't reinvent any wheels, but that might be worse, because they used the existing wheels to drive right off a cliff.
try { var date = DateTime.ParseExact(member.PubDate.ToString(), "M/d/yyyy h:mm:ss tt", null); objCustomResult.PublishedDate = date; } catch (Exception datEx) { }member.PubDate is a Nullable<DateTime>. So its ToString will return one of two things. If there is a value there, it'll return the DateTimes value. If it's null, it'll just return an empty string. Attempting to parse the empty string will throw an exception, which we helpfully swallow, do nothing about, and leave objCustomResult.PublishedDate in whatever state it was in- I'm going to guess null, but I have no idea.
Part of this WTF is that they break the advantages of using nullable types- the entire point is to be able to handle null values without having to worry about exceptions getting tossed around. But that's just a small part.
The real WTF is taking a DateTime value, turning it into a string, only to parse it back out. But because this is in .NET, it's more subtle than just the generation of useless strings, because member.PubDate.ToString()'s return value may change depending on your culture info settings.
Which sure, this is almost certainly server-side code running on a single server with a well known locale configured. So this probably won't ever blow up on them, but it's 100% the kind of thing everyone thinks is fine until the day it's not.
The punchline is that ToString allows you to specify the format you want the date formatted in, which means they could have written this:
var date = DateTime.ParseExact(member.PubDate.ToString("M/d/yyyy h:mm:ss tt"), "M/d/yyyy h:mm:ss tt", null);But if they did that, I suppose that would have possibly tickled their little grey cells and made them realize how stupid this entire block of code was?
[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today! -->CodeSOD: Would a Function by Any Other Name Still be WTF?
"Don't use exception handling for normal flow control," is generally good advice. But Andy's lead had a PhD in computer science, and with that kind of education, wasn't about to let good advice or best practices tell them what to do. That's why, when they needed to validate inputs, they wrote code C# like this:
public static bool IsDecimal(string theValue) { try { Convert.ToDouble(theValue); return true; } catch { return false; } }They attempt to convert, and if they succeed, great, return true. If they fail, an exception gets caught, and they return false. What could be simpler?
Well, using the built in TryParse function would be simpler. Despite its name, actually avoids throwing an exception, even internally, because exceptions are expensive in .NET. And it is already implemented, so you don't have to do this.
Also, Decimal is a type in C#- a 16-byte floating point value. Now, I know they didn't actually mean Decimal, just "a value with 0 or more digits behind the decimal point", but pedantry is the root of clarity, and the naming convention makes this bad code unclear about its intent and purpose. Per the docs there are Single and Double values which can't be represented as Decimal and trigger an OverflowException. And conversely, Decimal loses precision if converted to Double. This means a value that would be represented as Decimal might not pass this function, and a value that can't be represented as Decimal might, and none of this actually matters but the name of the function is bad.
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.Error'd: Cuts Like a Knife
Mike V. shares a personal experience with the broadest version of Poe's Law: "Slashdot articles generally have a humorous quote at the bottom of their articles, but I can't tell if this displayed usage information for the fortune command, which provides humorous quotes, is a joke or a bug." To which I respond with the sharpest version of Hanlon's Razor: never ascribe to intent that which can adequately be explained by incompetence.
Secure in his stronghold, Stewart snarks "Apparently my router is vulnerable because it is connected to the internet. So glad I pay for the premium security service."
The Beast in Black is back with more dross, asking "Oh GitLab, you so silly - y u no give proper reason?"
An anonymous reader writes "I got this when I tried to calculate the shipping costs for buying the Playdate game device. Sorry, I don't have anything snarky to say, please make something up." The comments section is open for your contributions.
Ben S. looking for logic in all the wrong places, wonders "This chart from my electric utility's charitable giving program kept my alumni group guessing all day. The arithmetic checks out, but what does the gray represent, and why is the third chart at a different scale?"
[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: Leap to the Past
Early in my career, I had the misfortune of doing a lot of Crystal Reports work. Crystal Reports is another one of those tools that lets non-developer, non-database savvy folks craft reports. Which, like so often happens, means that the users dig themselves incredible holes and need professional help to get back out, because at the end of the day, when the root problem is actually complicated, all the helpful GUI tools in the world can't solve it for you.
Michael was in a similar position as I was, but for Michael, there was a five alarm fire. It was the end of the month, and a bunch of monthly sales reports needed to be calculated. One of the big things management expected to see was a year-over-year delta on sales, and they got real cranky if the line didn't go up. If they couldn't even see the line, they went into a full on panic and assumed the sales team was floundering and the company was on the verge of collapse.
Unfortunately, the report was spitting out an error: "A day number must be between 1 and the number of days in the month."
Michael dug in, and found this "delight" inside of a function called one_year_ago:
Local StringVar yearStr := Left({?ReportToDate}, 4); Local StringVar monthStr := Mid({?ReportToDate}, 5, 2); Local StringVar dayStr := Mid({?ReportToDate}, 7, 2); Local StringVar hourStr := Mid({?ReportToDate}, 9, 2); Local StringVar minStr := Mid({?ReportToDate}, 11, 2); Local StringVar secStr := Mid({?ReportToDate}, 13, 2); Local NumberVar LastYear; LastYear := ToNumber(YearStr) - 1; YearStr := Replace (toText(LastYear),'.00' , '' ); YearStr := Replace (YearStr,',' , '' ); //DateTime(year, month, day, hour, min, sec); //Year + Month + Day + Hour + min + sec; // string value DateTime(ToNumber(YearStr), ToNumber(MonthStr), ToNumber(dayStr), ToNumber(HourStr), ToNumber(MinStr),ToNumber(SecStr) );We've all seen string munging in date handling before. That's not surprising. But what's notable about this one is the day on which it started failing. As stated, it was at the end of the month. But which month? February. Specifically, February 2024, a leap year. Since they do nothing to adjust the dayStr when constructing the date, they were attempting to construct a date for 29-FEB-2023, which is not a valid date.
Michael writes:
Yes, it's Crystal Reports, but surprisingly not having date manipulation functions isn't amongst it's many, many flaws. It's something I did in a past life isn't it??
The fix was easy enough- rewrite the function to actually use date handling. This made a simpler, basically one-line function, using Crystal's built in functions. That fixed this particular date handling bug, but there were plenty more places where this kind of hand-grown string munging happened, and plenty more opportunities for the report to fail.
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.Editor's Soapbox: AI: The Bad, the Worse, and the Ugly
- Mark Zuckerberg, presumably to one of his three friends
Since even the President of the United States is using ChatGPT to cheat on his homework and make bonkers social media posts these days, we need to have a talk about AI.
Right now, AI is being shoe-horned into everything, whether or not it makes sense. To me, it feels like the dotcom boom again. Millipedes.com! Fungus.net! Business plan? What business plan? Just secure the domain names and crank out some Super Bowl ads. We'll be RICH!
In fact, it's not just my feeling. The Large Language Model (LLM) OpenAI is being wildly overvalued and overhyped. It's hard to see how it will generate more revenue while its offerings remain underwhelming and unreliable in so many ways. Hallucination, bias, and other fatal flaws make it a non-starter for businesses like journalism that must have accurate output. Why would anyone convert to a paid plan? Even if there weren't an income problem—even if every customer became a paying customer—generative AI's exorbitant operational and environmental costs are poised to drown whatever revenue and funding they manage to scrape together.
Lest we think the problem is contained to OpenAPI or LLMs, there's not a single profitable AI venture out there. And it's largely not helping other companies to be more profitable, either.
A moment like this requires us to step back, take a deep breath. With sober curiosity, we gotta explore and understand AI's true strengths and weaknesses. More importantly, we have to figure out what we are and aren't willing to accept from AI, personally and as a society. We need thoughtful ethics and policies that protect people and the environment. We need strong laws to prevent the worst abuses. Plenty of us have already been victimized by the absence of such. For instance, one of my own short stories was used by Meta without permission to train their AI.
The Worst of AI
Sadly, it is all too easy to find appalling examples of all the ways generative AI is harming us. (For most of these, I'm not going to provide links because they don't deserve the clicks):
- We all know that person who no longer seems to have a brain of their own because they keep asking OpenAI to do all of their thinking for them.
- Deepfakes deliberately created to deceive people.
- Cheating by students.
- Cheating by giant corporations who are all too happy to ignore IP and copyright when it benefits them (Meta, ahem).
- Piles and piles of creepy generated content on platforms like Youtube and TikTok that can be wildly inaccurate.
- Scammy platforms like DataAnnotation, Mindrift, and Outlier that offer $20/hr or more for you to "train their AI." Instead, they simply gather your data and inputs and ghost the vast majority of applicants. I tried taking DataAnnotation's test for myself to see what would happen; after all, it would've been nice to have some supplemental income while job hunting. After several weeks, I still haven't heard back from them.
- Applicant Tracking Systems (ATS) block job applications from ever reaching a human being for review. As my job search drags on, I feel like my life has been reduced to a tedious slog of keyword matching. Did I use the word "collaboration" somewhere in my resume? Pass. Did I use the word "teamwork" instead? Fail. Did I use the word "collaboration," but the AI failed to detect it, as regularly happens? Fail, fail, fail some more. Frustrated, I and no doubt countless others have been forced to turn to other AIs in hopes of defeating those AIs. While algorithms battle algorithms, companies and unemployed workers are all suffering.
- Horrific, undeniable environmental destruction.
- Brace yourself: a 14 year-old killed himself with the encouragement of the chatbot he'd fallen in love with. I can only imagine how many more young people have been harmed and are being actively harmed right now.
The Best of AI?
As AI began to show up everywhere, as seemingly everyone from Google to Apple demanded that I start using it, I had initially responded with aversion and resentment. I never bothered with it, I disabled it wherever I could. When people told me to use it, I waved them off. My life seemed no worse for it.
Alas, now AI completely saturates my days while job searching, bringing on even greater resentment. Thousands of open positions for AI-based startups! Thousands of companies demanding expertise in generative AI as if it's been around for decades. Well, gee, maybe my hatred and aversion is hurting my ability to get hired. Am I being a middle-aged Luddite here? Should I be learning more about AI (and putting it on my resume)? Wouldn't I be the bigger person to work past my aversion in order to learn about and highlight some of the ways we can use AI responsibly?
I tried. I really tried. To be honest, I simply haven't found a single positive generative AI use-case that justifies all the harm taking place.
So, What Do We Do?
Here are some thoughts: don't invest in generative AI or seek a job within the field, it's all gonna blow. Lobby your government to investigate abuses, protect people, and preserve the environment. Avoid AI usage and, if you're a writer like me, make clear that AI is not used in any part of your process. Gently encourage that one person you know to start thinking for themselves again.
Most critically of all: wherever AI must be used for the time being, ensure that one or more humans review the results.
li { margin: 0.5em; list-style: circle } [Advertisement] Plan Your .NET 9 Migration with ConfidenceYour journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
CodeSOD: The Big Pictures
Loading times for web pages is one of the key metrics we like to tune. Users will put up with a lot if they feel like they application is responsive. So when Caivs was handed 20MB of PHP and told, "one of the key pages takes like 30-45 seconds to load. Figure out why," it was at least a clear goal.
Combing through that gigantic pile of code to try and understand what was happening was an uphill battle. Eventually, Caivs just decided to check the traffic logs while running the application. That highlighted a huge spike in traffic every time the page loaded, and that helped Caivs narrow down exactly where the problem was.
$first_image = ''; foreach($images as $the_image) { $image = $the_image['url']; if(file_exists($config->base_url.'/uploads/'.$image)) { if($first_image=='') { $first_image = $image; } $image_dimensions = '&w=648&h=432'; $get_dimensions = getimagesize('http://old.datacenter.ip.address/'.$config->base_url.'/uploads/'.$image); if($get_dimensions[0] < $get_dimensions[1]) $image_dimensions = '&h=432'; echo '<li>'.$config->base_url.'/timthumb.php?src='.$config->base_url.'/uploads/'.$image.'&w=125&h=80&zc=1'), 'javascript:;', array('onclick'=>'$(\'.image_gallery .feature .image\').html(\''.$config->base_url.'/timthumb.php?src='.$config->base_url.'/uploads/'.$image.$image_dimensions.'&zc=1').'\');$(\'.image_gallery .feature .title\').show();$(\'.image_gallery .feature .title\').html("'.str_replace('"', '', $the_image['Image Description']).'");$(\'.image_gallery .bar ul li a\').removeClass(\'active\');$(\'.image_gallery .bar ul li\').removeClass(\'active\');$(this).addClass(\'active\');$(this).parents(\'li\').addClass(\'active\');sidebarHeight();curImg=$(this).attr(\'id\');translate()','id'=>$img_num)).'</li>'; $img_num++; } }For every image they want to display in a gallery, they echo out a list item for it, which that part makes sense- more or less. The mix of PHP, JavaScript, JQuery, and HTML tags is ugly and awful and I hate it. But that's just a prosaic kind of awful, background radiation of looking at PHP code. Yes, it should be launched into the Kupier belt (it doesn't deserve the higher delta-V required to launch it into the sun), but that's not why we're here.
The cause of the long load times was in the lines above- where for each image, we getimagesize- a function which downloads the image and checks its stats, all so we can set $image_dimensions. Which, presumably, the server hosting the images uses the query string to resize the returned image.
All this is to check- if the height is greater than the width we force the height to be 432 pixels, otherwise we force the whole image to be 648x432 pixels.
Now, the server supplying those images had absolutely no caching, so that meant for every image request it needed to resize the image before sending. And for reasons which were unclear, if the requested aspect ratio were wildly different than the actual aspect ratio, it would also sometimes just refused to resize and return a gigantic original image file. But someone also had thought about the perils of badly behaved clients downloading too many images, so if a single host were requesting too many images, it would start throttling the responses.
When you add all this up, it meant that this PHP web application was getting throttled by its own file server, because it was requesting too many images, too quickly. Any reasonable user load hitting it would be viewed as an attempted denial of service attack on the file hosting backend.
Caivs was able to simply remove the check on filesize, and add a few CSS rules which ensured that files in the gallery wouldn't misbehave terribly. The performance problems went away- at least for that page of the application. Buried in that 20MB of PHP/HTML code, there were plenty more places where things could go wrong.
[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!CodeSOD: A Double Date
Alice picked up a ticket about a broken date calculation in a React application, and dropped into the code to take a look. There, she found this:
export function calcYears(date) { return date && Math.floor((new Date() - new Date(date).getTime()) / 3.15576e10) }She stared at it for awhile, trying to understand what the hell this was doing, and why it was dividing by three billion. Also, why there was a && in there. But after staring at it for a few minutes, the sick logic of the code makes sense. getTime returns a timestamp in milliseconds. 3.15576e10 is the number of milliseconds in a year. So the Math.floor() expression just gets the difference between two dates as a number of years. The && is just a coalescing operator- the last truthy value gets returned, so if for some reason we can't calculate the number of years (because of bad input, perhaps?), we just return the original input date, because that's a brillant way to handle errors.
As bizarre as this code is, this isn't the code that was causing problems. It works just fine. So why did Alice get a ticket? She spent some more time puzzling over that, while reading through the code, only to discover that this calcYears function was used almost everywhere in the code- but in one spot, someone decided to write their own.
if (birthday) { let year = birthday?.split('-', 1) if (year[0] != '') { let years = new Date().getFullYear() - year[0] return years } }So, this function also works, and is maybe a bit more clear about what it's doing than the calcYears. But note the use of split- this assumes a lot about the input format of the date, and that assumption isn't always reliable. While calcYears still does unexpected things if you fail to give it good input, its accepted range of inputs is broader. Here, if we're not in a date format which starts with "YYYY-", this blows up.
After spending hours puzzling over this, Alice writes:
I HATE HOW NO ONE KNOWS HOW TO CODE
[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.Error'd: Charge Me
The lights are on here and the roof is intact and I'm grateful. Is anybody home? You decide.
Pharm fan Ian S. clucked "Perhaps they'll put those as dates on my headstone." If you're very lucky.
An anonymous reader blew the whistle on their child labor practices. "This institution exclusively uses drivers who aren't legally old enough to drive."
Greg A. grumbled "Glad that the important notice that there was no important notice was given such prominence in the official ACT web page." I have nothing more to add.
Regular reader Michael R. reported "I can confirm Hermes knows how to navigate the unknown."
Finally, faithful follower B.J.H. has been around here long enough to see this one over and over again. "For some reason people keep thinking zip codes are numbers just because they are composed of digits. When EPIC sent paper mail asking for money in December the envelope used a zip code of 1740 (and it was delivered). They solved leading zero issue by switching to base 36." Or it might just be base 26, no way to tell.
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
CodeSOD: Pulling at the Start of a Thread
For testing networking systems, load simulators are useful: send a bunch of realistic looking traffic and see what happens as you increase the amount of sent traffic. These sorts of simulators often rely on being heavily multithreaded, since one computer can, if pushed, generate a lot of network traffic.
Thus, when Jonas inherited a heavily multithreaded system for simulating load, that wasn't a surprise. The surprise was that the developer responsible for it didn't really understand threading in Java. Probably in other languages too, but in this case, Java was what they were using.
public void startTraffic() { Configuration.instance.inititiateStatistics(); Statistics.instance.addStatisticListener(gui); if (t != null) { if (t.isAlive()) { t.destroy(); } } t = new Thread(this); t.start(); }Look, this is not a good way to manage threads in Java. I don't know if I'd call it a WTF, but it's very much a "baby's first threading" approach. There are better abstractions around threads that would avoid the need to manage thread instances directly. I certainly don't love situations where a Runnable also manages its own thread instance.
This is almost certainly a race condition, but I don't know if this function is called from multiple threads (but I suspect it might be).
But what's more interesting is where this code gets called. You see, starting a thread could trigger an exception, so you need to handle that:
public void run() { while (true) { try { loaderMain.startTraffic(); break; } catch (Exception e) { System.out.println("Exception in main loader thread!"); e.printStackTrace(); } } }Inside of an infinite loop, we try to start traffic. If we succeed, we break out of the loop. If we fail, well, we try and try again and again and again and again and again and again…
Jonas writes:
Since I'm the only one that dares to wade through the opaque mess of code that somehow, against all odds, manages to work most of the time, I get to fix it whenever it presents strange behavior.
I suspect it's going to present much more strange behavior in the future.
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.CodeSOD: Find the First Function to Cut
Sebastian is now maintaining a huge framework which, in his words, "could easily be reduced in size by 50%", especially because many of the methods in it are reinvented wheels that are already provided by .NET and specifically LINQ.
For example, if you want the first item in a collection, LINQ lets you call First() or FirstOrDefault() on any collection. The latter option makes handling empty collections easier. But someone decided to reinvent that wheel, and like so many reinvented wheels, it's worse.
public static LoggingRule FindFirst (this IEnumerable<LoggingRule> rules, Func<LoggingRule, bool> predicate) { foreach (LoggingRule rule in rules) { return rule; } return null; }This function takes a list of logging rules and a function to filter the logging rules, starts a for loop to iterate over the list, and then simply returns the first element in the list, thus exiting the for loop. If the loop doesn't contain any elements, we return null.
From the signature, I'd expect this function to do filtering, but it clearly doesn't. It just returns the first element, period. And again, there's already a built-in function for that. I don't know why this is exists, but I especially dislike that it's so misleading.
There's only one positive to say about this: if you did want to reduce the size of the framework by 50%, it's easy to see where I'd start.
[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!CodeSOD: The Wrong Kind of Character
Today's code, at first, just looks like using literals instead of constants. Austin sends us this C#, from an older Windows Forms application:
if (e.KeyChar == (char)4) { // is it a ^D? e.Handled = true; DoStuff(); } else if (e.KeyChar == (char)7) { // is it a ^g? e.Handled = true; DoOtherStuff(); } else if (e.KeyChar == (char)Keys.Home) { e.Handled = true; SpecialGoToStart(); } else if (e.KeyChar == (char)Keys.End) { e.Handled = true; SpecialGoToEnd(); }Austin discovered this code when looking for a bug where some keyboard shortcuts didn't work. He made some incorrect assumptions about the code- first, that they were checking for a KeyDown or KeyUp event, a pretty normal way to check for keyboard shortcuts. Under that assumption, a developer would compare the KeyEventArgs.KeyCode property against an enum- something like e.KeyCode == Keys.D && Keys.Control, for a CTRL+D. That's clearly not what's happening here.
No, here, they used the KeyPressEvent, which is meant to represent the act of typing. That gives you a KeyPressEventArgs with a KeyChar property- because again, it's meant to represent typing text not keyboard shortcuts. They used the wrong event type, as it won't tell them about modifier keys in use, or gracefully handle the home or end keys. KeyChar is the ASCII character code of the key press: which, in this case, CTRL+D is the "end of transmit" character in ASCII (4), and CTRL+G is the goddamn bell character (7). So those two branches work.
But home and end don't have ASCII code points. They're not characters that show up in text. They get key codes, which represent the physical key pressed, not the character of text. So (char)Keys.Home isn't really a meaningful operation. But the enum is still a numeric value, so you can still turn it into a character- it just turns into a character that emphatically isn't the home key. It's the "$". And Keys.End turns into a "#".
It wasn't very much work for Austin to move the event handler to the correct event type, and switch to using KeyCodes, which were both more correct and more readable.
[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: Objectifying Yourself
"Boy, stringly typed data is hard to work with. I wish there were some easier way to work with it!"
This, presumably, is what Gary's predecessor said. Followed by, "Wait, I have an idea!"
public static Object createValue(String string) { Object value = parseBoolean(string); if (value != null) { return value; } value = parseInteger(string); if (value != null) { return value; } value = parseDouble(string); if (value != null) { return value; } return string; }This takes a string, and then tries to parse it, first into a boolean, failing that into an integer, and failing that into a double. Otherwise, it returns the original string.
And it returns an object, which means you still get to guess what's in there even after this. You just get to guess what it returned, and hope you cast it to the correct type. Which means this almost certainly is called like this:
boolean myBoolField = (Boolean)createValue(someStringContainingABool);Which makes the whole thing useless, which is fun.
Gary found this code in a "long since abandoned" project, and I can't imagine why it ended up getting abandoned.
[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.