The Daily WTF

Subscribe to The Daily WTF feed
Curious Perversions in Information Technology
Updated: 1 hour 10 min ago

Error'd: ROUS

Fri, 2024-02-09 07:30

I don't think they exist.

Choosy Chris P. wants more choices, carping "You keep using that word, I don't think it means what you think it means."

 

Romantic Ryan S. rued buying holiday flowers, regretting "A sure fire way to end the love and laugh on valentine's day this year."

 

Nonlocal lothario romeo reminds us that it's important to validate your inputs. "After entering nonUSA format of data amount you get NaN for annual price and the same for discounted price"

 

From beyond the realm of space and time, Stewart sent the following. "Yet more proof of how hard dates are. Even the UKs largest broadband provider can't get it right."

 

Trader Tim R. thinks he found an angle. "While having a look at the $ to Euro exchange rate, I was surprised to see Google had such accurate predictions for 500 years into the future," he says. I say "how do you know they're accurate?"

 

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

A Bit About the HP3000

Thu, 2024-02-08 07:30

Today's anonymously submitted story is a case where the WTF isn't the code itself, per se. This arguably could be a CodeSOD, and we'll get to the code, but there's so much more to the story.

Our submitter, let's call them Janice, used to work for a financial institution with a slew of legacy systems. One such system was an HP3000 minicomputer. "Mini", of course, meant "refrigerator sized".

The HP3000 itself is an interesting, if peripheral story, because it's one of the tales of a product launch going incredibly wrong. Let's talk a little history.

We start with the HP2100 in 1966, which Hewlett Packard did nothing to design, and instead purchased the company that designed it. The core innovation of the HP2100 was that it was architecturally similar to a PDP-8, but supported full 16-bit memory, instead of PDP's 12-bit.

HP didn't really know what they had bought- they marketed it as a "test and instrumentation" system, and were surprised when businesses purchased it for back office operations. They ended up with one of the most popular minicomputers for office use, despite it not being designed for that purpose.

Thus began the projects "Alpha" and "Omega". Alpha was a hardware refresh of the 2100, with a better memory model. Omega was a ground-up redesign for 32-bit memory, which would allow it to support a whopping 4MB of RAM. There was just one problem with the Omega design: they didn't have funding to actually finish it. The project was killed in 1970, which threw some of the staff into "wear black armbands to work" levels of mourning.

Unfortunately, while work was done on Omega, the scope of Alpha crept, which resulted in another project management wasn't sure could be delivered. But the market was there for a time-sharing minicomputer, so they pressed on despite the concerns.

The HP2000-line, had time sharing system that used multiple processors. There was a front-end processor which handled user interactions. Then there was the actual CPU, which ran programs. This meant that time-sharing was simplified- the CPU just ran programs in a round-robin fashion, and didn't have to worry about pesky things like user inputs. Essentially, it was really just a batch processing system with a multi-user front-end.

The designers of Alpha wanted to support full multiprogramming, instead of this hybrid-ish model. But they also needed to support traditional batch processing, as well as real-time execution. So the team split up to build the components of the "Multi-Programming Executive" module, which would allow all of these features.

The Alpha, which was still 16-bit, didn't have the luxurious 4MB of RAM- it had 128kB. The MPE used much more memory than 128kB. This lead to a massive crunch as the programmers worked to shrink MPE into something usable, while marketing looked at the deadlines and said, "We were supposed to be selling this thing months ago!"

The result was a massive war between engineering and marketing, where marketing gave customers promises about what the performance would be, engineering told marketing what the actual performance would be (significantly worse than what marketing was promising), and then management would demand engineering "prove" that marketing's over-promises could be met.

The initial ship-date was November, 1972, and by god, they shipped on time. Nothing actually worked, but they shipped. The first computer out the door was returned almost immediately. It could only handle two simultaneous users before slowing to a crawl, and crashed every ten minutes. By December, HP had gotten that to "crashes every two hours". They kept shipping machines even as they had to cut features and reliability promises.

Those frequent crashes also concealed another bug: after running for 24 days, the HP3000's clock would overflow (2^31 milliseconds) and the clock would magically reverse by 25 days. As one sysop of a purchased HP3000 put it: "The original designers of MPE [apparently] never thought the OS would stay up for 25+ days in a row".

After a bunch of management shuffling, the titular Packard of Hewlett Packard sent a memo: production was stopping and all sold computers were being recalled. Customers were offered HP2000s in its place, or they could wait until fall 1973 for a revised version- that would only support 4-6 users, far fewer than marketing's initial promises of 64. This pleased no one, and it's reported that some customers cried over the disappointment.

With sales paused, the entire computer underwent a design overhaul. The resulting machine was faster and cheaper and could actually handle 8 simultaneous users. One year after the botched launch, the HP3000 went back on the market, and ended up being a full success.

It was so successful, HP continued supporting the HP3000 until 2010, which is where Janice enters our story. Circa 2006, she needed to update some Pascal code. That code used a lot of bit-masks to handle flags, which is normally a pretty easy function in Pascal- the language has a standard set of bitwise operations. So Janice was surprised to see:

