CodeSOD: List Incomprehension |
Loads of languages, like Python, have some sort of "comprehension" as a form of syntactic sugar. Instead of doing something awkward like:
my_list = [1, 2, 3, 4]
res = []
for x in my_list:
res.append(x*x)
# res contains: [1, 4, 9, 16]
You can instead do:
my_list = [1, 2, 3, 4]
res = [x * x for x in my_list]
# res contains: [1, 4, 9, 16]
Used correctly, it's not just code golf, but it can make the intent and purpose of your code more clear. Used incorrectly, you can accomplish the exact opposite.
Vincent took over a product with a lot of modules which had, at one time, been very important bits of functionality, but now were deprecated. For example, there used to be an lxml
-based parser which loaded data from an XML-based web-service. That webservice was long dead, the parser thus was no longer needed, but the code wasn't so well organized that you could just delete the module without doing a review.
That's how Vincent found this:
def scrape_ext(root, split_by):
return '\n'.join([
' '.join([b.strip() for b in c.split()]) for c in [_f for _f in [
y.strip() for y in
root.text_content().split(split_by)] if _f]])
This is the impressive triply-nested comprehension, with useless variable names and a bonus bit of awkward indentation to help keep in unreadable and unclear. So much for Python's whitespace-as-syntax helping developers keep their code blocks properly indented.
Let's see if we can make sense of this by taking it from the inside out. First:
[y.strip() for y in root.text_content().split(split_by)]
This is easy, on its own: take the text of an HTML element, and create a list by splitting on some character, but also stripping whitespace. This, alone, is a pretty textbook example of a simple comprehension: it iterates across a list and manipulates each item in the list in a small way. The next comprehension, wrapping around that:
[_f for _f in split_and_stripped if _f]
This highlights another feature of Python comprehensions, filtering. You have an if _f
at the end, which selects only the elements that are truthy values- any empty strings will be filtered out.
There's only one problem with that filter: it's not necessary. Because the next compression is for c in [_f for … if _f]
, so we could just as easily have done for c in split_and_stripped if c
. And what do we do with c
anyway?
Another nested comprehension:
[b.strip() for b in c.split()]
Split the string on whitespace, strip the whitespace… that we just split on. Python's split will remove all the whitespace characters, making the strip
unnecessary.
Then we ' '.join([b.strip() for b in c.split()])
, which shows us Python's unusual approach to joins (they're string methods, not array methods- this joins the array using a space between each element).
Then we join the results of all the other comprehensions with a \n
.
So the real purpose of this code: turn all the whitespace into single spaces, then replace an arbitrary character (split_by
) with a newline. But you wouldn't get that by just reading it, and I'm not entirely certain that's what the original developer actually realized they were doing, because this isn't the kind of code written by someone who understands the problem they're solving.
Like so much bad code, this was fortunately unused in the program, and Vincent was free to dispose of it.
Метки: CodeSOD |
Error'd: Perfunctory Yet Functional |
"This system is scheduled for a reboot at 26:00 hours on Monday. Or, as it's more commonly known, 'Tuesday'," Peter G. wrote.
"So I guess one person was just THAT torn on who to vote for," writes Joseph D.
David G. wrote, "To dash or not to dash, that is the question!"
"Yeah! How exactly does Lorem Ipsum work to...clean my house?" writes Yudderick D.
Rob G. wrote, "Maybe a file description is incoming, you know, after it starts responding."
Pete H. writes, "How 'nice' to have my special day recognized exactly as I'd expect it in an auto-generated email from a gigantic, international corporation."
Метки: Error'd |
Classic WTF: Manager of the Data Dump |
It's a holiday in the US, where we catalog the things we're thankful for. I'm thankful that developers collectively learned to understand how databases work, and didn't start releasing databases that stored flexible documents with no real schema and could just be used as a data dump. That would be terrible! This classic WTF illustrates that. Originally. --Remy
J.T. is not well liked amongst the developers at his organization. As a Database Administrator, it's J.T's job to make sure that database structures and queries maintain data integrity and do not put an unnecessarily load on the server. This often gets in the way of the developers, who prefer to think of the database as a giant dump site where data gets thrown and is rummaged through to be retrieved. Things like "indexes," "valid data," and "naming conventions" are merely obstacles put in place by J.T. to make their life harder.
Generally, the submission-review-rejection procedure happens once or twice with most of the developers. But one particular developer -- a newly hired ".NET Wizard" named Frank -- turns the procedure into a daily cycle that drags on for several weeks. Following is Frank's reply to the first in a chain of rejections on a project that Frank was leading up ...
J.T., > I cannot find the "DtxSurveys" table you asked to import this > into. Are you sure this is the right table? This is a new table we need. Please just use DTS to import the CSV file I sent and SQL Server will generate the table and the columns. That's how we're doing it in dev. Frank
J.T. replied, explaining explained that, per the database development guidelines, he must submit a CREATE TABLE script that explicitly defines the table.
J.T., Fine, here is a script: CREATE TABLE [DtxSurveys] ( [Status] varchar (8000) NULL, [Resp] varchar (8000) NULL, [Last] varchar (8000) NULL, ... snip ... [Supervisor_Name] varchar (8000) NULL, [Supervisor_CUID] varchar (8000) NULL, [Supervisor_UID] varchar (8000) NULL, [File_Data_Date] varchar (8000) NULL) Frank
J.T. wasn't quite sure where to begin. The script was useful, as in a catch-all dumpster sort of way. He replied to Frank explaining that the table had no primary key, no typed data, and exceeded SQL Server's maximum row size of 8060 bytes. Frank wasn't too pleased and replied back:
J.T., When we run the script in dev, all we get is a warning that the row is too long. It's not an error, but whatever. Here is the revised script: CREATE TABLE [DtxSurveys] ( [Status] [nvarchar](255) NULL, [Resp] [nvarchar](255) NULL, [Last] [nvarchar](255) NULL, ... snip ... [Supervisor_Name] [nvarchar](255) NULL, [Supervisor_CUID] [nvarchar](255) NULL, [Supervisor_UID] [nvarchar](255) NULL, [File_Data_Date] [smalldatetime] NULL) Frank
It was a slight improvement, as in a catch-almost-all garbage can sort of way. The only thing Frank changed was VARCHAR(8000) to NVARCHAR(255) and the File_Data_Date field. I'll spare you the rest of the back-and-forth on the CREATE TABLE script, but suffice it to say that it took several more revisions before it represented the actual: "Status" was a single letter, "Supervisor_UID" was a globally unique identifier, "Universal_ID" was an eight-character numeric identifier, etc.
I'll leave you with one of the last things that Frank sent over for review. It was the following query:
SELECT Product_ID, Product_name, case WHEN (SELECT SUM(ProductRequest.ProductFaceValue) FROM Requests WHERE (ProductRequest.ProductID = Product_ID) AND (ProductRequest_createdByUserUID = @uid) GROUP BY ProductRequest_productName) is null then 0 ELSE (SELECT SUM(ProductRequest.ProductFaceValue) FROM Requests WHERE (ProductRequest.ProductID = Product_ID) AND (ProductRequest_createdByUserUID = @uid) GROUP BY ProductRequest_productName) end as TotalDollarAmount FROM Requests WHERE (product_ID IN (@pid))
When J.T. mentioned that they will have to optimize the query because it ran for 2000 milliseconds, Frank explained that it's already optimized and can't run any faster. J.T. updated the query to use an ISNULL and increased the run time to 52 milliseconds. It was a small, 3800% decrease.
https://thedailywtf.com/articles/classic-wtf-manager-of-the-data-dump
Метки: Feature Articles |
CodeSOD: Repeat and Rinse |
The challenges of doing a national integration continue to plague Sergio. More specifically, the “solutions” left behind by his predecessors continue to annoy.
Sergio has inherited a system which needs to plug in to a national database. As the national integration was something which was added after the business processes were already determined, that means that certain terms/descriptors/captions/etc. are used internally than are required externally, and vice versa. So, for example, one laboratory test Sergio’s company performs might be called “QD1” internally, but is known by the government as “F3+”.
As you might imagine, Sergio’s predecessors solved this with a database table called MAPPINGS
. It contains all the mappings, so it might map lab test names, city codes, units of measure, group codes- just anything that could have a name that is possibly in conflict with the government’s requirements is in there. So the table has three key fields:
COD1
is the internal “code” for a thing. COD2
is the government’s code. And MAP_GROUP
is a category tag, probably hastily added after a naming conflict between two different TLAs.
So far, so good. That all makes sense.
So let’s take a look at how they query the database.
public static String MapLabTests(String original) {
String result = "";
Logger logger = new Logger("ThisClass.MapLabTests");
StringBuffer sbQuery = new StringBuffer();
AccessBdNonXa accessBdNonXa = new AccessBdNonXa();
HashMap parameters = new HashMap();
try {
sbQuery.append(" SELECT COD2 FROM MAPPINGS ");
sbQuery.append(" WHERE COD1= ?");
sbQuery.append(" AND MAP_GROUP= ?");
parameters.put("1", original);
parameters.put("2", "LAB_CODES");
@SuppressWarnings("rawtypes")
Vector vectorBD = accessBdNonXa.lookup(sbQuery.toString(), parameters);
logger.debug(" Query bbdd" + sbQuery.toString() + " " + parameters);
if (vectorBD != null && vectorBD.size() > 0) {
@SuppressWarnings("rawtypes")
HashMap hData = (HashMap) vectorBD.get(0);
result = (String) hData.get("COD2");
}
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
e.printStackTrace();
} finally {
if (accessBdNonXa != null) {
accessBdNonXa.close();
}
}
return result;
}
So, first off, the AccessBdNonXa
class is the completely in-house, rolled from the ground-up data access wrapper. Sergio didn’t provide any of that code, but it’s a reinvented wheel.
Beyond that, this isn’t good code, but it’s hardly a true WTF. One has to wonder why they use a StringBuffer
to construct a one-line query. I like the leading whitespace there, as it shows that there’s probably some copy-paste in its history, and the one character has been propagated everywhere that block is needed.
In fact, I know it was copy-pasted in a bunch of places.
That’s where this line comes in: parameters.put("2", "LAB_CODES");
A hard-coded value for a SQL query parameter should instantly make you suspicious.
What happens if you want to, I dunno, map city codes?
public static String MapDetGroup(String original) {
String result = "";
Logger logger = new Logger("ThisClass.MapCities");
StringBuffer sbQuery = new StringBuffer();
AccessBdNonXa accessBdNonXa = new AccessBdNonXa();
HashMap parameters = new HashMap();
try {
sbQuery.append(" SELECT COD2 FROM MAPPINGS ");
sbQuery.append(" WHERE COD1= ?");
sbQuery.append(" AND MAP_GROUP= ?");
parameters.put("1", original);
parameters.put("2", "GROUP_CITY_CODE");
@SuppressWarnings("rawtypes")
Vector vectorBD = accessBdNonXa.lookup(sbQuery.toString(), parameters);
logger.debug(" Query bbdd" + sbQuery.toString());
if (vectorBD != null && vectorBD.size() > 0) {
@SuppressWarnings("rawtypes")
HashMap hData = (HashMap) vectorBD.get(0);
result = (String) hData.get("COD2");
}
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
e.printStackTrace();
} finally {
if (accessBdNonXa != null) {
accessBdNonXa.close();
}
}
return result;
}
There is one version of this method, copy/pasted, for each kind of category that could be looked up- at least eight at the moment, but these methods also aren’t quite centralized in any one place, so there honestly may be more, and as new codes and categories for codes are added, more of these methods will need to be added.
If only there were some tool that they could use, some way to parameterize this query, so that they could write the method once and pass different parameters to it. If only…
Метки: CodeSOD |
Newly Singleton |
Shery was brought on to help with a project which was “going well”. “Going well” meant that all the little icons on the project management dashboard were green, which is one of the most effective ways to conceal a project which is struggling.
Shery’s component was large, and it was complicated, and it had a well defined interface to the rest of the application. Specifically, they had a documented JSON message format which her code would receive via JMS. That meant she could do the vast majority of her work in isolation, without talking too much to the existing team, so she did.
But the day came when they were going to send her messages. And the very first message her code received was a JSON document which looked like this: {}
.
That wasn’t correct. Shery’s code dutifully handled and logged the exception, and she took it on herself to diagnose the problem. She pulled up the code from the other part of the team.
The first thing Shery noticed was all the code copy/pasted from StackOverflow. She could tell it was copy/pasted because that was the only code with any sort of sane indenting. All the code developed in-house used indenting stochasticly. One group of developers had clearly turned off their autoindenting in the IDE, another group hadn’t, and the result was a mishmash.
Most of the code was clearly done via copy/paste. If someone wrote a block of code in one section of the application, and someone else needed that functionality, they’d just copy/paste it around. There were miles of unused imports at the top of pretty much every file, there were statements following the pattern if (someCondition) { } else if (theExactSameConditionAsTheIf) { }
. Suffice to say, there were nearly as many warnings as there were lines of code.
Shery decided she wasn’t going to debug or alter their code. Instead, she raised the issue she was seeing- empty messages- and politely suggested that she had noticed some “non-compliance” with the company’s coding standards which should probably be addressed, at some point.
While she was busy looking at the other team’s code, someone from the other team was looking at her code. And when she checked source control, there was a fresh commit at the head of the branch where they “fixed” some of her issues.
Shery had an object which provided a service. This object was itself stateful. That state should be the same everywhere in her component. So Shery created a Singleton.
Setting aside the concerns of managing any sort of global state, even in a Singleton, they were doing this in Spring. Spring, like most Java containers, has all sorts of features and functionality to manage object lifecycles. In “pure” Java, if you wanted a Singleton, you might do something like this:
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static Singleton getInstance() {
if (instance==null) {
instance=new Singleton();
}
return instance;
}
public void doSomething() {...}
}
It’s a load of boilerplate, but now you can interact with it by calling: Singleton.getInstance().doSomething()
. No matter where you are in the application, you’ll always be interacting with the same instance.
Java frameworks, like Spring, exist, in part, to hide this boilerplate from you. Spring lets you make components, and assumes that the default scope for that component should be singleton, so this:
@Component
public class Singleton {
public void doSomething() {...}
}
Is the same as before, but with a lot less garbage code.
Even better, if a class wants an instance, they can define the property like this:
@Autowired
private Singleton svc;
At runtime, the container will managed the instance, and when anyone requests it (using decorations like @Autowired
) the container will inject the correct instance. This is the “right way” to do this sort of thing in Spring.
When Shery checked the commit her peers had written, they had done this instead:
@Component
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static Singleton getInstance() {
if (instance==null) {
instance=new Singleton();
}
return instance;
}
public void doSomething() {}
}
In essence, they mixed the pure Java approach and the Spring approach to accomplish nothing. For bonus points, they kept the autowired svc
variable, but would interact with it like this:
svc.getInstance().doSomething()
.
Shery never got a chance to have a conversation with the developer responsible for that change. The project continued to “go well”, you see, so well in fact that half the team was sacked and the project was moved to another department to be taken to completion. Don’t worry, the dashboard continued to show green for all of their key-performance indicators.
Метки: Feature Articles |
CodeSOD: A Very Personal Role |
Nohemi has a program which needs to apply role-based security. Due to their organizational needs, the rules for role names are a bit unusual. Some roles have to be a case-insensitive match. But some roles have a more flexible pattern they need to have. This is how her co-worker implemented this:
public static String decodeRole(String role) {
String decodedRole = "";
if (role != null && !role.trim().equals("")) {
if (role.trim().equalsIgnoreCase(ROLE_1_STRING))
decodedRole = CODE_ROLE_1;
else if (role.trim().equalsIgnoreCase(ROLE_2_STRING))
decodedRole = CODE_ROLE_2;
else if (role.trim().equalsIgnoreCase(ROLE_3_STRING))
decodedRole = CODE_ROLE_3;
else if (personalContains(role.trim(), ROLE_4_STRING))
decodedRole = CODE_ROLE_4;
}
return decodedRole;
}
Here's the key method which does this translation. Roles 1, 2 and 3 must be an exact match. Role 4, on the other hand, has to apply their special rule, a rule so complicated it can only be evaluated via a regular expression:
private static final String REGEXP_SUFFIX = ").*$";
private static final String REGEXP_PREFIX = "^.*(";
public static boolean personalContains(String fatherString,
String toSearchString) {
Pattern p = Pattern.compile(REGEXP_PREFIX + toSearchString.toLowerCase()
+ REGEXP_SUFFIX);
Matcher m = p.matcher(fatherString.toLowerCase());
boolean matchFound = m.matches();
if (matchFound)
return true;
else
return false;
}
It's a contains check. If your search string were, say, ROLE_4
, the constructed regex would be ^.*(ROLE_4).*$
, which is "the start of the string, followed by zero or more of any character, followed by ROLE_4
, followed by zero or more of any character, followed by the end of the string.
It's a contains check. This isn't even the right way to do this is regular expressions, since ROLE_4
would be a regex which matches if any substring is ROLE_4
, but regexes aren't the right way to do this in the first place, since Java has a lovely String.contains
method already. The entire personalContains
method could be removed.
The real WTF, though, is that instead of returning m.matches()
, they have to do this:
if (matchFound)
return true;
else
return false;
Метки: CodeSOD |
Error'd: BSOD with a Side of Fries |
"Yes, I'd like to have a Quarter Pounder meal with a Coke and a Blue Screen of Death on the side. To go," Bruce W. writes.
"Am I going to be highly satisfied with my XPS system temperature? Well, Dell, I just don't know yet!" writes Ben S.
Steve M. wrote, "According to Chrome-based Microsoft Edge Dev channel, it's got 0 x 256 problems, but revealing my name ain't one."
"Dear Micsrosoft - Change text to 'bowling alley', please and thank you," writes Maxwell D.
Kosti J. wrote, "Wow! Some great deals in this AliExpress campaign, such as ...a cycling clothing set at a 138% markup!"
"At work, we use a pretty filthy iPad to work our coffee machine. Right now, there's an iOS update between me and my caffeine...and, well, I don't like it," Roger G. writes.
Метки: Error'd |
Representative Line: What Am I? |
Object oriented programming is the weapon of choice for many programmers, and when wielded properly, you can often rely on a mix of convention and strong types to make it clear what type of object you’re working with. Sometimes though, you need to check. In a language like Java, you have the instanceof
operator, a boolean comparison which answers if obj instanceof SomeClass
. Seeing a lot of that in a codebase is a clear code smell.
Sometimes, though, not seeing it is the code smell.
Chris S spotted this pattern repeatedly in their codebase:
setHasTypeCorporateAccountOrTypeCustomerAccount(hasTypeCorporateAccount() || hasTypeCustomerAccount() || hasTypeCorporateAccountOrTypeCustomerAccount());
This code, inside of the Account
class, is calling a set
method to set the value of hasTypeCorporateAccountOrTypeCustomerAccount
. It determines that based on related boolean values- hasTypeCorporateAccount
and hasTypeCustomerAccount
, and of course hasTypeCorporateOrTypeCustomerAccount
(the value being set).
As you might have guessed, this creates a situation where the boolean flags are used for a weird version of polymorphism elsewhere in the codebase, e.g., if (acct.hasTypeCorporateAccount()) {…}
. That’s already ugly, but as implemented, the underlying fields are just boolean values, and the set
methods just directly set the values, with no checks. So it’s entirely possible to have hasTypeCorporateAccount()
return true, while hasTypeCorporateAccountOrTypeCustomerAccount()
return false. And sometimes, that happens, and is the root cause of a bunch of different bugs.
Chris can change the getters to make this behave rationally, but fixing the root cause- the person who wrote this- might be a little harder.
Метки: Representative Line |
The Support Game |
In the 1970s, shortly before our friend Argle dared to do exactly what his boss asked of him in an efficient manner, he worked at the computer lab of a local community college. When his friend Terry was hired on as a new assistant, Argle sat down with her at the Tech Support desk for a run-down of hard-earned knowledge and best practices.
"The real trick to this job," Argle wrapped up, "is to realize that the only four answers you ever need to give are 'Yo,' 'Oh,' 'So,' and 'No.'"
"What?" Terry's brow furrowed. "That would never work!"
As if on cue, a student came up to the help desk just then. "Excuse me?"
"Yo!" Argle greeted, turning to face him.
"Professor Goddard wants me to use the astronomy tutorial," the student said.
"Oh?" Argle prompted.
"Well, I don't have time today."
"So?"
"Can I have it on a floppy disk to do it later?" the student requested.
The tutorial in question was a slick program written for the PDP-11 that ran on purely text-based dumb terminals. It was ingenious for the technology of the day, but not compatible with an Apple ][ or Commodore Pet, the likely targets for the student's use.
Argle wasn't about to over-explain anything to someone who wouldn't get it. "No, I'm afraid that's not possible," he said. "Sorry."
The student looked disappointed, but shrugged. "OK, thanks."
As he walked away, Terry doubled over in her chair and bit her lip, fighting off hysterics.
With her training complete, Terry was now ready to run the help desk herself. When her first client—a professor—approached, she knew exactly how to handle the matter. "Yo!"
Метки: Feature Articles |
CodeSOD: Never Refuse a Fifth |
Sometimes, you want your dates to look like this: 3/3/2019
. Other times, you want them to look like this: 03/03/2019
.
There are plenty of wrong ways to do this. There are far fewer right ways to do it, and they mostly boil down to “use your language’s date library.”
And then there’s this, which “QuakePhil” found.
function convertSingleDigitMonthOrDayToTwoDigitMonthOrDay($date) {
$month = '';
$day = '';
$secondChar = substr($date, 1, 1);
$thirdChar = substr($date, 2, 1);
$fourthChar = substr($date, 3, 1);
$fifthChar = substr($date, 4, 1);
if ($secondChar === '/') {
$month = '0' . substr($date, 0, 2);
} else if ($secondChar !== '/') {
$month = substr($date, 0, 3);
}
if ($thirdChar === '/' && $fifthChar === '/') {
$day = '0' . substr($date, 1, 2);
} else if ($secondChar === '/' && $fourthChar === '/') {
$day = '0' . $thirdChar . '/';
} else if ($secondChar === '/' && $fifthChar === '/') {
$day = substr($date, 2, 3);
} else if ($thirdChar === '/' && !in_array('/', array($secondChar, $fourthChar, $fifthChar))) {
$day = substr($date, 3, 3);
}
return $month . $day;
}
It’s worth noting that this project had just undergone a massive refactoring effort, and somehow this particular method snuck through.
It’s an… interesting approach to the logic. Instead of splitting the string on "/"
(which would still be wrong, but at least make sense), we check which position the "/"
falls. But we don’t do that by searching the string or indexing the string, but by doing a pile of substr
calls and saving the result.
Phil suggested changing the code to something more reasonable, but since there had just been a massive refactoring project, nobody want to make any code changes which weren’t new features.
Метки: CodeSOD |
An Excelent Start to a Career |
Hal was a wiz kid computer programmer at age 15 in 1976. He could make the toggle switches and LEDs on his Altair 8800 dance at will. In college, he was class valedictorian after earning his computer science degree in 1984. Hal was destined for greatness and the real world was about to get rocked.
Hal's college friend Victor, who graduated two years prior, was already running his own startup company that made Unix-based financial planning software. Remembering Hal's brilliance, Victor recruited him to join his company the day after graduation. Victor needed the wiz kid-turned-wiz adult to create the equivalent of Lotus 1-2-3 in Unix. It was a tall first project but it paid well, so Hal happily signed up. Besides, everyone knew that spreadsheets were gonna change the world.
Hal was so full of ideas, he felt like he could take Lotus 1-2-3 and make it better. He built Unixus 3-2-1 from scratch and vowed to turn it into a fun and useful program. Surely it was bound to make Victor's company millions upon release.
Victor didn't seem to notice Hal's efforts, though. He was seldom in the office any more, the parking spot for his Porsche frequently sitting empty. Whenever Victor was actually around, he seemed disheveled and claimed to be preparing for an important sales trip. There were myriad rumors going around that he was actually out partying with the cashflow from their first big sales.
One day, Hal came in to work only to find the doors chained shut. A note from Victor was taped to the door. In it, he explained how the company was going bankrupt and he had to sell it for pennies on the dollar to a larger tech firm. All of the company's assets now belonged to the new ownership, and everyone was out of a job. Hal felt crushed that he lost his first job in addition to his source code for Unixus 3-2-1.
Many years went by and Hal moved on to more stable employment. He initially held a strong resentment towards Victor but it gradually faded with each passing year. Victor too had found himself some more stability and his hard partying days were over. Victor and Hal eventually reconnected through a mutual friend.
"Hal! Good to see you, old buddy!" Victor shouted, rising from the restaurant table he reserved for them. Hal shook his hand, less enthusiastically than Victor did. "Hey, I just want to say right away that I'm sorry about the whole company closure way back when. I was young and immature and I felt so bad about costing everyone their jobs," Victor dropped his head, showing that his 80's permed hair was long gone.
"Hey, don't worry about it. It wasn't a big deal," Hal downplayed, failing to mention how he used to throw darts at a picture of Victor. Hal started to open up more while they reminisced about wild college stories. Victor's memory of things… was a bit different. Specifically, he was always the center of every story. He always was the big winner in every bit of college hijinks, and if Hal remembered anything embarassing about Victor, it was Hal's memory that was faulty- Victor was King of the School.
They eventually got around to talking about Unixus 3-2-1. "I know it was my first real project, but I felt like my spreadsheet program was really something. What ever happened to all the source code from that place?"
Victor laughed, "Oh Hal, you won't believe this! Most of our products were scrapped or hacked to bits and repurposed. But they realized Unixus 3-2-1 had potential. They added a few finishing touches then packaged it up and sold it off. Boy, I wish I could have gotten a better deal when I sold the joint. If I had only known…"
Hal failed to find any of that unbelievable. "Well, at least it made someone some money," Hal lamented. "Do you have any idea who they sold it to?"
"Well now, here's the crazy part," Victor paused before drawing a breath. "There was this up and coming software company around that time that rhymes with Bike-rosoft that was eager to get their hands on it. They took what you made and it became the core of a little program called Excel."
Hal instantly started to regret reuniting with Victor. Whether he was full of crap or not, Hal's resentment came flooding back during the rest of their lunch together. He made an excuse to leave right after the check came, hoping to avoid something like Victor claiming he helped Al Gore create the internet.
https://thedailywtf.com/articles/an-excelent-start-to-a-career
Метки: Feature Articles |
Error'd: Every System's Preferences |
Rob W. wrote, "Looks more like this process is responsible for customizing the entire solar system's preferences."
"I know it's confusing to cross the international date line on New Year's Eve, but I'm sorry, Expedia, adding -364 days is not the solution to that problem," Bruce R. writes.
"An elevator in the U.S. is rejecting the Euro? Sounds like it could be a touch of Brexit envy," writes Kevin O.
"You know, I wanted to buy this from Amazon, but I guess that I'm just not old enough!" Phil wrote.
Philip B. writes, "Thanks, but I'll just take the regular price, please."
Christopher K. wrote, "After showing off my Quicken screen, Bezos says that if he had my money, he'd burn his."
Метки: Error'd |
CodeSOD: Sorting Out a Late Night |
Karl’s trials of crunch (previously) didn’t end with a badly written brain-fart. After too many consecutive late nights, Karl noticed that their grid layout was wrong.
It did this:
Alpha | Beta |
Delta | Gamma |
Karl expected the alphabetic layout to be more like:
Alpha | Delta |
Beta | Gamma |
Specifically, this was the layout for a few checkboxes controlling how their system would autogenerate filenames. The field layout was not considered a problem by anyone, other than Karl, and certainly wasn’t the desperate issue that was driving the crunch.
After a few hours poking at this and having no real success, Karl did the natural thing: he roped in one of his co-workers. The two of them stared at the code, poked, prodded, cursed, made stupid syntax errors, and drank too much coffee.
By the time the sun rose, they had solved the problem, which again, wasn’t a problem anyone else had identified, with this masterpiece of .NET MVC:
HashSet fields = Util.GetAllFields(namingConvention, out nameConv);
displayNames.Add(new KeyValuePair("ArchiveImport", fields.Contains("ArchiveImport")));
foreach (var property in typeof(GuiImport).GetProperties()
.Where(g => g.PropertyType != typeof(bool) && g.PropertyType != typeof(bool?)).OrderBy(g => g.Name))
{
displayNames.Add(new KeyValuePair(property.Name.Trim(), fields.Contains(property.Name)));
}
//SNIP
//NEVER CHANGE THIS
#region DANGER
ViewBag.Lines = (int)Math.Ceiling((decimal)displayNames.Count() / 4);
int startAtColumn = (displayNames.Count() % 4) + 1;
for (int i = startAtColumn; i > 1 && i <= 4; i++)
{
if (i == 4) displayNames.Add(new KeyValuePair(null, false));
else displayNames.Insert(i * ViewBag.Lines - 1, new KeyValuePair(null, false));
}
ViewBag.PropertyNames = displayNames;
#endregion
When you see GetProperties
, you know you’re looking at .NET’s reflection API, and that means there’s a pretty high probability someone is doing things they shouldn’t be. Beyond that, we’ve got a weird fumbling attempt to sort in a non-conventional order.
Karl also promises: “ViewBag.PropertyNames is iterated through on the cshtml page to populate it in a wildly awful mix of logic and display which MVC is supposed to help prevent,” but didn’t supply that code.
But the clearest product of any late night coding session while coasting on the edge of burnout is:
//NEVER CHANGE THIS
#region DANGER
The real danger, of course, is the kind of working environment that leads to these kinds of late hours and their attendant mistakes.
Метки: CodeSOD |
How The Semester Ends |
Ginger recently finished an advanced degree, and during her work, she of course had to work as a TA for a number of classes. Computer science professors are, at least in theory, capable of programming, and thus can build automation around assignments- providing students with templates to highlight specific tools and techniques, or to automate the process of grading.
Dr. Buchler taught the computer graphics course, and the ultimate assignment was to build a simple 3D game. Buchler provided a pre-scaffolded project with a set of templates that could be filled in, so the students didn’t need to worry about a lot of the boilerplate. Beyond that, Buchler didn’t offer much guidance about how students should collaborate, so students did what came naturally: they set up git repos and shared code that way.
The students who used Git, which was essentially all of them, started contacting Ginger. “My code is broken!” “It worked, on my machine when I wrote it, but now it doesn’t! I haven’t changed anything!”
Obviously, there must be an issue with the professor’s template, but when Ginger mentioned this to Buchler, he dismissed the concern. “I’ve been using this template for years, and have never had a problem. The students must have errors in their code.”
Ginger worked closely with one of the student groups, and if there were errors in the code, she couldn’t see them. And what immediately leapt out to her was that code which worked would suddenly break- but it only seemed like it happened after a commit.
The core pattern was that the students would write a fragment of a shader, and then the project would merge their fragment with a surrounding template to create a full GLSL shader that could actually execute, akin to how Shader Toy injects some additional code around your key logic.
Now, when loading code into the template, Buchler had written something like this: String[] vscr = new Scanner(Paths.get(ShaderProgram.class.getResource(shader).toURI())).useDelimiter("\\Z").next().split("\r\n");
There was no real reason for the split, but Buchler wanted to use an array of lines instead of a blob of text. That was also the source of the problem.
The split would remove Windows line endings from the students’ code. For the students, who were frequently on Windows, this meant that when their shader got loaded, all the newlines would get stripped from their code.
This meant a simple shader, like:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
vec3 col = abs(vec3(sin(uv.x * 50.)));
fragColor = vec4(col,1.0);
}
would get mashed into: void mainImage( out vec4 fragColor, in vec2 fragCoord ){ vec2 uv = fragCoord/iResolution.xy; vec3 col = abs(vec3(sin(uv.x * 50.))); fragColor = vec4(col,1.0);}
. Not elegant, but syntactically valid.
Of course, if the students used any #define
directives, or included single-line comments, that could easily blow up their shaders. But that didn’t explain why, for many, the code worked until a commit.
Remember, Buchler’s system would mash the students’ shader code into his own template. His template was, at least in its current form, using Unix-style line endings- just \n
, not \r\n
. So the template wouldn’t have its line endings stripped out, at least not by default. Until the students committed the repo. At that point, the entire shader would be put on a single line, and Buchler's template did include some #define
statements.
Git has a core.autorcrlf
parameter. It makes sure that the version stored in the commits is using a Unix-style LF, but the version stored on your filesystem uses CRLF on Windows. It’s a change you’d never notice, as any text editor worth its salt is going to transparently handle whatever line-ending character its offered, but it emphatically breaks Buchler’s template.
Ginger showed the students how to update the template project to avoid this particular bug, and told Buchler what she’d found. “You could just remove the split
and it’d be fine,” Ginger suggested.
“I’ll look into it, I guess,” Buchler said.
Students finished their projects, Ginger moved on, but near the end of the following semester, she started hearing students complaining: “I’m doing Buchler’s graphics class, and my code just stopped working for no reason.”
Метки: Feature Articles |
CodeSOD: Assert Yourself |
Chris V does compliance testing. This often means they trace through logic in code to ensure that very specific conditions about the code’s behavior and logic are met. This creates unusual situations, where they might have access to specific and relevant pieces of code, but not the entire codebase. If they spot something unusual, but not within the boundaries of their compliance tests, they just pass on by it.
One of the C++ code bases Chris had to go through featured this “defensive” pattern everywhere.
if (someConditionalStatement)
{
// do stuff
}
else
{
runtime_assert(false && "some condition was not met");
}
Chris doesn't have access to runtime_assert
's definition. It *is* possible to `&&` a boolean and a string together- you get a boolean result, though. So the `"some condition was not met"` message just vanishes, as if it weren't there. Literally, this is just runtime_assert(false)
. Which, excepting the extraneous string, almost makes sense, if you want the failed condition to trigger some exception/error pathway.
Then there were these variations on the pattern:
if (someConditionalStatement)
{
// do stuff
}
else
{
runtime_assert(someConditionalStatement);
}
Which, again, essentially just means runtime_assert(false)
. And again, this almost makes sense. As Chris explains:
I mean, it works. It all works. It’s just a weird and unnecessary way to write it. …I get the feeling they have heard about error checking and defensive programming, but haven’t heard about exceptions and exception handling?
Метки: CodeSOD |
CodeSOD: One Way to Solve a Bug |
Startups go through a number of phases, and one specific phase is the transition from "just get it done and worry about the consequences tomorrow" into "wait, maybe if we actually did some planning and put some process around what we do, we won't constantly be one step behind the current disaster."
And that's when they start to hire people who have more management experience, but are also technical enough that they can contribute to the product directly. At BK's company, the latest hire in that category is Sylvester.
Sylvester is the new team lead, and he comes from a more "enterprise" background, which means he's had a very difficult time getting up to speed, and is unwilling or uncomfortable to make decisions with limited information. And also, Sylvester might not be particularly good at the job.
BK noticed that Sylvester had a commit sitting in code review, and it had been sitting there for some time, so they took a look. One of the first things they spotted was a method called SolveBug
, which made it clear they were in for a "treat".
void SolveBug (std::vector container){
std::sort(container.begin(), container.end());
const auto it = std::unique(container.begin(), container.end());
if (it != container.end()) {
container.erase(it);
}
}
So, one of the business rules about the vector of BusinessEntity
objects is that the entries should be unique. BK skimmed this method, and without looking at it too deeply, and without knowing the definition of std::unique
, made some assumptions based on the code. The std::unique
function must return the first duplicate, and then we erase it.
That couldn't be right though, because what if there were multiple duplicates? So, BK did what Sylvester obviously didn't, and did a quick search. std::unique
returns an iterator where consecutive duplicates are removed.
BK, being generous, assumed they were missing something, and asked Sylvester what that might be.
"Oh, thanks for spotting that! That's probably why it doesn't work."
Apparently, Sylvester had pushed code he knew was broken, and didn't mention it to anyone. He didn't ask anyone to take a look beyond submitting a code review. He didn't bother to check the documentation, and certainly didn't think about the code he was writing.
Perhaps "SolveBug" was less a description of what the method did, and more what Sylvester hoped the code review would do? In that case, the method was very accurately named.
Метки: CodeSOD |
Error'd: Watch the Skies! |
"In light of the imminent UFO strike, I may need to reconsider my flight plans...or leaving my house in general," writes Pedro.
Howard wrote, "I guess, in Dell's eyes, it's their site and anything can be on sale if they say it is."
"Hmmmm...Do I really trust Google Play Music to manage my Google Play Music library?" Ryan S. writes.
Rob K. writes, "I'm guessing I should take advantage of the sale price before Dan gets back to his computer."
"Saying that the app isn't working correctly is a bit obvious in this case," writes Jerry.
Adrien wrote, "The exception L'op'eration a r'eussi (Operation succeeded) being thrown leads me to believe there is nothing wrong here and this application is simply intolerant of the French language."
Метки: Error'd |
CodeSOD: Overlapping Complexity |
After his boss left the company, Joel C was promoted to team lead. This meant that Joel was not only responsible for their rather large production codebase, but also for interviewing new potential team members. There are a ton of coding questions that one can ask in a technical interview, and Joel figured he should ask one that they actually solve in their application: given two unordered sets of timestamps, calculate how much overlap (if any) is between the two series.
If you think about it for a minute, it's really quite simple: first, find the minimum and maximum values for each set to get the start and end times (e.g. [01:08:01,01:09:55] and [01:04:11,01:09:42]). Then, subtract the later start time (01:08:01) from the earlier end time (01:09:42) to get the overlap (01:09:42 - 01:08:01 = 00:01:41). A non-positive result would indicate there's no overlap (such as 12:00:04 - 13:11:43), and in that case, it should probably just be zero. Or, in a single line of code:
return max(min(max(a), max(b)) - max(min(a), min(b)), 0)
Of course, something more spaced out might help with readability, but Joel saw a lot of candidates overthink the problem. They would sort the lists, create unneeded temporary variables, not understand that they really only need the first and last elements of the list, etc. In many of those cases, Joel judged candidates quite harshly; it's a simple problem and if this confuses them, how could they handle more complex problems?
A handful of candidates recognized the problem for how simple it was, but one went so far as to ask, "there are a ton of ways to solve this in code; here's my solution, but I'm really curious how you solved it?"
Joel wasn't really sure how his former boss solved the problem. After spelunking through the codebase, he found out:
def compute(dev_a, dev_b): labeled_timestamps = [] for label, dev in ('a', dev_a), ('b', dev_b): for t in dev.timestamps: labeled_timestamps.append([t, label]) labeled_timestamps.sort() last_label = None start_overlap = None end_overlap = None last_t = None for t, label in labeled_timestamps: if last_label is not None and last_label != label: if start_overlap is None: start_overlap = t else: end_overlap = last_t last_label = label last_t = t if end_overlap is None: end_overlap = start_overlap overlap_time = pd.Timedelta(end_overlap - start_overlap, 's')
"I have seen some overly complex answers," Joel wrote, "but this is a level of overthinking that is beyond impressive. If someone had given me this in an interview, I would probably have been left completely dumbfounded, and submitted this as a Tales from the Interview. Instead... I just shut down my computer and started my weekend drinking a little early."
Метки: CodeSOD |
The Most Secure Option |
“The auditors have finished examining our codebase.”
That was how Randy’s boss started the meeting, and she delivered the line like a doctor who just got the tests back, and is trying to break the news gently.
After someone in another department did the whole “I found a thumb drive in the parking lot, let me plug it into my work laptop!” thing, management realized that they hadn’t done any kind of security evaluation in years, and brought in a bunch of highly paid consultants to evaluate their practices. Part of that meant doing audits of their software portfolio for compliance with the new security standards.
Now, Randy’s boss was running a cross-functional meeting- developers, operations, and even a few support desk representatives, to review the audit results. Most of the hits they took on the audit were the kind of slipshod stuff that accrues over years of under-budgeted, over-specced projects. Passwords stored in source control. A few SQL injection vulns. But the one that seemed like an easy win was the fact that they didn’t use any SSL on their web applications.
“Oh, we should be able to fix that, easy,” Randy said.
“Oh, we should, should we?” Benny, the sysadmin said. He leaned over the table, with his hands clasped. “How many SSL certs have you provisoned?”
“Well, a bunch, I’ve-”
“Because I have, and it’s no walk in the park, and it’s very expensive.”
Randy blinked, and glanced over at his boss. She didn’t have anything to add.
“That’s… not true?” Randy said. “It’s not that expensive to buy a cert, but we can also go with LetsEncrypt, which is free.”
“Ah ha!” Benny said. “It’s very expensive to do it right. You can’t just use some service from the Internet. We’re here to talk about our security audit, and using LetsEncrypt is not possible. Anything hosted externally and accessible via the Internet poses a huge organizational risk. Free SSL from the Internet is an easy target for a hacker.”
“Right,” Randy’s boss said. “We’ll table this for now, but it looks like we probably won’t add SSL until we have a better sense of the costs.”
“My advice is that we don’t use SSL at all,” Benny said. “That will be more secure than what Randy’s proposing.”
The audit happened early this year. No one has yet formulated a plan to move to SSL.
Метки: Feature Articles |
Representative Line: Time Dilation |
A good variable name is clear and specific about what the variable does. But sometimes you can have a variable name that's perhaps a little too specific. Victoria found this representative line of Rust code:
let threeseconds = time::Duration::from_secs(60);
Time certainly can stretch when you're deep in debugging, and three minutes can feel like an hour. And as you cross that event horizon, you start asking yourself questions. It's easy to understand how this code came to be: they though they needed three seconds for some task, but actually needed 60. They'd already made the variable name, and didn't want to trace through changing it everywhere.
But that's not really an explanation. Victoria shares her questions:
I then started wondering "so how did that code come to be? What kind of problem required them to think they'd need three seconds, but then bump that value up to 60?". That went a bit further with "So, judging by this representative line, what would the rest of the code look like, how would it even work?". I still haven't figured that last part out.
Don't worry, Victoria, if you need more time to work on understanding it, I know a great programming trick to make more time…
Метки: Representative Line |