Error'd: Out of Order |
"Considering the format died out a decade ago, I think I'm going to remove this one from my list and pass on the pre-order," John C. writes.
"Thanks to Bing, today I learned of the importance of International Women's Day in the Russian Revolutions," writes Andy M.
Niklas K. wrote, "Ever have that feeling that you possibly left your stove on and went all the way back home just to make sure? Well, I think my train did that too."
"It's too bad the green beans aren't available, I guess I'll just have to try the green beans instead," James wrote.
Pascal writes, "Logged in before I signed up? Now that's what I call predictive!"
Rob wrote, "The 'Contact Us' and 'FAQ' pages with equally angulared ng-ng-ng."
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
|
Метки: Error'd |
The Blanking Blank |
Since the dawn of software development, people have endlessly been searching for a way to modularize their applications efficiently. From the “do one thing and do it well (and also everything is text)” protocols of Unix-style command line tools, through to modern microservices, the key idea has always been to have simple, easy to understand components which interface in simple, easy to understand ways. Complex things are built from piles of small, tiny things.
Matt W was working on one of those simple modules in a Big Complicated Thing. The Big Complicated Thing was an enterprise “do everything” tool, and like most enterprise tools, it was larger and more complicated than it needed to be. Also, everything talked to everything else in XML, the most enterprise of cross-component interfaces.
Over the decades of continuous development, components had proliferated, interfaces had ball-of-mudded together, and seemingly arbitrary silos created boundaries where they didn’t make any sense. To wit, Matt needed to build a report out of the contents of some server-side session variables. Those session variables were populated by another component, which fetched the data from yet another component. Worse, many of those variables were optional, but despite using XML much of the data was stringly typed. Which meant, during testing, Matt saw this in a report:
Bill Lumbergh, null for Initech
Matt’s code dutifully checked for empty strings and actually null strings, but hadn’t been prepared for a "null". On a null or empty job title string, the code should output, “Bill Lumbergh, with Initech”. Matt didn’t particularly want to add that check to his code, since the data should be clean by the time he got it.
Matt chatted up Cecil, the developer who maintained the component that populated the session variables.
“Hey, could you do me a favor?” Matt asked. “If something comes through as the string ‘null’, could you make it blank?”
“Oh, yeah, no problem,” Cecil said. “I’ll roll that out later today.”
A few hours later, Matt grabbed the latest build and re-ran his test, confirming that Cecil had done exactly what he asked.
Bill Lumbergh, blank for Initech.
|
Метки: Feature Articles |
Announcements: Who's up for a scotch at InedoCon in Portland? |
A couple weeks back, I posted the Free Mug Day campaign: run through the quick BuildMaster tutorial, and I'll send you a free mug. Today, I have a slightly different offer: meet me at InedoCon (Portland, May 22/23) as a TDWTF delegate, and let's chat software over scotch!
Why Portland? As part of the mug campaign, I asked everyone to share their feedback/comments/advice, and I got lots of suggestions on how we can improve the software itself. That was great, and then I saw this:
TRWTF is that I had no idea what BuildMaster actually did until now. I've seen it advertised for years, I even went to your website once or twice, but I figured it had something to do with builds and didn't really care. Anyway, I was desperate for a free mug and just gave it a try...
Well, turns out I was wrong. It's a damn useful tool and I've going to start using it to replace our existing deployment tool disaster. I guess, my main advice... perhaps say what BuildMaster actually does? We could have used this years ago. Though I wouldn't have gotten my free mug...
This wasn't the first time I've heard this sort of feedback. I originally designed BuildMaster to be very flexible, and to solve all sorts of different problems in software delivery. For a lot of customers, it did exactly that.
Over the years, I'd proudly proclaim that "BuildMaster can do everything!" Turns out that "doing everything" may as well mean "doing nothing", and that's a problem when you're trying to sell a product. It has to "do something", and that something is called a product story. It's taken me a long time to understand that, and I learned a lot of it from TDWTF readers like you.
I'm confident my heart is in the right place. Ten years ago, I wrote that Programming Sucks! Or At Least, It Ought To, and I still believe that developers should work closely with business stakeholders to solve business problems with code. That's my vision of BuildMaster, and the other products I designed at Inedo.
If you share this vision, I'd would love to buy you a scotch and get your opinion on how we can better communicate this to the world. We're hosting our first-even user conference in Portland, and I'll be talking about how unnecessary complexity that's masquerading as innovation is stifling actual innovation.
Some of our very enthusiastic customers (and fellow TDWTF readers) will be share how they navigated this complexity, improved the lives of developers, and helped their organization as a whole:
• WebMD's Continuous Delivery Architect, Aaron Jensen will talk about how they successfully transitioned from a monolith application with no automation to a CI/CD platform with dozens of microservices using BuildMaster
• Robert Vandehey, Alkami's Vice President and Chief Architect, will show how they componentized a monolith into packages for microservices using ProGet as the key link in their deployment chain
• BuildMaster's product director John Rasch will demonstrate how you can add CI/CD to a legacy application through build, deployment, and process automation.
• Governmental organizations and other compliant-heavy industries have specific challenges for automation. Jody Dorchester will present how 100's of applications were automated with BuildMaster in less than a year, while maintaining compliance.
Even if you don't use Inedo's tools --- heck, especially if you don't use Inedo's tools --- the lessons we share will be helpful for you, and the outsider insights you have will be invaluable for everyone.
I've got three complimentary tickets for those who are interested, and a single travel voucher to cover your flight and hotel. Just fill out this form before 2019/Apr/5, and we'll be in touch.
https://thedailywtf.com/articles/who-s-up-for-a-scotch-at-inedocon-in-portland
|
Метки: Announcements |
CodeSOD: This Is Your Brain on PL/SQL |
The realest of real WTFs, the reigning champion for all eternity is and forever will be Oracle. Today, we’re going to take a look at a little code in PL/SQL.
PL/SQL is a weird language, a blend of SQL and, well, a Procedural Language, with a side of OO slapped on. The syntax does an excellent job giving you the feeling that it was designed in the 1970s, and each new feature or change to the language continues that tradition.
The structure of any PL/SQL code unit is going to be built around blocks. Each block represents a self-contained namespace. The basic anatomy is:
DECLARE
-- variable declarations go here
BEGIN
-- code goes here
EXCEPTIONS
-- exception handling code goes here, using WHEN clauses
END;
If you’re writing a stored procedure, or a trigger, you replace the DECLARE keyword with CREATE [OR REPLACE]. You can also nest blocks inside of other blocks, so it’s not uncommon to see code structured like this:
BEGIN
DECLARE
--stuff
BEGIN
--actions
END;
--more actions
END;
Yes, that does get confusing very quickly. And yes, if you want to really approximate structured error handling, you have to start nesting blocks inside of each other.
The language and database have other fun quirks. They didn’t get an IDENTITY column type until version 12c. In prior versions, you needed to use a SEQUENCE object and write procedures or triggers to actually force the autonumbering. You’d usually use a SELECT INTO… statement to populate a variable, with the bonus that Oracle SQL always requires a table in the FROM clause, so you have to use the made up table dual, e.g.:
CREATE TRIGGER "SOME_TABLE_AUTONUMBER"
BEFORE INSERT ON "SOME_TABLE"
FOR EACH ROW
BEGIN
SELECT myseq.nextval INTO :new.id FROM dual;
END;
:new in this context represents the row we’re autonumbering. That’s the “normal” way to create autonumbered columns in older versions of Oracle. Boneist found a different, slightly less normal way to do the same thing:
CREATE OR REPLACE TRIGGER "SCHEMA1"."TABLE1_TRIGGER"
BEFORE INSERT ON "SCHEMA1"."TABLE1"
FOR EACH ROW
BEGIN
DECLARE
pl_error_id table1.error_id%TYPE;
CURSOR get_seq IS
SELECT table1_seq.nextval
FROM dual;
BEGIN
OPEN get_seq;
FETCH get_seq
INTO pl_error_id;
IF get_seq%NOTFOUND
THEN
raise_application_error(-20001, 'Sequence TABLE1_SEQ does not exist');
CLOSE get_seq;
END IF;
CLOSE get_seq;
:new.error_id := pl_error_id;
END;
END table1_trigger;
There’s a lot going on here. First off, note that our DECLARE section contains a CURSOR statement. Cursors let you iterate across records. They’re very expensive, and in Oracle-land, they’re a resource that must be released.
This trigger uses a nested block for no particular reason. It also uses an extra variable, pl_error_id which isn’t necessary.
But the real weird part here is the IF get_seq%NOTFOUND block. That’s pretty simple: if our cursor didn’t return a row. This is something that, with this cursor, can’t possibly happen, so we’ll never hit this. The sequence will always return a value. That’s a good thing, if you look at the code which follows.
raise_application_error is Oracle’s “throw” equivalent. It will crawl up the stack of executing blocks until it finds an EXCEPTIONS section to handle the error. Note that we close the cursor after that statement- thus, we never actually close the cursor. Cursors, as mentioned, are expensive, and Oracle only lets you have so many of them.
Here we have a weird case of a developer defending against an error that can’t happen in a way which would eventually lead to more errors.
https://thedailywtf.com/articles/this-is-your-brain-on-pl-sql
|
Метки: CodeSOD |
CodeSOD: Doubly Encrypted Logins |
Providing authentication for your web-based APIs is both a challenging problem but also a largely solved problem. The hardest part is really just working your way through the various options, and from there it’s usually some variation on a drop-in component.
Done properly, it’s also client-agnostic. I can access the service from my browser, I can access it from a thick client, I can access it from cURL. Done incorrectly, well, you get what happened to Amira.
She was trying to pull some statistics from the backend, and couldn’t figure out how to authenticate. So she checked the front-end code to see how it authenticated:
crypt = new JSEncrypt();
crypt.setPublicKey('');
challenge = "";
function doChallengeResponse()
{
document.loginForm.password.value.replace(/&/g, '%26');
document.loginForm.password.value.replace(/\\+/g, '%2B');
document.loginForm.password.value = crypt.encrypt(document.loginForm.password.value);
document.loginForm.response.value = document.loginForm.password.value;
document.loginForm.password.value = '';
document.loginForm.submit();
}
On one hand, I want to guess that this code is very old, just based on the document.loginForm approach to interacting with DOM elements. On the other, JSEncrypt was first released in 2013, setting an upper limit on the age.
We send those credentials to the backend using a from submission, which our original developer decided needed some sanitization- all the & and + in the password are escaped, which… shouldn’t be necessary because the form should be doing a POST request, but also, we’ve encrypted the data.
Here’s my suspicion. This code is actually quite old. The original developer copied it from a circa 2005 StackOverflow post, and it didn’t include things like encryption or sending the form as a POST. Over the years, little things got added to it, one after the other, but the core mechanics never really changed.
Amira did check the history, and found that the previous version didn’t use encryption, and instead concatenated the password with the challenge variable, MD5 hashed the two, and sent that over the wire, which… well, almost kinda works, if you don't think about it too hard.
The good news is that once Amira figured out how to get the cookie she needed, the server never expires that token, so she’s free to keep it, send her requests via cURL, and never look at the web form again. She doesn’t have to worry about the cookie being compromised in transit, because the application uses and has always used SSL/TLS.
|
Метки: CodeSOD |
It's The End Of The Month As We Know It |
If you ask an engineer whether it's safe to cross a bridge, he'll happily walk you through how safe bridges are, how the mathematics work out, how far we've come in structural safety. You'll come away from the conversation feeling confident that no bridge will ever collapse anywhere on the face of the Earth. If you ask a software engineer about banks, however, you'll likely come away terrified, with a 50/50 chance you're now convinced to put all your money in bitcoin. Banks are notorious for bad software decisions—not so much because the decisions are worse, but because most people assume banks are more careful and security-minded.
Today's submitter, Kato, worked at Inibank, where they used a commercial product called T24 as the core of their banking system. T24 is used by hundreds of banks worldwide. It's customizable for a wide range of banking solutions, and so like most large customizable suites, there are programmers that specialize in writing custom code for it and consultants that will hold your hand through major upgrades.
Inibank brought in a consultant to take on a special project while their resources were busy. At the end of the business day, there is a Close Of Business process that has to be done to ensure all the money gets where it's going, all the appropriate outputs are recalculated, and all the relevant reports are run. In banks, this also changes the system's date to the next working day—which is why if you do online banking on a Sunday, none of it begins to process until Monday morning. The consultant was meant to create a new report that would run during the Close Of Business process, which would do extra processing if it were also the End of Month.
Kato sat down with the new guy, showing him how they'd set up their end of day reports. "You see here, there's a global for the last working day, one for today, and one for the next working day. Those are YYMMDD, so they're easier to work with."
"Right, right okay, gotcha. Gotcha. And what format is that?"
"... I don't know the standard off the top of my head, but it's year, month, day."
"Right, right, okay. Cool. I'll get right to work, then."
Kato walked away from the conversation with a sinking feeling in the pit of his stomach, but he tried to ignore it. The consultant said he was all set up and ready. Surely he knew what he was doing, right? Kato put it out of his mind, and didn't worry about it again until it came time to review the code, and he found this gem:
TH.DATE = R.DATES(EB.DAT.NEXT.WORKING.DAY)[1,6]:"01"
CALL CDT('ES00',TH.DATE,"-1C")
WTODAY = OCONV(DATE(),"DY") : FMT(OCONV(DATE(),"DM"),'R%2') : FMT(OCONV(DATE(),"DD"),'R%2')
IF TH.DATE EQ WTODAY THEN
In layman's terms:
Now, this ... works. Mostly. Except that if for some reason close of day ticked over past midnight on the second-to-last day of the month—not uncommon—it would incorrectly think it was the last day of the month and run the report. Which worked well with the next problem: if the same thing happened on the last day of the month, it would incorrectly not run the report. And the best bug yet: if the last day of the month was a Sunday, the server's calendar would never be set to it, since it skips non-working days.
Speaking of non-working days: as Inibank was US-based, there was no reason to use the Spain calendar. Sure, your months and weeks are the same, but the US bank holidays would need to be set in the Spain calendar in the software, or else it would still run. Finally, as if all this weren't bad enough, calling for the Date three times meant you could have inconsistencies if it straddled midnight exactly: the month retrieved before midnight, the day after.
Kato put in a comment, suggesting that the code be changed to:
IF R.DATES(EB.DAT.TODAY)[5,2] # R.DATES(EB.DAT.LAST.WORKING.DAY)[5,2] THEN
Five minutes later, the contractor was at his desk. "What is this change?"
Kato was in no mood to argue by this point. "Your code's broken, dude. You didn't need to do all that."
"I see, I see. Well, it's just, this is the standard operating procedure in the industry. But no matter."
Kato highly doubted this, but he shrugged it off. "Industry's wrong, then. I explained it all in the comment."
"I see. Yes, I read this. But I will read it again." And then he was gone, as suddenly as he'd appeared at Kato's desk.
The edits were made, Kato approved the PR, and the consultant vanished into the night.
Sometimes, late at night, Kato lies awake wondering if the consultant really understood what he did wrong, or if he were just paying lip service, collecting his check, and writing terrible code for twice Kato's salary somewhere else. Somewhere without in-house staff to look over the code.
But don't put all your money in bitcoin. That's even worse.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
https://thedailywtf.com/articles/it-s-the-end-of-the-month-as-we-know-it
|
Метки: Feature Articles |
Error'd: Killer Errors |
Adam wrote, "I hear that NewFeature1 is a real, ahem, killer feature of these Wi-Fi drivers."
"Gigabyte Easy Tune6 installer must release to the failure! There is no other option!" writes Jeff B.
"DPD is having a really hard time guessing which month my Ebay delivery will take place," Rupert W. writes.
"So this USB expander can power all my peripherals, an old timey typewriter, AND a fan? Sold."
"My wife is flying from Chicago to Atlanta and had to reschedule," Casey B. wrote, "Luckily, she'll arrive almost two days before she leaves."
Chris D. writes, "So is this like malware credit? Can I run a couple of thousand malicious programs before I need to worry about infection?"
|
Метки: Error'd |
CodeSOD: Swing and You're Out |
George G was hired to do some UI work for a company which sold a suite of networking hardware. As networking hardware needs to be highly configurable, George was hired on to do “some minor tweaks” to the UI. “It’s just some sizing issues, fonts, the like. I’m sure you’ll do something to the stylesheets or whatever,” said the boss.
The boss didn’t know what they were talking about. The UI for some of the products was a web based config tool. That was their consumer-grade products. Their professional grade products used the same Java program which was originally released 15 years earlier. There were no stylesheets. Instead, there was an ancient and wobbling pile of Java Swing UI code, maintained by a “master” who had Strong Opinions™ about how that code should look.
For example, dispatching a call to a method is indirection. Indirection is confusing. So inline those calls, especially if there's a conditional involved: inline all the “ifs”. Factory methods and other tools for constructing complex objects are confusing, so always inline your calls to constructors, and always pass as many parameters as you can, except for the times where you don’t do that, because why would we be consistent about anything?
All of the developers on the project had to attend to the master’s wishes during code reviews, where the master gleefully unrefactored code “to make it more clear.”
Also, keep in mind that this UI started back in an era where “800x600” was a viable screen resolution, and in fact, that’s the resolution it was designed against. On a modern monitor, it’s painfully tiny, stuffed with tabs and icons and other UI widgets. Over the years, the UI code has been tweaked to handle edge cases: one customer had the UI zoom turned on, so now there were piles of conditionals to check if the UI needed to be re-laid out. Somebody got a HiDPI display, so again, a bunch of checks and custom code paths, all piled together.
Speaking of layout, Swing was a prime case of Java taking object orientation to the extreme, so in addition to passing in widgets you want displayed, you also can supply a layout object which decides how to fill the frame. There was a whole library of them, but if you wanted a flexible layout that also handled screen scaling well, you had to use the most complicated one: Grid Bag. The Grid Bag, as the name implies, is a grid, but where the grid cells can be arbitrary sizes. You control this by adding constraints to the flow. It’s peak Java overcomplification, so even simple UIs tend to get convoluted, and with the “inline all the things” logic, you end up with code like this:
if( os.getSystemFontSize( NORMAL ) == 14 )
text = new JText("5", new GridBagConstraints(3, 1, 3,3, 1.0, 0.5, GridBagConstraints.PAGE_END, 1.5, Insets(10,5,10,5, 5, 5 ) );
else
text = new JText("5", new GridBagConstraints(3, 1, 3,3, 0.99, 0.4, GridBagConstraints.PAGE_END, 1.5, Insets(4,2,4,5, 5, 5 ) );
This particular code checks to see if the user has their font set to 14pt. If they do, we’ll set a constraint one way. If it’s any other value, we’ll set the constraint a different way. What is the expected result of that constraint? Why all this just to display the number 5? There are a lot of numbers other than 14, and they’re all going to impact the layout of the screen.
George made it two months, and then quit. This happened just a week after another developer had quit. Another quit a week later. No one in the management chain could understand why they were losing developers so quickly, with only the master remaining behind.
|
Метки: CodeSOD |
CodeSOD: Remove This |
Denae inherited some 90s-era GUI code. The original developers have long since gone away, and the source control history has vanished into oblivion. In the 90s, the Standard Template Library for C++ was still very new, so when Denae needs to debug things in this application, it means doing some code archaeology and picking through home-brewed implementations of common data structures.
Denae’s most recent bug involved understanding why some UI elements weren’t updating properly. The basic logic the application used is that it maintained a List of GUI items which need to be repainted. So Denae dug into the the List implementation.
template void List::Remove(Type t)
{
int i;
for (i=(num-1); i>=0; i--)
{
if(element[i] == t)
{
break;
}
}
LoudAssert(i>=0);
DelIndex(i);
}
template void List::DelIndex(int i)
{
LoudAssert(icode>
Let’s start by talking about LoudAssert. Denae didn’t provide the implementation, but had this to say:
LoudAssert is an internally defined assert that is really just an fprintf to stderr. In our system stderr is silenced, always, so these asserts do nothing.
LoudAssert isn’t an assert, in any meaningful way. It’s a logging method which also doesn’t log in production. Which means there’s nothing that stops the Remove method from getting a negative index to remove- since it loops backwards- and passing it over to DelIndex. If you try and remove an item which isn’t in the list, that’s exactly what happens. And note how num, the number of items in the list, gets decremented anyway.
Denae noticed that this must be the source of the misbehaving UI updates when the debugger told her that the list of items contained -8 entries. She adds:
We have no idea how this ever worked, or what might be affected by fixing it, but it’s been running this way for over 20 years
|
Метки: CodeSOD |
Exceptionally Serial |
You may remember Kara, who recently found some "interesting" serialization code. Now, this code happens to be responsible for sending commands to pieces of machine equipment.
Low-level machine interfaces remain one of the domains where serial protocols rule. Serial communications use simple hardware and have minimal overhead, and something like RS232 has been in real-world use since the 60s. Sure, it's slow, sure it's not great with coping with noise, sure you have to jump through some hoops if you want a connection longer than 15m, but its failures are well understood.
Nothing is so well understood that some developer can't make a mess of it.
Public Function SendCommand(ByVal cmd As String) As String
Dim status As Integer
' Write cmd to the serial port using a protocol that is too painful to reproduce here.
' status receives an appropriate value along the way as the protocol checks for various error
' conditions including timeout
If status <> 0 Then Throw MakeComPortException(status)
End Function
Private Function MakeComPortException(ByVal status As Integer) As ComPortException
Dim code As Integer
Dim message As String = Nothing
GetErrorCode(status, code, message)
Return New ComPortException(code, message)
End Function
Private Sub GetErrorCode(ByVal ErrorNum As Integer, ByRef code As Integer, ByRef message As String)
code = ErrorNum
Select Case ErrorNum
Case 129 : message = "Hardware error occured during Send Data" ' Talk Error'
Case 130 : message = "System asked to talk but did not recieve Previous Talk Command" ' Nothing to say
Case 131 :
SendCommand("ERRMS?")
Dim EMsg As String = GetResponse()
Dim EmsgStart As Integer = EMsg.IndexOf(" (")
Try
If EMsg.Contains("ERR=") Then code = CInt(EMsg.Substring(4, EmsgStart - 4))
Catch ex As Exception
End Try
message = EMsg.Substring(EmsgStart)
Case 132 : message = "H/W Error while system trying to accept data" 'Listen Error
Case 133 : message = "More than 80 characters received before term char"
Case 134 : message = "Archive media is full"
Case 135 : message = "Listen state interrupted by ESC key" ' Interrupted from keyboard
Case 136 : message = "Listen state interrupted by controller sending '*'" ' Interrupted by Controller
Case 137 : message = "Error Occured in UART"
Case StatusCodes.PortDeviceNotFoundErrorCode : message = "No device Found"
Case StatusCodes.PortTimeoutErrorCode : message = "COM port Timeout Error"
' This next occurs if cable is unplugged at controller
Case StatusCodes.PortDisconnectedErrorCode : message = "Serial cable disconnected"
Case Else : message = "Error #: " & ErrorNum
End Select
End Sub
So, the SendCommand method takes a string and passes it down the serial port. The protocol details were elided here, but we know that we receive a status number. MakeComPortException takes that number and helpfully looks up the message which goes with it, using GetErrorCode.
GetErrorCode is one gigantic switch statement. And let's pay close attention to the message lookup process for error 131. You'll note that we call SendCommand to ask the remote device to tell us what the error message was. But in some cases, it's going to reply to that request with an error code. Error 131, to be exact.
So if we trace this: we call SendCommand which gets a 131 error, which forces it to throw the results of calling MakeComPortException, which calls GetErrorCode, which calls SendCommand, which throws a new MakeComPortException, which…
There's an interesting side effect of this approach. Despite looking like a series of recursive calls, throw unwinds the stack, so this code will never actually trigger a stack overflow. It's actually more of an exception-assisted infinite loop.
For a bonus, note the PortTimeoutErrorCode entry. On the hardware side, they use a custom serial cable which wires a loopback on the RS232 "Ready to Send" and "Clear to Send" pins, which the software uses to detect that the cable is unplugged. It also has the side effect of ensuring that no off-the-shelf RS232 cables will work with the software. This is either a stupid mistake, or a fiendishly clever way to sell heavily marked-up replacement cables.
|
Метки: Feature Articles |
Portage and Portability |
Many moons ago, when PCs came housed within heavy cases of metal and plastic, Matt Q. and his colleague were assigned to evaluate a software package for an upcoming sales venture. Unfortunately, he and the colleague worked in different offices within the same metro area. As this was an age bereft of effective online collaboration tools, Matt had to travel regularly to the other office, carrying his PC with him. Each time, that meant unscrewing and unhooking the customary 473 peripheral cables from the back of the box, schlepping it through the halls and down the stairs, and catching the bus to reach the other office, where he got to do all those things again in reverse order. When poor scheduling forced the pair to work on the weekend, they hauled their work boxes between apartments as well.
As their work proceeded, Matt reached the limits of what his 20 MB hard drive could offer. From his home office, Matt filed a support ticket with IT. The technician assigned to his ticket—Gary—arrived at Matt's cubicle some time later, brandishing a new hard drive and a screwdriver. Gary shooed Matt away for more coffee to better focus on his patient. One minor surgery later, Matt's PC was back up and running with a bigger hard drive.
One day ahead of the project deadline, Matt was nearly done with his share of the work. He just had a few tweaks to make to his reports before copying them to the floppy disks needed by the sales team. Having hooked his PC back up within his cubicle, he switched it on—only to be greeted with a literal bang. The PC was dead and would not start.
After a panicked call to IT, Gary eventually reappeared at his desk with a screwdriver. Upon cracking open the PC case, he immediately cried, "Wait a minute! Have you been carting this PC around?"
Matt frowned. "Er, yes. Is that a problem?"
"I'll say! You weren't supposed to do that!" Gary scolded. "The hard drive's come loose and shorted out the workings!"
Matt darted over to Gary's side so he could see the computer's innards for himself. It didn't take long at all to notice that the new hard drive had been "secured" into place using Scotch tape.
"Hang on! I daresay you weren't supposed to do that!" Matt pointed to the offending tape. "Shall I check with your manager to be on the safe side?"
Gary's face crumpled. "I don't have access to the proper mountings!"
"Then find someone who does!"
Armed with his looming deadline and boss' approval, Matt escalated his support ticket even higher. It didn't take long at all for genuine mounting brackets to replace the tape. He never learned why IT techs were being deprived of necessary hardware; he assumed it was some fool's idea of a brilliant cost-cutting measure. He had to wonder how many desperate improvisations held their IT infrastructure together, and how much longer they would've gone unnoticed if it hadn't been for his PC-toting ways.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
|
Метки: Feature Articles |
Error'd: Do Not Read The Daily WTF |
Neil S. wrote, "MSN UK's reverse psychology marketing angle is pretty edgy."
"Batman & Lorem was a surprise flop at the box office," writes Art O.
Klaus asks, "Do I want to know, why Feedly associates Coffee with restrooms?"
"Those idiots, I clearly asked for null_-532419553!" wrote Raymond D.
Nigel writes, "I just hope their drugs aren't as genuine as their Windows licenses."
"At this point, I don't even remember how long I'd been waiting to get to this point," writes Jake B.
|
Метки: Error'd |
CodeSOD: Ancient Grudges |
Ignoring current events, England and the rest of Europe have had a number of historical conflicts which have left a number of grudges littered across the landscape.
Andro V lives in Norway, but is remotely collaborating with an English developer. That English developer wants their peers to know that Sweyn I and Thorkell the Tall’s invasions of southern England will never be forgotten.
///
/// A counter that is to be read in reverse and computed in a Viking way. It usually is followed by a buffer which must be parsed in reverse order.
///
public static int Read3F1(this BinaryReader reader, bool reverse = false)
{
const float VIKINGS_INVADES_ENGLAND = 1009.0f;
int counter = (reverse ? reader.ReadInt32Rev() :
reader.ReadInt32());
// Debug.Assert(counter % 0x3f1 == 0, "Wrong data for 3f1 counter: 0x" + counter.ToString("X8"));
counter = (int)Math.Round((counter / VIKINGS_INVADES_ENGLAND) - 1.0f); // The VIKINGs magic computation
return counter;
}
The year 1009 involved a lot of Viking invasions of southern England. But, like all old grudges, it arises from an even earlier grudge, as in 1002, English king Aethelred the Unready massacred all the Danes living in England. Of course, that was in response to a few years of raids prior, and, well… it’s ancient grudges and blood fueds all the way down.
|
Метки: CodeSOD |
How It's Made |
People like hot dogs until they see how it's made. Most people don't ask, because they don't want to know and keep eating hot dogs. In software, sometimes we have to ask. It's not just about solving problems, but because what scares some programmers is the knowledge that their car's software might be little more than the equivalent of driving duct-taped toothpicks down the highway at 70MPH. Our entire field is bad at what we do.
Brett worked as a system analyst for a medical research institution, MedStitute. MedStitute used proprietary software for data storage and analysis, called MedTech. Doctors and researchers like MedTech's results, but Brett his co-worker Tyree- know how it's made.
The software has no backend access, and all software development happens in a "click-to-program" GUI. The GUI looks like it was built from someone who learned to code by copy/pasting from 1990s era websites, watching ten minutes of Jurassic Park, and searching StackOverflow until something compiled. The "language" shows the same careful design philosophy. Every if must have an else. Some modules use booleans, some return an empty string to represent false values. Documentation is unclear about which situation is which. Essentially, every if statement becomes three statements.
Brett needed to launch a new study. A study depends on some basic set of statistics and groups patients based on a randomized variable. Brett looked through the list of variables he could randomize on, and the one he wanted was missing. Brett assumed he made a mistake, and went back a few screens to check the name, copying it down for reference. He went back to the list of randomizable variables. It wasn't there. He looked closer at the list. He noticed that the list of randomized variables only included data from multiple-choice fields. The field he wanted to randomize on was based on a calculated field.
Brett knew that Tyree had worked on another project that randomized on a calculated field, so he messaged Tyree on Slack. "How did you code this random variable? In Medtech it won't let you?"
"I'm on a conference call, let me call you afterward," Tyree wrote.
A few minutes later, Tyree called Brett.
"What you have to do is start with two fields. Let's call it $variable_choice, that's a multiple choice question, and $variable_calced that's your calculated field. When you want to create a variable that randomly selects based on your calculated field, you start by telling Medtech that this random variable is based on $variable_choice. Then you delete $variable_choice, and then rename $variable_calced to be $variable_choice."
"Wait, they allow you to do that, but don't allow you to randomize calculated fields any other way? And they don't check?"
"Hopefully, they don't decide to start checking before this project is over," Tyree said.
"This study is supposed to go on for ten years. This project succeeding comes down to them never treating this workaround as a bug?"
"It was the only solution I could find. Let me know if you need anything else?"
Brett wasn't completely satisfied with the hack and went back to the documentation. He found a "better" solution: he could make a read-only multiple-choice field with only one choice, the value of the calculated field, as the default answer. Unfortunately, it was possible that the user would alter the list unintentionally by answering the multiple-choice question before the calculated field was evaluated.
Ultimately, the only choice left to Brett was to take his lunch break, go to the cafeteria, and order two hot dogs.
|
Метки: Feature Articles |
CodeSOD: The Value of Your Code |
“We know that governmental data-systems aren’t generally considered ‘cutting edge’ or ‘well architected’, but we’ve brought together some top flight talent, and we’re building a RESTful architecture that guarantees your code gets nice, easy-to-consume JSON objects.”
That’s what Oralee B. was told when she was hired to work on it. The system owned pretty much the entirety of the municipal data management market, tracking birth certificates, marriage certificates, death certificates and pet licenses for nearly every city in the country. JSON is so simple, how can you screw it up?
The same way most people seem to screw it up: reinventing key/value pairs in your key/value data format.
{
"code": "Registrations",
"Configurations": [
{
"code": "Registration1",
"Configurations": [
{
"code": "IdRegistration",
"value": "94"
},
{
"code": "CaptionActive",
"value": "EF_PRE_CR"
},
{
"code": "DateEnd",
"value": "2019-01-01"
},
{
"code": "DateStart",
"value": "2019-01-01"
},
{
"code": "Units",
"Configurations": []
}
]
},
{
"code": "Registration2",
"Configurations": [
{
"code": "IdRegistration",
"value": "92"
},
{
"code": "CaptionActive",
"value": "EF_PRE"
},
{
"code": "DateEnd",
"value": "2019-01-01"
},
{
"code": "DateStart",
"value": "2019-01-01"
},
{
"code": "Units",
"Configurations": [
{
"Code": "Unit1",
"Value": null,
"Configurations": [
{
"code": "IdUnit",
"Value": "1",
"Configurations": []
},
{
"code": "CaptionUnit",
"value": "Hour",
"Configurations": []
}
]
},
{
"Code": "Unit2",
"Configurations": [
{
"code": "IdUnit",
"Value": "2",
"Configurations": []
},
{
"code": "CaptionUnit",
"value": "Entrance",
"Configurations": []
}
]
}
]
}
]
}
]
}
It’s not just the reinvention of key/value pairs as code/value pairs. They’re also reinvented as Code/value and Code/Value pairs. The inconsistent capitalization tells its own story about how this JSON is generated and consumed by other clients, and at least on the generation side, it clearly rhymes with “bling congratulation”.
When Oralee asked, “Why on Earth do you do it this way?”
“Because,” her boss explained, “this is the way we do it.”
|
Метки: CodeSOD |
CodeSOD: The God Page |
Mike inherited a data-driven application. Once upon a time, it started out pretty well architected. Yes, it was in PHP, but with good architecture, solid coding practices, and experienced developers, it was simple and easy to maintain.
Time passed. The architects behind it left, new developers were hired, management changed over, and it gradually drifted into what you imagine when you hear "PHP app" in 2019.
Mike's task was to start trying to clean up that mess, and that started all the way back in the database layer with some normalization. From there, it was the arduous task of going through the existing data access code and updating it to use the refined model objects.
While there was a mix of "already uses a model" and "just a string" SQL statements in the code, there was a clear data-access layer, and almost all of the string-based queries actually used parameters. At least, that was true until Mike took a look at the "god" page of the application.
You know how it is. Thinking through features and integrating them into your application is hard and requires coordinating with other team members. Slapping together a page which can generate any arbitrary SQL if you understand how to populate the drop-downs and check boxes correctly is "easy"- at least to start. And by the time it gets really hard, you've already created this:
$numIDs=0; $statusdb1 = ""; $statusdb2 = ""; if ($cstatus=='inactive') { $statusdb1 = "WHERE customer_status='0'"; $statusdb2 = "AND customer_status='0'"; $childIDs.=" and "; } elseif ($cstatus=='active') { $statusdb1 = "WHERE customer_status='1'"; $statusdb2 = "AND customer_status='1'"; $childIDs.=" and "; } $childIDs.="("; if($isParent) { $str="select ChildID from customer_parent_child where ParentID=$custID order by ChildID;"; $result=$database->query($str); if(count($result) > 0) { $row = $result[0]; $IDs[$numIDs]=$row['ChildID']; $childIDs.="custID='$IDs[$numIDs]'"; $numIDs++; } foreach ($result as $row){ $IDs[$numIDs]=$row['ChildID']; $childIDs.=" or custID='$IDs[$numIDs]'"; $numIDs++; } } $childIDs.=")"; if($numIDs==0) { $childIDs=" and custID=''"; } //echo "childIDs:$childIDs
"; if ($charge) { $chargedb = " customer_child=0"; } if(isset($_POST['cmpSearchFname'])){ $search=$_POST['fname']; if (!$_POST['fname']) { $search=$_GET['fname']; } if(isset($search)){ if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where customer_firstname like '%$search%' $statusdb2 $childIDs order by customer_firstname;"; } else{ if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by customer_firstname;"; } } elseif (isset($_POST['cmpSearchcustID'])){ $search=$_POST['custID']; if (!$_POST['custID']) { $search=$_GET['custID']; } if(isset($search)){ if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where custID like '%$search%' $statusdb2 $childIDs order by custID;"; }else{ if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by custID;"; } } elseif (isset($_POST['cmpSearchLname'])){ $search=$_POST['lname']; if (!$_POST['lname']) { $search=$_GET['lname']; } if(isset($search)){ if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where customer_lastname like '%$search%' $statusdb2 $childIDs order by customer_lastname;"; }else{ if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by customer_lastname;"; } } elseif(isset($_POST['cmpSearchCmpName'])){ $search=$_POST['cmpName']; if (!$_POST['cmpName']) { $search=$_GET['cmpName']; } if(isset($search)) { if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where customer_cmp_name like '%$search%' $statusdb2 $childIDs order by customer_cmp_name;"; } else { if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by customer_cmp_name;"; } } elseif (isset($_POST['cmpSearchPhone'])){ $search1=$_POST['phone1']; if (!$_POST['phone1']) { $search1=$_GET['phone1']; } $search2=$_POST['phone2']; if (!$_POST['phone2']) { $search2=$_GET['phone2']; } $search3=$_POST['phone3']; if (!$_POST['phone3']) { $search3=$_GET['phone3']; } $phonesearch = "FALSE"; if(isset($search1)){ $search=$search1; $phonesearch = "TRUE"; } if(isset($search2)){ $search.=$search2; $phonesearch = "TRUE"; } if(isset($search3)){ $search.=$search3; $phonesearch = "TRUE"; } if($phonesearch == "TRUE"){ if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where customer_contact_phone1 like '%$search%' $statusdb2 $childIDs order by customer_contact_phone1;"; } else { if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by customer_contact_phone1;"; } } elseif(isset($_POST['cmpSearchDID'])){ $search1=$_POST['DID1']; if (!$_POST['DID1']) { $search1=$_GET['DID1']; } //echo "DID1: ".$search1."
"; $search2=$_POST['DID2']; if (!$_POST['DID2']) { $search2=$_GET['DID2']; } //echo "DID2: ".$search2."
"; $search3=$_POST['DID3']; if (!$_POST['DID3']) { $search3=$_GET['DID3']; } //echo "DID3: ".$search3."
"; $DIDsearch = "FALSE"; if(isset($search1)){ $search=$search1; $DIDsearch = "TRUE"; } if(isset($search2)){ $search.=$search2; $DIDsearch = "TRUE"; } if(isset($search3)){ $search.=$search3; $DIDsearch = "TRUE"; } if($DIDsearch == "TRUE"){ //echo "DID: ".$search."
"; $str="select custID from DID where DID like '%$search%' $childIDs;"; $result=$database->query($str); $row = $result[0]; $numRows = count($result); if($numRows > 0){ $custSearch=$row['custID']; if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where custID like '%$custSearch%' $statusdb2 $childIDs;"; } else { if(($childIDs !== "") && ($numIDs >0)) { $childIDs=" and $childIDs"; } $str="select * from customer where customer_cmp_name like \"\" $statusdb2 $childIDs order by customer_cmp_name"; } } else { if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by customer_cmp_name"; } } else { if($statusdb1=="") { $statusdb1="where "; } $str="select * from customer $statusdb1 $childIDs order by customer_cmp_name"; } $result=$database->query($str);
Mike writes:
I expect that there are SQL injection vulnerabilities in here somewhere. I just can't read through this well enough to find them.
I read through it. There are definitely SQL Injection errors. I still couldn't tell you exactly what all this does, but it definitely has SQL injection errors. Also, if you don't POST your query parameters, it'll pull them from the GET, so, y'know, do either. It doesn't matter. Nothing matters.
Mike adds:
I did the only thing I could think of to do with this code - sumbit it to TheDailyWTF.
I hope you also deleted it and removed any trace of it from source control history, but submitting here first was the right call.
|
Метки: CodeSOD |
Error'd: No Matter Where You Go...an Error is There |
Michael P. wrote, "Only two minutes and a couple of blocks from my destination, Waze decided I should take a 2-hour, 80-mile detour."
"Thanks, Fry's, but I don't think I'll be needing a raincheck if you run out of those specials," Todd C. writes.
"I was digging around in the settings for the Outlook Web app and, well, it's good to see that even Microsoft has trouble with dates sometimes," John W. writes.
Dan writes, "In a situation like this, one would hope that a well-stocked convenience store stocks replacement memory."
"While waiting for take off at DCA, I noticed a big screen advertisement for a client MAC address," Ken L. wrote.
Job wrote, "Statements that something will or won't happen in Production the minute you are actually IN Production."
https://thedailywtf.com/articles/no-matter-where-you-go-an-error-is-there
|
Метки: Error'd |
CodeSOD: Offensively Defensive Offense |
Sometimes, the best defense is a good offense. Other times, the best offense is a good defense. And if you’re not sure which is which, you’ll never be a true strategic mastermind.
Tina’s co-worker understands that this is true for defensive programming. Always, always, always catch exceptions. That’s a good defense.
Project getProject() {
Project projectToReturn = null;
try {
projectToReturn = new Project();
} catch (Exception e) {
Logger.log("could not instantiate Project")
}
return projectToReturn;
}
The good offense is not actually doing anything useful with the exception and returning a null. Now the calling code needs to also go on the defense to make sure that they handle that null appropriately.
https://thedailywtf.com/articles/offensively-defensive-offense
|
Метки: CodeSOD |
Sponsor Post: Free TDWTF Mug Day 2019 |
Long time, no mug! It's been an insanely long time since we've held a Free TDWTF Mug Day. So long that I'm sure most of you have forgotten the joy that is free mug day. Here's how it works:
I've been pretty excited about BuildMaster 6.1, in part because it returns the product to my original vision of helping developers focus on writing great software instead of worrying about how to build, test, and deploy it from source code to production. Or, CI/CD as we'd call it today.
I'd love to get your feedback on the release, and perhaps ideas on how I can work to improve the product. If you'd be willing to help me, I'll send you one of these beautiful, oversized TDWTF mugs, as modeled by Jawaad M:

To get one, all you have to do is either download/install BuildMaster or spin up our pre-made virtual machine(AMI) image, then run through this quick configuration and fill out this form with your name, address, etc. It should take all of 15 minutes or so to complete.
Everything's free, and there's no credit card needed, or anything like that. In fact, you can keep using BuildMaster for free if you'd like -- there's no server, application, or even user limit.
This offer expires on March 31, 2019, and supply is limited to 250, so sign up soon! To get started, just follow this link and, in a few weeks time, you'll not only be more knowledgeable about BuildMaster, but you'll be enjoying beverages much more fashionably with these nice, hefty The Daily WTF mugs.
|
Метки: Sponsor Post |
CodeSOD: Switching to Offshore |
A lot of ink has been spilled talking about the perils and pitfalls of offshore development. Businesses love paying the wage arbitrage game, and shipping software development tasks to an outside vendor and paying half the wage they would for a dedicated employee.
Of course, the key difference is the word “dedicated”. You could have the most highly skilled offshore developer imaginable, but at the end of the day: they don’t care. They don’t care about you, or your business. They don’t care if their code is good or easy to maintain. They’re planning to copy-and-paste their way up the ranks of their business organization, and that means they want to get rotated off your contract onto whatever the next “plum” assignment is.
Jules H worked for a company which went so far as to mandate that every project needed to leverage at least one offshore resource. In practice, this meant any project of scale would shuffle through half a dozen offshore developers during its lifetime. The regular employees ended up doing more work on the project than they would have if it had been developed in-house, because each specification had to be written out in utterly exhaustive detail, down to explaining what a checkbox was, because if you simply told them to add “a checkbox”, you’d get a dropdown or a text area instead.
Jules got called in when someone noticed that the “save” functionality on one page had completely broken. Since the last change to that application had been a year ago, the feature hadn’t been working for a year.
The page used a client-side MVC framework that also tied into a .NET MVC view, and due to the way the page had been implemented, all the client widgets treated everything as stringly typed (e.g., "true", not true), but all the server-side code expected JSON data with real types.
The code which handled this conversion was attempting to access a field which didn’t exist, and that was the cause of the broken save functionality. That was easy for Jules to fix, but it also meant he had to take a look at the various techniques they used to handle this stringly-typed conversion.
var parseBoolean = function(string) {
var bool;
bool = (function() {
switch (false) {
case string.toLowerCase() !== 'true':
return true;
case string.toLowerCase() !== 'false':
return false;
}
})();
return bool;
};
When I first glanced at this code, I thought the biggest WTF was the switch. We switch on the value false, which forces all of our cases to do !==, only to return the opposite value.
But that’s when I noticed the return. The entire switch is, for some inexplicable reason, wrapped in an immediately invoked function expression- an anonymous function called instantly.
Jules made the bare minimum number of changes to get the save function working, but showed this to his management. This particular entry was the final straw, and after a protracted fight with upper management, Jules’s team didn’t have to use the offshore developers anymore.
Of course, that didn’t mean the contract was cancelled. Other teams were now expected to fully leverage that excess capacity.
|
Метки: CodeSOD |