FUNCTION BITON(A , B : INTEGER): BOOLEAN; VAR C : INTEGER; BEGIN CASE A OF 15 : C:=1; 14 : C:=2; 13 : C:=4; 12 : C:=8; 11 : C:=16; 10 : C:=32; 9 : C:=64; 8 : C:=128; 7 : C:=256; 6 : C:=512; 5 : C:=1024; 4 : C:=2048; 3 : C:=4096; 2 : C:=8192; 1 : C:=16384; 0 : C:=32768; OTHERWISE BITON:=FALSE; END; IF ((B DIV C) MOD 2) = 1 THEN BITON:=TRUE ELSE BITON:=FALSE; END; FUNCTION SETBITON(A, B : INTEGER) : INTEGER; VAR C : INTEGER; BEGIN CASE A OF 15 : C:=1; 14 : C:=2; 13 : C:=4; 12 : C:=8; 11 : C:=16; 10 : C:=32; 9 : C:=64; 8 : C:=128; 7 : C:=256; 6 : C:=512; 5 : C:=1024; 4 : C:=2048; 3 : C:=4096; 2 : C:=8192; 1 : C:=16384; 0 : C:=32768; OTHERWISE C:=0; END; IF NOT BITON(A,B) THEN SETBITON:=B + C ELSE SETBITON:=B; END; FUNCTION SETBITOFF(A, B : INTEGER) : INTEGER; VAR C : INTEGER; BEGIN CASE A OF 15 : C:=1; 14 : C:=2; 13 : C:=4; 12 : C:=8; 11 : C:=16; 10 : C:=32; 9 : C:=64; 8 : C:=128; 7 : C:=256; 6 : C:=512; 5 : C:=1024; 4 : C:=2048; 3 : C:=4096; 2 : C:=8192; 1 : C:=16384; 0 : C:=32768; OTHERWISE C:=0; END; IF BITON(A,B) THEN SETBITOFF:=B - C ELSE SETBITOFF:=B; END; FUNCTION LAND(A,B : INTEGER) : INTEGER; VAR I : INTEGER; BEGIN I:=0; REPEAT IF BITON(I,A) THEN IF BITON(I,B) THEN A:=SETBITON(I,A) ELSE A:=SETBITOFF(I,A) ELSE A:=SETBITOFF(I,A); I:=I + 1; UNTIL I > 15; LAND:=A; END;

