-Поиск по дневнику

Поиск сообщений в rss_thedaily_wtf

 -Подписка по e-mail

 

 -Постоянные читатели

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 06.04.2008
Записей:
Комментариев:
Написано: 0

The Daily WTF





Curious Perversions in Information Technology


Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://thedailywtf.com/.
Данный дневник сформирован из открытого RSS-источника по адресу http://syndication.thedailywtf.com/thedailywtf, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

Faking the Grade

Понедельник, 15 Июня 2020 г. 09:30 + в цитатник

Report Card - The Noun Project

Our friend and frequent submitter Argle once taught evening classes in programming at his local community college. These classes tended to be small, around 20-30 students. Most of them were already programmers and were looking to expand their knowledge. Argle enjoyed helping them in that respect.

The first night of each new semester, Argle had everyone introduce themselves and share their goals for the class. One of his most notable students was a confident, charismatic young man named Emmanuel. "Manny," as he preferred to be called, told everyone that he was a contract programmer who'd been working with a local company for over a year.

"I don't really need to be here," he said. "My employer thought it would be nice if I brushed up on the basics."

Argle's first assignment for the class was a basic "Hello, world" program to demonstrate knowledge of the development environment. Manny handed it in with an eye-roll—then failed to turn in any more homework for the rest of the semester. He skipped lectures and showed up only for exams, each time smirking like he had the crib sheet to the universe in his back pocket. And yet he bombed every test in spectacular fashion, even managing to score below 50% on the true/false midterm. A layperson off the street could've outperformed him with random guessing.

Argle made attempts to offer help during office hours, all of which Manny ignored. This being college and not grade school, there wasn't much else Argle could do. Manny was an adult who'd turned in an F performance, so that was the grade he ended up with.

A few days after final grades had been submitted, Argle received a phone call from Manny. "I don't understand why you failed me," he began with full sincerity.

Baffled, Argle was speechless at first. Is this a joke? he wondered. "You didn't turn in any assignments," he explained, trying to keep emotion out of his voice. "Assignments were worth two-thirds of the grade. It's in the syllabus, and I discussed it on the first day of class."

"I thought my test grades would carry me," Manny replied.

Argle's bafflement only grew. "Even if you'd gotten perfect scores on every test, you still would've failed the class. And you had nowhere near perfect scores on the tests."

Manny broke down crying. He kept talking, almost incomprehensible through his sobs. "My employer paid for the class! They're going to see the grade! I'm not losing my job over this. I'm contesting the F!" He abruptly hung up.

Argle made a quick preemptive phone call to his department head to explain the situation, and was assured that everything would be taken care of. Upon ending the call, he shook his head in astonishment. Had Manny's employer suspected that their contractor wasn't as skilled with programming as he pretended to be? Or would his F come as a total shock to them?

A programmer who doesn't know how to program, he mused to himself, and management who can't tell the difference. Sounds like a match made in heaven.

Argle never heard about the issue again, so he never learned Manny's fate. But once he discovered our website, he came to understand that Manny was far from the only "brillant" programmer out there, and far from the only one whose incompetence went undetected for so long.

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

https://thedailywtf.com/articles/faking-the-grade


Метки:  

Понравилось: 1 пользователю

Error'd: People also ask ...WTF?!

Пятница, 12 Июня 2020 г. 09:30 + в цитатник

"Exactly which people are asking this question?" Jamie M. wrote.

 

"More like a friendly reminder that for one solid day, 2000 years ago, I was insured," Kyle B. writes.

 

Jordan R. wrote, "Ok, I'll bite, how can NULL solve pain points of private clouds?'

 

"Tell me Jira, is the 'undefined' key anywhere near the 'any' key?" writes Gary A.

 

Quentin G. wrote, "92% sales tax seems a bit high, but shipping? I don't live in Outer Mongoila!"

 

Mike S. writes, "Only $1M a year? Cheap &&%$#$!!"

 

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

https://thedailywtf.com/articles/people-also-ask-wtf


Метки:  

The Time-Delay Footgun

Четверг, 11 Июня 2020 г. 09:30 + в цитатник

A few years back, Mike worked at Initech. Initech has two major products: the Initech Creator and the Initech Analyzer. The Creator, as the name implied, let you create things. The Analyzer could take what you made with the Creator and test them.

For business reasons, these were two separate products, and it was common for one customer to have many more Creator licenses than Analyzer licenses, or upgrade them each on a different cadence. But the Analyzer depended on the Creator, so someone might have two wildly different versions of both tools installed.

Initech wasn’t just incrementing the version number and charging for a new seat every year. Both products were under active development, with a steady stream of new features. The Analyzer needed to be smart enough to check what version of Creator was installed, and enable/disable the appropriate set of features. Which meant the Analyzer needed to check the version string.

From a user’s perspective, the version numbers were simple: a new version was released every year, numbered for the year. So the 2009 release was version 9, the 2012 was version 12, and so on. Internally, however, they needed to track finer-grained versions, patch levels, and whether the build was intended as an alpha, beta, or release version. This meant that they looked more like “12.3g31”.

Mike was tasked with prepping Initech Analyzer 2013 for release. Since the company used an unusual version numbering schema, they had also written a suite of custom version parsing functions, in the form: isCreatorVersion9_0OrLater, isCreatorVersion11_0OrLater, etc. He needed to add isCreaterVersion12_0OrLater.

“Hey,” Mike suggested to his boss, “I notice that all of these functions are unique, we could make a general version that uses a regex.”

“No, don’t do that,” his boss said. “You know what they say, ‘I had a problem, so I used regexes, now I have two problems.’ Just copy-paste the version 11 version, and use that. It uses string slicing, which performs way better than regex anyway.”

“Well, I think there are going to be some problems-”

“It’s what we’ve done every year,” his boss said. “Just do it. It’s the version check, don’t put any thought into it.”

“Like, I mean, really problems- the way it-”

His boss cut him off and spoke very slowly. “It is just the version check. It doesn’t need to be complicated. And we know it can’t be wrong, because all the tests are green.”

Mike did not just copy the version 11 check. He also didn’t use regexes, but patterned his logic off the version 11 check, with some minor corrections. But he did leave the version 11 check alone, because he wasn’t given permission to change that block of code, and all of the tests were green.

So how did isCreatorVersion11_0OrLater work? Well, given a version string like 9.0g57 or 10.0a12, or 11.0b9, it would start by checking the second character. If it was a ., clearly we had a single digit version number which must be less than 11. If the second character was a 0, then it must be 10, which clearly is also less than 11, and there couldn't possibly be any numbers larger than 11 which have a "0" as their second character. Any other number must be greater than or equal 11.

