What's the most unsound program you've had

2019-02-01 07:24发布

问题:

I periodically am called upon to do maintenance work on a system that was built by a real rocket surgeon. There's so much wrong with it that it's hard to know where to start.

No, wait, I'll start at the beginning: in the early days of the project, the designer was told that the system would need to scale, and he'd read that a source of scalability problems was traffic between the application and database servers, so he made sure to minimize this traffic. How? By putting all of the application logic in SQL Server stored procedures.

Seriously. The great bulk of the application functions by the HTML front end formulating XML messages. When the middle tier receives an XML message, it uses the document element's tag name as the name of the stored procedure it should call, and calls the SP, passing it the entire XML message as a parameter. It takes the XML message that the SP returns and returns it directly back to the front end. There is no other logic in the application tier.

(There was some code in the middle tier to validate the incoming XML messages against a library of schemas. But I removed it, after ascertaining that 1) only a small handful of messages had corresponding schemas, 2) the messages didn't actually conform to these schemas, and 3) after validating the messages, if any errors were encountered, the method discarded them. "This fuse box is a real time-saver - it comes from the factory with pennies pre-installed!")

I've seen software that does the wrong thing before. Lots of it. I've written quite a bit. But I've never seen anything like the steely-eyed determination to do the wrong thing, at every possible turn, that's embodied in the design and programming of this system.

Well, at least he went with what he knew, right? Um. Apparently, what he knew was Access. And he didn't really understand Access. Or databases.

Here's a common pattern in this code:

SELECT @TestCodeID FROM TestCode WHERE TestCode = @TestCode

SELECT @CountryID FROM Country WHERE CountryAbbr = @CountryAbbr

SELECT Invoice.*, TestCode.*, Country.*
   FROM Invoice
   JOIN TestCode ON Invoice.TestCodeID = TestCode.ID
   JOIN Country ON Invoice.CountryID = Country.ID
   WHERE Invoice.TestCodeID = @TestCodeID AND Invoice.CountryID = @CountryID

Okay, fine. You don't trust the query optimizer either. But how about this? (Originally, I was going to post this in What's the best comment in source code you have ever encountered? but I realized that there was so much more to write about than just this one comment, and things just got out of hand.) At the end of many of the utility stored procedures, you'll see code that looks like the following:

-- Fix NULLs
SET @TargetValue = ISNULL(@TargetValue, -9999)

Yes, that code is doing exactly what you can't allow yourself to believe it's doing lest you be driven mad. If the variable contains a NULL, he's alerting the caller by changing its value to -9999. Here's how this number is commonly used:

-- Get target value
EXEC ap_GetTargetValue @Param1, @Param2, OUTPUT @TargetValue
-- Check target value for NULL value
IF @TargetValue = -9999
    ...

Really.

For another dimension of this system, see the article on thedailywtf.com entitled I Think I'll Call Them "Transactions". I'm not making any of this up. I swear.

I'm often reminded, when I work on this system, of Wolfgang Pauli's famous response to a student: "That isn't right. It isn't even wrong."

This can't really be the very worst program ever. It's definitely the worst one I've worked on in my entire 30-year (yikes) career. But I haven't seen everything. What have you seen?

回答1:

I once tried to write an MP3 decoder. It didn't work.



回答2:

I maintained ExtUtils::MakeMaker. MakeMaker is certainly not the worst code I've had to maintain; it's actually an engineering marvel. However, it is in that unique class of coding horrors wherein the most mission-critical code is also the most terrifying.

MakeMaker is the installer for most Perl modules. When you run "Makefile.PL" you are invoking MakeMaker. If MakeMaker breaks, Perl breaks. Perl runs on everything, so MakeMaker has to run on everything. When I say everything I mean EVERYTHING. Every bizarre Unix variant. Windows 95 on up. And VMS. Yes, VMS.

What does MakeMaker do? Makefile.PL is a Perl program that writes a Makefile which contains shell commands, which often run Perl, to build and install a Perl module. Let me repeat: It writes shell commands to run Perl. Perl, the language which replaces shell scripts.

Oh, it can also compile and link C code. And it can also statically link Perl modules into perl. Oh, and it can manage RCS checkouts. Oh, and roll tarballs of your distribution... and zip files. And do all this other stuff vaguely related to installing modules.