This is a set of hand-reinvented bitwise operations, culminating in an LAND, which does a bitwise and (not a logical and, which makes it annoyingly misnamed). I wouldn't call the code a horrible approach to doing this, even if it's definitely an inefficient approach (and when you're running a 33 year old computer, efficiency matters), but absent built-in bitwise operations, I can't see a lot of other options. The biggest problem is that LAND will set bits on that are already on, which is unnecessary- an AND should really only ever turn bits off.

Which, as it turns out, is the root WTF. The developer responsible wasn't ignorant about bitwise operations. The version of Pascal that shipped on the HP3000 simply didn't have any. No and, or, not, or xor. Not even a shift-left or shift-right operation.

In any case, this is what happens when I start doing research on a story and end up getting sucked down a rabbit hole. As always, while Wikipedia's true value is as a bibliography. A lot of those links have much more detail, but I hope this quick overview was an interesting story.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: Return Country

Wed, 2024-02-07 07:30

Let's say you have a database table containing a list of countries. Given the primary key of a country in that table- an arbitrary ID field- you need to look up the name of that country.

Curtis's predecessor dropped this solution:

function return_country($id) { $sql = "SELECT * FROM countries"; $qry = db_query($sql); if(mysql_num_rows($qry)>0){ while($row = mysql_fetch_assoc($qry)){ $a[$row['id']] = $row['name']; } }else{ return array(); } return $a[$id]; }

I guess they got the memo about not doing SQL injection flaws, but missed the "because you use query parameters". Instead, this queries the entire list of countries, iterates across them to build a mapping of ID to country name, and then uses that map to return the correct result.

This code really "shines" in its details. Sure, we could solve this with a query, but even if we opt to iterate across the table, we could just return from inside the loop. But no, we build an associative array.

And while it'd be better to return an error when the ID can't be found, we could return an empty string, but no- we return an empty array.

Return country? I'd rather return this code.

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

CodeSOD: Max Character Width

Tue, 2024-02-06 07:30

One of the "features" of the Oracle database is that, in addition to the "wonderful" PL/SQL language for building stored procedures, you can also write stored procedures in Java.

Now, the skills of "being a good database programmer" and "being a good Java programmer" are not necessarily overlapping, especially when you're deep in the world of Oracle's approach to programming. Which is where this submission, from Tomas comes from.

private static final short m_maxCharWidth[][] = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 }, { 5, 1 }, { 6, 1 }, { 7, 1 }, { 8, 1 }, { 9, 1 // ... some time later ... 9998, 3 }, { 9999, 3 } };

The first thing to note is that this 2D array uses its first dimension to store index+1. Maybe someone really liked Matlab and wanted 1-based indexes. It's hard to say for sure.

It's also hard to say what this is actually for. My first thought was that it was mapping numbers to their number of digits, for display purposes. Given the name, it seems likely- but that would mean they're using 3 characters to display 4 digit numbers, which would be wrong. Maybe it's mapping Unicode code points to the number of bytes they require to be represented? That doesn't jive either. Or perhaps it's (short)log(n)- a truncated log, but that would be wrong for all the single digit numbers. Our submitter snipped the 2 digit range, so I'm not sure what was inside of there, and that might answer the question.

But then again, why am I even trying to answer this question? This code just shouldn't exist.

.comment { border: none; } [Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!
Categories: Computer

A Laboratory Upgrade

Mon, 2024-02-05 07:30

Unfortunately for Elena, the laboratory information management system (LIMS) her team used was being sunsetted by the vendor, Initech. She's a biochemist working in a pathology department, and this software is vital to tracking the tests need, the tests already run, and providing critical notifications about abnormal test results.

Since Initech was sunsetting its products, the hospital system put out an RFQ for a replacements, and after a multi-year bidding process, offered the contract for replacing the software to Initech.

And thus, IniLab went away, and was replaced by IniHeal. Gone was the classic terminal interface that everyone had learned to use, and in its place was a "user friendly" GUI- a GUI that buried functionality behind fifteen clicks, had no meaningful keyboard shortcuts, and was constantly changing as they pushed updates, making it impossible to adjust to.

Also gone was the IniLab scripting language. IniLab's scripting language was how they had assembled workflows for laboratory processes. You could have fine grained control of which scripts tied to which kinds of tests, and even build custom workflows as one-offs, if necessary. They had many thousands of lines of code that needed to be ported over into IniHeal. The problem was IniHeal's approach to scripting was… well, bad.

First, IniHeal doesn't allow you write scripts. Instead, it has a pair of "rule" files. The marketing copy calls these "declarative", and promises that they're an upgrade. In practice, these are gigantic files that contain a set of rules that are evaluated for every test and every sample.

But the worse problem in IniHeal is how it lets you handle data. In IniLab, if you wanted tho patient's age, you might reference the field by name: Patient.age. In IniLab, you need to use the PPPPLL. What is the PPPPLL?

PPPPLL stands for "position" and "length". Instead of referencing a field by name, you have to reference the field by its byte offset and length. So Patient.age might be [091001]. And no, there's no easy way to create constants or variables to hold your PPPPLLs, you just have to know that [091001] is age, and hope that the input data never changes its layout.

Now, one of the things Elena noticed while techs were on-site is that the length of the date time fields was 32 bits, and the data was seconds since the Unix epoch. So she asked one of the senior developers "Hey, is this software Year 2038 safe?"

The developer laughed. "I'll be retired by then."

The final insult to injury is that this software wasn't installed on any laboratory workstations. Instead, they had to use Remote Desktop to access it inside of a VM. While the environment was theoretically locked down so that the only program you could run was IniHeal, in practice it was trivially easy to get to a desktop inside the VM.

Curious, Elena poked around. She not only found the installation logs from her lab's deployment, but the installation logs from several other labs. These logs included various internal and private details, including account passwords. Somehow, log files from client installs had ended up in their installation media and were deployed to every client site.

Elena reported the potential data breach. The final finding was that the password in question was long defunct, which led management to decide "well, that's no problem then."

IniHeal has a slogan about equipping customers with innovative tools, but Elena proposes a new one:

"The Q in IniHeal is for Quality."

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

Error'd: Groundhog Day

Fri, 2024-02-02 07:30

In this week's episode we have some more adventures in shipping and misadventures with dates. I just asked "Hey Google, how many days are there in February THIS year" and they answered confidently "28 days." I briefly considered autoplaying that query on this page to see how many of your devices would answer correctly, but then I decided I should autoplay a command to order me a pizza and then I thought better of the whole thing.

Following up on an earlier submission, Dave P. reports "Much to my amazement, the package did arrive in time for Christmas, but just barely. USPS really came through on their end, taking less than 2 days to deliver the package, after the original delivery service took 12 days to find the USPS dropoff facility." Hah! Somebody owes me a nickel!

 

Peter S. on the other hand, had a less-stellar experience with the competition. "Not sure if I can trust UPS ever again. They didn't just lose my package, but also their homepage."

 

Supporter of the arts Chris remark'd "I know February 2024 is supposed to be longer than usual, but I expected just one extra day, not over 330..."

 

Old-timer Barry M. wistfully remembers this movie released in a year that never existed. "man, that's a real classic," he said softly. Wasn't that movie title actually "The Hand that Rocks the Cradle?" Two error'ds in one!

 

Finally, a new anonymous (that is to say, previous anonymous submissions were from different anonymice) sent us a lovely photo of a hill and a fully charged battery, exclaiming "I don't think I was supposed to see this class name. I wonder if I could inject something by escaping strings in my message!" Be careful, it might be a trap.

 

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

CodeSOD: A Well Known Address

Thu, 2024-02-01 07:30

Amanda's company wanted to restrict access to a service by filtering on the requestor's IP address. Yes, this is a terrible idea. So they wanted to make it a bit smarter, and also filter on various subnets. But they had a LOT of different subnets.

So the result was this:

ok = 0 ip = Request.ServerVariables("REMOTE_ADDR") if ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" or ip = "xxx.xxx.xxx.xxx" then ok = 1 end if ip2 = Split(ip,".") ip3 = ip2(0) &"."& ip2(1) &"."& ip2(2) if ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" then ok = 1 end if if ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" or ip3 = "xxx.xxx.xxx" then ok = 1 end if if ok = 1 then response.redirect "http://www.somedomain.com/something/that/is/meant/to/be/private" else response.redirect "index.asp?error=1" end if

Imagine that each xxx in there is part of an IP address. Whitespace as in original, apologies to your scrollbar.

This code is fairly old- classic ASP, but it was still in use as of a decade ago. Which, it happens, is when Amanda worked on it. She did the sane thing and deleted this block and just used the authentication system that the application already had. Customers were happy, as it meant they didn't need to get their IP address allowlisted, they could just sign in.

At least one manager was unhappy, because they were convinced that by allowlisting, they were enforcing a "per seat" license- "Every computer has a unique IP address!" they insisted. "Without this check, they could sign on from any computer, anywhere!"

Fortunately, that manager was eventually talked down when someone suggested that this gives each user their own account, and thus prevents two people from sharing the same computer.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!
Categories: Computer

CodeSOD: A Voice Map

Wed, 2024-01-31 07:30

Let's say your company wanted to offer special deals. When a customer calls about one of these deals, you want to play an automated customer support message using SignalWire, a tool for scripting phone voice trees.

This is a natural case for using a Map data structure. Which is what Ajay's predecessor did. They just… uh… weren't sure how to use a Map.

public String convert(Deal deal) { List<Deal> single = Collections.singletonList(deal); Map<Deal, String> swml = client.getSWMLs(single, version); for(Map.Entry<Deal, String> entry : swml.entrySet()) { if (deal.equals(entry.getKey())) { return entry.getValue(); } } return ""; }

This Java code looks like a case of trying to fit some APIs together awkwardly.

Our function takes a Deal object. The client.getSWMLs function apparently requires a list as its input, because we start by converting the input into a singletonList- a list with one item in it.

client.getSWMLs then returns a map, presumably mapping every input in the list to the appropriate SWML script. Of course, we only sent it one, so I suspect that our map only has one key. No problem, though, as we can just call get on the Map…

Except that's not what we do. We convert the Map into a Set of Map.Entrys, and then iterate across the set. If the Deal in the set is the Deal we're looking for, we've found our value.

Bonus points for simply returning an empty string instead of a meaningful error when the input doesn't map to an output. I'm sure that never created any awkward customer support moments.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: 2019 Was a Fine Year

Tue, 2024-01-30 07:30

Efren's employer recently acquired a competitor. The competitor had been struggling for a number of years, and the acquisition was a last ditch attempt to preserve at least some of the business (a complete closure was the only other option).

Now, "struggling for a number of years" sounds fairly vague, but due to some bad database design, we actually have a clear indicator of exactly when the company gave up:

CASE YEAR(SomeDate) WHEN 2013 THEN SUM(Estimate2013) WHEN 2014 THEN SUM(Estimate2014) WHEN 2015 THEN SUM(Estimate2015) WHEN 2016 THEN SUM(Estimate2016) WHEN 2017 THEN SUM(Estimate2017) WHEN 2018 THEN SUM(Estimate2018) WHEN 2019 THEN SUM(Estimate2019) WHEN 2020 THEN SUM(Estimate2019) WHEN 2021 THEN SUM(Estimate2019) WHEN 2022 THEN SUM(Estimate2019) WHEN 2023 THEN SUM(Estimate2019) WHEN 2024 THEN SUM(Estimate2019) WHEN 2025 THEN SUM(Estimate2019) WHEN 2026 THEN SUM(Estimate2019) END

Here we have the classic "add a new column for every unit of time" design anti-pattern. A fun, traditional piece of difficult maintenance. But here we see exactly when maintenance stopped: sometime in late 2019 or 2020.

Now, I'm speculating a bit, but, I suspect this is pretty clear evidence of how the pandemic may have impacted some businesses. Someone was supposed to add the new column. They may have been laid off, or retasked. The maintenance task that had seemed so affordable to the business a year prior suddenly became untenable.

"Debts that can't be paid won't be paid," as the saying goes. Here's technical debt that couldn't be paid. So it wasn't. And the result was a company going under.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: Bent Struts

Mon, 2024-01-29 07:30

Luke has inherited a Java Struts-based application. Struts is one of the many Java web frameworks, designed around having HTTP requests trigger actions- that is routing HTTP requests to a specific function call.

Now, on one screen, the user filled in a form, and then the corresponding action on the server side needed to read the form data for an eventId and act upon it. In Struts, this can be very simple:

int event_id = Integer.parseInt(request.getParameter("event_id"));

That's the assumed way of doing that. But if you don't learn how to use the framework properly, you might end up writing something else:

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { int event_id = 0; try { InputStream is = request.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); String eventStr = ""; StringBuffer buff = new StringBuffer(); String line; do { line = br.readLine(); buff.append(line); } while (br.readLine() != null); eventStr = buff.toString(); StringTokenizer parseEventId = new StringTokenizer(eventStr, ","); while (parseEventId.hasMoreTokens()) { String eventString = parseEventId.nextToken(); if (eventString.startsWith("event_id")) { event_id = Integer.parseInt(eventString.substring(eventString.indexOf("=") + 1)); } } } catch (Exception e) { e.printStackTrace(); } ... }

This pile of code opts to read in the entire body of the input stream as a string, and then parse that string using a tokenizer, searching for substring which starts with event_id, at which point they can split on the = and get the integer value.

All of this is too complicated and reinventing a wheel badly, but the specific token we split on hints at deeper problems: ", ", as well as the fact that our read do/while loop only reads every other line.

An HTML form POST request encodes the data either as application/x-www-form-urlencoded or multipart/form-data. Neither of those formats sends up commas to separate key/value pairs. Either the client side is applying its own custom formatting, which we need to parse, or this code is just plain wrong.

But also, Struts does have a whole model/form binding feature set, so the "official" way to do this would be to just map to a Java Bean object.

Everything about this is wrong and overengineered, and smells like it was written by someone who was "smarter" than everyone else, and thus couldn't be bothered with using standard approaches to anything.

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

Error'd: Ride Ride Ride

Fri, 2024-01-26 07:30

File under "Old Business": Swissrail just can't catch a break.

Diligent Richard B. dug into the news and reports "After following the link from this week's Error'd, I came across this interesting description in Swissrail's Member list. The filename means 'Company description En(glish)'. They have the same thing in French; the German description is normal."

 

Allie C. didn't order this. "Sorting by views descending works perfectly with no issues at all!" I don't think we found a catchy name for this category yet. I'm leaning towards assortment.

 

Persistent Peter G. grumbles "This building company claims to have been in the business for a long time. By their testimonials I'd say at least two thousand years."

 

Faithful Adam R. comments "Comcast is having an outage in my area, and this time they're not even giving me a bad guess for the Estimated Time of Recovery."

 

Finally, conscientious Carly G. discovered a very disappointing windfall. "Cashfoward Bonus® would be more accurate. I wonder what happens if I spend it." Don't give it to me!

 

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: Control Tree

Thu, 2024-01-25 07:30

User interfaces tend to map fairly naturally to trees as a data structure. In the modern world where we've forgotten how to make native UIs and do everything as web applications pretending to be native, the "tree" model is explicit via the DOM. But even in native UIs, we tend to group controls into containers and panels and tabs.

Now, there may be cases where we need to traverse the tree, and trees present us with a very natural way to traverse them- recursion. To search the tree, first search the subtree. Recursion can have its own problems, but the UI tree of a native application is rarely deep enough for those problems to be an issue.

Nick inherited some VB .Net code that iterates across all the controls in the screen. It does not use recursion, which isn't a problem in and of itself. Every other choice in the code is the problem.

For Each x In Me.Controls Select Case (x.GetType.ToString) Case "System.Windows.Forms.Panel" For Each y In x.Controls Select Case (y.GetType.ToString) Case "System.Windows.Forms.TabControl" For Each z In y.Controls Select Case (z.GetType.ToString) Case "System.Windows.Forms.TabPage" For Each w In z.Controls If w.Tag = "Store Info" Then strName = CheckForExceptions(w.Name) iIndexStoreInfoValues = (arrStoreInfoValues.Length - 1) If w.GetType().ToString() = "System.Windows.Forms.CheckBox" Then arrStoreInfoValues(iIndexStoreInfoValues) = CType(w, CheckBox).CheckState Else arrStoreInfoValues(iIndexStoreInfoValues) = w.Text End If ReDim Preserve arrStoreInfoValues(iIndexStoreInfoValues + 1) End If Next End Select Next Case "System.Windows.Forms.GroupBox" For Each z In y.Controls If z.Tag = "Store Info" Then iIndexStoreInfoValues = (arrStoreInfoValues.Length - 1) arrStoreInfoValues(iIndexStoreInfoValues) = z.Text ReDim Preserve arrStoreInfoValues(iIndexStoreInfoValues + 1) End If Next Case Else If y.Tag = "Store Info" Then iIndexStoreInfoValues = (arrStoreInfoValues.Length - 1) strName = CheckForExceptions(y.Name) arrStoreInfoValues(iIndexStoreInfoValues) = y.Text ReDim Preserve arrStoreInfoValues(iIndexStoreInfoValues + 1) End If End Select Next Case Else '"System.Windows.Forms.TabControl" For Each y In x.Controls If y.Tag = "Store Info" Then iIndexStoreInfoValues = (arrStoreInfoValues.Length - 1) arrStoreInfoValues(iIndexStoreInfoValues) = y.Text ReDim Preserve arrStoreInfoValues(iIndexStoreInfoValues + 1) End If Next End Select Next

At its deepest, this particular pile of code has 12 levels of indenting. Which I've cleaned up a bit here- as submitted, the code was using 8 spaces per tab stop, which made it entirely unreadable in a window of reasonable width. Not that it'd be readable at any width, mind you.

I'm not going to go line by line through this code. It's not worth the psychic damage I'd inflict upon you. But I do want to call out a few highlights.

Note the repeated uses of ReDim Preserve arrStoreInfoValues.... This is an ugly VB hack for pretending to have arrays that can grow- ReDim creates an entirely new array, at the new size, and Preserve ensures that all the data in the original array gets copied over to the new array. It's expensive and time consuming, and .NET has plenty of built-in data structures that make this unnecessary. Likely, this was inherited from some VB6 code that got ported over to .NET. It was a weirdly common convention in VB6, instead of using any sort of data structure that can grow.

In a few cases, we use a Select with only one branch to it- making it a fancy If statement but less readable. Now, there's a good logic to this, I suppose, in that you may want more branches at some point, but I still hate reading it. Even worse, those switch statements are switching on the string representation of the type of the control. That particular code stench is always a "fun" thing to see, and it hints at an easy way to clean up this code: refactor those branches into methods, and use function overloading and the type system to replace the switches- if that were even necessary, as many of the branches do basically the same thing anyway.

But there's no real point to cleaning up this code, because as Nick discovered: it didn't actually work. The assumptions the code makes about how the controls are organized are false assumptions. It misses loads of controls in its traversal.

Nick replaced it with a 10 line recursive function that never needs to check the GetType of any of the controls.

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

CodeSOD: Replacing a City

Wed, 2024-01-24 07:30

Mateus inherited some code where half the variables were named things like strAux1 and strAux2. It was a data driven application, and the data had some conventions that weren't always ideal; for example, every city also had an associated abbreviation stored in its name field, e.g., "SAO PAULO (SP)" and "RIO DE JANEIRO (RJ)".

The original developers wanted to be able to present the names of cities without the abbreviation, so they wrote this:

RTRIM(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(UPPER(ISNULL(C.CITY,'')),'(RS)',''), '(SC)',''),'(PR)',''),'(SP)',''),'(MG)',''),'(RJ)',''), '(DF)',''),'(ES)',''),'(MS)',''),'(MT)',''),'(BA)',''), '(PI)',''),'(AL)',''),'(TO)',''),'(PE)',''),'(PA)',''), '(CE)',''),'(RN)',''),'(MA)',''),'(AP)',''),'(RR)',''), '(RN)',''),'(AM)',''),'(AC)',''),'(XX)',''),'(EX)',''), '(SE)',''),'(PB)',''),'(GO)',''),'(RO)',''))