Mike describes this as a “time-delayed footgun”. Because it was “right” for about a decade. Unfortunately, Initech Analyzer 2020 might be having some troubles right now…

Mike adds:

Now, I no longer work at Initech, so unfortunately I can’t tell you the fallout of what happened when that foot-gun finally went off this year.

[Advertisement] ProGet can centralize your organization's software applications and components to provide uniform access to developers and servers. Check it out!

https://thedailywtf.com/articles/the-time-delay-footgun


Метки:  

CodeSOD: Sort Yourself Out

Среда, 10 Июня 2020 г. 09:30 + в цитатник

Object-Relational-Mappers (ORMs) are a subject of pain, pleasure, and flamewars. On one hand, they make it trivially easy to write basic persistence logic, as long as it stays basic. But they do this by concealing the broader powers of relational databases, which means that an ORM is a leaky abstraction. Used incautiously or inappropriately, and they stop making your life easy, and make it much, much harder.

That’s bad, unless you’re Tenesha’s co-worker, because you apparently want to suffer.

In addition to new products, Tenesha’s team works on a legacy Ruby-on-Rails application. It’s an ecommerce tool, and thus it handles everything from inventory to order management to billing to taxes.

Taxes can get tricky. Each country may have national tax rates. Individual cities may have different ones. In their database, they have a TaxesRate table which tracks the country name, the city name, the region code name, and the tax rate.

You’ll note that the actual database is storing names, which is problematic when you need to handle localizations. Is it Spain or Espa~na? The ugly hack to fix this is to have a lookup file in YAML, like so:

i18n_keys:
  es-ca: "Canary Islands"
  es: "Spain"
  eu: "Inside European Union"
  us: "United States"
  ar: "Argentina"
  la: "Latin America"
  as-oc: "Asia & Oceania"
  row: "Rest of the world"

Those are just the countries, but there were similar structures for regions, cities, and so on.

The YAML file took on more importance when management decided that the sort order of the tax codes within a region needed to be a specific order. They didn’t want it to be sorted alphabetically, or by date added, or by number of orders, or anything: they had a specific order they wanted.

So Tenesha’s co-worker had a bright idea: they could store the lookup keys in the YAML file in the order specified. It meant they didn’t have to add or manage a sort_order field in the database, which sounded easier to them, and would be easier to implement, right?

Well, no. There’s no easy way to tell an SQL order-by clause to sort in an arbitrary order. But our intrepid programmer was using an ORM, so they didn’t need to think about little details like “connecting to a database” or “worrying about round trips” or “is this at all efficient”.

So they implemented it this way:

  # Order locations by their I18n registers to make it easier to reorder
  def self.order_by_location(regions)
    codes = I18n.t("quotation.selectable_taxes_rate_locations").keys.map{ |k| k.to_s }
    regions_ordered = []

    codes.each do |code|
      regions_ordered.push(regions.where(region_code: code))
    end

    # Insert the codes that are not listed at the end
    regions_ordered.push(regions.where("region_code NOT IN (?)", codes)).flatten
  end

This is called like so:

# NOTE: TaxesRate.all_rates returns all records with unique region codes,
#   ignoring cities; something like `TaxesRate.distinct(:region_code)`.
regions = order_by_location(TaxesRate.all_rates)

We should be thankful that they didn’t find a way to make this execute N2 queries, but as it is, it needs to execute N+1 queries.

First, we pull the rate locations from our internationalization YAML file. Then, for each region code, we run a query to fetch the tax rate for that one region code. This is one query for each code. Based on the internationalization file, it’s just the codes for one country, but that can still be a large number. Finally, we run one final query to fetch all the other regions that aren’t in our list.

This fetches the tax code for all regions, sorted based on the sort order in the localization file (which does mean each locale could have a different sort order, a feature no one requested).

Tenesha summarizes it:

So many things done wrong; in summary:
* Country names stored with different localization on the same database, instead of storing country codes.
* Using redundant data for storing region codes for different cities.
* Hard-coding a new front-end feature using localization keys order.
* Performing N+1 queries to retrieve well known data.

Now, this was a legacy application, so when Tenesha and her team went to management suggesting that they fix this terrible approach, the answer was “Nope!” It was a legacy product, and was only going to get new features and critical bug fixes.

Tenesha scored a minor victory: she did convince them to let her rewrite the method so that it fetched the data from the database and then sorted using the Array#index method, which still wasn’t great, but was far better than hundreds of database round trips.

[Advertisement] Ensure your software is built only once and then deployed consistently across environments, by packaging your applications and components. Learn how today!

https://thedailywtf.com/articles/sort-yourself-out


Метки:  

Representative Line: The Truest Comment

Вторник, 09 Июня 2020 г. 09:30 + в цитатник

Usually, when posting a representative line, it’s a line of code. Rarely, it’s been a “representative comment”.

Today’s submitter supplied some code, but honestly, the code doesn’t matter- it’s just some method signatures. The comment, however, is representative. Not just representative of this code, but honestly, all code, everywhere.

        // i should have commented this stupid code

As our submitter writes:

I wrote this code. I also wrote that comment. That comment is true.
Why do I do this to myself?

I sometimes ask myself the same question.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

https://thedailywtf.com/articles/the-truest-comment


Метки:  

Editor's Soapbox: On Systemic Debt

Понедельник, 08 Июня 2020 г. 09:30 + в цитатник

I recently caught up with an old co-worker from my first "enterprise" job. In 2007, I was hired to support an application which was "on its way out," as "eventually" we'd be replacing it with a new ERP. September 2019 was when it finally got retired.

Interest on the federal debt

The application was called "Total Inventory Process" and it is my WTF origin story. Elements of that application and the organization have filtered into many of my articles on this site. Boy, did it have more than its share of WTFs.

"Total Inventory Process". Off the bat, you know that this is a case of an overambitious in-house team trying to make a "do everything" application that's gonna solve every problem. Its core was inventory management and invoicing, but it had its own data-driven screen rendering/templating engine (which barely worked), its own internationalization engine (for an application which never got localized), a blend of web, COM+, client-side VB6, and hooks into an Oracle backend but also a mainframe.

TIP was also a lesson in the power of technical debt, how much it really costs, and why technical solutions are almost never enough to address technical debt.

