What the Fun Holiday Activity: A Visit From "Coding Cats"? |
Thanks again to everyone who submitted a holiday tale for our What the Fun Holiday special. Like all good holiday traditions, our winner indulges in a bit of nostalgia for a Christmas classic, by adapting the classic "A Visit From St. Nicolas", a trick we've done ourselves. But like a good WTF, Lee R also mixes in some frustration and anger, and maybe a few inside jokes that we're all on the outside of. Still, for holiday spirit, this tale can't be beat.
Now, our normal editorial standards avoid profanity, but in the interests of presenting the story as Lee submitted it, we're going to suspend that rule for today. It is, after all, the holidays, and we're all miserable.
In December 2018, our distributed Sports app team at FOX was to the wall. We needed to release a new version with pay-per-view streaming before an immovable sporting event date in Q1. I frequently explained away my bugs and other failures by blaming our cats, since they walk on the keyboard and "write the code."
I decided the team needed to loosen up and (again blaming the "codin' cats") posted the following on our main dev Slack channel right before Christmas break.
Has anyone seen this error? error CS1524: Expected catch - lame attempt at humor not wrapped in a try – nottolaugh blockwith apologies to Clement Clarke Moore
T’was the night before boxing when all throughout FOX
All the VP’s were praying that someone would watch
The backlog was groomed, we had our ducks in a row
And visions of Team Pages meant app usage would grow
Then a bad feeling – oh no, not the plan?!
Zac’s calling from LA: the shit just hit the fan
Forget about Team Pages and all of that rot
The ‘wheels’ just bought boxing! (believe it or not)
I guess that’s ok .. is it really that new?
uh streaming on Delta and you guessed it: pay per view
Streaming without BAM? well now – that’s a great gift!
No, you don’t get it – you have to write it in Swift
But we use Xamarin – our shared code is like glue
We’re sorry, you know these guys don’t have a clue
But don’t worry – we’re talkin’ big bucks – I mean fees
This is straight from the top – the very biggest of cheese
Well the schedule – I heard that it’s end of Q2
We can probably get there with James, Greg and Sue
Guess again amigo: time to size – let’s throw darts
Forget about June – would you believe March?
Get crackin, get codin, so what if it’s ugly
Do it yourself or send it to Willow tree
On Battalions, On Regiments, On Squads (what a scheme)
How’s this for innovation: we just call it a “team”
Doesn’t have to be pretty, watch us rake in the cash
and no unit tests - just keep slingin’ hash
that’s close enough – stop - let’s call it a day
Our asses are covered with Ben and Sam on Q/A
We did it – we pulled together – that’s always the key
Now one last question – can we go to Disney?papa: sorry we didn’t get the video code done. Slick got into the Christmas Catnip again – he didn’t just walk on the keyboard, he was flyin’. this looks like maybe .. Lisp? hopefully you can patch it up and do the PR for us.
-- Smokey, Twoface, Slick, and Benzira (the codin’ cats)
Really the main WTF was just the schedule. This post eventually found its way to management, and I learned that (thankfully) they do have a sense of humor.
"Management had a real sense of humor" is a true Christmas Miracle.
https://thedailywtf.com/articles/what-the-fun-holiday-activity-a-visit-from-coding-cats
Метки: Feature Articles |
What the Fun Holiday Activity: The Gift of the Consultant and The Holiday Push |
As we roll into the last few days before Christmas, it's time to share what our readers sent in for our "What the Fun Holiday" contest. It was a blast going through the submissions to see what our holiday experiences looked like.
Before we dig in to our contest winners, our first honorable mention is to David N, who shared with us "The Worm Before Christmas", a classic from 1988 that was new to us.
Our first runner up story comes from Mike. This is an Easter Tale, complete with a death, a resurrection, and thirty pieces of silver (or whatever amount you give highly paid consultants).
We had a workflow engine that severely needed to be rewritten. We brought in an outside consultant to rewrite it. For months, we heard how crappy the original code was and he didn't understand how it could even work. Every week, we would hear how he shaved large percentages of time off of the engine. He would brag "I cut 127% off of the processing time", then the following week another 42% and so on. All the while, bad-mouthing the current development staff.
We put his code into QA and all seemed to go well. We really thought we had a vastly improved workflow engine. Since there would be little to no volume on Easter weekend, we picked that Friday night to put his code into production.
All went well, until Saturday morning. None of the workflows were running properly and large chunks of logic was missing. The consultant assured us it was minor issue and instead of rolling back, we should leave it there and give him time to fix it. Saturday came and went with half a dozen 'fixes' that didn't fix anything. On Easter Sunday, I got a call that we were rolling everything back.
That was the easy part.
Now we had to identify which workflows were triggered and try to retrigger them. It was a SAAS operation and the DBs were multi-tenant as well. Needless to say, there was no Easter dinner for any of us. We spent Sunday and Monday undoing what he had done.
When we looked into his code, we saw that it code was only processing the workflows. But it didn't do any of the events based on the workflow outcome. So, his huge percentage gains didn't exist. Our huge percentage gain was showing him the door.
At least we didn't see the Easter Bunny, but this Easter Egg was more than enough.
When you're coming up on a big holiday break, there's always a push to get things done in time. No one wants to come back to a gigantic backlog after a holiday. That little push is a gift you give your team- but sometimes that gift isn't appreciated. B shares their story:
It was around the time of our favorite holiday: Christmas.
My employer was having a nice get together with all employees after work. It was scheduled at 5 p.m. If our work was done, we could share some holiday cheer with our peers.
As usual, I started quite early in the morning and worked hard to finish my project to earn my three weeks of holiday. I was nearly finished with my work at around 1 p.m.
Now, my co-worker, "Lou", knew about my long holiday and needed something finished before my leave so that he could continue to work on it. He estimated it should take me half an hour, one hour tops. So I wrapped up my current project in 15 minutes and started with Lou's top priority work. At 5.30 p.m I was not nearly finished an called him (his status in the booking system which tracks the working hours showed him online and working). No response from him. Another co-worker in his room told me he had already gone to the party an hour ago.
Being a professional, I wrote him an email with the current status and the next steps. By the time I got to the party it was 6.15 p.m and dear old Lou had already left. I drank some hot wine, chatted with my fellows and then went home enjoying Christmas with my family.
Now the real WTF was on my return, three weeks later. The first thing Lou told me when I saw him was and asked about the project was: "I did not have time for it.". I was speechless. He had 10 days to work on it, "made" me work longer than needed and all just to leave it to rot.
At least I learned not to believe his priorities. And it was the first drop that caused me to hunt for, and eventually get a better job.
Thanks so much to all our submitters. Mike and B, someone will be reaching out soon to get your prizes out to you, and tomorrow, we'll reveal our grand prize winner, and maybe kick off a new holiday tradition?
Метки: Feature Articles |
CodeSOD: All About the Details |
Dora's AngularJS team (previously) wanted to have their display be "smart" enough to handle whether or not you were editing a list of "Users" or just one "User", so they implemented a function to turn a plural word into a singular word.
Which, of course, we already know the WTF implementation of it: they just chop off the last letter. So "Potatoes" becomes "Potatoe", "Moose" becomes the noise cows make, and I'm just left saying "oh, gees". But they managed to make it worse than that.
The actual display might be used like so:
<label> Edit {{$ctrl.singular}}label>
singular
is a property on the controller for this component. But how does that get populated?
function getSingular(){
if($ctrl.type){
$ctrl.singular = $ctrl.type === 'details' ? $ctrl.type : $ctrl.type.slice(0, -1);
}
}
So, it's important to note, getSingular
isn't a "get" method in the conventional sense. It populates the singular
property, but doesn't return anything. Hopefully this method gets called sometime before we try and display $ctrl.singular
- it won't crash or anything, but it also won't display any useful information.
For extra weirdness, though, it also has a extra gate: "details" is always plural. This means you might use it like so: Edit User {{$ctrl.singular}}
to display "Edit User Details", and it will always be plural.
It's a bad implementation of a bad idea, with a confusing method name, inconvenient calling semantics, and a trap-door that could easily surprise you if you're not details oriented. That's a lot of bad code in a tight space.
Метки: CodeSOD |
Error'd: Something Has Gone Wrong |
"Ooh! That moment when you're listening to music and a you get a vague pop up message that fills you with existential dread," writes Noah B.
"You know, Domino's, you're right...I'm just not feeling null today," writes Mark W.
Greg wrote, "If you're going to output diagnostic data in Production, you might want to pick a section that isn't next to a (dubious?) reassurance about security."
"Well to be fair, it doesn't say to enter 'numbers' or 'letters'," Pascal wrote.
Benjamin writes, "Maybe write a line of code to check if it's already expired before sending it...? Just a thought."
Метки: Error'd |
CodeSOD: All the Angles |
Web frameworks are a double edged sword. They are, as a rule, bloated, complicated, opinionated and powerful. You can get a lot done, so long as you stick to the framework's "happy path", and while you can wander off and probably make it work, there be dragons. You also run into a lot of developers who, instead of learning the underlying principles, just learn the framework. This means they might not understand broader web development, but can do a lot with Angular.
And then you might have developers who don't understand broader web development or the framework they're using.
Dora has someone on their team which meets that criteria.
The first sign there was a problem was this line in a view object:
<a href class="btn" ng-click="doSomething()" ng-disabled="$ctrl.numFormErrors > 0">save forma>
First off, ng-disabled
adds the disabled
attribute to the DOM element, which doesn't do anything to a
nchor tags. The goal here is to disable the "save" button if there are validation errors, and already the goal has been missed. However, that's not the weirdest part of this. Angular provides loads of helper variables and functions, including a form.$invalid
which helpfully tells you if there are validation errors. So where is $ctrl.numFormErrors
coming from?
$scope.$watch("form.$error", function(errors) {
$ctrl.numFormErrors = 0;
$ctrl.fieldsWithErrors = [];
_.forEach(errors, function (errs) {
for (var i = 0; i < errs.length; i++) {
if ($ctrl.fieldsWithErrors.indexOf(errs[i].$name) < 0) {
$ctrl.fieldsWithErrors.push(errs[i].$name);
$ctrl.numFormErrors++;
}
}
});
}, true);
Oh, that's simple enough. So much clearer than using the built in form.$invalid
.
If you're not "up" on Angular, and without diving too deep on the mechanics, $scope.$watch
registers a callback: every time form.$error
changes, we invoke this function. In this callback, we clear out our $ctrl.numFormErrors
and $ctrl.fieldsWithErrors
. The keys of errors
are validation failures, like maxlength
and pattern
, so we use the lodash library to forEach
through each of those keys.
The values are arrays of which fields have the given error, so we for
loop across errs
(I guess we didn't want to use lodash again?). If we haven't already tracked an error for the field with this $name
, we add it to our array and increment the numFormErrors
field.
Now, every time the user edits the form, we'll have a list of exactly which fields are invalid, and an exact count about how many there are. That's not something Angular makes obvious, so we've accomplished something, right?
Well, the only problem is that this code never actually uses $ctrl.fieldsWithErrors
and it only ever checks if $ctrl.numFormErrors > 0
, so no- we didn't need to do any of this.
Dora threw out the $watch
and just replaced the a
nchor with a button
done the "Angular" way:
<button class="btn" ng-click="doSomething()" ng-disabled="form.$invalid">save formbutton>
Метки: CodeSOD |
CodeSOD: Stringing Your Date Along |
One of the best parts of doing software development is that you're always learning something new. Like, for example, I thought I'd seen every iteration on bad date handling code. But today, I learned something new.
Katharine picked up a pile of tickets, all related to errors with date handling. For months, the code had been running just fine, but in November there was an OS upgrade. "Ever since," the users complained, "it's consistently off by a whole month!" This was a bit of a puzzle, as there's nothing in an OS upgrade that should cause date strings to be consistently off by a whole month. Clearly, there must be a bug, but why did it only now start happening? Katharine pulled up the C code, and checked.
static const char *month_name[] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul"
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
It took a few passes before Katharine spotted the problem. Once she did, though, it was perfectly clear that the OS upgrade had nothing to do with it, and the userbase clearly had not been checking the output of the program since July.
If you haven't spotted it yet, note the missing ",
" after "Jul"
. In C, you're allowed to concatenate strings if a line ends with a string and the next line starts with a string, so this calendar goes from "Jun" straight to "JulAug", then the eight month of the year is "Sep".
Even after making the fix, several of the users were happy that "They fixed whatever the OS upgrade broke," but wished "they didn't change things that were working just fine!"
Метки: CodeSOD |
CodeSOD: Drop into the Deep End |
Would you like to guarantee your project ends up on this site? Antoon's employer has a surefire technique. First, hire a freshly graduated architect with no programming experience. Second, chuck them into a project in a programming language they don't know. Third, give them absolutely no supervision and no guidance or support, and watch what happens.
The WTF, of course, is less the code itself, and more the process which lead to it, but there are things in this code which terrify me. I never want to see a DROP TABLE
just sorta chucked into the middle of a SQL injection vulnerability. Having the connection string in a global $link
variable doesn't bother me, but why is $result
an array? What is $copy
? Why might it be "error", and why do we drop the table before checking if that's an error? And why is the variable that can be either "EXIST" or "NOTEXIST" called $size
? Wait, let's go back to $result
, because I just realized that db_query
must be a wrapper and it also uses the $result
global variable to store anything that happens. But also it returns a value if it succeeds or fails and…
Sorry. The more time I spend looking at this code, the worse I feel about the experience. Again, I don't fault the underqualified developer, I fault the organization which put them in a position they were guaranteed to fail. If anything, they should be proud that they failed in such a stomach-churning way. Better that than being boring.
Метки: CodeSOD |
CodeSOD: Count on Me |
There is no task so simple that a developer can't find a harder, more wasteful, and pointless way to do it. For example, let's say you want to know how many rows are in a data grid object?
Hans found this C# snippet in production a few years ago:
int count = 0;
for(int i = 0; i <= dgView.Rows.Count; i++)
{
count++;
}
To find out how many rows are in our dgView
, we start a for loop which counts from 0 to the… Rows.Count
in the view. Worse, this has an off-by-one bug, as they use <=
to compare, ensuring an extra iteration.
The old saying is "work smarter, not harder," but this code doesn't just work harder, it also works dumber.
Метки: CodeSOD |
Error'd: One of These Must Be Correct |
"Walmart seems to think if they throw enough time estimates at the user, one of them is bound to be close...probably," Joe wrote.
"In Youmail's voicemail app, I noticed an odd way of sorting," Andrew writes, "I've heard of rollover minutes - is this what rollover MONTHS look like!?"
Shawn wrote, "Leave it up to Microsoft to find a way to make using a computer slower."
Tim R. writes, "Hmmm, with winter coming and another lockdown on the way in England, I'm going to need some more appropriate footwear, best checkout the typing trainers on eBay!"
Tomek writes, "During these uncertain times, long term planning is essential, even when it comes to food delivery."
https://thedailywtf.com/articles/one-of-these-must-be-correct
Метки: Error'd |
The Secret is… … … … Timing |
Scott was new to the team, and so when a seemingly simple bug came in, he picked up the ticket. It should be an easy win, and help him get more familiar with the code base.
The reported problem was that a user entered the time, and saved it. Several screens later, when that time was redisplayed, it was incorrect, off by some number of hours. Clearly, it was a small timezone issue, which should be easy to fix.
Except that was easier said than done. Scott's company made a hosted service, and quite reasonably, each customer had their own database instance. But they also all had their own branch in source control. That's where things started to get problematic, as there was no coherent naming convention, each customer's version had its own customization and a blend of merges and cherry-picked commits that made it impossible to, at a glance, understand what was or wasn't deployed to any given branch.
Once Scott figured out which branch to use, he stood up a simulation environment and tried to replicate the error. He entered the date. He checked the database- the date was stored correctly. He went to the screen which was incorrect, and saw that it was off- though by a totally different number of hours than the customer reported. On a different screen, however, the date displayed correctly. Digging through the code, the incorrect screen was adjusting the timezone from UTC to the local timezone, while the correct screens weren't doing any timezone adjustment.
Confused, Scott went to his boss, Zofia. "I assume it's doing the adjustment when it shouldn't, but I'm just confused by why it's doing that?"
Zofia sighed. "Because a very long time ago, I let the owner talk me into a very bad idea. We don't store timezone information with the times. Most of the time, we convert them to UTC and store that, and then convert to the local timezone on display. But because we were in a 'move fast and break things' phase, we didn't do that with all of our times. Some we just store in the local timezone- without storing any timezone info."
The result is that a timestamp, stored in the database, might either be in the local timezone of the person who created the record, or might be in UTC, but you couldn't tell which by looking at the database. So you had to check the code which handled the storage and display. That code was in a twisted network of git branches, so for any given customer, the rules might be utterly different, and since commits were sometimes cherry-picked across branches (as a useful feature for one customer becomes useful for another), the rules might suddenly change if you weren't careful.
"Oh… that's… horrifying."
Zofia nodded. "You think that's bad, keep in mind that our reporting engine is a third party product that doesn't use the branching structure we set up in git. One change to dates in the application could mean you spend the next few days tracking down every reference to it and making sure you're only making changes that impact the one customer and nobody else."
"I… maybe I should pass this ticket off to someone else?" Scott was certainly a lot less interested in tackling this "simple" bug.
"Nah," Zofia said. "It'll be a good way to learn the codebase. This is what we do every day."
Метки: Feature Articles |
CodeSOD: Passive Regressions |
Sometimes, our readers send us a story. Sometimes they send us some bad code. Sometimes, they just need a place to vent their frustrations. Paul T had some frustrations. Paul's team is migrating from .Net to .NetCore, and as one might imagine, that creates a lot of build failures.
Their Team City build environment sometimes doesn't give the most helpful messages:
[15:50:31][Step 1/9] publish (3s)
[15:50:31][publish] Starting: "c:\Program Files\dotnet\dotnet.exe" publish AutoDTD\DTD.sln --configuration Release --runtime win10-x86 --self-contained false @C:\TeamCity\buildAgent\temp\agentTmp\1.rsp
[15:50:31][publish] in directory: C:\TeamCity\buildAgent\work\39a981e90a22aaab
[15:50:31][publish] Microsoft (R) Build Engine version 16.0.462+g62fb89029d for .NET Core
[15:50:31][publish] Copyright (C) Microsoft Corporation. All rights reserved.
[15:50:31][publish]
[15:50:34][publish]
[15:50:34][publish] Build FAILED.
[15:50:34][publish] 0 Warning(s)
[15:50:34][publish] 0 Error(s)
[15:50:34][publish]
[15:50:34][publish] Time Elapsed 00:00:01.65
[15:50:34][publish]
[15:50:34][publish] Process exited with code 1
[15:50:34][Step 1/9] Process exited with code 1
Failed with no warnings and no errors. This is the build service equivalent of "If you don't know what's wrong, I'm certainly not going to tell you."
Метки: CodeSOD |
CodeSOD: Going with the Flow |
The power of a good devops team is that you can get fast, reliable, and repeatable deployments. It lays out a foundation for rapid iteration and quick releases. With that in mind, Lakeisha was pretty excited to start a new job working in devops.
She walked in the door, with visions of well architected, scripted flows, robust automated testing, and strict architectural guidelines. About fifteen minutes in, she learn that FISI guided their entire devops workflow. If it worked, it worked.
A few years back, someone had discovered MuleSoft's devops solutions and their "enterprise service bus". The ESB lets developers define "flows", which describe how messages pass between components in a large-scale, complex application.
That much was fine, but no one on the team decided to learn how to do things in Mule's tools. Any time there was a new requirement, someone would say, "We can build a Mule flow for that!" They'd then dig through the existing flows for any similar components, copy-and-paste them in a new order to create their new flow, and once it looked like it mostly worked, deploy it and move on.
Flows are designed in a graphical editor, but persisted as XML. Lakeisha sent us a block of common exception logging code, copy-and-pasted into every single flow.
<catch-exception-strategy doc:name="Catch Exception Strategy">
<logger message="#['#DEBUG. ERROR Payload: '+message.payloadAs(java.lang.String]" level="INFO" doc:name="Logger payload ERROR"/>
<logger message="#['#DEBUG. Failed '+exception.getVerboseMessage()]" level="INFO" doc:name="Logger verbose"/>
<logger message="#['#DEBUG. flowVars: '+flowVars]" level="INFO" doc:name="Logger flowVars"/>
<flow-ref name="jms_error_subFlow" doc:name="jms_error_subFlow"/>
catch-exception-strategy>
This uses Log4j to actually handle the logging, which I bring up because Log4j is extremely flexible about how you can use logging levels. There's no reason to log everything to "INFO". To the contrary, that alone is enough to basically ensure the logs are full of useless spam. Log4j also allows categories, which would be really helpful for understanding any problems in your enterprise application, especially one that's wired together by a gigantic service bus. But we don't use that here.
And finally, every single log message starts with #DEBUG
. Lakeisha adds: "This prefix is ALL over the place in every logging statement of literally every flow in the company; some developers even managed to define this prefix as a variable in a Java properties file."
Why? Because back before they officially adopted the tool as their standard, one of the developers experimented with it to see if it was fit-for-purpose. While experimenting, they slapped this logging block together, just to prove that it worked. When the company started building real flows using the tool, someone saw a logging block, copy/pasted it into their flow, and the rest is history.
Метки: CodeSOD |
Mandatory Confusion |
CorpCo was a small company; it consisted of Tymeria, the president, and two programmers, Kylie and Ronnie. Kylie had seniority, having been working there for 6 or 7 years, but Ronnie, our submitter, had been working there a hefty 4 years herself. The company purchased a legacy VB web app from a client company, AClientCompany; AClientCompany had been working on the app for 15 years, with their lead programmer Michelle as the sole programmer. The app had been written in classic ASP using VBScript, though at some point Michelle had begun converting the project to ASP.NET with VB.NET. (Did you know you can mix and match classic ASP with ASP.NET and classic VBScript with VB.NET in the same solution? I didn't!). Of course, the app was riddled with security vulnerabilities, copypasta, and spaghetti code. One main class, called DBFunctions.vb, was over 30,000 lines!
Fast forward to September 2019, shortly after CorpCo took over. The application involved multiple forms that customers had to fill out, some of which had customizable fields that the users could adjust on the fly. All of this was stored in a SQL Server database, hence DBFunctions.vb, the functions needed to pull data out of the database and render the forms for customers to enter and/or edit the forms by the internal users. A feature request came in to be able to mark some of the additional fields mandatory for completing the form: pretty basic stuff for a forms editor at heart. Ronnie wasn't familiar with the code, so she budgeted 13 story points, or just under a week.
Before any of us dig into the code behind a project, we have certain basic assumptions about what we're going to find. Probably, form fields are stored in the database. Probably, there's a table for additional fields to which Ronnie could add a column in order to mark the field mandatory or optional. Probably, there's some identifier that marks the field as belonging to a specific form, and maybe even one for the order in which the questions go on the form so that order can be preserved. You know. Normal stuff.
If those assumptions held up, we wouldn't be running this story, would we? As it turned out, instead of one AdditionalField table in the database, there were about 20 of them, one for each question type in the system. Why had Michelle done it this way? When Ronnie shot her an email, she replied sheepishly that she was concerned about the table getting to be "too big." "We don't want to get into big data territory," she said, proving that she knew nothing about big data and very little about databases.
But whatever. Ronnie added a Mandatory column to each of the tables, updating all the SQL queries that were hardcoded inline without parameterization in the DBFunctions.vb file. Then she added a checkbox on each of the additional field edit screens, one for each type of question. She added JavaScript validation to the fields, though some of them (the checkbox lists in particular) didn't play nice with jQuery's validator, so she had to add custom code for those boxes. So finally, she finished, sat back, and ran the program to do her basic developer sanity checks.
And ... it didn't work. The checkbox showed up, but checking it seemed to do nothing.
Her code was still all in a branch at this point, so she wasn't holding anything up by fiddling with it. So she spent the rest of the week fiddling, trying to figure out what she missed. But it still wasn't quite working. She asked Kylie to look at the code, but Kylie had literally never used VB before, having been primarily a C#.NET developer. She asked Tymeria, but she was too busy with other projects to spend much time looking at it. So there wasn't much help to be had.
And so the branch just ... rotted.
It's now the end of 2020, over a year since the feature was requested. The branch is still languishing, rotting away. There's only a few bugs at this point, but nobody's able to quite figure them out and there's no more help to be had. There's been numerous other bug fixes and feature requests, and the code's been cleaned up a bit and a refactor is in the works. There's even an experimental branch where they imported a bunch of C# code to replace some of the worst bits of VBScript. But this branch is still sitting out there, languishing. Will CorpCo ever finish the feature request? Maybe.
Probably not. But maybe.
Only 3 days left before the auto-closer closes the ticket as "stale" ...
Метки: Feature Articles |
Announcements: What the Fun Holiday Activity? |
You've got one week left to teach us the real meaning of WTFmas. What's your holiday tale that we'll revisit winter after winter?
Can you teach us the true meaning of WTFMas?
We want your best holiday story. Any holiday is valid, though given the time of year, we're expecting one of the many solstice-adjacent holidays. This story can be based on real experiences, or it can be entirely fictional, because what we really want is a new holiday tradition.
The best submissions will:
Are you going to write a traditional story? Or maybe a Dr. Seussian rhyme? A long letter to Santa? That's up to you.
Submissions are open from now until December 11th. Use our submission form. Check the "Story" box, and set the subject to WTF Holiday Special
. Make sure to fill out the email address field, so we can contact you if you win!
The best story will be a feature on our site, and also receive some of our new swag: a brand new TDWTF hoodie, a TDWTF mug, and a variety of stickers and other small swag.
The 2 runners up will also get a mug, stickers and other small swag.
Get writing, and let's create a new holiday tradition where opening the present may create more questions than it answers.
https://thedailywtf.com/articles/what-the-fun-holiday-activity-2
Метки: Announcements |
Error'd: Mandatory Pants Day |
"I wonder if the people behind the ad campaign ever said they could 'sell ice to a snowman'," Loren writes.
"Umm...Thanks for the suggestion?" John wrote.
Kolja writes, "So, wait, do I type 'ENTER' or just press the 'ENTER' key? And what if I hae a 'Return' key instead?"
"Given how annoying and defective software licensing usually is, an operation completing successfully should indeed be considered as an error," wrote Carl C.
Tim P. writes, "If you're thinking about buying your candy by the kilo, beware of that sugar tax!"
Метки: Error'd |
CodeSOD: WWJSD? |
A few months ago, Lee was reviewing a pull request from Eddie. Eddie was a self-appointed "rockstar" developer. Eddie might be a "junior" developer in job title, but they're up on the latest, greatest, bestest practices, and if everyone would just get out of Eddie's way, the software would get so much better.
Which is why the pull request Lee was looking at touched nearly every file. Every change was some variation of this:
- if (obj == null)
+ if (obj is null)
"Did… did you do a 'Replace in Files' on the entire solution?" Lee asked.
"Well, I had to use the regex find-and-replace," Eddie said, "have you ever used that? It's a great refactoring tool, you can get it with control-H, and then you have to check a box-"
"I know how it works," Lee said. "Why?"
"Well, a regex, or a regular expression is a-"
"No, why did you make the change?"
"Oh! Well, that's the correct way to check for nulls. The equality operator could be overloaded, and that overload might have bugs."
"I mean, it's a correct way," Lee said, "but the equality operator is also fine. Any bugs in our overloads are likely already caught. There's no reason to make a change like this through the whole codebase."
The debate went on for longer than it should have. Eventually, Eddie defended his choice by saying, "Well, it's the way Jon Skeet does it," at which point Lee threw up his hands.
"Fine." Lee approved the request. After all, what was the worst that could happen?
What happened was that a month or two later, the application started throwing NullReferenceException
s in production. The change which caused the behavior was on line:
Coordinate p = maybeGetCoordinate();
if (p != null)
{
p.Project(alternateCoordinateSystem); // NullReferenceException
}
Look at that, someone using the more traditional equality operator! Maybe Eddie had a point, maybe an overloaded equality operator was the problem. Certainly, the bug went away if Lee used !(p is null)
(or, in C# 9.0, p is not null
).
Still, it would be good to fix the bug. Lee took a peek at the overloaded operators:
public static bool operator == (Coordinate pt1, Coordinate pt2) =>
!(pt1 is null) && !(pt2 is null)
&& pt1.Longitude == pt2.Longitude
&& pt1.Latitude == pt2.Latitude;
public static bool operator !=(Coordinate pt1, Coordinate pt2) => !(pt1 == pt2);
The key bug is !(pt1 is null) && !(pt2 is null)
- if one or more of the operands is null, then return false. This means null == null
is always false, which means the p != null
check returns false if p
is null.
As it turned out, Eddie did have a point about favoring is null
checks. Someone might add a buggy operator overload which could cause unexpected behavior. Who did add that particular buggy overload? Why, Eddie, of course.
Lee submitted a patch, and gently suggested Eddie might need to spend a little more time in the woodshed before declaring themselves a rockstar.
Метки: CodeSOD |
A New Bean |
It was Paramdeep's first corporate IT job. He was assigned a mentor, Rajiv, who would train him up on the application he would be supporting: a WebSphere-based Java application full of Enterprise Java Beans.
Paramdeep reserved time with Rajiv in Outlook, arranging to meet at Rajiv's cubicle for half an hour. Rajiv accepted. At the agreed-upon time, Paramdeep walked over with a notebook and pencil in hand, intent on copying down all the pertinent information he would hear. When he reached Rajiv's desk, however, the elder developer waved Paramdeep away from his spare chair before he had a chance to sit down.
"Sorry, more urgent stuff came up," Rajiv said, turning back to his monitor. "The best way to learn about the application is to dive right in. I'll give you one of the simpler tasks. All you need to do is write a bean that'll call a Sybase stored procedure to get the next sequence ID of a domain table, then create the next object based on that ID."
Paramdeep stood there wide-eyed and frozen, pencil hovering uselessly over paper. "What?"
"I've already built an EJB for the database connection layer and all the handling code," Rajiv continued, still intent on his screen. "There's also a stored procedure in the common schema for getting the ID. You just have to put them all together."
"Uh—?"
"I'll send an email with the relevant class names," Rajiv cut him off.
"OK. I'll, uh, let you know if I have any trouble?"
Rajiv was too caught up in his urgent business to respond.
Paramdeep staggered back to his desk: scared, confused, and a bit dejected. But hey, maybe Rajiv was right. Once he dove into the code, he'd have a better understanding of what to do. He'd done JDBC before. It wasn't hard.
Unfortunately, there was a stark difference between a computer science student's homework assignments and the inner workings of a creaky, enterprisey corporate behemoth. Try as he might, Paramdeep simply couldn't connect to the database schema Rajiv had told him about. He was connecting to a default schema instead, which lacked the stored procedure he needed. All of Paramdeep's attempts to solicit help from Rajiv—whether emailed, messaged, or sought in person—met with failure. His boss promised to get Rajiv talking, but that promise never materialized into Paramdeep and Rajiv spending quality time together.
A strict deadline was looming. Desperate times called for desperate measures. Paramdeep emailed the database developer, Amita, suggesting his solution: creating a new stored procedure to call and return the value from the actual stored procedure, which would then return the value to his new bean.
Minutes later, his phone rang. Caller ID showed it was Amita.
"You can't possibly want this!" she declared without preamble. "Just use the stored procedure in the schema."
"I can't connect to it," Paramdeep explained.
"What do you mean, you can't?"
"I just can't!"
"Who's the tech lead on this project?"
"Rajiv."
"Ohhh." A weary understanding permeated her tone, taking all the fight out of it. "OK, I get it. Fine, I'll make a synonym in the default schema."
And that was why the ID generating procedure also existed in the default schema. Paramdeep couldn't help but wonder how many of the procedures in the default schema had gotten there this way.
Метки: Feature Articles |
CodeSOD: A Tight Fitter |
Part of the pitch of Python is that it's a language which values simplicity and ease of use. Whether it lives up to that pitch is its own discussion, but the good news is that if you really want to create really complex code with loads of inheritance and nasty interrelationships, you can.
Today's anonymous submitter started out by verifying some of the math in some extremely math-y analytical code. Previously, they'd had problems where the documentation and the code were subtly different, so it was important that they understood exactly what the code was doing.
In one file, they found the core class they cared about:
class WeibullFitter(KnownModelParametricUnivariateFitter):
# snip: some math
Now, the math looked more or less right. But there was just one problem: who knows how the superclass interacts with that? Subtle changes in behavior could appear there.
class KnownModelParametricUnivariateFitter(ParametricUnivariateFitter):
_KNOWN_MODEL = True
The "base" class isn't terribly "base" at all, as you can see: all it does is set a property to True
. So once again, up the inheritance tree we go, to see the base class:
class ParametricUnivariateFitter(UnivariateFitter):
# ...snip...
We're still not at the actual base class, though this one has some implementation, at least? Is that a good thing? We haven't hit TRWTF just yet, but this strikes me as a good chance to talk about why "inheritance is considered harmful": inheritance is an automatic dependency. To understand the behavior of a child class, you have to also understand the behavior of its ancestor classes. Certainly, well implemented inheritance should keep those boundaries neat, but as we can see, this example isn't "well implemented".
More than that, Python is purposefully loosely typed, so one of the key benefits of inheritance, polymorphism isn't even a benefit here. And yes, one could use Python's type annotations to get some lint-time type checking, which would sort-of bring back polymorphism, it still doesn't justify this whole inheritance tree.
That all aside, one of the main reasons we use inheritance is so that we can cut out common conditional logic and let the type system worry about it. I call concreteInstance.someMethod()
and the right thing happens, even if I have a dozen possible types, each with some differing behavior. I bring this up, because in the ParametricUnivariateFitter
class, we have this:
def _fit_model(self, Ts, E, entry, weights, show_progress=True):
if utils.CensoringType.is_left_censoring(self): # Oh no.
negative_log_likelihood = self._negative_log_likelihood_left_censoring
elif utils.CensoringType.is_interval_censoring(self): # Oh no no no.
negative_log_likelihood = self._negative_log_likelihood_interval_censoring
elif utils.CensoringType.is_right_censoring(self): # This is exactly what I think it is isn't it.
negative_log_likelihood = self._negative_log_likelihood_right_censoring
# ...snip...
Comments provided by the submitter. In addition to having a whole tree of child classes, each of these child classes may have a censoring type applied, and our behavior is different based on the censoring type. This is 100% a code smell, and it becomes more clear when we take a look at CensoringType
.
class CensoringType(Enum): # enum.Enum from the standard library
LEFT = "left"
INTERVAL = "interval"
RIGHT = "right"
@classmethod
def right_censoring(cls, function: Callable) -> Callable:
@wraps(function) # functools.wraps from the standard library
def f(model, *args, **kwargs):
cls.set_censoring_type(model, cls.RIGHT)
return function(model, *args, **kwargs)
return f
@classmethod
def left_censoring(cls, function: Callable) -> Callable:
@wraps(function)
def f(model, *args, **kwargs):
cls.set_censoring_type(model, cls.LEFT)
return function(model, *args, **kwargs)
return f
@classmethod
def interval_censoring(cls, function: Callable) -> Callable:
@wraps(function)
def f(model, *args, **kwargs):
cls.set_censoring_type(model, cls.INTERVAL)
return function(model, *args, **kwargs)
return f
@classmethod
def is_right_censoring(cls, model) -> bool:
return cls.get_censoring_type(model) == cls.RIGHT
@classmethod
def is_left_censoring(cls, model) -> bool:
return cls.get_censoring_type(model) == cls.LEFT
@classmethod
def is_interval_censoring(cls, model) -> bool:
return cls.get_censoring_type(model) == cls.INTERVAL
@classmethod
def get_censoring_type(cls, model) -> str:
return model._censoring_type
@classmethod
def str_censoring_type(cls, model) -> str:
return model._censoring_type.value
@classmethod
def set_censoring_type(cls, model, censoring_type) -> None:
model._censoring_type = censoring_type
For those not up on Python, Enum
is exactly what you think it is, and the @classmethod
decorator is Python's way of making static methods. In the same way instance methods take self
as their first parameter (the Python-ic "this"), static methods take cls
as their first parameter- a reference to the class itself.
It's also important to note the methods like right_censoring
, because those themselves are "decorator" definitions. See how they def
a local function, itself decorated with @wraps
? The right_censoring(cls, function)
signature expects a callable (probably a constructor method), and replaced its implementation with the implementation of f
- the inner function. Here, it tampers with the input parameters to the constructor before calling the constructor itself.
If you aren't doing a lot of Python, you might be completely confused at this point, so let me just show you how this gets used:
@CensoringType.right_censoring
class SomeCurveFitterType(SomeHorribleTreeOfBaseClasses):
def __init__(self, model, *args, **kwargs):
# snip
instance = SomeCurveFitterType(model, *args, **kwargs)
On that last line, it doesn't directly call the __init__
constructor, it first passes through that inner f
function, which , most notably, does this: cls.set_censoring_type(model, cls.RIGHT)
before invoking the constructor.
If you're confused by all of this, don't feel bad. Decorators are a Pythonic way to tamper with the implementation of classes and functions, allowing you to mix declarative programming with more traditional techniques. In the end, to understand what the WeibullFitter
class does, you have to walk up a half dozen ancestor classes to reach the BaseFitter
type, and you have to note what decorators are applied to it, and any of its ancestor classes, and know what their implementation is.
If you're the person who wrote this code, this mix of decorators and inheritance probably feels wonderfully extensible. It's probably quick and easy to slap a new curve fitting function into the framework, at least if you're the galaxy-brained individual who dreamed up this over-engineered framework. The rest of us just have to poke at it with sticks until the behavior we want falls out.
Our anonymous submitter adds:
I almost feel bad about this. The library is generally good, the math itself is good, this is a very useful library that has made my job a lot easier…
This sort of behavior actually has real performance implications. I had to re-implement a different piece because the sheer number of nested function calls (some of which were actually recursive, in a chain that spanned three different classes) completely destroyed the performance of a conceptually simple process, such that it could take upwards of ten minutes.
The rewritten function runs near-instantly and fits in 20 lines with comments.
Метки: CodeSOD |
CodeSOD: To Coalesce a Null |
As we all know, managing null values is its own challenge, especially when you're working in a functional style. So as languages like .NET add functional approaches like LINQ extension methods, they also add null coalescing operators and nullable types, making it easy to pass values around without getting surprised by an unexpected null.
Unless you're whoever wrote the code that Abbie found, because they've managed to keep some surprises.
List someObjects;
using (var session = dataStore.OpenSession())
{
someObjects = session.Query().OrderBy(s => s.PrimaryKey).ThenBy(s => s.EventDateLocal).ThenBy(s => s.SystemTransactionDateTimeUtc).ToList();
}
return someOtherCollectionOfObjects
.GroupJoin(someObjects, o => o.EventDates.PrimaryKey, s => s.PrimaryKey, (o, s) =>
{
var sList = s as IList ?? s.ToList();
return new
{
//omitted
};
})
The submitter anonymized the class names and variable names a bit, so it's hard to see exactly what the intent is, but we can still spot the "odd" choices. The goal is to take our someOtherCollectionOfObjects
as the "outer" side of a join, and someObjects
is the "inner". Each "outer" element will get matched with all of the "inner" elements based on primary keys.
So the first "odd" choice is that when we fetch our inner objects, we sort them. someObjects
is sorted first by primary key, then by event date, then by the transaction datetime. That first sort absolutely doesn't matter: since we're joining by primary key, the GroupJoin
function preserves the order of the outer elements- the someOtherCollectionOfObjects
.
But the line that shows a real fundamental misunderstanding of what's going on is this one:
var sList = s as IList ?? s.ToList();
s
here, is our "inner" side of the join- the someObjects
. They are passed to the lambda as an IEnumerable
, which for some reason we choose to cast as an IList
… but note the ??
operator. If s
is null, we will call s.ToList()
. In no case could s
ever be null, but if it were, calling a ToList
function on it wouldn't work.
Not only is this line unnecessary, but if it were, it wouldn't work anyway.
Метки: CodeSOD |
Error'd: You Can't Argue with the Polish Government |
"In Poland, if you test positive for COVID-19, or come in contact with someone who has, you must stay home for a mandatory 10-day quarantine. During that time, you must use the government's mobile app named 'Home Quarantine' which tracks your location and requires you to send a selfie every couple of hours," wrote Jan K., "The app also reports if you are using a GPS spoofing app. For example, in this screenshot, it has detected a location spoofing app by the name of ...'Calendar'. Naturally, there are stiff penalties for violating rules of your quarantine like this. Also, as expected, there is no appealing the 'findings' of a buggy app like this."
Harry writes, "Of course I can trust this update! My Manufacturer wrote it after all!"
"Testing in Production is only good if you tell everybody that you're testing in Production," Nate L. wrote.
Bob T. writes, "This is why you add '+1 Day' and not '+24 Hours' when you attempt date math."
"Newegg-speak: War is peace, freedom is slavery, ignorance is strength and Out-of-Stock is In-Stock," wrote Nicolas L.
https://thedailywtf.com/articles/you-can-t-argue-with-the-polish-government
Метки: Error'd |