Whitespace added for readability(?).

The good news is that I don't think Brazil is going to be adding any major cities deserving of their own abbreviation any time soon. But by effectively hard-coding the list of viable abbreviations instead of using more advanced string splitting (or *gasp* a regex), or y'know actually normalizing the data correctly, they've done a nice job making the code harder to maintain.

REPLACE REPLACE REPLACE REPLACE this code.

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

CodeSOD: Name a Valid Email

Tue, 2024-01-23 07:30

Correctly validating an email address, per the spec, is actually surprisingly hard. This is, in part, because there are a lot of features in the email address that nobody ever uses. Even so, you can easily get close enough with basic regex, and that's what most people do.

Niels H's predecessor wanted to go a little farther. They wanted to ensure that the supplied domain name had a valid DNS entry. Overkill? Probably. But, fortunately for them the .NET framework supplies a handy System.Net.Dns.GetHostEntry method, which resolves a domain name. That at least makes the problem easy to solve.

It makes the problem easy to solve if you use it. But why use a built in method when you can do it yourself?

private bool IsValidDomain(string EmailAddress) { List<byte> ByteList = new List<byte>(); string DNS = GetDnsAddress(); if (DNS == "") { throw new Exception("No DNS is found."); } if (EmailAddress.Contains("@")) { try { var HostName = EmailAddress.Split('@')[1]; ByteList.AddRange(new byte[] { 88, 89, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 }); var UDPC = new UdpClient(DNS, 53); foreach (string S in HostName.Split('.')) { ByteList.Add(Convert.ToByte(S.Length)); char[] chars = S.ToCharArray(); foreach (char c in chars) ByteList.Add(Convert.ToByte(Convert.ToInt32(c))); } ByteList.AddRange(new byte[] { 0, 0, Convert.ToByte("15"), 0, 1 }); byte[] Req = new byte[ByteList.Count]; for (int i = 0; i < ByteList.Count; i++) Req[i] = ByteList[i]; UDPC.Send(Req, Req.Length); IPEndPoint ep = null; byte[] recv = UDPC.Receive(ref ep); UDPC.Close(); var Resp = new int[recv.Length]; for (int i = 0; i < Resp.Length; i++) Resp[i] = Convert.ToInt32(recv[i]); int status = Resp[3]; if (status != 128) { return false; } int answers = Resp[7]; if (answers == 0) { return false; } } catch { return false; } } else { return false; } return true; }