TIP was used to track the consumption of inventory at several customers' factories to figure out how to invoice them. We sold them the paint that goes on their widgets, but it wasn't as simple as "You used this much paint, we bill you this much." We handled the inventory of everything in their paint line, from the paint to the toilet paper in the bathrooms. The customer didn't want to pay for consumption, they wanted to pay for widgets. The application needed to be able to say, "If the customer makes 300 widgets, that's $10 worth of toilet paper."

The application handled millions of dollars of invoices each year, for dozens of customer facilities. When I was hired, I was the second developer hired to work full time on supporting TIP. I wasn't hired to fix bugs. I wasn't hired to add new features. I was hired because it took two developers, working 40 hours a week, just to keep the application from falling over in production.

My key job responsibility was to log into the production database and manually tweak records, because the application was so bug-ridden, so difficult to use, and so locked down that users couldn't correct their own mistakes. With whatever time I had left, I'd respond to user requests for new functionality or bug-fixes.

It's usually hard to quantify exactly what technical debt costs. We talk about it a lot, but it very often takes the form of, "I know it when I see it," or "technical debt is other people's code." Here, we have a very concrete number: technical debt was two full-time salaries to just maintain basic operations.

My first development task was to add a text-box to one screen. Because other developers had tried to do this in the past, the estimate I was given was two weeks of developer time. 80 hours of effort for a new text box.

Once, the users wanted to be able to sort a screen in descending order. The application had a complex sorting/paging system designed to prevent fetching more than one page of data at a time for performance reasons. While it did that, it had no ability to sort in descending order, and adding that functionality would have entailed a full rewrite. My "fix" was to disable paging and sorting entirely on the backend, then re-implement in on the client-side in JavaScript for that screen. The users loved it, because suddenly one screen in the application was actually fast.

Oh, and as it was, "sorting" on the backend was literally putting the order-by clause in the URL and then SQL injecting it.

There were side effects to this quantity of technical debt. Since we were manually changing data in production, we were working closely with a handful of stakeholder users. Three, in specific, who were the "triumvirate" of TIP. Those three people were the direct point of contact with the developers. They were the direct point of contact to management. They set priorities. They entered bug reports. They made data change requests. They ruled the system.

They had a lot of power over this system, and this was a system handling millions of dollars. I think they liked that power. To this day, one of them holds that the "concept" of TIP was "unbeatable", and its replacement system is "cheap" and "crap". Now, from a technical perspective, you can't really get crappier than TIP, but from the perspective of a super-user, I can understand how great TIP felt.

I was a young developer, and didn't really like this working environment. Oh, I liked the triumvirate just fine, they were lovely people to work with, but I didn't like spending my days as nothing more than an extension of them. They'd send me emails containing UPDATE statements, and just ask that I execute them in production. There's not a lot of job satisfaction in being nothing more than "the person with permission to change data."

Frustrated, I immediately starting looking for ways to pay down that technical debt. There was only so much I could do, for a number of reasons. The obvious one was the software. If "adding a textbox" could reasonably take two weeks, "restructuring data access so that you can page data sorted in descending order" felt impossible. Even "easy" changes could unleash chaos as they triggered some edge case no one knew about.

Honestly, though, the technical obstacles were the small ones. The big challenges were the cultural and political ones.

First, the company knew that much of the code running in production was bad. So they created policies which erected checkpoints to confirm code quality, making it harder to deploy new code. Much harder, and much longer: you couldn't release to production until someone with authority signed off on it, and that might take days. Instead of resulting in better code, it instead meant the old, bad code stayed bad, and new, bad code got rushed through the process. "We have a hard go-live date, we'll improve the code after that." Or people found other workarounds: your web code had to go through those checkpoints, but stored procedures didn't, so if you had the authority to modify things in production, like I did, you could change stored procedures to your heart's content.

Second, the organization as a whole was risk-averse. The application was handling millions of dollars of invoices, and while it required a lot of manual babysitting, by the time the invoices went out, they were accurate. The company got paid. No one wanted to make sweeping changes which could impact that.

Third, the business rules were complicated. "How many rolls of toilet paper do you bill per widget?" is a hard question to answer. It's fair to say that no one fully understood the system. On the business side, the triumvirate probably had the best handle on it, but even they could be blindsided by its behavior. It was a complex system that needed to stay functioning, because it was business critical, but no one knows exactly what all of those functions are.

Fourth, the organization viewed "support" and "enhancement" the way other companies might view "operational" and "capital" budgets. They were different pools of money, and you weren't supposed to use the support money to pay for new features, nor could enhancement money be used to fix bugs.

Most frustrating was that I would sometimes get push-back from the triumvirate. Oh, they loved it when I could give them a button to self-service some feature which needed to use manual intervention, so long as the interface was just cumbersome enough that only they could use it. They hated it when a workflow got so streamlined that any user could do it. Worse, for them, was that as the technical debt got paid down, we started transitioning more and more of the "just change production data" to low-level contractors. Now the triumvirate no longer had a developer capable of changing code at their beck and call. They had to surrender power.

Fortunately, management was sensitive to the support costs around TIP. Once we started to build a track-record of reduced costs, management started to remove some of the obstacles. It was a lot of politics. I suspect some management were wary of how much power the triumvirate had, and were happy when that could get reduced. After a few years of work on TIP, I mostly rolled off of it onto other projects. Usually, I'd just pop in for month-end billing support, or being the expert who understood some of the bizarre technical intricacies. Obviously, the application continued working just fine for years without me, and I can't say that I miss it.

TIP accrued technical debt far more quickly than most systems would. Some of that comes from ambition: it tried to be everything, including reinventing functions which had nothing to do with its core business. This led to a tortured development process, complete with death marches, team restructurings, "throw developers at this until it gets back on track," and several ultimatums like "If we don't get something in production by the end of the month, everyone is fired." It was born in severe debt, within an organization which didn't have good mechanisms to manage that debt. And the main obstacles to paying down that debt weren't technical: they were social and political.

