CodeSOD: Extending Yourself |
Optional parameters are a great tool for building flexible APIs. In most languages, they're not strictly necessary- if you have function overloading, the difference between optional parameters and an overloaded function is just the quantity of boilerplate- but they're certainly a nice to have.
Well, they're a nice to have in the right hands.
Scattered through their code base, Ian P saw many, many calls to dutData.GetMessages(). As they explored the code base, they used Visual Studio's "go to definition" feature to jump to the implementation- and found they were sometimes ending up in different spots.
public static IEnumerable<string> GetMessages(this ScreenScraper screenScraper)
{
//Do some stuff to get the messages
}
//snip 100+ lines
public static IEnumerable<string> GetMessages(this ScreenScraper screenScraper, long timeSpane = 30000, int timeInterval = 500)
{
//Do some different stuff to get the messages
}
Frankly, I'm a little surprised that this is even possible. It offered warnings, but no errors, presumably because this is an odd bit of syntactic sugar in C# called "extension methods", which are C#'s answer to "monkey patching". Neither GetMessages is actually a member of ScreenScraper, but if you call GetMessages on a ScreenScraper object, C# will call GetMessages and pass an instance of ScreenScraper into it.
The C# compiler is the real WTF. As for Ian, the fix was simple: rename some methods for clarity, and convert extensions to instance methods as appropriate.
[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 |
Representative Line: You Can Only Get What You Have |
Sean's ongoing career as a consultant keeps supplying him with amazing code.
Let's talk about encapsulation. We put getters and setters around our objects internalsto help keep our code modular. Given an object, we call something like getUsername to extract a piece of that object safely.
In the same codebase as IsTableEmpty, Sean found this short-but-sweet gem:
public String getUsername(String username)
{
return username;
}
This version of getUsername requires you to already have the username. Oddly, it behaves unpredictably, as if you pass it a postal code, it returns the postal code, and doesn't complain that it isn't a username.
https://thedailywtf.com/articles/you-can-only-get-what-you-have
|
Метки: Representative Line |
CodeSOD: Is This Terning Into a Date? |
Bad date handling code is like litter: offensive, annoying, but omnipresent. Nobody is specifically responsible for it, nobody wants to clean it up, and we end up ignoring it.
Murtaza K offers up a particularly malodorous example, though, that's worthy of note. It's got everything: string mangling, assumptions that ignore locale, bad ternaries, and it's completely unnecessary.
string Stdt = String.Empty;
string FromDt = stDtTime.ToShortDateString();
string[] varFromDate = FromDt.Split('/');
string mth = Convert.ToInt32(varFromDate[0]) < 10 ? varFromDate[0] : varFromDate[0];
string date = Convert.ToInt32(varFromDate[1]) < 10 ? varFromDate[1] : varFromDate[1];
FromDate = mth + "/" + date + "/" + varFromDate[2];
Stdt = FromDate;
Let's start with "unnecessary". Assuming a US-like locale, or as I like to call it, "middle-endian dates", Stdt will contain what was in FromDt right at the start, rendering all of this pointless. Pointless, anyway, if your goal is to handle dates. But there's so much else special about this, and specifically, I'm talking about those ternaries.
string mth = Convert.ToInt32(varFromDate[0]) < 10 ? varFromDate[0] : varFromDate[0];
I have a hunch about what's going on here. Imagine, if you will, you don't actually know how to write software. What you do know is a vague bit about how to use Visual Studio, and how to search Stack Overflow. Over the years of your career, you haven't really learned anything or gotten better at your job, but you have built an increasingly complex pile of code snippets you've copy/pasted from Stack Overflow. They've mutated as you try and adapt them to new scenarios.
So, what is in varfromDate[0]? Why, a number. I mean, the datatype is string, but you don't understand what that means. You just know it's a number. So you know you need to convert it to a string to do some string concatenation. One of your many snippets was a homework question about converting numbers to strings. The goal was to teach students how to do modular arithmetic to get the ones place, tens place, etc., but the snippet you copied was a bad answer which used individual ternary blocks to pick off individual digits.
You don't know what it is, or what you're doing, but you copy that code in blindly. It doesn't work. You tweak things until it does, and in this case, you return the same value from either branch.
Then you mash the strings back together to make a date. Once it works, you copy/paste it back into the Word document you use for tracking all your "knowledge". You can now reuse this solution in the future.
Do I know that's what happened? Of course not. Is that still probably what happened? Yeah, I think so.
[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/is-this-terning-into-a-date
|
Метки: CodeSOD |
Leaky Fun For the Whole Family |
Those of us that had the luxury of learning to program in C or other non-auto-gc'd langauges, learned early on the habit of writing the allocation and deallocation of a block of memory at the same time, and only then filling in the code in between afterward. This prevented those nasty I-forgot-to-free-it memory leaks.
Of course, that doesn't guarantee that memory can't ever leak; it just eliminates the more obvious sources of leakage.
Daniel worked on an installation for a theme park attraction sometime back. The task was to use a computer vision system to track the movement of a number of moving projection screens, and then send the tracking data to a video system in order to move the image around within the projection raster to keep it static on the screen.
He was using a specialised product designed for industrial machine-vision applications. It was essentially a camera with a small Windows XP machine inside, which ran a custom application developed using a commercial machine vision programming toolkit. The software looked for the position of 4 infra-red LEDs using the camera and then output their coordinates via ethernet to the video system.
After the installation was complete, Daniel was back in the home office when he got a call from the park. Apparently the camera software was crashing after around 10 days of uptime. He remoted in and saw the cause was an out of memory failure. This was his worst nightmare.
A nice feature of these boxes was that they had a mechanism to essentially 'lock out' changes from the hard drive. After the system was setup and working, an option was enabled which diverted all hard drive writes to RAM, discarding them on a reboot or power down. This had the advantage the camera system didn't require any software maintenance as it would be 'fresh' every time you turned it on, which is great for an installation that could be in place for over 10 years. However he was concerned about what this might mean for uptime as any process which repeatedly wrote any significant amount of data to the drive would quickly fill up the 1GB of RAM available on the cameras. Around 60% of this was used by the running process anyway, so there wasn't a huge amount of headroom.
This wasn't a large source of concern when developing the installation as there was an understanding that the system would be powered down every night when the park was closed (which would have been the easy solution to this problem). He'd noticed that the RAM was filling up slowly as the camera ran, but not at a rate which would be dangerous for the target uptime of 24 hours.
Unfortunately, the daily power cycle didn't happen, and any kind of system failure caused the on-site techs to get nervous; clearly this was an issue that would have to be fixed the hard way. He was convinced that the drive lock-out feature was the cause of this issue.
From then on, every 10 days, when the park was closed, Daniel remoted in and tried various steps on the cameras to find the process that was writing to "disk". The complete installation had 6 cameras so he would try different steps on each system to try and diagnose the issue, enabling/disabling various system processes and options within the machine vision development toolkit used to write the application. He would leave it for 10 days and then wait to hear from the onsite techs if the changes had been successful. These failures went on for around 2 months.
Finally in desperation, Daniel sent off the custom machine vision application to the company who developed the programming toolkit for their developers to analyze and see if they could point to the process causing the hard drive write.
Around a week later, they emailed back saying We couldn't find any hard drive write, but we did locate a small memory leak in one of your routines, around 8 bytes per image frame. The routine in question was in the main image analysis path. The cameras ran at 60 fps, so some quick arithmetic yields:
8 bytes/frame * 60 fps * 86,400 sec/day * 10 days = 414,720,000 bytes = 0.41 gigabytes == total available memory
Aha; it was an old-fashioned memory leak after all!
https://thedailywtf.com/articles/leaky-fun-for-the-whole-family
|
Метки: Feature Articles |
Error'd: The Illusion of Choice |
"So I can keep my current language setting or switch to Pakistani English. THERE IS NO IN-BETWEEN," Robert K. writes.
"I guess robot bears aren't allowed to have the honey, or register the warranty on their trailer hitch" wrote Charles R.
"Not to be outdone by King's Cross's platform 0 [and fictional platform 9 3/4], it looks like Marylebone is jumping on the weird band-wagon," David L. writes.
Alex wrote, "If the percentage it to be believed, I'm downloading Notepad+++++++++++++++."
"Hmm, so many choices?" writes Dave A.
Ergin S. writes, "My card number starts with 36 and is 14 digits long so it might take me a little while to get there, but thanks to the dev for at least trying to make things more convenient."
|
Метки: Error'd |
Representative Line: Tern This Statement Around and Go Home |
When looking for representative lines, ternaries are almost easy mode. While there’s nothing wrong with a good ternary expression, they have a bad reputation because they can quickly drift out towards “utterly unreadable”.
Or, sometimes, they can drift towards “incredibly stupid”. This anonymous submission is a pretty brazen example of the latter:
return (accounts == 1 ? 1 : accounts)
Presumably, once upon a time, this was a different expression. The code changed. Nobody thought about what was changing or why. They just changed it and moved on. Or, maybe, they did think about it, and thought, “someday this might go back to being complicated again, so I’ll leave the ternary in place”, which is arguably a worse approach.
We’ll never know which it was.
Since that was so simple, let’s look at something a little uglier, as a bonus. “WDPS” sends along a second ternary violation, this one has the added bonus of being in Objective-C. This code was written by a contractor (whitespace added to keep the article readable- original is all on one line):
NSMutableArray *buttonItems = [NSMutableArray array];
buttonItems = !negSpacer && !self.buttonCog
? @[] : (!negSpacer && self.buttonCog
? @[self.buttonCog] : (!self.buttonCog && negSpacer
? @[negSpacer] : @[negSpacer,self.buttonCog]));
This is a perfect example of a ternary which simply got out of control while someone tried to play code golf. Either this block adds no items to buttonItems, or it adds a buttonCog or it adds a negSpacer, or it adds both. Which means it could more simply be written as:
NSMutableArray *buttonItems = [NSMutableArray array];
if (negSpacer) {
[buttonItems addObject:negSpacer];
}
if (self.buttonCog) {
[buttonItems addObject:self.buttonCog];
}
[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/tern-this-statement-around-and-go-home
|
Метки: Representative Line |
CodeSOD: Isn't There a Vaccine For MUMPS? |
Alex F is suffering from a disease. No, it’s not disfiguring, it’s not fatal. It’s something much worse than that.
It’s MUMPS.
MUMPS is a little bit infamous. MUMPS is its own WTF.
Alex is a support tech, which in their organization means that they sometimes write up tickets, or for simple problems even fix the code themselves. For this issue, Alex wrote up a ticket, explaining that the users was submitting a background job to run a report, but instead got an error.
Alex sent it to the developer, and the developer replied with a one line code fix:
i $$zRunAsBkgUser(desc_$H,"runReportBkg^KHUTILLOCMAP",$na(%ZeData)) d
. w !,"Search has been started in the background."
e w !,"Search failed to start in the background."
Alex tested it, and… it didn’t work. So, fully aware of the risks they were taking, Alex dug into the code, starting with the global function $$zRunAsBkgUser.
Before I post any more code, I am legally required to offer a content warning: the rest of this article is going to be full of MUMPS code. This is not for the faint of heart, and TDWTF accepts no responsibility for your mental health if you continue. Don’t read the rest of this article if you have eaten any solid food in the past twenty minutes. If you experience a rash, this may be a sign of a life threatening condition, and you should seek immediate treatment. Do not consume alcohol while reading this article. Save that for after, you’ll need it.
;---------
; NAME: zRunAsBkgUser
; SCOPE: PUBLIC
; DESCRIPTION: Run the specified tag as the correct OS-level background user. The process will always start in the system default time zone.
; PARAMETERS:
; %uJobID (I,REQ) - Free text string uniquely identifying the request
; If null, the tag will be used instead but -- as this is not guaranteed unique -- this ID should be considered required
; %uBkgTag (I,REQ) - The tag to run
; %uVarList (I,OPT) - Variables to be passed from the current process' symbol table
; %uJobParams (I,OPT) - An array of additional parameters to be passed to %ZdUJOB
; Should be passed with the names of the parameters in %ZdUJOB, e.g. arr("%ZeDIR")="MGR"
; Currently supports only: %ZeDIR, %ZeNODE, %ZeBkOv
; %uError (O,OPT) - Error message in case of failure
; %uForceBkg (I,OPT) - If true, will force the request to be submitted to %ZeUMON
; %uVerifyCond (I,OPT) - If null, this tag will return immediately after submitting the request
; If non-null, should contain code that will be evaluated to determine the success or failure of the request
; Will be executed as s @("result=("_%uVerifyCond_")")
; %uVerifyTmo (I,OPT) - Length of time, in seconds, to try to verify the success of the request
; Defaults to 1 second
; RETURNS: If %uVerifyCond is not set: 1 if it's acceptable to run, 0 otherwise
; If %uVerifyCond is set: 1 if the condition is verified after the specified timeout, 0 otherwise
zRunAsBkgUser(%uJobID,%uBkgTag,%uVarList,%uJobParams,%uError,%uForceBkg,%uVerifyCond,%uVerifyTmo) q $$RunBkgJob^%ZeUMON($$zCurrRou(),%uJobID,%uBkgTag,%uVarList,.%uJobParams,.%uError,%uForceBkg,%uVerifyCond,%uVerifyTmo) ;;#eof# ;;#inline#
Thank the gods for comments, I guess. Alex’s eyes locked upon the sixth parameter- %uForceBkg. That seems a bit odd, for a function which is supposed to be submitting a background job. The zRunAsBkgUser function is otherwise quite short- it’s a wrapper around RunBkgJob.
Let’s just look at the comments:
;---------
; NAME: RunBkgJob
; SCOPE: INTERNAL
; DESCRIPTION: Submit request to monitor daemon to run the specified tag as a background process
; Used to ensure the correct OS-level user in the child process
; Will fork off from the current process if the correct OS-level user is already specified,
; unless the %uForceBkg flag is set. It will always start in the system default time zone.
; KEYWORDS: run,background,job,submit,%ZeUMON,correct,user
; CALLED BY: ($$)zRunAsBkgUser
; PARAMETERS:
; %uOrigRou (I,REQ) - The routine submitting the request
; %uJobID (I,REQ) - Free text string uniquely identifying the request
; If null, the tag will be used instead but -- as this is not guaranteed unique -- this ID should be considered required
; %uBkgTag (I,REQ) - The tag to run
; %uVarList (I,OPT) - Variables to be passed from the current process' symbol table
; If "", pass nothing; if 1, pass everything
; %uJobParams (I,OPT) - An array of additional parameters to be passed to %ZdUJOB
; Should be passed with the names of the parameters in %ZdUJOB, e.g. arr("%ZeDIR")="MGR"
; Currently supports only: %ZeDIR, %ZeNODE, %ZeBkOv
; %uError (O,OPT) - Error message in case of failure
; %uForceBkg (I,OPT) - If true, will force the request to be submitted to %ZeUMON
; %uVerifyCond (I,OPT) - If null, this tag will return immediately after submitting the request
; If non-null, should contain code that will be evaluated to determine the success or failure of the request
; Will be executed as s @("result=("_%uVerifyCond_")")
; %uVerifyTmo (I,OPT) - Length of time, in seconds, to try to verify the success of the request
; Defaults to 1 second
; RETURNS: If %uVerifyCond is not set: 1 if it's acceptable to run, 0 otherwise
; If %uVerifyCond is set: 1 if the condition is verified after the specified timeout, 0 otherwise
Once again, the suspicious uForceBkg parameter is getting passed it. The comments claim that this only controls the timezone, which implies either the parameter is horribly misnamed, or the comments are wrong. Or, possibly, both. Wait, no, it's talking about ZeUMON. My brain wants it to be timezones. MUMPS is getting to me. Since the zRunAsBkgUser has different comments, I suspect it’s both, but it’s MUMPS. I have no idea what could happen. Let’s look at the code.
RunBkgJob(%uOrigRou,%uJobID,%uBkgTag,%uVarList,%uJobParams,%uError,%uForceBkg,%uVerifyCond,%uVerifyTmo) ;
n %uSecCount,%uIsStarted,%uCondCode,%uVarCnt,%uVar,%uRet,%uTempFeat
k %uError
i %uBkgTag="" s %uError="Need to pass a tag" q 0
i '$$validrou(%uBkgTag) s %uError="Tag does not exist" q 0
;if we're already the right user, just fork off directly
i '%uForceBkg,$$zValidBkgOSUser() d q %uRet
. d inheritOff^%ZdDEBUG()
. s %uRet=$$^%ZdUJOB(%uBkgTag,"",%uVarList,%uJobParams("%ZeDIR"),%uJobParams("%ZeNODE"),$$zTZSystem(1),"","","","",%uJobParams("%ZeOvBk"))
. d inheritOn^%ZdDEBUG()
;
s:%uJobID="" %uJobID=%uBkgTag ;this *should* be uniquely identifying, though it might not be...
s ^%ZeUMON("START","J",%uJobID,"TAG")=%uBkgTag
s ^%ZeUMON("START","J",%uJobID,"CALLER")=%uOrigRou
i $$zFeatureCanUseTempFeatGlobal() s %uTempFeat=$$zFeatureSerializeTempGlo() s:%uTempFeat'="" ^%ZeUMON("START","J",%uJobID,"FEAT")=%uTempFeat
m:$D(%uJobParams) ^%ZeUMON("START","J",%uJobID,"PARAMS")=%uJobParams
i %uVarList]"" d
. s ^%ZeUMON("START","J",%uJobID,"VARS")=%uVarList
. d inheritOff^%ZdDEBUG()
. i %uVarList=1 d %zSavVbl($name(^%ZeUMON("START","J",%uJobID,"VARS"))) i 1 ;Save whole symbol table if %uVarList is 1
. e f %uVarCnt=1:1:$L(%uVarList,",") s %uVar=$p(%uVarList,",",%uVarCnt) m:%uVar]"" ^%ZeUMON("START","J",%uJobID,"VARS",%uVar)=@%uVar
. d inheritOn^%ZdDEBUG()
s ^%ZeUMON("START","G",%uJobID)="" ;avoid race conditions by setting pointer only after the data is complete
d log("BKG","Request to launch tag "_%uBkgTag_" from "_%uOrigRou)
q:%uVerifyCond="" 1 ;don't hang around if there's no need
d
. s %uError="Verification tag crashed"
. d SetTrap^%ZeERRTRAP("","","Error verifying launch of background tag "_%uBkgTag)
. s:%uVerifyTmo<1 %uVerifyTmo=1
. s %uIsStarted=0
. s %uCondCode="%uIsStarted=("_%uVerifyCond_")"
. f %uSecCount=1:1:%uVerifyTmo h 1 s @%uCondCode q:%uIsStarted
. d ClearTrap^%ZeERRTRAP
. k %uError
i %uError="",'%uIsStarted s %uError="Could not verify that job started successfully"
q %uIsStarted
;
q ;;#eor#
Well, there you have it, the bug is so simple to spot, I’ll leave it as an exercise to the readers.
I’m kidding. The smoking gun, as Alex calls it, is the block:
i '%uForceBkg,$$zValidBkgOSUser() d q %uRet
. d inheritOff^%ZdDEBUG()
. s %uRet=$$^%ZdUJOB(%uBkgTag,"",%uVarList,%uJobParams("%ZeDIR"),%uJobParams("%ZeNODE"),$$zTZSystem(1),"","","","",%uJobParams("%ZeOvBk"))
. d inheritOn^%ZdDEBUG()
;
This is what passes for an “if” statement in MUMPS. Specifically, if the %uForceBkg parameter is set, and the zValidBkgOSUser function returns true, then we’ll submit the job. Otherwise, we don’t submit the job, and thus get errors when we check on whether or not it’s done.
So, the underlying bug, such as it were, is a confusing parameter with an unreasonable default. This is not all that much of a WTF, I admit, but I really really wanted you all to see this much MUMPS code in a single sitting, and I wanted to remind you: there are people who work with this every day.
https://thedailywtf.com/articles/isn-t-there-a-vaccine-for-mumps
|
Метки: CodeSOD |
A Shell Game |
When the big banks and brokerages on Wall Street first got the idea that UNIX systems could replace mainframes, one of them decided to take the plunge - Big Bang style. They had hundreds of programmers cranking out as much of the mainframe functionality as they could. Copy-paste was all the rage; anything to save time. It could be fixed later.
Senior management decreed that the plan was to get all the software as ready as it could be by the deadline, then turn off and remove the mainframe terminals on Friday night, swap in the pre-configured UNIX boxes over the weekend, and turn it all on for Monday morning. Everyone was to be there 24 hours a day from Friday forward, for as long as it took. Air mattresses, munchies, etc. were brought in for when people would inevitably need to crash.
While the first few hours were rough, the plan worked. Come Monday, all hands were in place on the production floor and whatever didn't work caused a flurry of activity to get the issue fixed in very short order. All bureaucracy was abandoned in favor of: everyone has root in order to do whatever it takes on-the-fly, no approvals required. Business was conducted. There was a huge sigh of relief.
Then began the inevitable onslaught of add this and that for all the features that couldn't be implemented by the hard cutoff. This went on for 3-4 years until the software was relatively complete, but in desperate need of a full rewrite. The tech people reminded management of their warning about all the shortcuts to save time up front, and that it was time to pay the bill.
To their credit, management gave them the time and money to do it. Unfortunately, copy-paste was still ingrained in the culture, so nine different trading systems had about 90% of their code identical to their peers, but all in separate repositories, each with slightly different modification histories to the core code.
It was about this time that I joined one of the teams. The first thing they had me do was learn how to verify that all 87 (yes, eighty seven) of the nightly batch jobs had completed correctly. For this task, both the team manager and lead dev worked non-stop from 6AM to 10AM - every single day - to verify the results of the nightly jobs. I made a list of all of the jobs to check, and what to verify for each job. It took me from 6AM to 3:00PM, which was kind of pointless as the markets close at 4PM.
After doing it for one day, I said no way and asked them to continue doing it so as to give me time to automate it. They graciously agreed.
It took a while, but I wound up with a rude-n-crude 5K LOC ksh script that reduced the task to checking a text file for a list of OK/NG statuses. But this still didn't help if something had failed. I kept scripting more sub-checks for each task to implement what to do on failure (look up what document had the name of the job to run, figure out what arguments to pass, etc., get the status of the fix-it job, and notify someone on the upstream system if it still failed, etc). Either way, the result was recorded.
In the end, the ksh script had grown to more than 15K LOC, but it reduced the entire 8+ hour task to checking a 20 digit (bit-mask) page once a day. Some jobs failed every day for known reasons, but that was OK. As long as the bit-mask of the page was the expected value, you could ignore it; you only had to get involved if an automated repair of something was attempted but failed (this only happened about once every six months).
In retrospect, there were better ways to write that shell script, but it worked. Not only did all that nightly batch job validation and repair logic get encoded in the script (with lots of documentation of the what/how/why variety), but having rid ourselves of the need to deal with this daily mess freed up one man-day per day, and more importantly, allowed my boss to sleep later.
One day, my boss was bragging to the managers of the other trading systems (that were 90% copy-pasted) that he no longer had to deal with this issue. Since they were still dealing with the daily batch-check, they wanted my script. Helping peer teams was considered a Good Thing™, so we gave them the script and showed them how it worked, along with a detailed list of things to change so that it would work with the specifics of their individual systems.
About a week later, the support people on my team (including my boss) started getting nine different status pages in the morning - within seconds of each other - all with different status codes.
It turns out the other teams only modified the program and data file paths for the monitored batch jobs that were relevant to their teams, but didn't bother to delete the sections for the batch jobs they didn't need, and didn't update the notification pager list with info for their own teams. Not only did we get the pages for all of them, but this happened on the one day in six months that something in our system really broke and required manual intervention. Unfortunately, all of the shell scripts attempted to auto correct our failed job. Without. Any. Synchronization. By the time we cleared the confusion of the multiple pages, figured out the status of our own system, realized something required manual fixing and started to fix the mess created by the multiple parallel repair attempts, there wasn't enough time to get it running before the start of business. The financial users were not amused that they couldn't conduct business for several hours.
Once everyone changed the notification lists and deleted all the sections that didn't apply to their specific systems, the problems ceased and those batch-check scripts ran daily until the systems they monitored were finally retired.
|
Метки: Feature Articles |
A Tapestry of Threads |
A project is planned. Gantt charts are drawn up. Timelines are set. They're tight up against the critical path, because including any slack time in the project plan is like planning for failure. PMs have meetings. Timelines slip. Something must be done, and the PMs form a nugget of a plan.
That nugget squeezes out of their meeting, and rolls downhill until it lands on some poor developer's desk.
That poor developer was Alona.
"Rex is the lead architect on this," her manager said. "And the project is about 90% complete… but even with that, they're never going to hit their timeline. So we're going to kinda do an 'all hands' thing to make sure that the project completes on time."
Alona was a junior developer, but even with that, she'd seen enough projects to know: slamming new resources onto a project in its final days never speeds up its completion. Even so, she had her orders.
Alona grabbed the code, checked the project backlog, confirmed her build environment, and then talked to the other developers. They had some warnings.
"It uses a lot of threads, and… well, the thread model is kinda weird, but Rex says it's the right way to do this."
"I don't understand what's going on with these threads, but I'm sure Rex could explain it to you."
"It's heavily CPU bound, but we're using more threads than we have cores, but Rex says we have to do it that way in order to get the performance we need."
Alona had never met Rex, but none of that sounded good. The first tasks Alona needed to grab off the backlog didn't have anything to do with the threads, so she spent a few days just writing code, until she picked up a bug which was obviously caused by a race condition.
From what she'd seen in the documentation and the code, that meant the problem had to be somewhere in MainComputationThread. She wasn't sure where it was defined, so she just did a quick search for the term class MainComputationThread.
It returned twenty hits. There was no class called MainComputationThread, though there was an interface. There were also classes which implemented that interface, named things like MainComputationThread1 and MainComputationThread17. A quick diff showed that all twenty of the MainComputationThreadn classes were 1,243 lines of perfectly identical code.
They were also all implemented as singletons.
Alona had never met Rex, and didn't want to, but she needed to send him an email and ask: "Why?"
Threading is a pretty advanced programming topic, so I put together an easy to use framework for writing threaded code, so that junior developers can use it. Just use it. -Rex
|
Метки: Feature Articles |
Error'd: Truth in Errors |
Jakub writes, "I'm not sure restarting will make IE 'normal', but yeah, I guess it's worth a shot."
"What else can I say? Honest features are honest," wrote Matt H.
"This was the sign outside the speaker ready-room for the duration of American Mensa's 2018 Annual Gathering at the JW Marriott in Indianapolis," Dave A., "Of course, we weren't in control of the signs...or the fans...or the fan speeds."
"Well, Cisco made an attempt to personalize this email," wrote Pascal.
Dave A. wrote, "Skynet is coming (to the Chubby Squirrel Brewery, opened that very day), but we can do anything we want with it because there are no Term(s) of Use."
"Yeah, GMail, I agree, there's no way that number of fireworks all at once is safe," Ranuka wrote.
|
Метки: Error'd |
CodeSOD: Knowledge Transfer |
Lucio Crusca is a consultant with a nice little portfolio of customers he works with. One of those customers was also a consultancy, and their end customer had a problem. The end customer's only in-house developer, Tyrell, was leaving. He’d worked there for 8 years, and nobody else knew anything about his job, his code, or really what exactly he’d been doing for 8 years.
They had two weeks to do a knowledge transfer before Tyrell was out the door. There was no chance of on-boarding someone in that time, so they wanted a consultant who could essentially act as a walking, talking USB drive, simply holding all of Tyrell’s knowledge until they could have a full-time developer.
As you can imagine, the two week brain-dump turned into a two week “documentation crunch” as pretty much nothing had any real documentation. That lead to comments like:
/**
* Parses a log file. This function looks for the string "ERR-001" in the log file
* produced by the parser daemon (see project NetParserDaemon).
* If found it returns the file contents with the string. Otherwise it
* returns the file contents without it.
* Known bug: it needs more optimizations to handle very big files. In the
* meantime we manually restart the parser daemon from time to time, so the
* log file doesn't grow too much.
* @param log_file_name File name
* @return ArrayList
*/
public static ArrayList parseLogFile(String log_file_name) {
…
}
Read that comment, read the signature, and tell me if you have any idea what that method does? Because trust me, after reading the implementation, it’s not going to get any clearer.
public static ArrayList parseLogFile(String log_file_name)
{
try
{
ArrayList result = new ArrayList();
File f = new File(log_file_name);
FileInputStream s = new FileInputStream(log_file_name);
InputStreamReader r = new InputStreamReader(s);
BufferedReader r2 = new BufferedReader(r);
String line = null;
int retry = 1;
while (r2.readLine() != null)
{
try
{
for (int i = 0; i < retry; i++)
{
line = r2.readLine();
result.add(line);
}
if (line.contains("ERR-001"))
return result;
}
catch (OutOfMemoryError e)
{
System.gc();
line = "Retry";
retry = (int)(Math.random() * 10 + 10);
}
}
}
catch(Exception e)
{
ArrayList result = new ArrayList();
result.add("ERR-001: File not found");
return result;
}
finally
{
// always return the file contents, even when there are exceptions
return parseLogFile(log_file_name);
}
}
There’s just… so much going on here. First, this method must be dead code. The return in the finally block always trumps any other return in Java, which means it would call itself recursively until a stack overflow. Also, don’t put returns in your finally block.
So it doesn’t work, clearly hasn’t been tested, and certainly can’t be invoked.
But the core loop demonstrates its own bizarre logic. We retry reading within a for loop, by default though, we only do 1 retry. If and only if we encounter an out of memory exception, then we set the retry to a random value and repeat the loop. Oh, and don’t forget to garbage collect first. If any other exception happens, we’ll catch that and just return an ArrayList with “ERR–001: File not found” in it, which raises some questions about what on earth ERR-001 actually means to this application.
By the time the company actually hired on a full-time developer, Lucio had already forgotten most of the knowledge dump, and the rushed documentation and broken code meant that there really wasn’t much knowledge to transfer to the new developer, beyond, “Delete it, destroy the backups, and start from scratch.”
|
Метки: CodeSOD |
CodeSOD: CDADA |
If there’s one big problem with XML, it’s arguably that XML is overspecified. That’s not all bad- it means that every behavior, every option, every approach is documented, schematized, and defined. That might result in something like SOAP, which creates huge, bloated payloads, involves multiple layers of wrapping tags, integrates with discovery schemas, has additional federation and in-built security mechanisms, each of which are themselves defined in XML. And let’s not even start on XSLT and XQuery.
It also means that if you have a common task, like embedding arbitrary content in a safe fashion, there’s a well-specified and well-documented way to do it. If you did want to embed arbitrary content in a safe fashion, you could use the directive. It’s not a pretty way of doing it, but it means you don’t have to escape anything but ]]>, which is only a problem in certain esoteric programming languages with rude names.
So, there’s an ugly, but perfectly well specified and simple to use method of safely escaping content to store in XML. You know why we’re here. Carl W was going through some of the many, many gigs of XML data files his organization uses, and found:
<%3bPATH>%3bSOME_VALUE_HERE<%3b/PATH>%3b
The specific sequence of mangling operations that were performed aren’t documented anywhere, but you can figure it out. To decode this, you first have to convert the character entities back into actual characters- which really is just the ampersands.
Now you have: <%3bPATH>%3bSOME_VALUE_HERE<%3b/PATH>%3b.
This is obviously URL encoded. So we can reverse that, yielding <PATH>SOME_VALUE_HERE</PATH>.
Now, we can decode the character entities here.
SOME_VALUE_HERE
XML documents nest quite neatly, so why even do this escaping rigamarole? If you don't want it as XML, why not use CDATA? Why URL encode any of this? Carl had neither the time nor the documentation to figure it out. He simply changed SOME_VALUE_HERE to NEW_VALUE_HERE, and moved on to the next problem.
|
Метки: CodeSOD |
CodeSOD: This Interview Doesn't Count |
There are merits and disadvantages to including any sort of programming challenge in your interview process. The argument for something like a FizzBuzz challenge is that a surprising number of programmers can’t actually do that, and it weeds out the worst candidates and the liars.
Gareth was interviewing someone who purported to be a senior developer with loads of Java experience. As a standard part of their interview process, they do a little TDD based exercise: “here’s a test, here’s how to run it, now write some code which passes the test.”
The candidate had no idea what to make of this exercise. After about 45 minutes which resulted in three lines of code (one of which was just a closing curly bracket) Gareth gave the candidate some mercy. Interviews are stressful, the candidate might not be comfortable with the tools, everybody has a bad brainfart from time to time. He offered a different, simpler task.
“Heres’s some code which generates a list of numbers. I’d like you to write a method which finds the number which appears in the list most frequently.”
import java.io.*;
import java.util.*;
class Solution {
public static void main(String[] args) {
List numbers = new Vector();
numbers.add(5);
numbers.add(14);
numbers.add(6);
numbers.add(7);
numbers.add(7);
numbers.add(7);
numbers.add(20);
numbers.add(10);
numbers.add(10);
// find most common item
for(Integer num : numbers){
if(num == 5 ){
int five += 1;
}
else if(num == 14 ) [
int foue=rteen += !:
}
}
}
Gareth brought the interview to a close. After this, he didn’t want to spend another foue=rteen minutes trying to find a test the candidate could pass.
https://thedailywtf.com/articles/this-interview-doesn-t-count
|
Метки: CodeSOD |
Representative Line: Constantly True |
An anonymous reader had something to share.
"I came across this code in a 13,000 line file called Constants.cs."
public const string TRUE = "TRUE";
public const string True = "True";
public const string FALSE = "FALSE";
"I'm submitting my resignation letter on Monday."
There's not much more to say, really. Good job hunting.
|
Метки: Representative Line |
Error'd: What the Truck?! |
"I think I'll order the big-busted truck," writes Alicia.
Kevin D. writes, "Well, I guess when it comes to Microsoft and crashes, it's either go big or go home!"
"I was checking the status of a refund and learned that, in my specific case, time had gone negative," Dave L. wrote.
"Ok, Thunderbird, I know I'm an email packrat, but I really didn't think I had over 100 TB of junk stashed away!" Rich P. writes.
Mike S. wrote, "We usually complain about error dialogs without enough information. Then there's GIT."
"After installing an old PCI-e Serial card with accompanying driver, circa 2006, and then updating, the device shows as an 'unusable Parallel Port' in the list," writes Ralph E., "Oh, and the text in 'Ger"atestatus' is German and means 'The device is working properly.'"
[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: The Mike Test |
The Joel Test is about to turn 18 this year. Folks have attempted to “update” it, but even after graduating high school, the test remains a good starting point for identifying a “good” team.
Mike was impressed to discover a PHP script which manages to fail a number of points on the Joel Test in only 8 lines.
user@devcomputer:~/codearchive$ head -8 check-tank-balOLD.php
|
Метки: CodeSOD |
CodeSOD: Fortran the Undying |
There are certain languages which are still in use, are still changing and maturing, and yet are also frozen in time. Fortran is a perfect example- over the past 40–60 years, huge piles of code, mostly for scientific and engineering applications, was written. It may be hard to believe, but modern Fortran supports object-oriented programming and has a focus on concurrency.
Most of the people using Fortran, it seems, learned it in the 70s. And no matter what happens to the language, they still write code like it’s the 70s. Fortran’s own seeming immortality has imbued its users with necromantic energy, turning them into undying, and unchanging Liches.
Which brings us to Greg. Greg works with an engineer. This engineer, the Dark Lich Thraubagh, has a personal “spellbook” containing snippets of Fortran they’ve picked up over the past 40 years. For example, there’s this block, which creates an array of every number from 1–999, padded out to three characters.
I don’t know Fortran, so I give credit to this engineer/lich for writing code which even I can understand why it’s wrong and bad.
character*3 c(999)
character*1 b(9)
b(0)='0'
b(1)='1'
b(2)='2'
b(3)='3'
b(4)='4'
b(5)='5'
b(6)='6'
b(7)='7'
b(8)='8'
b(9)='9'
do 1 i=1,999
c(i)(3:3)=b( int( 10*((i/10.0)-int(i/10.0) ) ) )
c(i)(2:2)=b( int( 10*((i/100.0)-int(i/100.0) ) ) )
c(i)(1:1)=b( int( 10*((i/1000.0)-int(i/1000.0) ) ) )
1 continue
Start by creating a 1000-element array of 3-character long strings. Then create a 10-element array of 1-character long strings, containing the numbers 0–9.
Then we loop, and populate the strings in that larger array by doing some modular arithmetic and selecting from the 10-element array.
Fun fact: even in Fortran 77, there were a number of formatting options. If, for example, you wanted to put a number into a padded string, you could do write(string_var,"I3.3") 99. string_var now contains "099". Maybe not the clearest thing, but much simpler, certainly.
Funner fact: the c array was basically not used anywhere, in the particular program which caused Greg to send this along. The Dark Lich Thraubagh simply copies their “spellbook” into every program they write, gradually adding new methods to the spellbook, but never updating or modifying the old. They’ve been doing this for 40 years, and plan to do it for at least a century more.
|
Метки: CodeSOD |
Undermining the Boss |
During the browser wars of the late 90's, I worked for a company that believed that security had to consist of something you have and something you know. As an example, you must have a valid site certificate, and know your login and password. If all three are valid, you get in. Limiting retry attempts would preclude automated hack attempts. The security (mainframe) team officially deemed this good enough to thwart any threat that might come from outside our firewall.
As people moved away from working on mainframes to working on PCs, it became more difficult to get current site certificates to every user every three months (security team mandate). The security team decreed that e/snail-mail was not considered secure enough, so a representative of our company had to fly to every client company, go to every user PC and insert a disk to install the latest site certificate. Every three months. Ad infinitum.
You might imagine that this quickly became a rather significant expense for the business (and you'd be right), so they asked our department to come up with something less costly.
After a month of designing, our crack engineers came up with something that would cost several million dollars and take more than a year to build. I tried, but failed to stifle a chuckle. I told them that I could do it for $1500 (software license) and about two days of work. Naturally, this caused a wave of laughter, but the boss+1 in charge asked me to explain.
I said that we could put an old PC running a web server outside the firewall and manually dump all the site certificate installer programs on it. Then we could give the help desk a simple web page to create a DB entry that would allow our users to go to that PC, load the single available page to enter the unique code provided by the help desk, and get back a link to download a self-installing program to install the site certificate.
To preempt the inevitable concerns, I pointed out that while I had some knowledge of how to secure PCs and databases, that I was not by any means an expert, but that our Security Analysts (SAs) and DBAs were. We could have the SA's strip out all but the most necessary services, and clamp down the firewall rules to only let it access a dedicated DB on an internal machine on a specific port. The DBA's could lock down the dedicated DB with a single table to only allow read access from the web page; to pass in the magic phrase and optionally spit back a link to download the file.
Of course, everyone complained that the PC in-the-wild would be subject to hacking.
Since I believe in hoping for the best but planning for the worst, I suggested that we look at the worst possible case. I take out a full page ad in Hacker's Weekly saying "Free site certificates on exposed PC at IP a.b.c.d. They can be used at http://www.OurCompany.com. Enjoy!" After all, it can't get worse than that, right? So Mr. Hacker goes to the page and downloads the site certificate installation programs for every user and then goes to our website. What's the first thing he faces? Something he has and something he knows. He has the certificates, but doesn't know any login/passwords. Since the security people have already blessed this as "Good Enough", we should be safe.
After much discussion, everyone agreed that this made sense, but that they (reasonably) wanted to verify it. It was agreed that the SA's and DBA's had the needed expertise to strip and lock down the PC, firewall and DB. I took an old PC out of one of the closets, did a fresh install, put on the latest web server and relevant software, and then installed the few things we needed. Then I handed it to the SA's and told them to strip it and lock it down. I created a tiny DB with a single table and two stored procedures; one for the help desk to add a new time-limited entry for a user and the other to check to see if an unexpired entry existed and return a link to the installer on the exposed PC. Then I handed it to the DBA's and told them to restrict it so the table could only be accessed via the two stored procs, and to only allow the Help desk to call the proc that created the time limited entry for the user, and the external IP to call the proc to query the table. Since all of our users already had credentials to call the help desk, this was only a minimal additional cost.
We threw a couple of test certificate installers on it and put it outside the firewall. After I tested the "good" paths, I had the SA's and DBA's try to hack around their restrictions. When they couldn't, it was deemed safe and loaded up with all the certificates. I wrote up a very short how-to manual and had it installed in production.
This reduced the certificate installations to one trip per quarter to our data center.
The user was pleased at having saved millions of dollars on an ongoing basis.
I found out later that I inadvertently pissed off the boss+1 because he was planning on hiring more people for this project and I negated the need for him to expand his empire.
Whoops.
|
Метки: Feature Articles |
Old Lennart |
Gustav's tech support job became a whole lot easier when remote support technology took off. Instead of having to slowly describe the remedy for a problem to a computer-illiterate twit, he could connect to their PC and fix it himself. The magical application known as TeamViewer was his new best friend.
Through Gustav's employer's support contract with CAD+, a small engineering design firm, he came to know Roger. The higher-ups at CAD+ decided to outsource most of their IT work and laid off everyone except Roger, who was to stay on as liaison to Gustav and Co. Roger was the type whose confidence in his work did not come close to matching the quality of it. He still felt like he could accomplish projects on his own without any outside help. Thanks to that, Gustav had to get him out of a jam several times early in their contract.
Roger's latest folly was upgrading all of the office workstations from the disaster that was Windows Vista to the much more reliable Windows 7. "I had such a smooth rollout to Windows 7, I tell ya," Roger bragged over the phone. "I brilliantly used this cloning process to avoid installing the same things repeatedly!" Gustav rolled his eyes while wondering if this was a support call or if Roger just needed someone to talk to. "Well anywho, I had two system images - one with the basic software everyone gets, and one that included SolidWorks for our CAD designers. Seems they don't have SolidWorks even though I installed it. Can you do your support wizardry and take a look?"
Gustav agreed and was transferred to Dave, their lead CAD designer. He figured Dave just didn't know where to find SolidWorks on the new OS and it would be a quick call. He got Dave's TeamViewer ID and connected to his PC in no time. He decided to ignore the fact that Dave had a browser open to an article about the twenty greatest Michael Bolton songs of all time.
"Thanks Dave, I'm in. Are you able to see me moving your mouse?" Gustav asked. He wasn't. "Ok then, there must be some lag here. I'm going to look around to find where you have SolidWorks and make a shortcut for you." Gustav spent the next few minutes looking at the places any sane person would install SolidWorks. Since Roger wasn't sane, he didn't find it in a logical directory. He then went to Programs and Features and noticed that it wasn't installed anywhere.
"Hey Dave, bad news," Gustav informed. "It seems like your PC got the wrong image from Roger and your CAD software isn't on there."
"Bummer. I didn't know if you were doing anything, on my side the computer screen was just sitting there," Dave replied, just as an incredible ruckus broke out behind him. Gustav could hear doors slamming and someone cursing at Roger in the background. Dave chuckled quietly into the phone.
"... is everything ok there?" Gustav asked, concerned.
"Oh, that's just Old Lennart throwing a fit. He's our cranky old Vice President. He's super pissed because Roger keeps hacking his computer and messing around on it."
Gustav suddenly had a suspicion. "Dave, don't take this the wrong way, but were you reading an article about Michael Bolton's greatest hits?" he asked cautiously.
"Hell no, why would I be doing that?" Dave shot back, almost sounding insulted. Gustav apologized and quickly ended their call.
Once Roger was done getting chewed out by Old Lennart, Gustav gave him a call. In their discussion, Roger revealed how he'd installed TeamViewer before making his system clones to save time. Gustav explained how that caused every system image to have the same TeamViewer ID, which was bad. Since Old Lennart was always the first one in the office each morning, any remote connection through TeamViewer would connect to his PC. Thus, whenever Gustav or one of his cohorts connected for remote support, it seemed like someone was hacking in to Lennart's computer.
Roger remedied the problem over the next couple days by reinstalling TeamViewer and bringing in a series of "I'm sorry" baked goods for Old Lennart; but it wasn't enough to save his hide. By the end of the week, he was informed that their IT department would be further reduced from one employee to zero. With his computer and Roger problems addressed, Old Lennart could return to researching his favorite performing artist.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
|
Метки: Feature Articles |
Error'd: When BSODs Pile Up |
"I suppose the ropes were there to keep the infection from spreading to other screens," Stan I. wrote.
"I was visiting the ESA Shop and, well, though I'm not actually in the UK, it looks like I'll be shopping there!" wrote Ben S.
Marcin K. writes, "I spotted this one at a Warsaw subway station and, I'm pleased to say, that this billboard was the only thing that crashed."
"Life is full of important, deep questions. Ubuntu 18.04 seems to feel the same way during install when I tried to create an encrypted volume," writes Bernard M.
Brad W. wrote, "Sometimes it's easy to see how test artifacts slip into production, other times it's not."
"This is what happens when you are looking for answers on CodeProject, but you find only more questions," writes David.
|
Метки: Error'd |