Right off the bat, IsValidDomain is a badly named function, and badly designed. It takes a string containing an email address, and returns true or false if the email address contains a valid domain. I'd argue that a cleaner design would be to take the domain name part as the input, and let some other function worry about splitting an email address apart.

But that misses the forest for the trees, doesn't it. This function has it all. It starts by hand-laying out the byte structure of a DNS request packet. Then iterating across a string to convert each character into a byte. And since we're doing that in a List structure, we need to convert it into an array- but we can't use the ToArray function, so we manually copy each byte into the array.

Then we send and receive a UDP packet for our DNS request. Note that we do create a variable to hold an IPEndPoint, the return value of GetHostEntry, which hints that maybe at some point, someone thought about using the build in function.

But once we've gotten our bytes back from the UDP packet, we copy the bytes into an array of 32-bit integers, simply upscaling each byte into 32-bits. Why? I have no idea. We still treat the array like bytes, moving to specific offsets to detect whether or not our request succeeded.

As a final note, I'm not the kind of person that gets hung up on "return from only one place in a function!" type rules, I think multiple returns are fine and can be cleaner to read in a lot of cases. But this particular one strikes me as annoyingly complex to follow, since we're not just returning from multiple locations, but from multiple levels of blocks.

On the upshot, I've learned a little something about the structure of DNS packets today.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: Pretty POed