I was lucky. I was given the freedom to tackle that debt (or, if we're being fully honest, I also took some freedom under the "it's easier to seek forgiveness than to ask permission" principle). In a lot of systems, the technical debt accrues until the costs of servicing the debt are untenable. You end up paying so much in "interest" that you stop being able to actually do anything. This is a failure mode for a lot of projects, and that's usually when a "ground up" rewrite happens. Rewrites have a mixed record, though: TIP itself was actually a rewrite of an older system and promised to "do it right" this time.

Technical debt has been on my mind because I've been thinking a lot lately about broader, systemic debt. Any systems humans build—software systems, mechanical systems, or even social systems—are going to accrue debt. Decisions made in the history of the system are going to create problems in the future, whether those decisions were wrong from the get-go, or had unforeseen consequences, or just the world changed and they're no longer workable.

The United States, right now, is experiencing a swell of protests unlike anything I've seen in my lifetime. I think it's fair to say that these protests are rooted in historical inequities and injustices which constitute a form of social debt. Our social systems, especially around the operation of police, but broadly in the realm of race, are loaded with debt from the past.

I see similarities in the obstacles to paying down that debt. Attempts to make changes by policy end up creating roadblocks to change, or simply fail to accomplish their goals. Resistance to change because change entails risk, and managing or mitigating those risks feels more important than actually fixing the problems. The systems we're talking about are complicated, and it's difficult to even build consensus on what better versions look like, because it's difficult to even understand what they currently are. And finally, there are groups that have power, and in paying down the social debt, they would have to give up some of that power.

That's a big, hard-to-grapple-with quantity of debt. It's connected to a set of interlocking systems which are difficult to pick apart. And it's frighteningly important to get right.

All systems accrue systemic debt. Software systems accrue debt. Social systems accrue debt. Even your personal mental systems, your psychological health, accrue debt. Without action, the debt will grow, the interest on that debt grows, and more energy ends up servicing that debt. One of the ways in which systems fail is when the accumulated debt gets too high. Bankruptcy occurs. Of all of these kinds of systems, the software debt tends to be the trivial one. Software products become unsupportable and get replaced. Psychological debt can lead towards things like depression or other mental health problems. Social debt can lead to unrest or perpetuations of injustice.

Systemic debt may be a technical problem at its root, but its solution is always going to require politics. TIP couldn't be improved without overcoming political challenges. US social debt can't be resolved without significant political changes.

What I hope people take away from this story is an awareness of systemic debt, and an understanding of some of the long-term costs of that debt. I encourage you to look around you and at the systems you interact with. What sources of debt are there? What is that debt costing the system and the people who depend on it? What are the obstacles to paying down that debt? What are the challenges of making the systems we use better?

Systemic debt doesn't go away by itself, and left unmanaged, it will only grow. If you don't pay attention to it, broken systems end up staying in production for way too long. TIP was used for nearly 18 years. Don't use broken things for that long, please.

[Advertisement] ProGet can centralize your organization's software applications and components to provide uniform access to developers and servers. Check it out!

https://thedailywtf.com/articles/on-systemic-debt


Метки:  

Error'd: Just a Big Mixup

Пятница, 05 Июня 2020 г. 09:30 + в цитатник

Daniel M. writes, "How'd they make this mistake? Simple. You add the prices into the bowl and turn the mixer on."

 

"I'm really glad to see a retailer making sure that I get the most accurate discount possible," Kelly K. wrote.

 

"I sure hope they're not also receiving invalid maintenance," Steven S. wrote.

 

Ernie writes, "Recently, I was looking for some hints on traditional bread making and found some interesting sources. Some of them go back to the middle ages."

 

"Tried to get a refund travel voucher through KLM, and well, obviously they know more than everyone else," Matthias wrote.

 

Roger G. writes, "I'm planning my ride in Mapometer and apparently I'm descending 100,000ft into the earths core. I'll let you know what I find..."

 

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

https://thedailywtf.com/articles/just-a-big-mixup


Метки:  

CodeSOD: Scheduling your Terns

Четверг, 04 Июня 2020 г. 09:30 + в цитатник

Mike has a co-worker who’s better at Code Golf than I am. They needed to generate a table with 24 column headings, one for each hour of the day, formatted in HAM- the hour and AM/PM. As someone bad at code golf, my first instinct is honestly to use two for loops, but in practice I’d probably do a 24 iteration loop with a branch to decide if it’s AM/PM and handle it appropriately, as well as a branch to handle the fact that hour 0 should be printed as 12.

Which, technically, more or less what Mike’s co-worker did, but they did in in golf style, using PHP.



This is code written by someone who just recently discovered ternaries. It’s not wrong. It’s not even a complete and utter disaster. It’s just annoying. Maybe I’m jealous of their code golf skills, but this is the kind of code that makes me grind my teeth when I see it.

It’s mildly… clever? $i%12?$i%12:12- i%12 will be zero when i is 12, which is false, and our false branch says to output 12, and our true branch says to output i%12. So that’s sorted, handles all 24 hours of the day.

Then, for AM/PM, they ($i/12>=1?'pm':'am')- which also works. Values less than 12 fail the condition, so our false path is 'am', values greater than 12 will get 'pm'.

But wait a second. We don’t need the >= or the division in there. This could just be ($i>11?'pm':'am').

Well, maybe I am good at Code Golf.

I still hate it.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

https://thedailywtf.com/articles/scheduling-your-terns


Метки:  

CodeSOD: Synchronize Your Clocks

Среда, 03 Июня 2020 г. 09:30 + в цитатник

Back when it was new, one of the “great features” of Java was that it made working with threads “easy”. Developers learning the language were encouraged to get a grip right on threads right away, because that was the new thing which would make their programs so much better.

Well, concurrency is hard. Or, to put it another way, “I had a problem, so I decided to use threads. prhave twI Now o oblems.”

Another thing that’s hard in Java is working with dates and times.

Larisa inherited some code which wanted to be able to check the current system time in a threadsafe fashion. They weren’t doing anything fancy- no timezones, no formatting, just getting the Unix Timestamp off the clock. If you’re thinking to yourself, “It’s just a read operation and there’s no need to bring threads into this at all,” you obviously didn’t write today’s code block.

The right way to do this in Java 8+, would be to use the built-in java.time objects, but in older versions of Java you might need to do something like this:

long currentTime = System.currentTimeMillis();

But that doesn’t involve any design patterns, any synchronized code blocks to protect against multiple threads, and simply isn’t Enterprise enough.

public class Clock {
    private static Clock sfClock = null;

    protected static synchronized void register(Clock testClock) {
        sfClock = testClock;
    }

    public static synchronized Clock getIt() {
        if (sfClock == null) {
            sfClock = new Clock();
        }
        return sfClock;
    }

    public static long now() {
        return getIt().nowImpl();
    }

    protected long nowImpl() {
        return System.currentTimeMillis();
    }

}

This is an attempt to implement the Singleton pattern, which is the go to pattern for people to use, because it’s the easiest to understand and implement and doubles as what is basically a global variable.

You’ll note that there’s no constructor, since there’s no internal state, so there’s no point in making this a singleton.

getIt will create an instance if there isn’t one, but you can also supply an instance via register. You might think that the developer put some thought into how this class would be tested, but again- there’s no internal state or even any internal logic. You could inherit from Clock to make a MockClock that could be used in testing, but that is a long hill to climb to justify this.

The real genius, though, is that our ugly getIt method doesn’t ever have to be directly invoked. Instead, now does that for you. Clock.now() will call getIt, which gets an instance of Clock, then invoke nowImpl, the actual implementation of our now method.

For bonus points, the reason Larisa found this was that there are a lot of threads in this program, and they’re tying timestamps to actions on the regular, so the fact that getIt is synchronized was actually killing performance.

None of this code is necessary. It’s an over-engineered solution to a problem nobody actually had.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

https://thedailywtf.com/articles/synchronize-your-clocks


Метки:  

CodeSOD: Try a Different Version

Вторник, 02 Июня 2020 г. 09:30 + в цитатник

Back when I was still working for a large enterprise company, I did a lot of code reviews. This particular organization didn’t have much interest in code quality, so a lot of the code I was reviewing was just… bad. Often, I wouldn’t even need to read the code to see that it was bad.

In the olden times, inconsistent or unclear indentation was a great sign that the code would be bad. As IDEs started automating indentation, you lost that specific signal, but gained a new one. You can just tell code is bad when it’s shaped like this:

public List getDocSectionsChanged(CustomerVersionTag versionTag) {
	Set sections = new HashSet<>();
	for (Map.Entry> entry : getVersionChanges().get(versionTag).entrySet()) {
		for (F.Tuple>> tuple : getDocSectionToSdSection()) {
			for (Map.Entry> entry2 : tuple._2.entrySet()) {
				if (entry.getKey().startsWith(entry2.getKey())) {
					for (String change : entry.getValue()) {
						for (String lookFor : entry2.getValue()) {
							if (change.startsWith(lookFor)) {
								sections.add(getDocSectionNumber(tuple._1));
							}
						}
					}
				}
			}
		}
	}
	return sections.stream().sorted(Integer::compareTo).collect(Collectors.toList());
}

Torvalds might not think much of 80 character lines, but exceedingly long lines are definitely a code smell, especially when they're mostly whitespace.

This is from a document management system. It tracks versions of documents, and a new feature was requested: finer grained reporting on which sections of the document changed between versions. That information was already stored, so all a developer needed to do was extract it into a list of section numbers.

Edda’s entire team agreed that this would be a simple task, and estimated a relatively short time to build it- hours, maybe a day at the outside. Two weeks later when it was finally delivered, the project manager wanted to know how their estimate had gotten so off.

At a glance, you know the code is bad, because it’s shaped badly. That level of indentation is always a quick sign that something’s badly built. But then note the tested loops: a startsWith in a loop in a loop in a startsWith in a loop in a loop in a loop. The loops don’t even always make sense to be nested- the outermost loops across an entrySet to get entries, but the next loop iterates across the result of getDocSectionToSdSection(), which takes no parameters- the 2nd loop isn’t actually driven by anything extracted in the 1st loop. The inner-most pair of loops seem to be an attempt compare every change in two entry objects to see if there’s a difference at any point.

I don’t know their API, so I certainly don’t know the right approach, but at a glance, it’s clear that this is the wrong approach. With the nesting code structures and the deeply nested generics (types like F.Tuple>> are another key sign somebody messed up), I don’t have any idea what the developer was thinking or what the purpose of this code was. I don’t know what they were going for, but I hope to the gods they missed.

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

https://thedailywtf.com/articles/try-a-different-version


Метки:  

CodeSOD: Don't be so Negative Online

Понедельник, 01 Июня 2020 г. 09:30 + в цитатник

It's fair to say that regardless of their many advantages, "systems languages", like C, are much harder to use than their more abstract cousins.Vendors know this, which is why they often find a way to integrate across language boundaries. You might write critical functions in C or C++, then invoke them in Python or from Swift or… Visual Basic 6.

And crossing those language boundaries can pose other challenges. For example, Python has a built-in boolean type. C, for quite a long time didn't. Which means a lot of C code has blocks like this:

#define BOOL int #define FALSE 0 #define TRUE 1 #define FILE_NOT_FOUND 2

Carl C provides that block, just for illustration purposes. Awhile back, he inherited a big pile of antique COM+ and the associated VB6 front end, along with a herd of "COM Wizards" and "Junior VB Programmers" to help maintain it.

The idea of the system was that all the "hard stuff" would be done in C++, while the UI would be a VB6 application. The C++ COM layer talked to some hardware which could be attached to the PC, and the VB6 layer let the user check the status and interact with the device.

Unfortunately, the Junior VB Programmers quickly encountered a problem: they could NEVER get the device online. Plugging, unplugging, rebooting, trying different ports, different computers, it never worked. But when the "COM wizards" tossed them a diagnostic program written in C++, things worked fine.

"Must be a problem in your VB code," was the obvious conclusion.

Dim oHardware as New HardwareServer ' Initialize Hardware oHardware.Initialize 0 If oHardware.ONLINE = True Then Set oActuator = oHardware.Actuator Else MsgBox "Hardware did not initialize correctly." End End If

Reading through that code, it's hard to see at a glance what could be wrong about it. Could the problem be in the COM layer?

interface IHardwareServer : IDispatch { [propget, id(1)] HRESULT Actuator([out, retval] IActuator* *pVal); [propget, id(2)] HRESULT ONLINE([out, retval] BOOL *pVal); [id(3)] HRESULT Initialize(short interfaceID); }; coclass HardwareServer { [default] interface IHardwareServer; };

While the COM approach to defining a property is a little awkward, nothing at a glance looks wrong here, either. ONLINE is a property that returns a BOOL.

But this is a C++ boolean. Defined so that true is one and false is zero.

Visual Basic, in addition to letting arrays start at 1 if you really wanted to, had another quirk. It was pretty loosey-goosey with types (defaulting to the handy Variant type, which is a fancy way of saying "basically no type at all"), and the internal implementation of its types could be surprising.

For example, in VB6, False was zero, much like you'd expect. And True was… -1. Yes, -1. Carl suggests this was to "kinda sorta mostly hide the distinction between bitwise and logical operations", which does sound like the sort of design choice Visual Basic would make. This is also TRWTF.

Now, it's easy to see how the Visual Basic code above is wrong: oHardware.ONLINE = True is testing to see if ONLINE is equal to -1, which is not true. A more correct way of writing the Visual Basic would be simply to test if oHardware.ONLINE then…. Visual Basic is okay with falsy/truthy statements, so whether ONLINE is 1 or -1, that would evaluate as true.

That doesn't let the COM programmers off the hook though. COM was designed to work across languages, and COM was designed with the understanding that different languages might have different internal representations of boolean values.

As Carl adds:

Of course if they were really COM wizards they would have used the VARIANT_BOOL type in the first place, and returned VARIANT_TRUE or VARIANT_FALSE.

[Advertisement] ProGet can centralize your organization's software applications and components to provide uniform access to developers and servers. Check it out!

https://thedailywtf.com/articles/don-t-be-so-negative-online


Метки:  

Error'd: A Pattern of Errors

Пятница, 29 Мая 2020 г. 09:30 + в цитатник

"Who would have thought that a newspaper hired an ex-TV technician to test their new CMS with an actual test pattern!" wrote Yves.

 

"Guess I should throttle back on binging all of Netflix," writes Eric S.

 

Christian K. wrote, "So, does this let me listen directly to my network packets?"

 

"I feel this summarizes very well the current Covid-19 situation in the US," Henrik B. wrote.

 

Steve W. writes, "I don't know if I've been gardening wrong or computing wrong, but at least know I know how best to do it!"

 

"Oh, how silly of me to search a toy reseller's website for 'scrabble' when I really meant to search for 'scrabble'. It's so obvious now!"

 

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

https://thedailywtf.com/articles/a-pattern-of-errors


Метки:  

CodeSOD: This is Your Last Birthday

Четверг, 28 Мая 2020 г. 09:30 + в цитатник

I have a philosophy on birthdays. The significant ones aren’t the numbers we usually choose- 18, 21, 40, whatever- it’s the ones where you need an extra bit. 2, 4, 8, and so on. By that standard, my next birthday landmark isn’t until 2044, and I’m a patient sort.

Christian inherited some legacy C# code which deals in birthdays. Specifically, it needs to be able to determine when your last birthday was. Now, you have to be a bit smarter than simply “lop off the year and insert this year,” since that could be a future birthday, but not that much smarter.

The basic algorithm most of us would choose, though, might start there. If their birthday is, say, 12/31/1969, then we could ask, is 12/31/2020 in the future? It is. Then their last birthday was on 12/31/2019. Whereas, for someone born on 1/1/1970, we know that 1/1/2020 is in the past, so their last birthday was 1/1/2020.

Christian’s predecessor didn’t want to do that. Instead, they found this… “elegant” approach:

static DateTime GetLastBirthday(DateTime dayOfBirth)
{
    var now = DateTime.Now;

    var former = dayOfBirth;
    var current = former.AddYears(1);

    while (current < DateTime.Now)
    {
        former = current;
        current = current.AddYears(1);
    }

    return former;
}

Start with their birthdate. Then add one to the year, and store that as current. While current is in the past, remember it as former, and then add one to current. When current is finally a date in the future, former must be a date in the past, and store their last birthday.

The kicker here, though, is that this isn’t used to calculate birthdays. It’s used to calculate the “Start of the Case Year”. Which operates like birthdays, or any anniversary for that matter.

var currentCaseYearStart = GetLastBirthday(caseStart);

Sure, that’s weird naming, but Christian has this to add:

Anyways, [for case year starts] it has a (sort of) off-by-one error.

Christian doesn’t expand on that, and I’m not entirely certain what the off-by-one-like behavior would be in that case, and I assume it has something to do with their business rules around case start dates.

Christian has simplified the date calculation, but has yet to rename it: it turns out this method is called in several places, but never to calculate a birthday.

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!

https://thedailywtf.com/articles/this-is-your-last-birthday


Метки:  

CodeSOD: Is We Equal?

Среда, 27 Мая 2020 г. 09:30 + в цитатник

Testing for equality is hard. Equal references are certainly equal, but are equal values? What does it mean for two objects to “equal” each other? It’s especially hard in a language like JavaScript, which is “friendly” about type conversions.

In JavaScript land, you’re likely to favor a tool like “lodash”, which provides utility functions like isEqual.

Mohsin was poking around an old corner of their codebase, which hadn’t been modified in some time. Waiting there was this “helpful” function.

import _ from 'lodash';

export function areEqual(prevProps, nextProps) {
  if (_.isEqual(prevProps, nextProps)) {
    return true;
  }
  return false;
}

In this case, our unknown developer is the best kind of correct: grammatically correct. isEqual should rightly be called areEqual, since we’re testing if two objects “are equal” to each other.

Does that justify implementing a whole new method? Does it justify implementing it with an awkward construct where we use an if to determine if we should return true or false, instead of just, y’know, returning true or false.

isEqual already returns a boolean value, so you don’t need that if: return _.isEqual(…) would be quite enough. Given that functions are data in JavaScript, we could even shorten that by export const areEqual = _.isEqual.

Or, we could just not do this at all.

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

https://thedailywtf.com/articles/is-we-equal


Метки:  

A Vintage Printer

Вторник, 26 Мая 2020 г. 09:30 + в цитатник

IBM 1130 (16758008839)

Remember Robert, the student who ruined his class curve back in the 1960s? Well, proving the old adage that the guy who graduates last from medical school is still a doctor, he managed to find another part-time job at a small hospital, earning just enough to pay his continued tuition.

Industry standard in those days was the IBM System/360 series, but it was out of the price range of this hospital. Instead, they had an IBM 1130, which was designed to be used in laboratories and small scientific research facilities. It used FORTRAN, which was pretty inappropriate for business use, but a set of subroutines offered by IBM contained routines for dealing with currency values and formatting. The hospital captured charges on punch cards and those were used as input to a billing program.

The printer was a monstrous beast, spinning a drum of characters and firing hammers to print characters as they went by. In order to print in specific boxes on the billing forms, it was necessary to advance the paper to a specific point on the page. This was done using a loop of paper tape that had 12 channels in its width. A hole was punched at the line in the tape where the printer needed to stop. Wire brushes above the tape would hit the hole, making contact with the metal drum inside the loop and stopping the paper feed.

There was one box in the billing form that was used infrequently, only every few days. When the program issued the code to skip to that channel, paper would begin spewing for a few seconds, and then the printer would shut down with a fault. This required stopping, removing the paper, typing the necessary data into the partially-printed bill, and then restarting the job from the point of failure.

IBM Field Engineering was called, but was unable to find a reason for the problem. Their considered opinion was that it was a software fault. After dealing with the problem on a fairly regular basis, things escalated. The IBM Systems Engineer assigned to the site was brought in.

Robert's boss, the author of the billing software, had relied on an IDEAL subroutine package provided by IBM—technically unsupported, but written by IBM employees, so generally one would assume it was safe to use. The Systems Engineer spent a while looking over that package, but eventually declared it innocent and moved on. He checked over the code Robert's boss had written, but ultimately that, too, failed to provide any answers.

"Then it must be the machine," Robert's boss stated.

This was the wrong thing to say. "It couldn't be the machine!" The Engineer, a prideful young woman, bristled at the insinuation. "These machines are checked. Everything's checked before it leaves the factory!"

Tempers flared, voices on the edge of shouting. Robert ducked back into the room with the computer, followed rapidly by the Field Engineer who had come along earlier in the day to do his own checks. Trying to pretend they couldn't hear the argument, the pair began another once-over on the machine, looking for any sign of mechanical fault.

"Hey, a question," said Robert, holding the thick cable that connected the printer to the computer. "Could it be a problem with the cable?"

The Field Engineer unplugged the cable and examined it. "The pin for that channel doesn't look seated," he admitted sheepishly. "Let's replace it and see what happens."

That day Robert learned two valuable lessons in debugging. Number one: when in doubt, go over each piece of the machine, no matter how unlikely. Number two: never tell an IBM Engineer that the problem is on their end.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

https://thedailywtf.com/articles/a-vintage-printer


Метки:  

CodeSOD: Classic WTF: A Char'd Enum

Понедельник, 25 Мая 2020 г. 09:30 + в цитатник
It's a holiday in the US today, so we're reaching back into the archives while doing some quarantine grilling. This classic has a… special approach to handling enums. Original. --Remy

Ah yes, the enum. It's a convenient way to give an integer a discrete domain of values, without having to worry about constants. But you see, therein lies the problem. What happens if you don't want to use an integer? Perhaps you'd like to use a string? Or a datetime? Or a char?

If that were the case, some might say just make a class that acts similarly, or then you clearly don't want an enum. But others, such as Dan Holmes' colleague, go a different route. They make sure they can fit chars into enums.

'******* Asc Constants ********
Private Const a = 65
Private Const b = 66
Private Const c = 67
Private Const d = 68
Private Const e = 69
Private Const f = 70
Private Const H = 72
Private Const i = 73
Private Const l = 76
Private Const m = 77
Private Const n = 78
Private Const O = 79
Private Const p = 80
Private Const r = 82
Private Const s = 83
Private Const t = 84
Private Const u = 85
Private Const x = 88

  ... snip ...

'******* Status Enums *********
Public Enum MessageStatus
  MsgError = e
  MsgInformation = i
  ProdMsg = p
  UpLoad = u
  Removed = x
End Enum

Public Enum PalletTable
  Shipped = s   'Pallet status code
  Available = a
End Enum
[Advertisement] Ensure your software is built only once and then deployed consistently across environments, by packaging your applications and components. Learn how today!

https://thedailywtf.com/articles/classic-wtf-a-char-d-enum


Метки:  

Error'd: Rest in &;$(%{>]$73!47;lb*#’v\

Пятница, 22 Мая 2020 г. 09:30 + в цитатник

"Should you find yourself at a loss for words at the loss of a loved one, there are other 'words' you can try," Steve M. writes.

 

"Cool! I can still use the premium features for -3 days! Thanks, Mailjet!" writes Thomas.

 

David C. wrote, "In this time of virus outbreak, we all know you've been to the doctor so don't try and lie about it."

 

Gavin S. wrote, "I guess Tableau sets a low bar for its Technical Program Managers?"

 

"Ubutuntu: For when your Linux desktop isn't frilly enough!" Stuart L. wrote.

 

"Per Dropbox's rules, this prompt valid only for strings with a length of 5 that are greater than or equal to 6," Robert H. writes.

 

[Advertisement] ProGet supports your applications, Docker containers, and third-party packages, allowing you to enforce quality standards across all components. Download and see how!

https://thedailywtf.com/articles/rest-in-73-47-v


Метки:  

CodeSOD: Checking Your Options

Четверг, 21 Мая 2020 г. 09:30 + в цитатник

If nulls are a “billion dollar mistake”, then optional/nullable values are the $50 of material from the hardware store that you use to cover up that mistake. It hasn’t really fixed anything, but if you’re handy, you can avoid worrying too much about null references.

D. Dam Wichers found some “interesting” Java code that leverages optionals, and combines them with the other newish Java feature that everyone loves to misuse: streams.

First, let’s take a look at the “right” way to do this though. The code needs to take a list of active sessions, filter out any older than a certain threshold, and then summarize them together into a single composite session object. This is a pretty standard filter/reduce scenario, and in Java, you might write it something like this:

return sessions.stream()
  .filter(this::filterOldSessions)
  .reduce(this::reduceByStatus);

The this::… syntax is Java’s way of passing references to methods around, which isn’t a replacement for lambdas but is often easier to use in Java. The stream call starts a stream builder, and then we attach the filter and reduce operations. One of the key advantages here is that this can be lazily evaluated, so we haven’t actually filtered yet. This also might not actually return anything, so the result is implicitly wrapped in an Optional type.

With the “right” way firmly in mind, let’s look at the body of a method D. Dam found.

   Optional theSession;

   theSession = sessions.stream()
                     .filter(session -> filterOldSessions(session))
                     .reduce((first, second) -> reduceByStatus(first, second));

   if (theSession.isPresent()) {
        return Optional.of(theSession.get());
   } else {
        return Optional.empty();
   }

This code isn’t wrong, it just highlights a developer unfamiliar with their tools. First, note the use of lambdas instead of the this::… syntax. It’s functionally the same, but this is harder to read- it’s less clear.

The real confusion, though, is after they’ve gotten the result. They understand that the stream operation has returned an Optional. So they check if that Optional isPresent- if it has a value. If it does, they get the value and wrap it in a new Optional (Optional.of is a static factory method which generates new Optionals). Otherwise, if it’s empty, we return an empty optional. Which, if they’d just returned the result of the stream operation, they would have gotten the same result.

It’s always frustrating to see this kind of code. It’s a developer who is so close to getting it, but who just isn’t quite there yet. That said, it’s not all bad, as D. Dam points out:

In defense of the original code: it is a little more clear that an Optional is setup properly and returned.

I’m not sure that it’s necessary to make that clear, but this code isn’t bad, it’s just annoying. It’s the kind of thing that you need to bring up in a code review, but somebody’s going to think you’re nit-picking, and when you start using words like readability, there’ll always be a manager who just wants this commit in production yesterday and says, “Readability is different for everyone, it’s fine.”

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

https://thedailywtf.com/articles/checking-your-options


Метки:  

CodeSOD: A Maskerade

Среда, 20 Мая 2020 г. 09:30 + в цитатник

Josh was writing some code to interact with an image sensor. “Fortunately” for Josh, a co-worker had already written a nice large pile of utility methods in C to make this “easy”.

So, when Josh wanted to know if the sensor was oriented in landscape or portrait (or horizontal/vertical), there was a handy method to retrieve that information:

// gets the sensor orientation
// 0 = horizontal, 1 = vertical
uint8_t get_sensor_orient(void);

Josh tried that out, and it correctly reported horizontal. Then, he switched the sensor into vertical, and it incorrectly reported horizontal. In fact, no matter what he did, get_sensor_orient returned 0. After trying to diagnose problems with the sensor, with the connection to the sensor, and so on, Josh finally decided to take a look at the code.


#define BYTES_TO_WORD(lo, hi)   (((uint16_t)hi << 8) + (uint16_t)lo)
#define SENSOR_ADDR             0x48  
#define SENSOR_SETTINGS_REG     0x24

#define SENSOR_ORIENT_MASK      0x0002

// gets the sensor orientation  
// 0 = horizontal, 1 = vertical  
uint8_t get_sensor_orient(void)  
{
    uint8_t buf;  
    read_sensor_reg(SENSOR_ADDR, SENSOR_SETTINGS_REG, &buf, 1);

    uint16_t tmp = BYTES_TO_WORD(0, buf) & SENSOR_ORIENT_MASK;

    return tmp & 0x0004;  
}

This starts reasonable. We create byte called buf and pass a reference to that byte to read_sensor_reg. Under the hood, that does some magic and talks to the image sensor and returns a byte that is a bitmask of settings on the sensor.

Now, at that point, assuming the the SENSOR_ORIENT_MASK value is correct, we should just return (buf & SENSOR_ORIENT_MASK) != 0. They could have done that, and been done. Or one of many variations on that basic concept which would let them return either a 0 or a 1.

But they can’t just do that. What comes next isn’t a simple matter of misusing bitwise operations, but a complete breakdown of thinking: they convert the byte into a word. They have a handy macro defined for that, which does some bitwise operations to combine two bytes.

Let’s assume the sensor settings mask is simply b00000010. We bitshift that to make b0000001000000000, and then add b00000000 to it. Then we and it with SENSOR_ORIENT_MASK, which would be b0000000000000010, which of course isn’t aligned with the layout of the word, so that returns zero.

There’s no reason to expand the single byte into two. That BYTES_TO_WORD macro might have other uses in the program, but certainly not here. Even if it is used elsewhere in the program, I wonder if they’re aware of the parameter order; it’s unusual (to me, anyway) to accept the lower order bits as the first parameter, and I suspect that’s part of what tripped this programmer up. Once they decided to expand the word, they assumed the macro would expand it in the opposite order, in which case their bitwise operation would have worked.

Of course, even if they had correctly extracted the correct bit, the last line of this method completely undoes all of that anyway: tmp & 0x0004 can’t possibly return a non-zero value after you’ve done a buf & 0x0002, as b00000100 and b00000010 have no bits in common.

As written, you could just replace this method with return 0 and it’d do the same thing, but more efficiently. “Zero” also happens to be how much faith I have in the developer who originally wrote this.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

https://thedailywtf.com/articles/a-maskerade


Метки:  

The Dangerous Comment

Вторник, 19 Мая 2020 г. 09:30 + в цитатник

It is my opinion that every developer should dabble in making their own scripting language at least once. Not to actually use, mind you, but to simply to learn how languages work. If you do find yourself building a system that needs to be extendable via scripts, don’t use your own language, but use a well understood and well-proven embeddable scripting language.

Which is why Neil spends a lot of time looking at Tcl. Tcl is far from a dead language, and its bundled in pretty much every Linux or Unix, including ones for embedded platforms, meaning it runs anywhere. It’s also a simple language, with its syntax described by a relatively simple collection of rules.

Neil’s company deployed embedded network devices from a vendor. Those embedded network devices were one of the places that Tcl runs, and the company which shipped the devices decided that configuration and provisioning of the devices would be done via Tcl.

It was nobody’s favorite state of affairs, but it was more-or-less fine. The challenges were less about writing Tcl and more about learning the domain-specific conventions for configuring these devices. The real frustration was that most of the time, when something went wrong, especially in this vendor-specific dialect, the error was simply: “Unknown command.”

As provisioning needs got more and more complicated, scripts calling out to other scripts became a more and more common convention, which made the “Unknown command” errors even more frustrating to track down.

It was while digging into one of those that Neil discovered a special intersection of unusual behaviors, in a section of code which may have looked something like:

# procedure for looking up config options
proc lookup {fname} {
  # does stuff …
}

Neil spent a good long time trying to figure out why there was an “Unknown command” error. While doing that hunting, and referring back to the “Dodekalogue” of rules which governs Tcl, Neil had a realization, specifically while looking at the definition of a comment:

If a hash character (“#”) appears at a point where Tcl is expecting the first character of the first word of a command, then the hash character and the characters that follow it, up through the next newline, are treated as a comment and ignored. The comment character only has significance when it appears at the beginning of a command.

In Tcl, a command is a series of words, where the first word is the name of the command. If the command name starts with a “#”, then the command is a comment.

That is to say, comments are commands. Which doesn’t really sound interesting, except for one very important rule about this vendor-specific deployment of Tcl: it restricted which commands could be executed based on the user’s role.

Most of the time, this never came up. Neil and his peers logged in as admins, and admins could do anything. But this time, Neil was logged in as a regular user. It didn’t take much digging for Neil to discover that in the default configuration the “#” command was restricted to administrators.

The vendor specifically shipped their devices configured so that comments couldn’t be added to provisioning scripts unless those scripts were executed by administrators. It wasn’t hard for Neil to fix that, but with the helpful “Unknown Command” errors, it was hard to find out what needed to be fixed.

[Advertisement] Otter - Provision your servers automatically without ever needing to log-in to a command prompt. Get started today!

https://thedailywtf.com/articles/the-dangerous-comment


Метки:  

Поиск сообщений в rss_thedaily_wtf
Страницы: 124 ... 92 91 [90] 89 88 ..
.. 1 Календарь