Ulvhamne sends us some bad code that, well, I think at this point we should really coin a name for this particular anti-pattern.
@Override
public int getNumOfItemsInDataContainer(int parDataId)
{
int numberOfItems = 0;
for (Integer x : myTransactionDataContainerMap.keySet())
{
numberOfItems ++;
}
return numberOfItems;
}
This Java function wants to get the number of items contained in a dictionary. To do that, it iterates across the set of keys, and increments a counter. This is instead of using the size
field that's part of the dictionary container.
This one adds a lovely bonus of taking a parameter parDataId
, and doing nothing with it.
Marking the parameter as par
is an example of so-called "Apps Hungarian", where the prefix is meant to represent the "logical" datatype or purpose of the variable- semantic information, instead of type information. While marginally better than iDataId
, I still hate it. Also, if your methods have so many local variables in scope that you need to distinguish them from the parameters somehow, lest you get confused, that's an even bigger code smell.
All the Hungarian nonsense aside, this is an example of the very common anti-pattern of manually counting the items in a container when the container already can report that information.
So what do we call that? "Container Countification"? "Fingers and Toes"? "Sesame Street Counting"?
Bob's employer had a data-driven application which wasn't performing terribly well. They had some in-house database administrators, but their skills were more "keep things running," and less "do deep optimizations". The company opted to hire a contract DBA to come in, address the performance problems, and leave.
In actual fact, the DBA came in, ran some monitoring, and then simply wrote some guidance- generic, and frankly useless guidance. "Index on frequently queried fields," and "ensure database statistics are gathered on the appropriate schedule."
The only meaningful output of the contractor's visit was a list of the badly performing stored procedures. Bob was given the list and told, "Go fix these."
One stored procedure needed to populate variables with data from the order
table. Specifically, it needed to collect sender_id
, user_group
, and file_ref
from the orders
table. Here's how it did that:
if (@origin = -2 or @origin = -4 or @origin = -5 or
@origin = -6 or @origin = -7 or @origin = -8 or @origin = -9)
begin
select @id = sender_id from order where ref = @mur
select @group = user_group from order where ref = @mur
if (@ltaId is null)
begin
select @ltaId = null
end
select @file_ref = file_ref from order where ref = @mur
select @ref = ref from order where mur = @mur
end
else
begin
select @id = sender_id from order where mur = @mur
select @group = user_group from order where mur = @mur
if (@ltaId is null)
begin
select @ltaId = null
end
select @file_ref = file_ref from order where mur = @mur
select @ref = ref from order where mur = @mur
end
Let's start with the if
condition. We never love magic numbers, but that's hardly a WTF. The real problem is that the two branches differ only in whitespace. There's no reason for the if
. Perhaps once upon a time there was, but there isn't now.
Now, all the fields we need are in the table order
, they're all selected by the field mur
which is a unique key, but for some reason, this code needs to run the query three times to get its data. Also, mur
is a unique key in the domain but not in the database- no unique constraint or index was applied, which is part of the reason performance was as bad as it was.
But the real highlight, for me, is how @ltaId
gets set. If it's null, set it equal to null. That's a ^chef's kiss^ right there. Beautiful(ly horrible).
Marcus's team was restructuring the API, and the architect thus wanted a number of methods marked obsolete, to encourage developers to move to the new version of the API. So the architect created a Jira task, assigned it to a dev, and moved on.
Somehow, this C# code got committed and merged, despite being code reviewed:
public static void OBSOLETE_PopulateOfferNodes(Ecometry.New.Model.CategoryHierarchyNode _mainNode, ref Ecometry.New.Model.CategoryHierarchyNode _defaultOffersNode, ref Ecometry.New.Model.CategoryHierarchyNode _foundOffersNode)
{
foreach (Ecometry.New.Model.CategoryHierarchyNode offersNode in _mainNode.Children)
{
if (offersNode.Category.Name.ToUpper().Trim().Equals("_DEFAULT")) _defaultOffersNode = offersNode;
if (offersNode.Category.Name.ToUpper().Trim().Equals(Ecometry.New.Impl.ExecutionContextFactory.ExecutionContext.CurrentCart.SourceCode.Code.ToUpper().Trim()) && Snapshot.OfferRelatedUtilities.isDateOK2Show(offersNode)) _foundOffersNode = offersNode;
if (_defaultOffersNode != null && _foundOffersNode != null) break;
}
return;
}
Now, what the architect had meant was that the developer should use the [Obsolete]
attribute, which lets you annotate methods like so:
[Obsolete("Foo is deprecated, please use Bar instead")]
public void Foo() {}
What the developer heard, instead, was that they should do a find and replace of every use of the method name and prepend OBSOLETE_
to it.
The annotation has a number of advantages: unlike the name, it produces a compiler warning, and that warning can be promoted to a compiler error in future versions, without fully removing the method. It also helpfully avoids this drunken form of Hungarian notation that sneaks what is essentially version information into the method name.
This is the first step on a path that ends with methods named New_UseThisOne_Foo()
.
All that aside, the if
statements are a mouthful in there. They're not a WTF, but… boy, they could probably be simplified for readability. I suspect that's part of why this method is obsolete.
As oft discussed, null-terminated C-style strings are an endless source of problems. But there's no problem so bad that it can't be made worse by a sufficiently motivated developer.
Today's rather old code comes from Mike, who inherited an old, MFC application. This code is responsible for opening a file dialog, and the key goal of the code is to configure the file filter in that dialog. In MFC, this is done by passing a delimited string containing a caption and a glob for filtering. E.g., "Text Files (.txt) | *.txt" would open a dialog for finding text files.
char szFileName[MAX_FILE_NAME];
char szDlgTitle[] = "Save File As";
char szDefExt[] = "log";
char* szPos;
WORD nFileOffset;
WORD nFileExtension;
char szFileFilter[MAX_FILE_NAME];
char szBuffer[128];
std::ifstream inFile;
memset(szFileFilter, NULL, MAX_FILTER_SIZE);
memset(szFileName, NULL, MAX_FILE_NAME);
strcpy_s(szFileFilter, sizeof(szFileFilter), "*.log - Debug Log File|");
szPos = szFileFilter + strlen("*.log - Debug Log File|") + 1;
strcpy_s(szPos, sizeof( szPos), "*.log");
if(!OpenSaveFile(TRUE, szFileName, MAX_FILE_NAME, szFileFilter, szDlgTitle,
nFileOffset, nFileExtension, szDefExt, hWndInstance, hWndMain))
break;
The impressive thing about this code is that this was released, sent to customers, and crashed consistently- until people started using the 64-bit build, when it started working again.
After declaring some variables, we start by using memset
to null out some character arrays. This isn't particularly necessary, but it's mostly harmless- or at least it would be if they actually read their own code.
szFileFilter
is declared using the size MAX_FILE_NAME
, but when set to null, a space equal to MAX_FILTER_SIZE
is used. If MAX_FILTER_SIZE
is less than or equal to MAX_FILE_NAME
this is fine- but if it's ever larger- welp, we've got an out of bounds access.
That's not what guarantees a crash, that's just bad practice.
Next, we use strcpy_s
to copy the caption into our szFileFilter
array. Then we calculate an offset within that array, to store in szPos
. We then use strcpy_s
again, copying our filter in to the end of the string.
This is the line that's guaranteed to crash. Because note that they pass a size to strcpy_s
- sizeof(szPos)
. szPos
is a pointer, not an array. So unlike all the other strings used in this example, sizeof
won't tell you its length, it'll just tell you how much memory a pointer takes up. On 32-bit, that'd be 4 bytes. On 64-bit, that'd be 8. And that's why it suddenly started working when people changed builds.
Also, the ideal-world version of Hungarian notation is that, by specifying the types in the variable name, you can defend against these errors- but because they used sz
for all strings, whether stored in allocated arrays or as pointers, they didn't have the information they needed.
Also, instead of doing all this weird string offset stuff with multiple copies, or doing any strcat_s
s, they could have just…
strcpy_s(szFileFilter, sizeof(szFileFilter), "*.log - Debug Log File|*.log);
This week we have a special visit from a mythical beast: the snarklemma. But first, a non-error Error'd.
Obsessive Optimizer Ian K. "Walmart's nationwide network of warehouse stores means they can save time and money by shipping locally. FedEx has them covered: Their nationwide shipping fleet determined the shortest path from Houston to its largest suburb goes via Georgia." Not the shortest path, nor the fastest, but surely the cheapest one that meets the delivery date requirement. It's probably not an error, and I believe it, but I still can't believe it!
Possibly our most faithful contributor, Michael R. is also a Voyager fan. He shared this snapshot with us, commanding "Adjust our delefctor!" I think that's probably just the way they spefl thingf int he 24f sentry.
An anonymous poster shared this image with us, remarking: "I'm not sure..."
A second anonymous poster was caught on the horns of a snarklemma. Please help them out by voting in the comments. Would you prefer snark A: "Disney is acquiring so many things nowadays. I didn't know it was also acquiring Android notifications." Or would you prefer snark B: "I only have one TV and only one Disney+ account playing only one animated series. And yet, Google Play Services generated several notifications. Thats not Disney+, that's Disney+++++".
Miquel B. eschews Netflix in favor of the edgier material available elsewhere. "Someone posted Lorem Ipsum for their review of the series." I couldn't bear to watch it; I have test anxiety.
Yesterday we talked about bad CSS. Today, we're going to talk about bad HTML.
Corey inherited a web page that, among other things, wanted to display a bulleted list of links. Now, you or I might reach for the ul
element, which is for displaying bulleted lists. But we do not have the galaxy sized brains of this individual:
<table style="font-family: Verdana;">
<tr>
<td valign="top">
•
</td>
<td>
Google
<br />
<a href="http://www.google.com" target="_blank">http://www.google.com</a>
</td>
</tr>
<tr>
<td valign="top">
•
</td>
<td>
Yahoo
<br />
<a href="http://www.yahoo.com" target="_blank">http://www.yahoo.com/</a>
</td>
</tr>
<tr>
<td valign="top">
•
</td>
<td>
Bing
<br />
<a href="http://www.bing.com" target="_blank">http://www.bing.com</a>
</td>
</tr>
</table>
Here, they opted to use a table, where each list item is a row, and the bullet is a literal •
symbol in the code.
For web developers of a certain age, we remember when laying out your entire page inside of tables was a common practice. This let you easily define distinct header, sidebar, and footer sections in an era before CSS and div
s everywhere.
But they were never meant to be used like this.
There is a surprising amount of debate about how to use CSS classes. The correct side of this debate argues that we should use classes to describe what the content is, what role it serves in our UI; i.e., a section of a page displaying employee information might be classed employee
. If we want the "name" field of an employee to have a red underline, we might write a rule like:
.employee .name { text-decoration: underline red; }
The wrong side argues that this doesn't tell you what it looks like, so we should have classes like red-underline
, because they care what the UI element looks like, not what it is.
Mark sends us some HTML that highlights how wrong things can go:
<div class="print ProfileBox floatRight" style="background-color:#ffffff;" >
<div class="horiz">
<div class="horiz">
<div class="vert">
<div class="vert">
<div class="roundGray">
<div class="roundPurple">
<div class="roundGray">
<div class="roundPurple">
<div class="roundGray">
<div class="roundPurple">
<div class="roundGray">
<div class="roundPurple">
<div class="bold">
Renew Profile
<br><br>
Change of Employer
<br><br>
Change of Position
<br><br>
Print Training Reports
<br><br><br><br>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="floatRight"> </div>
<div class="ProfileBox print floatRight" style="background-color:#ffffff" >
<div class="horiz">
<div class="horiz">
<div class="vert">
<div class="vert">
<div class="roundGray">
<div class="roundPurple">
<div class="roundGray">
<div class="roundPurple">
<div class="roundGray">
<div class="roundPurple">
<div class="roundGray">
<div class="roundPurple">
<div class="bold">
Profile Status:
<br><br>
Registry Code:
<br><br>
Career Level:
<br><br>
Member Since:
<br><br>
Renewal Date:
<br><br>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
It's worth noting, this was not machine-generated HTML. The developer responsible wrote this code. Mark didn't supply the CSS, so I have no idea what it would display like, but the code looks horrible.
Mark adds:
A colleague of mine committed this code. This is the perfect example of why you define your CSS classes based on the content, not the styling they apply.
Once upon a time, I was tuning a database performance issue. The backing database was an Oracle database, and the key problem was simply that the data needed to be partitioned. Great, easy, I wrote up a change script, applied it to a test environment, gathered some metrics to prove that it had the effects we expected, and submitted a request to apply it to production.
And the DBAs came down on me like a sledgehammer. Why? Well, according to our DBAs, the license we had with Oracle didn't let us use partitioning. The feature wasn't disabled in any way, but when an Oracle compliance check was performed, we'd get dinged and they'd charge us big bucks for having used the feature- and if we wanted to enable it, it'd cost us $10,000 a year, and no one was willing to pay that.
Now, I have no idea how true this actually was. I have no reason to disbelieve the DBAs I was working with, but perhaps they were being overly cautious. But the result is that I had to manually partition the data into different tables. The good news was all the writes always went into the most recent table, almost all of the reads went to either the current table or last month's table, and everything else was basically legacy and while it might be used in a report, if it was slower than the pitch drop experiment, that was fine.
It was stupid, and it sucked, but it wasn't the worst sin I'd ever committed.
Which is why I have at least some sympathy for this stored procedure, found by Ayende.
ALTER PROCEDURE GetDataForDate
@date DATETIME
AS
DECLARE @sql nvarchar(max)
SET @sql = 'select * from data_' + convert(nvarchar(30),getdate(),112)
EXEC sp_executesql @sql
Now, this is for an MS SQL database, which does not have any weird licensing around using partitions. But we can see here the manual partitioning in use.
There are a set of data_yyyymmdd
tables. When we call this function, it takes the supplied date and writes a query specific to that table. This means that there is a table for every day.
Ayende got called in for this because one of the reports was running slowly. This report simply… used all of the tables. It just UNION
ed them together. This, of course, removed any benefit of partitioning, and didn't exactly make the query planning engine's job easy, either. The execution paths it generated were not terribly efficient.
At the time Ayende first found it, there were 750 tables. And obviously, as each day ticked past, a new table was created. And yes, someone manually updated the view, every day.
Ayende sent this to us many tables ago, and I dread to think how many tables are yet to be created.
As oft stated, the specification governing email addresses is complicated, and isn't really well suited for regular expressions. You can get there, but honestly, most applications can get away with checking for something that looks vaguely email like and call it a day.
Now, as complicated as the "accurate" regex can get, we can certainly find worse regexes for validating emails. Morgan did, while on a contract.
The client side had this lovely regex for validating emails:
/*
Check if a string is in valid email format.
Returns true if valid, false otherwise.
*/
function isEmail(str)
{
var regex = /^[-_.a-z0-9]+@(([-_a-z0-9]+\.)+(ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$/i;
return regex.test(str);
}
They check a long list of TLDs to ensure that the email address is potentially valid, or accept an email address. Is the list exhaustive? Of course not. There are loads of TLDs not on this list- perhaps not widely used ones, but it's incomplete. And also, unnecessary.
But not so unnecessary that they didn't do it twice- they mirrored this code on the server side, in PHP:
function isEmail($email)
{
return(preg_match("/^[-_.[:alnum:]]+@((([[:alnum:]]|[[:alnum:]][[:alnum:]-]*[[:alnum:]])\.)+(ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|com|coop|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|in|info|int|io|iq|ir|is|it|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nt|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)$|(([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.){3}([0-9][0-9]?|[0-1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5]))$/i"
,$email));
}
Bad code is even better when you have to maintain it in two places and in two languages. I suppose I should just be happy that they're doing some kind of server-side validation.
There are an infinite variety of ways to be wrong, but only very small number of ways to be right.
Patient Peter W. discovers that MS Word is of two minds about English usage. "Microsoft Word just can't seem to agree with itself on how to spell paycheck/pay check." Faithful readers know it's even worse than that.
Slow Daniel confused me with this complaint. He writes "It seems that the eclipse has reversed the flow of time for the traffic-free travel time." I don't get it. Can readers explain? The only WTF I see here is how much faster it is to walk than to drive 2 miles. Franconia isn't Conway!
Parsimonious Adam found a surprise discount. "This album was initially listed as pay-what-you-want. I tried to pay $4 for it, but the price changed to a minimum of $5 before I was able to check out, and checkout rightfully failed. My shopping cart then reverted to saying this." I want to know what happened next.
Some of you dislike it when I thaw a thematic item from the deep freeze, so please note that B.J. sent us this sometime last year, noting that "Time works in mysterious ways for some companies. I earned a Verizon community 2 Year badge 36 hours after earning a 4 Year badge." I did consider saving it until July 26 this year but I'm just not that patient.
By comparison, I only had to drag this entry
from the far back bottom corner of February's fridge.
I imagine chill
Paul
intoning with his radio voice
"This next saturday is going to be all ice ice baby."
So many languages eschew "truth" for "truthiness". Today, we're looking at PHP's approach.
PHP automatically coerces types to a boolean with some fairly simple rules:
false
is false0
is false, as is the float 0.0
and -0.0
."0"
are falseNULL
is falseHonestly, for PHP, this is fairly sane and reasonable. The string "0"
makes my skin itch a bit, and the fact that the documentation page needs a big disclaimer warning that -1
is true hints at some people blundering as they convert from other languages.
But we're not doing a language critique. We're looking at a very specific block of code, which Jakub inherited. You see, someone didn't like this, so they implemented their own version:
protected static function emulate_filter_bool( $value ) {
// @codingStandardsIgnoreStart
static $true = array(
'1',
'true', 'True', 'TRUE',
'y', 'Y',
'yes', 'Yes', 'YES',
'on', 'On', 'ON',
);
static $false = array(
'0',
'false', 'False', 'FALSE',
'n', 'N',
'no', 'No', 'NO',
'off', 'Off', 'OFF',
);
// @codingStandardsIgnoreEnd
if ( is_bool( $value ) ) {
return $value;
} elseif ( is_int( $value ) && ( 0 === $value || 1 === $value ) ) {
return (bool) $value;
} elseif ( ( is_float( $value ) && ! is_nan( $value ) ) && ( (float) 0 === $value || (float) 1 === $value ) ) {
return (bool) $value;
} elseif ( is_string( $value ) ) {
$value = trim( $value );
if ( in_array( $value, $true, true ) ) {
return true;
} elseif ( in_array( $value, $false, true ) ) {
return false;
} else {
return false;
}
}
return false;
}
I love when a code block starts with an annotation to force the linter to ignore them. Always great.
We start by declaring arrays of a variety of possible true and false values. Most of the variations are in capitalization, but not exhaustive variations in capitalization, which I'm sure will cause problems at some point. But let's see how this code works.
First, we check if the value is a boolean, and if so, return it. Fine, very reasonable.
Second, we check if it's an integer, and if it is either 0 or 1, we cast it to a boolean and return it. In pure PHP, anything non-zero is true, but here only 1
is true.
We then do a similar check for floats- if it's a float, it is a number, and it's either 0 or 1, we cast it to float and return it. Note, however, that they're doing a simple equality comparison- which means that floating point inputs like 1.000000001
will fail this test, but will often still get printed as 1
when formatted for output, causing developers to get very confused (ask Jakub how he knows).
Finally, we check for strings, and do an in_array
comparison to check if the value is either true or false. If it's neither true nor false, we simply return false (instead of FILE_NOT_FOUND
). This raises the obvious question: why even bother with the check against the false values array, when we can just assume the input is false if it isn't in the true values array?
Jakub has this to add:
This method finds application in numerous places, particularly where front-end interfaces display checkboxes. I truly dread discovering the full extent of the code responsible for that...
Strings in C are a unique collection of mistakes. The biggest one is the idea of null termination. Null termination is not without its advantages: because you're using a single byte to mark the end of the string, you can have strings of arbitrary length. No need to track the size and worry if your size variable is big enough to hold the end of the string. No complicated data structures. Just "read till you find a 0 byte, and you know you're done."
Of course, this is the root of a lot of evils. Malicious inputs that lack a null terminator, for example, are a common exploit. It's so dangerous that all of the str*
functions have strn*
versions, which allow you to pass sizes to ensure you don't overrun any buffers.
Dmitri sends us a simple example of someone not quite fully understanding this.
strcpy( buffer, string);
strcat( buffer, "\0");
The first line here copies the contents of string
into buffer
. It leverages the null terminator to know when the copy can stop. Then, we use strcat
, which scans the string for the null terminator, and inserts a new string at the end- the new string, in this case, being the null terminator.
The developer responsible for this is protecting against a string lacking its null terminator by using functions which absolutely require it to be null terminated.
C strings are hard in the best case, but they're a lot harder when you don't understand them.
Gloria was a senior developer at IniMirage, a company that makes custom visualizations for their clients. Over a few years, IniMirage had grown to more than 100 people, but was still very much in startup mode. Because of that, Gloria tried to keep her teams sized for two pizzas. Thomas, the product manager, on the other hand, felt that the company was ready to make big moves, and could scale up the teams: more people could move products faster. And Thomas was her manager, so he was "setting direction."
Gloria's elderly dog had spent the night at the emergency vet, and the company hadn't grown up to "giving sick days" yet, so she was nursing a headache from lack of sleep, when Thomas tried to initiate a Slack huddle. He had a habit of pushing the "Huddle" button any time the mood struct, without rhyme or reason.
She put on her headphones and accepted the call. "It's Gloria. Can you hear me?" She checked her mic, and repeated the check. She waited a minute before hanging up and getting back to work.
About five minutes later, Thomas called again."Hey, did you call me like 10 minutes ago?"
"No, you called me." Gloria facepalmed and took a deep, calming breath. "I couldn't hear you."
Thomas said, "Huh, okay. Anyway, is that demo ready for today?"
Thomas loved making schedules. He usually used Excel. There was just one problem: he rarely shared them, and he rarely read them after making them. Gloria had nothing on her calendar. "What demo?"
"Oh, Dylan said he was ready for a demo. So I scheduled it with Jack."
Jack was the CEO. Dylan was one of Gloria's peers. Gloria checked Github, and said, "Well, Dylan hasn't pushed anything for… months. I haven't heard anything from him. Has he showed you this demo?"
Gloria heard crunching. Thomas was munching on some chips. She heard him typing. After a minute, she said, "Thomas?"
"Oh, sorry, I was distracted."
Clearly. "Okay, I think we should cancel this meeting. I've seen this before, and with a bad demo, we could lose buy in."
Thomas said, "No, no, it'll be fine."
Gloria said, "Okay, well, let me know how that demo goes." She left the call and went back to work, thinking that it'd be Thomas's funeral. A few minutes before the meeting, her inbox dinged. She was now invited to the demo.
She joined the meeting, only to learn that Dylan was out sick and couldn't make the meeting. She spent the time giving project updates on her work, instead of demos, which is what the CEO actually wanted anyway. The meeting ended and everyone was happy- everyone but Gloria.
Gloria wrote an email to the CEO, expressing her concerns. Thomas was inattentive, incommunicative, and had left her alone to manage the team. She felt that she was doing more of the product management work than Thomas was. Jack replied that he appreciated her concerns, but that Thomas was growing into the position.
Julia, one of the other product managers, popped by Gloria's desk a few weeks later. "You know Dylan?"
Gloria said, "Well, I know he hasn't push any code in a literal year and keeps getting sick. I think I've pushed more code to his project than he has, and I'm not on it."
Julia laughed. "Well, he's been fired, but not for that."
Thomas had been pushing for more demos. Which meant he pulled Dylan into more meetings with the CEO. Jack was a "face time" person, and required everyone to turn on their webcams during meetings. It didn't take very many meetings to discover that Dylan was an entirely different person each time. There were multiple Dylans.
"But even without that, HR was going to fire him for not showing up to work," Julia said.
"But… if there were multiple people… why couldn't someone show up?" Gloria realized she was asking the wrong question. "How did Thomas never realize it?"
And if he was multiple people, how could he never get any work done? Dylan was a two-pizza team all by himself.
After the Dylan debacle, Thomas resigned suddenly and left to work at a competitor. A new product manager, Penny, came on board, and was organized, communicative, and attentive. Gloria never heard about Dylan again, and Penny kept the team sizes small.
It's incredibly common to convert objects to dictionaries/maps and back, for all sorts of reasons. Jeff's co-worker was tasked with taking a dictionary which contained three keys, "mail", "telephonenumber", and "facsimiletelephonenumber" into an object representing a contact. This was their solution:
foreach (string item in _ptAttributeDic.Keys)
{
string val = _ptAttributeDic[item];
switch (item)
{
case "mail":
if (string.IsNullOrEmpty(base._email))
base._email = val;
break;
case "facsimiletelephonenumber":
base._faxNum = val;
break;
case "telephonenumber":
base._phoneNumber = val;
break;
}
}
Yes, we iterate across all of them to find the ones that we want. The dictionary in question is actually quite large, so we spend most of our time here looking at keys we don't care about, to find the three we do. If only there were some easier way, some efficient option for finding items in a dictionary by their name. If only we could just fetch items by their key, then we wouldn't need to have this loop. If only.
A twitchy anonymous reporter pointed out that our form validation code is flaky. He's not wrong. But at least it can report time without needing emoticons! :-3
That same anon sent us the following, explaining "Folks at Twitch are very brave. So brave, they wrote their own time math."
Secret Agent Sjoerd reports "There was no train two minutes ago so I presume I should have caught it in an alternate universe." Denver is a key nexus in the multiverse, according to Myka.
Chilly Dallin H. is a tiny bit heated about ambiguous abbreviations - or at least about the software that interprets them with inadequate context. "With a range that short, I'd hate to take one of the older generation planes." At least it might be visible!
Phoney François P. "I was running a return of a phone through a big company website that shall not be named. Thankfully, they processed my order on April 1st 2024, or 2024年4月1日 in Japanese. There is a slight delay though as it shows 14月2024, which should be the 14th month of 2024. Dates are hard. Date formatting is complicated. For international date formatting, please come back later."
At some time in the past, the original Adam R. encountered a time slip. We're just getting to see it even now. "GitHub must be operating on a different calendar than the Gregorian. Comments made just 4 weeks ago [today is 2023-02-07] are being displayed as made last year."
Yes, despite endless hype about getting rich and a new economic paradigm, you made $0 from April 4th 2021 to today. Including inflation, you are down 15%.
This lags everything: gold, stocks, treasury bills, even compounded interest in a savings account.
Meanwhile, you had to stomach 80% drawdowns to make this $0 return.
Hi there. I come from crypto, and I come with respect. TIL that there's a Reddit community dedicated to the idea that crypto is a scam. I'm just curious about a few things, again, with complete respect and curiosity:
Why do people come on a forum just to talk negatively about a technology / crypto / coin or whatever? Why not just refrain from buying the coin or being involved? What is the use of coming here and making fun of crypto?
The reason why I ask is because mainstream media is already full of news narratives that talk down on crypto. Most of the world thinks crypto is a scam. To me, there doesn't seem to be the need for a dedicated reddit community to reinforce an already extremely popular world view.
Typically, the people who get into crypto are contrarian, taking contrarian bets and thinking they're the underdogs. It's usually the underdogs who band together in communities because they're alienated in other forums... right?
Anyway, thank you for answering me and again I genuinely ask this from a really good place. I'm here to learn, and maybe to get involved.
Also, why so much hate for crypto? By default I assume (hopefully not wrongly) that most of you are proponents of traditional paper money, which is being inflated away every day. Why is this the preference of some or most of you here?
Thank you again for responding!
EDIT: What did I learn? I came here respectfully and asked genuine questions. In response, I lost a lot of karma and had very few fruitful discussions. There was profanity, incorrect information, and a general lack of a willingness to discuss further than one or two shots at me. Of the few people who did respond constructively, here's what I learned:
--Some people are here because they want to get a laugh out of the crypto enthusiasts and "take the piss out of them," or watch them burn. That's all fine, and a valid reason to dedicate a community to anti-crypto.
--Some people here are staunchly against fraud, which they believe is heavily fueled by crypto. My response was that well over 99% of fraud is done with fiat money, not crypto. Less than 1% of any fraud is done with crypto, and this is a fact. Their response was, well, crypto is ONLY used for fraud, and not in any corporate or global financial setting, whereas even though fiat is used for fraud, it's still used for other things (obviously).
I'll add more things as they come.
Well, the other main arguments are BTC is used for illegal things so it should be banned. With that said, the internet, guns, dollars, medicine, knives, cars are all used for illegal things too. So are cameras and phones. Should we ban those?
It’s 24 Feb 2024. Btc is around 50k. Eth is around 2.9k. I think btc will hit 100k and eth 10k. Approximately. This is my opinion. These are investment vehicles. I’m an investor and so I invest. If you think Tesla will hit 10k, you’d probably buy it too.
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8">Judge Kaplan: Madam Foreperson, I understand the jury has reached a verdict. Please hand it to Andy. Thank you. The clerk will publish the verdict. Please rise.
— Inner City Press (@innercitypress) November 2, 2023
Count 1: Guilty.
Count 2: Guilty.
Count 3: Guilty.
I gotta say I find it hilarious that so many Butters feel genuinely threatened by this sub. I feel like I am seeing more and more posts by Butters accusing this sub of trying to sabotage crypto currencies. I guess I can only speak for myself, but to me they are grossly overestimating how seriously this sub takes itself.
The purpose of this sub is not to "take down" bitcoin or cryptocurrencies in general. I don't think anyone here has such delusions of grandeur. The subs icon is an ass for Christ's sake. This sub is a coping mechanism where we use comedy to maintain our sanity as best we can as we watch this slow motion train wreck of mass psychology unfold.
And no, when I say "coping mechanism" I don't mean coping with our lack of lambos or missing out on going "TO THE MOON!", I mean coping with the fact that we see bitcoin as having a significant net negative impact on humanity and the planet as a whole.
Obviously if the discourse in this sub can help even one Butter, or would-be Butter have a moment of clarity I think we could considered that a massive success, but Butters believing that this sub is going to have any real impact on cryptocurrencies as a whole just highlights their disconnect with reality.
What do you think Linux would look like today if the kernel team was friendly and outgoing and willing to put on kid gloves and help all the newcomers? What would it look like if everyone's code were "good enough"?
What would enterprise look like if Linus was polite and held everybody's hand who wanted to push code into the kernel? Would Linux be as far along as it is? Would it be the same quality?