Mon, 2024-01-22 07:30

"QPirate" was debugging an issue with purchase order version information. The format was supposed to be "Revision#.Version#", essentially "major" and "minor" versions for the various steps as the purchase order wormed its way through the company's incredibly complicated purchasing process.

Unfortunately, that isn't what was happening. Many numbers behaved in unexpected ways when they were versioned too many times. QPirate dug in, and found this:

double PRVN = Convert.ToDouble(dr.PoRevNumber) + (Convert.ToDouble(dr.POVerNumber) / 10);

In a reverse of the usual problem of stringly typed information, here we try and represent what should be strings as doubles, and there lies the root of our problem. POVerNumber gets divided by 10, which if you only have 9 versions, that's fine- but as soon as you have ten or more, your POVerNumber starts getting added to your PoRevNumber.

The fields were actually strings to begin with, which adds insult to injury. They had to convert them to doubles to do this arithmetically challenged solution to version numbers, instead of just doing some string concatenation.

For bonus points, I really enjoy the inconsistent capitalization of PO.

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

Error'd: Grasshopper

Fri, 2024-01-19 07:30

One of my multitude of peeves is the phrase "special characters." Somehow, our lexicon has been corrupted by semiliterate middle-schoolers, who have never learned that the name for ,.;: and so on is punctuation and/or symbol. Dustin Hoffman's Raymond was a special character. Period ain't.