And it has to do all this in a portable, backwards compatible fashion. It has to deal with variants of and bugs in...

  • make (GNU make, BSD make, nmake, dmake, mms, mmk to name a few)
  • shell
  • Perl
  • The filesystem (if you don't think that's a big deal, try VMS)
  • C compilers & linkers

It absolutely, positively can not fail and must remain 100% backwards compatible.

Oh, and it has very little in the way of a real extension API, so it has to remain compatible with the ad hoc Makefile hackery people have to do to extend it.

Why does it do all this? 15 years ago when Perl only ran on Unix this seemed like a great idea. Why write a whole build system when you can just use make? Perl's a text processing language; we'll just use it to write a Makefile!

Fortunately there is a replacement, Module::Build, and I pinned my hopes that it would swiftly kill MakeMaker. But its uptake has been slow and the community very resistant to the change, so I'm stuck maintaining MakeMaker.



回答3:

What’s the most unsound program you’ve had to maintain?

Everything I've ever written!

Seriously. The more I read blogs, listen to podcasts, and follow sites like this, the more I learn every day. And every day I basically realize everything I wrote yesterday is wrong in some way. I feel for the poor saps that are maintaining the things I wrote early in my career.



回答4:

The one I have just started on.

  1. No Source control.
  2. All source is edited live. To stop mistakes, there are backup files like db-access.php.070821 littering the source tree.
  3. The code is exceptionally brittle - there is very little in the way of error checking and absolutely no fall back if it does.


回答5:

I once had to maintain a legacy C application which had previously been written and maintained by some programmers who had lost the will to program (and possibly live). It had too many WTFs to mention, but I do remember a boolean function which under various special cases would return TRUE+1, TRUE+2, etc.

Then I read Roedy Green's essay and laughed a lot, until I realised that the reason I found it funny was that I recognised most of the examples from the code I was maintaining. (That essay has become a bit bloated over years of additions, but it's still worth a look.)



回答6:

I used to be a COBOL programmer (shudder). All of our code fell into the "unsound" category. In COBOL, you have no namespaces, all variables are global and there's a lot of mandatory duplication of filenames and other resources. To call a procedure, you set global variables, call the procedure, and then inspect the contents of those global variables (or others that might get set).

The worst, though, was maintaining a COBOL program written before I was born (I was born in 1967) and its exclusive method of flow control was the GOTO. It was an absolute mess and impossible to follow. Minor changes to a variable type could take days to work out. There were no automated tests and manual test plans were never saved, so every change required a new manual test plan be written out, followed exhaustively, and turned in with the code.

Ironically, this is what makes COBOL so successful. COBOL is often executed by Job Control Language (JCL). Since COBOL is so weak, programs don't do a lot, so JCL would allocate some disk space (often down to the cylinder level), and execute a small COBOL program to read data and then write out just the data you need. Then JCL might call a sort program to sort the resulting file. Then another COBOL program would be called to read the sorted file and summarize the data and maybe re-extract the needed results. And maybe JCL would be used again to to move the file somewhere else, and yet another COBOL program would get called to read the results and store them in a database, and so on. Each COBOL program tended to only do one thing and a primitive version of the Unix pipeline model was created -- all because COBOL is too hard to maintain or do anything complicated with. We had loose coupling and tight cohesion (between programs, not in them) because it was almost impossible to write COBOL any other way.



回答7:

Right out of grad school at Lucent I was given a compiler and interpreter to maintain, written in PL/I. The language being compiled described a complex set of integrity constraints, and the interpreter ran those constraints against a large data set that would later be formed into the initial database controlling the 4ESS switch. The 4ESS was, and still is, a circuit-switch for long distance voice traffic.

The code was a jumble. There was a label to branch to called "NORTH40". I asked the original developer what it meant.

"That's where the range checks are done, you know, the checks to make sure each field has a correct value."

"But why 'NORTH40'?"

"You know, 'Home, home on the range.'"

"Huh?"

Turned out 'NORTH40' meant a farm's north 40 acres, which in his city-bred mind had an obscure connection to a cattle ranch.

Another module had two parallel arrays called TORY and DIREC, which were updated in parallel and so were an obviously misguided attempt to model a single array containing pairs of data. I couldn't figure out the names and asked the developer. Turned out they were meant to be read together: "direc-tory". Great.

