Yes == No |
For decades, I worked in an industry where you were never allowed to say no to a user, no matter how ridiculous the request. You had to suck it up and figure out a way to deliver on insane requests, regardless of the technical debt they inflicted.
Users are a funny breed. They say things like I don't care if the input dialog you have works; the last place I worked had a different dialog to do the same thing, and I want that dialog here! With only one user saying stuff like that, it's semi-tolerable. When you have 700+ users and each of them wants a different dialog to do the same thing, and nobody in management will say no, you need to start creating table-driven dialogs (x-y coordinates, width, height, label phrasing, field layout within the dialog, different input formats, fonts, colors and so forth). Multiply that by the number of dialogs in your application and it becomes needlessly pointlessly impossibly difficult.
But it never stops there. Often, one user will request that you move a field from another dialog onto their dialog - just for them. This creates all sorts of havoc with validation logic. Multiply it by hundreds of users and you're basically creating a different application for each of them - each with its own validation logic, all in the same application.
After just a single handful of users demanding changes like this, it can quickly become a nightmare. Worse, once it starts, the next user to whom you say no tells you that you did it for the other guy and so you have to do it for them too! After all, each user is the most important user, right?
It doesn't matter that saying no is the right thing to do. It doesn't matter that it will put a zero-value load on development and debugging time. It doesn't matter that sucking up development time to do it means there are less development hours for bug fixes or actual features.
When management refuses to say no, it can turn your code into a Pandora's-Box-o-WTF™
However, there is hope. There is a way to tell the users no without actually saying no. It's by getting them to say it for you and then withdrawing their urgent, can't-live-without-it, must-have-or-the-world-will-end request.
You may ask how?
The trick is to make them see the actual cost of implementing their teeny tiny little feature.
Yes, we can add that new button to provide all the functionality of Excel in an in-app calculator, but it will take x months (years) to do it, AND it will push back all of the other features in the queue. Shall I delay the next release and the other feature requests so we can build this for you, or would you like to schedule it for a future release?
Naturally you'll have to answer questions like "But it's just a button; why would it take that much effort?"
This is a good thing because it forces them down the rabbit hole into your world where you are the expert. Now you get to explain to them the realities of software development, and the full cost of their little request.
Once they realize the true cost that they'd have to pay, the urgency of the request almost always subsides to nice to have and gets pushed forward so as to not delay the scheduled release.
And because you got them to say it for you, you didn't have to utter the word no.
|
Метки: Feature Articles |
CodeSOD: CHashMap |
There’s a phenomenon I think of as the “evolution of objects” and it impacts novice programmers. They start by having piles of variables named things like userName0, userName1, accountNum0, accountNum1, etc. This is awkward and cumbersome, and then they discover arrays. string* userNames, int[] accountNums. This is also awkward and cumbersome, and then they discover hash maps, and can do something like Map* users. Most programmers go on to discover “wait, objects do that!”
Not so Brian’s co-worker, Dagny. Dagny wanted to write some C++, but didn’t want to learn that pesky STL or have to master templates. Dagny also considered themselves a “performance junkie”, so they didn’t want to bloat their codebase with peer-reviewed and optimized code, and instead decided to invent that wheel themselves.
Thus was born CHashMap. Now, Brian didn’t do us the favor of including any of the implementation of CHashMap, claiming he doesn’t want to “subject the readers to the nightmares that would inevitably arise from viewing this horror directly”. Important note for submitters: we want those nightmares.
Instead, Brian shares with us how the CHashMap is used, and from that we can infer a great deal about how it was implemented. First, let’s simply look at some declarations:
CHashMap bills;
CHashMap billcols;
CHashMap summary;
CHashMap billinfo;
Note that CHashMap does not take any type parameters. This is because it’s “type ignorant”, which is like being type agnostic, but with more char*. For example, if you want to get, say, the “amount_due” field, you might write code like this:
double amount = 0;
amount = Atof(bills.Item("amount_due"));
Yes, everything, keys and values, is simply a char*. And, as a bonus, in the interests of code clarity, we can see that Dagny didn’t do anything dangerous, like overload the [] operator. It would certainly be confusing to be able to index the hash map like it were any other collection type.
Now, since everything is stored as a char*, it’s onto you to convert it back to the right type, but since chars are just bytes if you don’t look too closely… you can store anything at that pointer. So, for example, if you wanted to get all of a user’s billing history, you might do something like this…
CHashMap bills;
CHashMap billcols;
CHashMap summary;
CHashMap billinfo;
int nbills = dbQuery (query, bills, billcols);
if (nbills > 0) {
// scan the bills for amounts and/or issues
double amount;
double amountDue = 0;
int unresolved = 0;
for (int r=0; r= 0) {
amountDue += amount;
if (Strcmp(bills.Item("status",r),BILL_STATUS_WAITING) == 0) {
unresolved += 1;
billinfo.AddItem ("duedate", FormatTime("YYYY-MM-DD hh:mm:ss",cvtUTC(bills.Item("due_date",r))));
billinfo.AddItem ("biller", bills.Item("account_display_name",r));
billinfo.AddItem ("account", bills.Item("account_number",r));
billinfo.AddItem ("amount", amount);
}
}
else {
amountDue += 0;
unresolved += 1;
billinfo.AddItem ("duedate", FormatTime("YYYY-MM-DD hh:mm:ss",cvtUTC(bills.Item("due_date",r))));
billinfo.AddItem ("biller", bills.Item("account_display_name",r));
billinfo.AddItem ("account", bills.Item("account_number",r));
billinfo.AddItem ("amount", "???");
}
summary.AddItem ("", &billinfo);
}
}
}
Look at that summary.AddItem ("", &billinfo) line. Yes, that is an empty key. Yes, they’re pointing it at a reference to the billinfo (which also gets Clear()ed a few lines earlier, so I have no idea what’s happening there). And yes, they’re doing this assignment in a loop, but don’t worry! CHashMap allows multiple values per key! That "" key will hold everything.
So, you have multi-value keys which can themselves point to nested CHashMaps, which means you don’t need any complicated JSON or XML classes, you can just use CHashMap as your hammer/foot-gun.
//code which fetches account details from JSON
CHashMap accounts;
CHashMap details;
CHashMap keys;
rc = getAccounts (userToken, accounts);
if (rc == 0) {
for (int a=1; a<=accounts.Count(); a++) {
cvt.jsonToKeys (accounts.Item(a), keys);
rc = getAccountDetails (userToken, keys.Item("accountId"), details);
}
}
// Details of getAccounts
int getAccounts (const char * user, CHashMap& rows) {
//
AccountClass account;
for (int a=1; a<=count; a++) {
// Populate the account class
//
rows.AddItem ("", account.jsonify(t));
}
With this kind of versatility, is it any surprise that pretty much every function in the application depends on a CHashMap somehow? If that doesn’t prove its utility, I don’t know what will. How could you do anything better? Use classes? Don’t make me laugh!
As a bonus, remember this line above? billinfo.AddItem ("duedate", FormatTime("YYYY-MM-DD hh:mm:ss",cvtUTC(bills.Item("due_date",r))))? Well, Brian has this to add:
it’s worth mentioning that our DB stores dates in the typical format: “YYYY-MM-DD hh:mm:ss”. cvtUTC is a function that converts a date-time string to a time_t value, and FormatTime converts a time_t to a date-time string.
|
Метки: CodeSOD |
Error'd: Version-itis |
"No thanks, I'm holding out for version greater than or equal to 3.6 before upgrading," writes Geoff G.
"Looks like Twilio sent me John Doe's receipt by mistake," wrote Charles L.
"Little do they know that I went back in time and submitted my resume via punch card!" Jim M. writes.
Richard S. wrote, "I went to request a password reset from an old site that is sending me birthday emails, but it looks like the reCAPTCHA is no longer available and the site maintainers have yet to notice."
"It's nice to see that this new Ultra Speed Plus™ CD burner lives up to its name, but honestly, I'm a bit scared to try some of these," April K. writes.
"Sometimes, like Samsung's website, you have to accept that it's just ok to fail sometimes," writes Alessandro L.
|
Метки: Error'd |
CodeSOD: The Same Date |
Oh, dates. Oh, dates in Java. They’re a bit of a dangerous mess, at least prior to Java 8. That’s why Java 8 created its own date-time libraries, and why JodaTime was the gold standard in Java date handling for many years.
But it doesn’t really matter what date handling you do if you’re TRWTF. An Anonymous submitter passed along this method, which is meant to set the start and end date of a search range, based on a number of days:
private void setRange(int days){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd")
Date d = new Date();
Calendar c = Calendar.getInstance()
c.setTime(d);
Date start = c.getTime();
if(days==-1){
c.add(Calendar.DAY_OF_MONTH, -1);
assertThat(c.getTime()).isNotEqualTo(start)
}
else if(days==-7){
c.add(Calendar.DAY_OF_MONTH, -7)
assertThat(c.getTime()).isNotEqualTo(start)
}
else if (days==-30){
c.add(Calendar.DAY_OF_MONTH, -30)
assertThat(c.getTime()).isNotEqualTo(start)
}
else if (days==-365){
c.add(Calendar.DAY_OF_MONTH, -365)
assertThat(c.getTime()).isNotEqualTo(start)
}
from = df.format(start).toString()+"T07:00:00.000Z"
to = df.format(d).toString()+"T07:00:00.000Z"
}
Right off the bat, days only has a handful of valid values- a day, a week, a month(ish) or a year(ish). I’m sure passing it as an int would never cause any confusion. The fact that they don’t quite grasp what variables are for is a nice touch. I’m also quite fond of how they declare a date format at the top, but then also want to append a hard-coded timezone to the format, which again, I’m sure will never cause any confusion or issues. The assertThat calls check that the Calendar.add method does what it’s documented to do, making those both pointless and stupid.
But that’s all small stuff. The real magic is that they never actually use the calendar after adding/subtracting dates. They obviously meant to include d = c.getTime() someplace, but forgot. Then, without actually testing the code (they have so many asserts, why would they need to test?) they checked it in. It wasn’t until QA was checking the prerelease build that anyone noticed, “Hey, filtering by dates doesn’t work,” and an investigation revealed that from and to always had the same value.
[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.
|
Метки: CodeSOD |
CodeSOD: A Password Generator |
Every programming language has a *bias* which informs their solutions. Object-oriented languages are biased towards objects, and all the things which follow on. Clojure is all about function application. Haskell is all about type algebra. Ruby is all about monkey-patching existing objects.
In any language, these things can be taken too far. Java's infamous Spring framework leaps to mind. Perl, being biased towards regular expressions, has earned its reputation as being "write only" thanks to regex abuse.
Gert sent us along some Perl code, and I was expecting to see regexes taken too far. To my shock, there weren't any regexes.
Gert's co-worker needed to generate a random 6-digit PIN for a voicemail system. It didn't need to be cryptographically secure, repeats and zeros are allowed (they exist on a keypad, after all!). The Perl-approach for doing this would normally be something like:
sub randomPIN {
return sprintf("%06u",int(rand(1000000)));
}
Gert's co-worker had a different plan in mind, though.
sub randomPIN {
my $password;
my @num = (1..9);
my @char = ('@','#','$','%','^','&','*','(',')');
my @alph = ('a'..'z');
my @alph_up = ('A'..'Z');
my $rand_num1 = $num[int rand @num];
my $rand_num2 = $num[int rand @num];
my $rand_num3 = $num[int rand @num];
my $rand_num4 = $num[int rand @num];
my $rand_num5 = $num[int rand @num];
my $rand_num6 = $num[int rand @num];
$password = "$rand_num1"."$rand_num2"."$rand_num3"."$rand_num4"."$rand_num5"."$rand_num6";
return $password;
}
This code starts by creating a set of arrays, @num, @char, etc. The only one that matters is @num, though, since this generates a PIN to be entered on a phone keypad and touchtone signals are numeric and also there is no "(" key on a telephone keypad. Obviously, the developer copied this code from a random password function somewhere, which is its own special kind of awful.
Now, what's fascinating is that they initialize @num with the numbers 1 through 9, and then use the rand function to generate a random number from 0 through 8, so that they can select an item from the array. So they understood how the rand function worked, but couldn't make the leap to eliminate the array with something like rand(9).
For now, replacing this function is simply on Gert's todo list.
|
Метки: CodeSOD |
An Obvious Requirement |
Requirements. That magical set of instructions that tell you specifically what you need to build and test. Users can't be bothered to write them, and even if they could, they have no idea how to tell you what they want. It doesn't help that many developers are incapable of following instructions since they rarely exist, and when they do, they usually aren't worth the coffee-stained napkin upon which they're scribbled.
That said, we try our best to build what we think our users need. We attempt to make it fairly straightforward to use what we build. The button marked Reports most likely leads to something to do with generating/reading/whatever-ing reports. Of course, sometimes a particular feature is buried several layers deep and requires multiple levels of ribbons, menus, sub-menus, dialogs, sub-dialogs and tabs before you find the checkbox you want. Since us developers as a group are, by nature, somewhat anal retentive, we try to keep related features grouped so that you can generally guess what path to try to find something. And we often supply a Help feature to tell you how to find it when you can't.
Of course, some people simply cannot figure out how to use the software we build, no matter how sensibly it's laid out and organized, or how many hints and help features we provide. And there is nothing in the history of computer science that addresses how to change this. Nothing!
Dimitri C. had a user who wanted a screen that performed several actions. The user provided requirements in the form of a printout of a similar dialog he had used in a previous application, along with a list of changes/colors/etc. They also provided some "helpful" suggestions, along the lines of, "It should be totally different, but exactly the same as the current application." Dimitri took pains to organize the actions and information in appropriate hierarchical groups. He laid out appropriate controls in a sensible way on the screen. He provided a tooltip for each control and a Help button.
Shortly after delivery, a user called to complain that he couldn't find a particular feature. Dimitri asked "Have you tried using the Help button?" The user said that "I can't be bothered to read the instructions in the help tool because accessing this function should be obvious".
Dimitri asked him "Have you looked on the screen for a control with the relevant name?" The user complained that "There are too many controls, and this function should be obvious". Dimitri asked "Did you try to hover your mouse over the controls to read the tooltips?" The user complained that "I don't have the time to do that because it would take too long!" (yet he had the time to complain).
Frustrated, Dimitri replied "To make that more obvious, should I make these things less obvious?". The user complained that "Everything should be obvious". Dimitri asked how that could possibly be done, to which the user replied "I don't know, that's your job".
When he realized that this user had no clue how to ask for what he wanted, he asked how this feature worked in previous programs, to which the user replied "I clicked this, then this, then this, then this, then this, then restarted the program".
Dimitri responded that "That's six steps instead of the two in my program, and that would require you to reenter some of the data".
The user responded "Yes, but it's obvious".
So is the need to introduce that type of user to the business end of a clue-bat.
|
Метки: Feature Articles |
CodeSOD: Philegex |
Last week, I was doing some graphics programming without a graphics card. It was low resolution, so I went ahead and re-implemented a few key methods from the Open GL Shader Language in a fashion which was compatible with NumPy arrays. Lucky for me, I was able to draw off many years of experience, I understood both technologies, and they both have excellent documentation which made it easy. After dozens of lines of code, I was able to whip up some pretty flexible image generator functions. I knew the tools I needed, I understood how they worked, and while I was reinventing a wheel, I had a very specific reason.
Philemon Eichin sends us some code from a point in his career where none of these things were true.
Philemon was building a changelog editor. As such, he wanted an easy, flexible way to identify patterns in the text. Philemon knew that there was something that could do that job, but he didn’t know what it was called or how it was supposed to work. So, like all good programmers, Philemon went ahead and coded up what he needed- he invented his own regular expression language, and built his own parser for it.
Thus was born Philegex. Philemon knew that regexes involved slashes, so in his language you needed to put a slash in front of every character you wanted to match exactly. He knew that it involved question marks, so he used the question mark as a wildcard which could match any character. That left the ’|" character to be optional.
So, for example: /P/H/I/L/E/G/E/X|??? would match “PHILEGEX!!!” or “PHILEGEWTF”. A date could be described as: nnnn/.nn/.nn. (YYYY.MM.DD or YYYY.DD.MM)
Living on his own isolated island without access to the Internet to attempt to google up “How to match patterns in text”, Philemon invented his own language for describing parts of a regular expression. This will be useful to interpret the code below.
| Philegex | Regex |
|---|---|
| Maskable | Matches |
| p1 | Pattern / Regex |
| Block(s) | Token(s) |
| CT | CharType |
| SplitLine | ParseRegex |
| CC | currentChar |
| auf_zu | openParenthesis |
| Chars | CharClassification |
With the preamble out of the way, enjoy Philemon’s approach to regular expressions, implemented elegantly in VB.Net.
Public Class Textmarker
Const Datum As String = "nn/.nn/.nnnn"
Private Structure Blocks
Dim Type As Chars
Dim Multi As Boolean
Dim Mode As Char_Mode
Dim Subblocks() As Blocks
Dim passed As Boolean
Dim _Optional As Boolean
End Structure
Public Shared Function IsMaskable(p1 As String, Content As String) As Boolean
Dim ID As Integer = 0
Dim p2 As Chars
Dim _Blocks() As Blocks = SplitLine(p1)
For i As Integer = 0 To Content.Length - 1
p2 = GetCT(Content(i))
START_CASE:
'#If CONFIG = "Debug" Then
' If ID = 2 Then
' Stop
' End If
'#End If
If ID > _Blocks.Length - 1 Then
Return False
End If
Select Case _Blocks(ID).Mode
Case Char_Mode._Char
If p2.Char_V = _Blocks(ID).Type.Char_V Then
_Blocks(ID).passed = True
If Not _Blocks(ID).Multi = True Then ID += 1
Exit Select
Else
If _Blocks(ID).passed = True And _Blocks(ID).Multi = True Then
ID += 1
GoTo START_CASE
Else
If Not _Blocks(ID)._Optional Then Return False
End If
End If
Case Char_Mode.Type
If _Blocks(ID).Type.Type = Chartypes.any Then
_Blocks(ID).passed = True
If Not _Blocks(ID).Multi = True Then ID += 1
Exit Select
Else
If p2.Type = _Blocks(ID).Type.Type Then
_Blocks(ID).passed = True
If Not _Blocks(ID).Multi = True Then ID += 1
Exit Select
Else
If _Blocks(ID).passed = True And _Blocks(ID).Multi = True Then
ID += 1
GoTo START_CASE
Else
If _Blocks(ID)._Optional Then
ID += 1
_Blocks(ID - 1).passed = True
Else
Return False
End If
End If
End If
End If
End Select
Next
For i = ID To _Blocks.Length - 1
If _Blocks(ID)._Optional = True Then
_Blocks(ID).passed = True
Else
Exit For
End If
Next
If _Blocks(_Blocks.Length - 1).passed Then
Return True
Else
Return False
End If
End Function
Private Shared Function GetCT(Char_ As String) As Chars
If "0123456789".Contains(Char_) Then Return New Chars(Char_, 2)
If "qwertzuiop"uasdfghjkl"o"ayxcvbnmss".Contains((Char.ToLower(Char_))) Then Return New Chars(Char_, 1)
Return New Chars(Char_, 4)
End Function
Private Shared Function SplitLine(ByVal Line As String) As Blocks()
Dim ret(0) As Blocks
Dim retID As Integer = -1
Dim CC As Char
For i = 0 To Line.Length - 1
CC = Line(i)
Select Case CC
Case "("
ReDim Preserve ret(retID + 1)
retID += 1
Dim ii As Integer = i + 1
Dim auf_zu As Integer = 1
Do
Select Case Line(ii)
Case "("
auf_zu += 1
Case ")"
auf_zu -= 1
Case "/"
ii += 1
End Select
ii += 1
Loop Until auf_zu = 0
ret(retID).Subblocks = SplitLine(Line.Substring(i + 1, ii - 1))
ret(retID).Mode = Char_Mode.subitems
ret(retID).passed = False
Case "*"
ret(retID).Multi = True
ret(retID).passed = False
Case "|"
ret(retID)._Optional = True
Case "/"
ReDim Preserve ret(retID + 1)
retID += 1
ret(retID).Mode = Char_Mode._Char
ret(retID).Type = New Chars(Line(i + 1), Chartypes.other)
i += 1
ret(retID).passed = False
Case Else
ReDim Preserve ret(retID + 1)
retID += 1
ret(retID).Mode = Char_Mode.Type
ret(retID).Type = New Chars(Line(i), TocType(CC))
ret(retID).passed = False
End Select
Next
Return ret
End Function
Private Shared Function TocType(p1 As Char) As Chartypes
Select Case p1
Case "c"
Return Chartypes._Char
Case "n"
Return Chartypes.Number
Case "?"
Return Chartypes.any
Case Else
Return Chartypes.other
End Select
End Function
Public Enum Char_Mode As Integer
Type = 1
_Char = 2
subitems = 3
End Enum
Public Enum Chartypes As Integer
_Char = 1
Number = 2
other = 4
any
End Enum
Structure Chars
Dim Char_V As Char
Dim Type As Chartypes
Sub New(Char_ As Char, typ As Chartypes)
Char_V = Char_
Type = typ
End Sub
End Structure
End Class
I’ll say this: building a finite state machine, which is what the core of a regex engine is, is perhaps the only case where using a GoTo could be considered acceptable. So this code has that going for it. Philemon was kind enough to share this code with us, so we knew he knows it’s bad.
|
Метки: CodeSOD |
Error'd: Billboards Show Obvious Disasters |
"Actually, this board is outside a casino in Sheffield which is next to the church, but we won't go there," writes Simon.
Wouter wrote, "The local gas station is now running ads for Windows 10 updates."
"If I were to legally change my name to a GUID, this is exactly what I'd pick," Lincoln K. wrote.
Robert F. writes, "Copy/Paste? Bah! If you really want to know how many log files are being generated per server, every minute, you're going to have to earn it by typing out this 'easy' command."
"I imagine someone pushed the '10 items or fewer' rule on the self-checkout kiosk just a little too far," Michael writes.
Wojciech wrote, "To think - someone actually doodled this JavaScript code!"
[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/billboards-show-obvious-disasters
|
Метки: Error'd |
CodeSOD: If Not Null… |
Robert needed to fetch some details about pump configurations from the backend. The API was poorly documented, but there were other places in the code which did that, so a quick search found this block:
var getConfiguration = function(){
....
var result = null;
result = getPumpConfiguration (areaID,subStationID,mngmtUnitID,lastServiceDate,service,format,result);
result = getPumpConfiguration (areaID,subStationID,null,lastServiceDate,null,format,result);
result = getPumpConfiguration (areaID,subStationID,null,lastServiceDate,service,null,result);
result = getPumpConfiguration (areaID,subStationID,mngmtUnitID,lastServiceDate,null,null,result);
result = getPumpConfiguration (areaID,subStationID,null,lastServiceDate,null,null,result);
return result;
}
This collection of lines lurked at the end of a 100+ line function, which did a dozen other things. At a glance, it’s mildly perplexing. I can see that result gets passed into the function multiple times, so perhaps this is an attempt at a fluent API? So this series of calls awkwardly fetches the data that’s required? The parameters vary a little with every call, so that must be it, right?
Let’s check the implementation of getPumpConfiguration…
function getPumpConfiguration (areaID,subStationID,mngmtUnitID,lastServiceDate,service,format,result) {
if (result==null) {
...
result = queryResult;
...
}
return result;
}
Oh, no. If the result parameter has a value… we just return it. Otherwise, we attempt to fetch data. This isn’t a fluent API which loads multiple pieces of data with separate requests, it’s an attempt at implementing retries. Hopefully one of those calls works.
|
Метки: CodeSOD |
The Search for Truth |
Every time you change existing code, you break some other part of the system. You may not realize it, but you do. It may show up in the form of a broken unit test, but that presumes that a) said unit test exists, and b) it properly tests the aspect of the code you are changing. Sadly, more often than not, there is either no test to cover your change, or any test that does exist doesn't handle the case you are changing.
This is especially true if the thing you are changing is simple. It is even more true when changing something as complex as working with a boolean.
Mr A. was working at a large logistics firm that had an unusual error where a large online retailer was accidentally overcharged by millions of dollars. When large companies send packages to logistics hubs for shipment, they often send hundreds or thousands of them at a time on the same pallet, van or container (think about companies like Amazon). The more packages you send in these batches the less you pay (a single lorry is cheaper than a fleet of vans). These packages are lumped together and billed at a much lower rate than you or I would get.
One day, a particular developer saw something untidy in the code - an uninitialized Boolean variable in one of the APIs. The entire code change was from this:
parcel.consolidated;
to this:
parcel.consolidated = false;
There are some important things to note: the code was written in NodeJS and didn't really have the concept of Boolean, the developers did not believe in Unit Testing, and in a forgotten corner of the codebase was a little routine that examined each parcel to see if the discount applied.
The routine to see if the discount should be applied ran every few minutes. It looked at each package and if it was marked as consolidated or not, then it moved on to the next parcel. If the flag was NULL, then it applied the rules to see if was part of a shipment and set the flag to either True or False.
That variable was not Boolean but rather tri-state (though thankfully didn't involve FILE_NOT_FOUND). By assuming it was Boolean and initializing it, NO packages had a discount applied. Oopsie!
It took more than a month before anyone noticed and complained. And since it was a multi-million dollar mistake, they complained loudly!
Even after this event, Unit Testing was still not accepted as a useful practice. To this day Release Management, Unit Testing, Automated Testing and Source Code Management remain stubbornly absent...
Not long after this, Mr. A. continued his search for truth elsewhere.
|
Метки: Feature Articles |
The Big Balls of… |
The dependency graph of your application can provide a lot of insight into how objects call each other. In a well designed application, this is probably mostly acyclic and no one node on the graph has more than a handful of edges coming off of it. The kinds of applications we talk about here, on the other hand, we have a name for their graphs: the Enterprise Dependency and the Big Ball of Yarn.
Thomas K introduces us to an entirely new iteration: The Big Ball of Mandelbrot

This gives new meaning to points “on a complex plane”.
What you’re seeing here is the relationship between stored procedures and tables. Circa 1995, when this application shambled into something resembling life, the thinking was, “If we put all the business logic in stored procedures, it’ll be easy to slap new GUIs on there as technology changes!”
Of course, the relationship between what the user sees on the screen and the underlying logic which drives that display means that as they changed the GUI, they also needed to change the database. Over the course of 15 years, the single cohesive data model ubercomplexificaticfied itself as each customer needed a unique GUI with a unique feature set which mandated unique tables and stored procedures.
By the time Thomas came along to start a pseudo-greenfield GUI in ASP.Net, the first and simplest feature he needed to implement involved calling a 3,000 line stored procedure which required over 100 parameters.
|
Метки: Feature Articles |
Representative Line: The Truth About Comparisons |
We often point to dates as one of the example data types which is so complicated that most developers can’t understand them. This is unfair, as pretty much every data type has weird quirks and edge cases which make for unexpected behaviors. Floating point rounding, integer overflows and underflows, various types of string representation…
But file-not-founds excepted, people have to understand Booleans, right?
Of course not. We’ve all seen code like:
if (booleanFunction() == true) …
Or:
if (booleanValue == true) {
return true;
} else {
return false;
}
Someone doesn’t understand what booleans are for, or perhaps what the return statement is for. But Paul T sends us a representative line which constitutes a new twist on an old chestnut.
if ( Boolean.TRUE.equals(functionReturningBooleat(pa, isUpdateNetRevenue)) ) …
This is the second most Peak Java Way to test if a value is true. The Peak Java version, of course, would be to use an AbstractComparatorFactoryFactory to construct a Comparator instance with an injected EqualityComparison object. But this is pretty close- take the Boolean.TRUE constant, use the inherited equals method on all objects, which means transparently boxing the boolean returned from the function into an object type, and then executing the comparison.
The if (boolean == true) return true; pattern is my personal nails-on-the-chalkboard code block. It’s not awful, it just makes me cringe. Paul’s submission is like an angle-grinder on a chalkboard.
https://thedailywtf.com/articles/the-truth-about-comparisons
|
Метки: Representative Line |
Error'd: Placeholders-a-Plenty |
"On my admittedly old and cheap phone, Google Maps seems to have confused the definition of the word 'trip'," writes Ivan.
"When you're Gas Networks Ireland, and don't have anything nice to say, I guess you just say lorem ipsum," wrote Gabe.
Daniel D. writes, "Google may not know how I got 100 GB, but they seem pretty sure that it's expiring soon."
Peter S. wrote, "F1 finally did it. The quantum driver Lastname is driving a Ferrari and chasing him- or herself in Red Bull."
Hrish B. writes, "I hope my last name is not an example as well."
Peter S. wrote, "Not sure what IEEE wants me to vote for. But I would vote for hiring better coders."
"Well, at least they got my name right, half of the time," Peter S. writes.
[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 |
CodeSOD: A Problematic Place |
In programming, sometimes the ordering of your data matters. And sometimes the ordering doesn’t matter and it can be completely random. And sometimes… well, El Dorko found a case where it apparently matters that it doesn’t matter:
DirectoryInfo di = new DirectoryInfo(directory);
FileInfo[] files = di.GetFiles();
DirectoryInfo[] subdirs = di.GetDirectories();
// shuffle subdirs to avoid problematic places
Random rnd = new Random();
for( int i = subdirs.Length - 1; i > 0; i-- )
{
int n = rnd.Next( i + 1 );
DirectoryInfo tmp = subdirs[i];
subdirs[i] = subdirs[n];
subdirs[n] = tmp;
}
foreach (DirectoryInfo dir in subdirs)
{
// process files in directory
}
This code does some processing on a list of directories. Apparently while coding this, the author found themself in a “problematic place”. We all have our ways of avoiding problematic places, but this programmer decided the best way was to introduce some randomness into the equation. By randomizing the order of the list, they seem to have completely mitigated… well, it’s not entirely clear what they’ve mitigated. And while their choice of shuffling algorithm is commendable, maybe next time they could leave us a comment elaborating on the problematic place they found themself in.
|
Метки: CodeSOD |
The Proprietary Format |
Have you ever secured something with a lock? The intent is that at some point in the future, you'll use the requisite key to regain access to it. Of course, the underlying assumption is that you actually have the key. How do you open a lock once you've lost the key? That's when you need to get creative. Lock picks. Bolt cutters. Blow torch. GAU-8...
In 2004, Ben S. went on a solo bicycle tour, and for reasons of weight, his only computer was a Handspring Visor Deluxe PDA running Palm OS. He had an external, folding keyboard that he would use to type his notes from each day of the trip. To keep these notes organized by day, he stored them in the Datebook (calendar) app as all-day events. The PDA would sync with a desktop computer using a Handspring-branded fork of the Palm Desktop software. The whole Datebook could then be exported as a text file from there. As such, Ben figured his notes were safe. After the trip ended, he bought a Windows PC that he had until 2010, but he never quite got around to exporting the text file. After he switched to using a Mac, he copied the files to the Mac and gave away the PC.
Ten years later, he decided to go through all of the old notes, but he couldn't open the files!
Uh oh.
The Handspring company had gone out of business, and the software wouldn't run on the Mac. His parents had the Palm-branded version of the software on one of their older Macs, but Handspring used a different data file format that the Palm software couldn't open. His in-laws had an old Windows PC, and he was able to install the Handspring software, but it wouldn't even open without a physical device to sync with, so the file just couldn't be opened. Ben reluctantly gave up on ever accessing the notes again.
Have you ever looked at something and then turned your head sideways, only to see it in a whole new light?
One day, Ben was going through some old clutter and found a backup DVD-R he had made of the Windows PC before he had wiped its hard disk. He found the datebook.dat file and opened it in SublimeText. There he saw rows and rows of hexadecimal code arranged into tidy columns. However, in this case, the columns between the codes were not just on-screen formatting for readability, they were actual space characters! It was not a data file after all, it was a text file.
The Handspring data file format was a text file containing hexadecimal code with spaces in it! He copied and pasted the entire file into an online hex-to-text converter (which ignored the spaces and line breaks), and voil~A , Ben had his notes back!
[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 |
CodeSOD: Breaking Changes |
We talk a lot about the sort of wheels one shouldn’t reinvent. Loads of bad code stumbles down that path. Today, Mary sends us some code from their home-grown unit testing framework.
Mary doesn’t have much to say about whatever case of Not Invented Here Syndrome brought things to this point. It’s especially notable that this is Python, which comes, out of the box, with a perfectly serviceable unittest module built in. Apparently not serviceable enough for their team, however, as Burt, the Lead Developer, wrote his own.
His was Object Oriented. Each test case received a TestStepOutcome object as a parameter, and was expected to return that object. This meant you didn’t have to use those pesky, readable, and easily comprehensible assert… methods. Instead, you just did your test steps and called something like:
outcome.setPassed()
Or
outcome.setPassed(False)
Now, no one particularly liked the calling convention of setPassed(False), so after some complaints, Burt added a setFailed method. Developers started using it, and everyone’s tests passed. Everyone was happy.
At least, everyone was happy up until Mary wrote a test she expected to see fail. There was a production bug, and she could replicate it, step by step, at the Python REPL. So that she could “catch” the bug and “prove” it was dead, she wanted a unit test.
The unit test passed.
The bug was still there, and she continued to be able to replicate it manually.
She tried outcome.setFailed() and outcome.setFailed(True) and outcome.setFailed("OH FFS THIS SHOULD FAIL"), but the test passed. outcome.setPassed(False), however… worked just like it was supposed to.
Mary checked the implementation of the TestStepOutcome class, and found this:
class TestStepOutcome(object):
def setPassed(self, flag=True):
self.result = flag
def setFailed(self, flag=True):
self.result = flag
Yes, in Burt’s reluctance to have a setFailed message, he just copy/pasted the setPassed, thinking, “They basically do the same thing.” No one checked his work or reviewed the code. They all just started using setFailed, saw their tests pass, which is what they wanted to see, and moved on about their lives.
Fixing Burt’s bug was no small task- changing the setFailed behavior broke a few hundred unit tests, proving that every change breaks someone’s workflow.
[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.
|
Метки: CodeSOD |
CodeSOD: All the Things! |
Yasmin needed to fetch some data from a database for a report. Specifically, she needed to get all the order data. All of it. No matter how much there was.
The required query might be long running, but it wouldn’t be complicated. By policy, every query needed to be implemented as a stored procedure. Yasmin, being a smart prograammer, decided to check and see if anybody had already implemented a stored procedure which did what she needed. She found one called GetAllOrders. Perfect! She tested it in her report.
Yasmin expected 250,000 rows. She got 10.
She checked the implementation.
CREATE PROCEDURE initech.GetAllOrders
AS
BEGIN
SELECT TOP 10
orderId,
orderNo,
orderCode,
…
FROM initech.orders INNER JOIN…
END
In the original developer’s defense, at one point, when the company was very young, that might have returned all of the orders. And no, I didn’t elide the ORDER BY. There wasn’t one.
|
Метки: CodeSOD |
Error'd: Surgeons, Put Down Your Scalpels |
"I wonder what events, or lawsuits, lead TP-Link to add this warning presumably targeted individuals who updated firmware just ahead of performing medical procedures," writes Andrew.
"WalMart was very concerned I didn't bag my bags," wrote Rob C.
"I sure hope the pilots' map is more accurate than the one they show the passengers," wrote Maddie J.
"To me, messages like this are almost like saying to the user 'See? You are the reason why this is broken. Now go and code a fix for it.'," writes Philip B.
Brian J. wrote, "To add insult to injury here, neither the 'Yes' or 'No' button worked. Especially the "No" button."
Aankhen wrote, "If nothing else, CrashPlan is very confident, I’ll give it that."
[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/surgeons-put-down-your-scalpels
|
Метки: Error'd |
To Suffer The Slings and Arrows of Vendor Products… |
Being a software architect is a difficult task. Part of the skill is rote software design based upon the technology of choice. Part of it is the very soft "science" of knowing how much to design to make the software somewhat extensible without going so far as to design/build something that is overkill. An extreme version of this would be the inner platform effect.
Way back when I was a somewhat new developer, I was tasked with adding a fairly large feature that required the addition of a database to our otherwise database-less application. I went to our in-team architect, described the problem, and asked him to request a modest database for us. At the time, Sybase was the in-house tool. He decreed that "Sybase sucks", and that he could build a better database solution himself. He would even make it more functional than Sybase.
At the time, I didn't have a lot of experience, especially with databases, but intuition told me that Sybase had employed countless people for more than a decade to build and tweak Sybase. When I pointed this out, and the fact the it was unlikely that he was going to build a better database than all that effort - by himself - in only a few days, I received a full-on dressing down because I didn't know what was possible, and that a good architect could design and build anything. While I agreed that given enough time it might be possible, it was highly unlikely that it would happen in the next three days (because I needed time to do my coding against the database to meet the delivery schedule). I was instructed to wait and he would get it to me in time.
My Spidy-Sense™ told me not to trust him, so I went to the DBAs that day and told them what I needed. Since I had little relevant experience with setting up a database, I told them of my inexperience with such things and asked them to optimize it with indices, etc. They created it for me that day. Since it was their implementation of my (DB) requirements, I knew that it would at least pass their review. I then coded the required feature and delivered on time. Was it perfect? No. Could it have been designed better? In retrospect, sure. But I was new to databases and it was fast enough for the need at the time.
At every meeting for the next three months, our manager asked the architect how his Sybase-replacement was coming along. He sheepishly admitted that while it was coming along well, coming up with a design that would support all of the features provided by Sybase was proving to be a bit more involved than he had imagined, and that it would take a while longer.
Several months after that, he was still making schematics and flow diagrams to try and build a new and improved Sybase.
Our manager never did do anything to stop him from wasting time.
As for me, I learned an important lesson about knowing when to write code, and when not to write code.
https://thedailywtf.com/articles/to-suffer-the-slings-and-arrows-of-vendor-products
|
Метки: Feature Articles |
Sponsor Post: Make Your Apps Faster With Raygun APM |
Your software is terrible, but that doesn’t make it special. All software is terrible, and yes, you know this is true. No matter how good you think it is, bugs and performance problems are inevitable.
But it’s not just the ugly internals and mysterious hacks and the code equivalent of duct-tape and chewing gum which make your software terrible. Your software exists to fill some need for your users, and how do you know that’s happening? And worse, when your application fails, how do you understand what happened?
In the past, we’ve brought your attention to Raygun, which allows you to add a real-time feedback loop that gives you a picture of exactly what’s happening on their device or their browser. And now, Raygun is making it even better, with Raygun APM.
Raygun Application Performance Monitoring (APM) tackles the absolute worst part of releasing/supporting applications: dealing with performance issues. With Raygun APM, you can get real-time execution stats on your server-side code, and find out quickly which specific function, line, or database call is slowing down your application.

You won’t have to wait for someone to notice the issue, either- Raygun APM proactively identifies performance issues and builds a workflow for solving them. Raygun APM sorts through the mountains of data for you, surfacing the most important issues so they can be prioritized, triaged and acted on, cutting your Mean Time to Resolution (MTTR) and keeping your users happy.

In addition to all this, Raygun is adding tight integration with source control, starting with GitHub.
Request access to the beta here. Or if you’re already tired of searching logs for clues in an effort to replicate an issue, try out Raygun’s current offerings and resolve errors, crashes and performance issues with greater speed and accuracy.
https://thedailywtf.com/articles/make-your-apps-faster-with-raygun-apm
|
Метки: Sponsor Post |