This week, Robert F. had the misfortunate to step into it, and observed "What boggles my mind is that someone had to actively choose to create a validation rule specifically for this."

 

Saver Stewart was frustrated by Fido's failure to refresh a field in their messages app. "It isn't reassuring when a major financial corporation can not count to zero correctly," he fretted. I figure they just reached zero and didn't update every element on-screen immediately.

 

In an echo of earlier error'ds, an anonymous member of our Helvetian contigent reminds us that Swiss Railway's apps (not to be confused with SwissRail's Zoppas) sind immer noch schlecht. Sagt mann "Ah, perfect! The Swiss Railways' app is telling me that I'll be there in NULL time 👌" Super!

 

"Even in the best families..." laments Lucio Crusca , shaking his head sadly. "This is WhatsApp Web since yesterday on my PC."

 

Finally, "I didn't think my age would be a problem..." stammered junior Jake. The cutoff age must be 34.5. Patience, grasshopper.

 

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!
Categories: Computer

CodeSOD: Get Results as What?

Thu, 2024-01-18 07:30

Before RESTful web services and JSON as a serialization format, XML was going to conquer the world. Circa 2001, I remember going to user's groups only to hear about how XML was going to allow legacy mainframes to be connected to modern applications (without discussing the fact that the legacy mainframe still needed maintenance and code support). These days, XML is (nearly) dead, and lighter-weight markup languages have replaced it, including JSON.

Which brings us to this method, from Chris:

public string GetResultsAsJSON() { return base.XmlResultText; }

This does, in fact, return a JSON string.

It's a delightful mix of stringly typed data (XML and JSON, I think we can agree, are different types), legacy code not being fully modernized, and fields being re-used. The exact kind of code you see in a codebase you inherit, you groan and grumble about, and then you pinch your nose and get back to work, because you know there are things that are much more urgent than fixing this.

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

A Continental Divide

Wed, 2024-01-17 07:30

Our friend Argle has shared a great many stories with us. Today's story is one that Argle relates secondhand, about one of Argle's former students: Marie.

Marie was a prized student, and kept in touch with Argle after classes. At various points, they were co-workers at other companies, or when Argle founded his startup, Marie was one of the first hires. In between those points where their career paths converged, they kept in touch, even visiting each other at work.

For a few years, Marie ended up in Initech's telecom division. Her office was not in the traditional cube farm, but a small room (with a door and everything!) buried in the basement, far from windows. It may have been a bit of a dank, ugly, cave, but Marie, like all of her fellow Morlocks, did what she could to make it more homey.

Given the era, and the specific computer-nerd subculture, that meant a lot of the ThinkGeek-style desk toys and posters. A Netwon's cradle over there, a fossilized shark's tooth there, and on the wall a Firefly poster sandwiched between a "Reunite Gondwonaland" poster and a "Pangean Liberation Front" poster. It wasn't much, but it was a home away from home.

For months, everything was happy, or as happy as one can be in a basement cave in at Initech. At some point, a new manager was hired, took a tour of the basement, introduced himself, and shook everybody's hand. Well, everybody's hand but Marie's.

Later that day, when she popped out of the Underdark to forage for lunch at the taco place down the street, she saw the new manager huddled up with the older managers, whispering to themselves around the coffee maker. When she passed by, they clammed up until she was back out of ear shot.

When Marie returned to her desk, there was an email waiting for her- she was to report to the office of her Two Levels Up boss immediately. That couldn't be good. She dutifully trudged to that office. That triggered a call for all the other implicated managers on the team, and before long a half dozen people were crammed into this single office with stern looks on their faces.

"It's come to our attention that you've been displaying… inappropriate items in your office." "Flagrantly!" "Potentially offensive!" "Inappropriate for work, certainly, but also inappropriate for anywhere else!" "Blatantly political items are inappropriate for company owned spaces."

The managers, in chorus, explained to her letter and verse exactly which sections of the employee manual she had violated, and exactly what those consequences were going to be. What they didn't explain was how exactly she had violated them.

"I'm confused, what items am I displaying that are blatantly political?"

"Why, those posters!" New Manager said. "Not just blatantly political, but also offensive to many of your co-workers."

Marie was aware that Firefly's cancellation had been cause for much outcry amongst its fans, but didn't see how that was political- and then she realized that it was the other two posters: "Reunite Gondwonaland" and "Pangean Liberation Front" that had drawn their ire.

Marie could have, at that point, defended herself. She could have explained the joke (which always makes it funnier), and attempted to explain geology and continental drift to a horde of managers that had already worked themselves into a frenzy over things they didn't understand. And while she would have been right, it certainly wouldn't have won her anything, and would likely have made the situation worse.

So instead, she said, "Oh, I'm sorry, I didn't realize. I'll take them down." After a little more scolding, she was released to go back to her cave, where she did what she promised, packing the posters neatly into the original packing tubes they'd come in. And after that, she touched up her resume- it wasn't going to be hard to find a place where she could express herself "politically".

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: Clear This

Tue, 2024-01-16 07:30

Andy found this simple function in a C project he's working on.

void clearVal(int x) { x = 0; }

This is a thing of beauty, right here. From the very first premise, the function is useless: set an integer to zero. It's not even clearer about its intent than the original one liner- arguably it's less clear.

But it also has the benefit of not doing anything. Whoever wrote this function perhaps intended it to take a pointer. But as written, this just creates a local variable called x, which starts with whatever value the caller passed in, and sets the value of the local variable to zero. Nothing outside the scope of this function gets changed.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!
Categories: Computer

CodeSOD: A Simple List Copy

Mon, 2024-01-15 07:30

Mike's team had a new hire. They did great on the interview, really shined in the coding challenges, so it was a no-brainer hire. In the middle of on-boarding, the team got slammed, so this new hire ended up being left to fend for themselves.

This was a mistake.

public class ElementHandler { private final List<Element> elements = new ArrayList<Element>(); public final List<Element> getElements() { final Element[] allElements= elements.toArray(new Element[elements.size()]); final List<Element> result = new ArrayList<Element>(); for(int i =0; i<allElements.length; i++){ result.add(allElements[i]); } return result; } }

The getElements function here looks like a basic accessor method. Clearly, the developer was concerned about just returning a reference to the class's internal state- a wise concern- and opted to return a copy.

They then just failed to use any of the basic .NET Framework methods for making a copy and made their own. Badly.

First, they convert the list toArray. This does a deep copy on all the elements. Then they iterate across all of those elements to populate a new List, which is the return value.

There are some fun .NET internals here- allocating an empty ArrayList creates an array list with an internal buffer of 0 elements. When you add the first element, the ArrayList creates a buffer that can hold 4 elements. When that buffer gets filled, the ArrayList creates a new buffer, twice that size, and copies all the elements into the new buffer. Each time the buffer fills up, all the items get copied again.

So, depending on how big this input list is, this code could be inserting a lot of copy operations that you never see, copying the same elements over and over again.

What makes this so frustrating is that .NET has a perfectly simple way to create a copy and preallocate an array of the correct size, all in one step:

return new ArrayList<int>(elements)

This version has only one copy operation per element in the list, not potentially many. And, honestly, it's easier to read and clearer about its purpose.

But, I guess the "right" way doesn't give you the option to stress test the garbage collector.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!
Categories: Computer

Pages