The poor guys that had to write the integrity constraints had no user manual to guide them, just an oral tradition plus hand-written notes. Worse, the compiler did no syntax-checking, and very often they wound up specifying a constraint that was mapped quietly into the wrong logic, often with very bad consequences.

Worse was to come. As I delved into the bowels of the interpreter, I discovered it was built around a giant sort process. Before the sort was an input process that generated the sort input data from the raw data and the integrity constraints. So if you had a table with 5,000 trunk definitions in it, and each trunk record had three field values that had to be unique across the whole input data set, the input process would create 3 * 5,000 = 15,000 sort input records, each a raw data record prefixed by the integrity constraint number and a copy of the field value to be sorted. Instead of doing three 5,000 record sorts, it did one 15,000 record sort. By the time you factored in hundreds of intra- and inter-table integrity constraints, and some very large tables, you had a combinatorial nightmare.

I was able to refactor a bit, document the language, and add syntax checking with comprehensible error messages, but a few months later jumped at a transfer to a new group.



回答8:

I'm maintaining a scheduling web-application we use in our intranet. When I've been asked if I could remove an agent from the scheduler I thought, sure why not. When I took a look into the source code I figured out that every hour of this agent's day was coded separately. So was every day of his week. And so was every week of every agent of this region. And so was every region of about 5 regions. Html fies that hold asp-code all over the place.

One day I took some time to guess how many lines of code are in these various files and I estimated about 300000. Three-hundred thousand lines of code of once handwritten and then copy and pasted code.

But this number convinced my manager pretty quickly that we would need a new scheduling app very quickly.



回答9:

I once worked on a CAD application written in BASIC where the company policy was that every program must begin with the statement:

ON ERROR RESUME

jMM



回答10:

A PHP/MySQL-driven online contact management system, where the contact table did not have a natural key. There were numerous instances of database fields containing composite data in the form of a delimited string that subsequently needed to be parsed by application code.

The HTML and logic were intertwined, almost no functions were used, but rather code was cut and pasted into dozens of source code files. Data was not sanitized and so fields with (e.g.) embedded vertical tabs caused the XML returned by Ajax calls to malfunction, and to top it all off, files with dozens if not hundreds of empty statements consisting of closing braces immediately followed by semi-colons: "};"



回答11:

Anything I've ever written that's originally supposed to be a quick prototype and ends up staying around a while. My problem domain requires a lot of throwaway prototyping by its nature. For these prototypes, it's sometimes reasonable to violate every best practice and rule of good style, just get it done and clean up later if the prototype ends up being worth keeping. However, occasionally these prototypes end up being very difficult to get working properly, but then end up being keepers. In these cases, I usually end up putting off refactoring/rewriting the thing indefinitely because I'm afraid I'll never get it working again. Further lessening my motivation is that my boss is a domain expert who doesn't program at all.



回答12:

The interpreter for a CAD/CAM geometry processing language (P1 = 10,10; P2 = 20,20; L1 = P1,P2; - that sort of thing), written in Microsoft BASIC Professional Development System (PDS), with minimal length variable names (it ran out of single letters quickly, so moved on to double letters. PP, PQ, PR, anyone?). And, to be fair, some comments. In Italian.

Funnily enough, it actually worked, and I was able to add some functionality to it, but it was like amateur dentistry - painful, and certainly not recommended...



回答13:

I once got called in to help track down a periodic crash in an EDIF reader. Almost immediately I started getting a headache. The original author seemed to feel that Yacc was going to penalize him for whitespace, and his Yacc grammar was a dense, unreadable mess. I spent a couple of hours formatting it, adding rules for missing terminals as they emerged, structuring the declarations to avoid stack growth, and voila, the crash was extinguished.

So remember, for every time you wait while Yacc processes your grammar, there will be thousands of runs with the generated parser. Don't be cheap with the whitespace!



回答14:

Maintaining ASP applications for a specific company who hires developers to maintain their previous hires... All these applications are not documented, neither there are any comments.

Every function is copied and pasted in every ASP page. So no functions are defined or whatsoever... Every day I'm crippled by their environments, because I have first to remote to a server, to bypass the DMZ. After that I have to remote in to a production server where I have to make the changes.