Old-School Coding Techniques You May Not Miss
CWmike writes "Despite its complexity, the software development process has gotten better over the years. 'Mature' programmers remember manual intervention and hand-tuning. Today's dev tools automatically perform complex functions that once had to be written explicitly. And most developers are glad of it. Yet, young whippersnappers may not even be aware that we old fogies had to do these things manually. Esther Schindler asked several longtime developers for their top old-school programming headaches and added many of her own to boot. Working with punch cards? Hungarian notation?"
Some of those are obnoxious and good to see them gone. Others, not so much. For instance, sorting/searching algorithms, data structures, etc. Don't they still make you code these things in school? Isn't it good to know how they work and why?
On the other hand, yeah... fuck punch cards.
Heh, I had to turn in a punched card assignment in college (probably the last year THAT was ever required)... but I was smart enough to use an interactive CRT session to debug everything first... then simply send the corrected program to the card punch.
I was an early adopter of the "let the machine do as much work as possible" school of thought.
This issue is a bit more complicated than you think.
The one thing I don't think I'll ever, ever miss is writing loaders for some of the stupider file formats out there. Sure, it's not hard, per se, to write a .bmp loader, but once you've done it once or twice it gets old. Eventually I wrote a helper image library to do it all but it still would occasionally come across some obscure variant that it wouldn't load. Far worse were early 3D model formats, even now I tend to stick with .md2 for hobby projects just because it's simple, does what I want, and EVERYTHING exports to it.
Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
I don't get what the big deal is with Hungarian Notation. Why do people consider it a bad thing?
Modern IDEs might reduce the need for it, but not everyone uses an IDE to read or write code.
My Sysadmin Blog
For instance, one of my programming friends fit a 7K print driver into 5K by shifting (assembly language) execution one bit to the right.
I don't understand why they said that Object oriented code eliminated the need for structured programming. OO isn't any better than structured, just different. It has some good uses, but a lot of overhead and is not the best fit for all problems.
That said, I remember spaghetti code. There was a point when FORTRAN got if/then/else statements. It was obvious in one piece of code that they told the developers to start using them. They had written
If (a .eq. 1) then
go to 20
else
go to 30
endif
20 continue
My brain hurt after reading that.
Everything you know is wrong, Just forget the words and sing along.
Yeah, some of these are pretty old. I do remember working on a machine where the compiler wasn't smart enough to make the code really fast so I would get the .s file out and hand edit the assembly code. This resulted in some pretty spectacular speedups (8x for instance). Mind you, more recently I was forced to do something similar when working with some SSE code written for the Intel chips which was strangely slower on AMD. Turned out it was because the Intel chips (PIII and P4) were running on a 32 bit bus and memory access in bytes was pretty cheap. The Athlons were on the 64 bit EV6 bus and so struggled more so were slower. Once I added some code to lift the data from memory in 64 bit chunks and then do the reordering it needed using SSE the AMD chips were faster than the Intel ones.
Sometimes I think we have lost more than we have gained though with our reliance on compilers being smarter. It was great fun getting in there with lists of instruction latencies and manually overlapping memory loads and calculations. Also when it comes to squeezing the most out of machines with few resources, I remember being amazed when someone managed to code a reasonably competent Chess game into 1K on the Sinclair ZX81. Remember too that the ZX81 had to store the program, variables, and display all in that 1K. For this reason, the chess board was up at the left top of the screen. It was the funniest thing to be writing code on a 1K ZX81 and as the memory got full you could see less and less of your program until the memory was completely full and you could only see one character on screen....
"I have the attention span of a strobe lit goldfish, please get to the point quickly!"
Documentation!
First of all, most actual practices mentioned are well alive today -- it's just most programmers don't have to care about them because someone else already did it. And some (systems and libraries developers) actually specialize on doing just those things. Just recently I had a project that almost entirely consisted of x86 assembly (though at least 80% of it was in assembly because it was based on very old code -- similar projects started now would be mostly in C).
Second, things like spaghetti code and Hungarian notation are not "old", they were just as stupid 20 years ago as they are now. There never was a shortage of stupidity, and I don't expect it any soon.
Contrary to the popular belief, there indeed is no God.
Hollerith constants
Equivalences
Computed Gotos
Arithmetic Ifs
Common blocks
There were worse things, horrible things... dirty tricks you could play to get the most out of limited memory, or to bypass Fortran's historical lack of pointers and data structures. Fortran-90 and its successors have done away with most of that cruft while also significantly modernizing the language.
They used to say that real men programmed in Fortran (or should I say FORTRAN). That was really before my time, but I've seen the handiwork of real men: impressive, awe-inspiring, crazy, scary. Stuff that worked, somehow, while appearing to be complete gibberish -- beautiful, compact, and disgustingly ingenious gibberish.
Long live Fortran! ('cause you know it's never going to go away)
Getting tired of Slashdot... moving to Usenet comp.misc for a while.
CFront
I can think of at least two instances on entirely different hardware (an IBM 370 and a Motorola 68000) where I had to discover that it was not working as promised. This involved a test-and-set instruction which was essential for Oracle (in the early 1980's days) to function reliably. Now of course with the multi-core CPUs they have to get these things right -- but back in the "old" days the hardware engineers could be more careless.
A test-and-set instruction, for those uninformed, is an instruction which locks the memory to prevent other CPUs from changing it when one needs an exclusive lock on it.
It is interesting, though infuriating, from a software standpoint when one is using it to diagnose hardware problems.
Are probably the greatest things (productivity wise) they ever added to an IDE.
No more :
...code...
PRINT "Got to this point!"
...code...
...code...
PRINT "Now I'm here!"
Visit the Arcade Restoration Workshop @ http://www.arcaderestoration.com
Yikes!
About the only one I never used was self-modifying code. Does patching binaries with a hex editor count?
I always thought hungarian notation was a crock. It alway seemed to be preoccupied with low-level data types, when what you were supposed to to was define data types that said what your data did, and leave it at that. I actually rather liked the way Pascal defined numeric types, leaving it up to the compiler to select an appropriate representation while you go on with your work.
...laura, who has programmed in APL and who has used punched cards
For some reason the article says that only variables beginning with I,J,and K were implicitly integers in Fortran. Actually, it was I-N.
Duff's Device. Pre-ANSI C-language means of unrolling an arbitrary-length loop. We had an Evans and Sutherland Picture System II at the NYIT Computer Graphics Lab, and Tom wrote this to feed it IO as quickly as possible.
Bruce Perens.
First off, most of the things on the list haven't gone away, they've just moved to libraries. It's not that we don't need to understand them, it's just that not everyone needs to implement them (especially the data structures one- having a pre-written one i good, but if you don't understand them thoroughly you're going to have really bad code)..
On top of that, some of their items
*Memory management- still needs to be considered about in C and C++, which are still top 5 languages. You can't even totally ignore it in Java- you get far better results from the garbage collector if you null out your references properly, which does matter if your app needs to scale.
I'd even go so far as to say ignoring memory management is not a good thing. When you think about memory management, you end up with better designs. If you see that memory ownership isn't clearcut, it's usually the first sign that your architecture isn't correct. And it really doesn't cause that many errors with decent programmers(if any- memory errors are pretty damn rare even in C code). As for those coders who just don't get it- I really don't want them on my project even if the language doesn't need it. If you can't understand the request/use/release paradigm you aren't fit to program.
*C style strings
While I won't argue that it would be a good choice for a language today (heck even in C if it wasn't for compatibility I'd use a library with a separate pointer and length), its used in hundreds o thousands of existing C and C++ library and programs. The need to understand it isn't going to go away anytime soon. And anyone doing file parsing or network IO needs to understand the idea of terminated data fields.
I still have more fans than freaks. WTF is wrong with you people?
* Sorting algorithms
If you don't know them, you're not a programmer. If you don't ever implement them, you're likely shipping more library code than application code.
* Creating your own GUIs
Umm.. well actually..
* GO TO and spaghetti code
goto is considered harmful, but it doesn't mean it isn't useful. Spaghetti code, yeah, that's the norm.
* Manual multithreading
All the time. select() is your friend, learn it.
* Self-modifying code
Yup, I actually write asm code.. plus he mentions "modifying the code while it's running".. if you can't do that, you shouldn't be wielding a debugger, edit and continue, my ass.
* Memory management
Yeah, garbage collection is cheap and ubiquitous, and I'm one of the few people that has used C++ garbage collection libraries in serious projects.. that said, I've written my own implementations of malloc/free/realloc and gotten better memory performance. It's what real programmers do to make 64 gig of RAM enough for anyone.
* Working with punch cards
Meh, I'm not that old. But when I was a kid I wrote a lot of:
100 DATA 96,72,34,87,232,37,49,82,35,47,236,71,231,234,207,102,37,85,43,78,45,26,58,35,3
110 DATA 32,154,136,72,131,134,207,102,37,185,43,78,45,26,58,35,3,82,207,34,78,23,68,127
on the C64.
* Math and date conversions
Every day.
* Hungarian notation
Every day. How about we throw in some reverse polish notation too.. get a Polka going.
* Making code run faster
Every fucking day. If you don't do this then you're a dweeb who might as well be coding in php.
* Being patient
"Hey, we had a crash 42 hours into the run, can you take a look?"
"Sure, it'll take me about 120 hours to get to it with a debug build."
How we know is more important than what we know.
Given that half the stuff people supposedly don't have to worry about are just things taken over by the kernel, I'm guessing she didn't poll many of them...
Does having a witty signature really indicate normality?
I can attest that we still do a fair bit of this stuff. We implement our own sorts/searches, data-structures, GUIs, low to high level threading primitives, and memory management. We aren't afraid of pointers, we do strange things to make stuff run fast, and use self-modifying code in some cases (in-game dynamic scripting, for example.)
Most of us don't use gotos or hungarian notation anymore, although occasionally you'll find some brain dead game studios who still do.
Also, the article was written by someone who isn't a hardcore developer. I can smell a casual from a 32 hops away.
Try overlays...
Back in the day we had do all the memory management by hand. Programs (FORTRAN) had a basic main "kernel" that controlled the overall flow and we grouped subprograms (subroutines and functions) into "overlays" that were swapped in as needed. I spent hours grouping subprograms into roughly equal sized chunks just to fit into core, all the while trying to minimize the number of swaps necessary. All the data was stored in huge COMMON blocks so it was available to the subprograms in every overlay. You'd be fired if you produced such code today.
Virtual memory is more valuable than full screen editors and garbage collection is just icing on a very tall layer cake...
Yeah yeah, I've heard this story before. Hey what about 64K memory segments? I'm at least old enough to have had a headache or two with that...
It's a pretty good indicator that the programmer was inept and trying to hide that fact with an obnoxious coding convention. I don't recall ever seeing a piece of production code using it where this wasn't the case.
I'm trying to teach myself to set people on fire with my mind... Is it hot in here?
Circa 1984, when I did summer programming jobs at Digital Research (purveyors of CP/M), one of the programmers there showed me how you could put a transistor radio inside the case of your computer. You could tell what the computer was doing by listing to the sounds it picked up via the RF emissions from the computer. For instance, it would go into a certain loop, and you could tell because the radio would buzz like a fly.
Documentation was a lot harder to come by. If you wanted the documentation for X11, you could go to a big bookstore like Cody's in Berkeley, and they would have it in multiple hardcover volumes. Each volume was very expensive. The BSD documentation was available in the computer labs at UC Berkeley in the form of 6-foot-wide trays of printouts. (Unix man pages existed too, but since you were using an ADM3A terminal, it was often more convenient to walk over to the hardcopy.)
On the early microcomputers, there was no toolchain for programming other than MS BASIC in ROM. Assemblers and compilers didn't exist. Since BASIC was slow, if you wanted to write a fast program, you had to code it on paper in assembler and translate it by hand into machine code. But then in order to run your machine code, you were stuck because there was no actual operating system that would allow you to load it into memory from a peripheral such as a cassette tape drive. So you would first convert the machine code to a string of bytes expressed in decimal, and then write a BASIC program that would do a dummy assignment into a string variable like 10 A$="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx". Then you would write self-modifying code in BASIC that would find the location where the string literal "xxx...." was stored, and overwrite it with your machine code. So now if you gave the LIST command, it would display the program on the screen, with the string literal displayed as goofy unprintable characters. Then you would code the program so it would execute the machine code stored at the address of the variable A$. Finally you'd save the program onto cassette.
Find free books.
You will never find a programming language that frees you from the burden of clarifying your thoughts.
http://www.xkcd.com/568/
I tought that Schindler's List was from the 40's, but this one seems a bit more recent.
And old people have powered wheel chairs so it's even. Not that I'm implying a 35 year old programmer would need one lol. In fact, they'll have robolegs or their brain in a robot body when they're old :P
Google's Super Secret Search Algorithm: SELECT @search_results FROM internet WHERE @search_results = 'good'
If you're going to talk about old school, you gotta mention Mel.
The determined Real Programmer can write Fortran programs in any language.
http://www.computerworld.com/action/article.do?command=printArticleBasic&taxonomyName=Development&articleId=9132061&taxonomyId=11
Self-modifying code
Yup, I actually write asm code.. plus he mentions "modifying the code while it's running".. if you can't do that, you shouldn't be wielding a debugger.
Code that generates code is occasionally necessary, but code that actually modifies itself locally, to "improve performance", has been obsolete for a decade.
IA-32 CPUs still support self-modifying code for backwards compatibility. (On most RISC machines, it's disallowed, and code is read-only, to simplify cache operations.) Superscalar IA-32 CPUs still support self-modifying code. But the performance is awful. Here's what self-modifying code looks like on a modern CPU:
Execution is going along, with maybe 10-20 instructions pre-fetched and a few operations running concurrently in the integer, floating point, and jump units. Alternate executions paths may be executing simultaneously, until the jump unit decides which path is being taken and cancels the speculative execution. The retirement unit looks at what's coming out of the various execution pipelines and commits the results back to memory, checking for conflicts.
Then the code stores into an instruction in the neighborhood of execution. The retirement unit detects a memory modification at the same address as a pre-fetched instruction. This triggers an event which looks much like an interrupt and has comparable overhead. The CPU stops loading new instructions. The pipelines are allowed to finish what they're doing, but the results are discarded. The execution units all go idle. The prefetched code registers are cleared. Only then is the store into the code is allowed to take place.
Then the CPU starts up, as if returning from an interrupt. Code is re-fetched. The pipelines refill. The execution units become busy again. Normal execution resumes.
Self-modifying code hasn't been a win for performance since the Intel 286 (PC-AT era, 1985) or so. It might not have hurt on a 386. Anything later, it's a lose.
It started with "640K ought to be enough for anybody". That led to an incredible series of hacks (terminate-and-stay-resident, upper memory address space overcommit, small-frame expanded memory, large-frame expanded memory, the "extra" 64K segment, VCPI, DPMI) that promised to maintain backwards compatibility with existing DOS software while allowing slick new applications to use multiples of the 640K limit. Sometimes they did, until the customer installed some new package which used other undocumented tricks that didn't play well with the first vendor's. Then the customer support lines would light up, and everyone would point fingers at everyone else.
Meanwhile, Intel came up with a 16-bit chip that allowed apps to use as much as 16M (considered a huge amount of memory in those days), but only in "protected mode", which wasn't meant to be compatible with DOS. It turned out that compatibility was something that customers cared about. So startups including Phar Lap and Rational (no relation to the later ClearCase vendor) came up with system software to allow DOS applications to leverage 16-bit protected mode (not too hard), in a graceful and robust manner in the presence of other DOS-extended apps (hard). Microsoft wrote its own DOS extender for Windows 3; customers could run Windows in 16-bit real mode, in 80286 protected mode, or in 80386 protected mode. Marketers were apparently convinced that there was a huge base of installed 80286 machines; while this was undoubtedly the case, most of those users had no intention of upgrading to Windows 3 or the GUI spreadsheets and word processing packages that ran atop it. But engineers at the time spent absurd amounts of time trying to get their programs working in the "brain dead" 80286 protected mode environment, with signs like "SS != DS" posted in cubicles as reminders of pitfalls to avoid.
And that was just memory management aspect of DOS and Windows 3 (which was really just an graphical DOS extender, as Andrew Schulman pointed out in his book "Undocumented Windows").
"Top-down" coding produced readable but horribly inefficient code. Doesn't do any good for the code to work if it doesn't fit in the e-prom.
"Bottom up" code produced reasonably efficient spaghetti. Good luck remembering how it worked in 6 months.
"Inside-out" coding was the way to go.
You wrote your inside loops first, then the loop around that, then the loop around that. Assuming the problem was small enough that you could hold the whole thing in your head at one time, the "inside-out" technique guaranteed the most efficient code, and was moderately readable.
At least, that's the way I remember it. 'S been a long time...
Now, these new-fangled tools do all the optimizing for you. 'S taken all the fun outta coding.
Oliver's law of assumed responsibility: If you're seen fixing it, you will be blamed for breaking it.
Hehe.. dude, the purpose of self-modifying code these days is mostly as an anti-debugging trick.. although sometimes its as a replacement for this code:
static bool flag = true; /* do something once only */
if (flag) {
flag = false;
}
but I've not seen any compilers that do it automatically. When I worked at VMWare there was actually a bit of code that did self modification specifically to *cause* a cache clear of the current page.
How we know is more important than what we know.
x = x xor y
y = x xor y
x = x xor y
Now you know!
Have you ever wondered why procedural spagetti code is hard to read? Me too. Maybe it is because the code is full of references to a myriad places which makes it hard to follow. However, that may be applied to object oriented programming too. Have you ever looked at at class and wondered which and what the...
1. Writing on a slate. Man, did that compile slow!
2. When you heard "stack," it meant firewood.
3. The error message was a ruler across the knuckles.
4. Memory overloads. My own memory, I mean.
5. If you wanted to change your resolution, you had to wait until New Years.
6. Try coding when you're drinking the original mountain dew.
7. The rantings of code guru SFBM, the inventor of open Morse code.
What if I do the same thing, and I do get different results?
programming without a decent source control system. Too many programmed without any source control system. And for a long time the ones that existed generally used exclusive locking to avoid conflicts. Ugh.
Can you imagine trying to synchronize software without having the patch utility? It is just how things were until Larry Wall got tired of people doing it by hand and breaking rn horribly. And even once diff and patch existed, it took a while for people to accept that you could use it as a basis for an effective source control system. Kids today have no idea what an advance CVS was in its heyday.
Another item that should be there is programming without having unit testing, but far too many people still don't do that. However unit testing is not particularly new. The first version of Perl in 1987 came with a set of unit tests, and the test suite is one of the reasons that Perl managed to be ported to so many systems.
I am still waiting in vain for other mainstream programming groups to learn what has been standard in Perl since CPAN started - every library you get should come with a set of unit tests, and those unit tests should run successfully before you install the library. Some almost get it. For example lots of Ruby folks write unit tests, but unfortunately by default rubygems does not run them before installation.
Ah, the good old days...
Wait, this was yesterday.
I developed a program on a PDP-11. It was a 16-bit computer and had 64kB memory. It didn't have virtual memory so in order to fit a large program you had to build an overlay tree.
Consider if function a() called b0() and b1(). And b0() called c0() and c1().
By knowing the call tree in your program and some other stuff about the dynamics of your program you could arrange so that b0() and b1() shared the same space in memory. Likewise for c0() and c1().
By studying linker maps you could create an overlay description file to make your program fit into 64kB. The OS would use this to automatically bring pieces of code in and out.
You can only imagine the consequences when you start to change the program and the pieces grow in size or new calls are added (b1() now calls c0()). You'd often have to manually do a new overlay tree.
No wonder VAX/VMS was such a hit in the late seventies with 32-bit computing and virtual memory support.
)9TSS
Regardless if the Hungarian Notation is considered an old skool trick (which I wasn't aware of), I'd still find it useful because it kind of gives your code a certain amount of self-documentation just by glancing at it.
Example:
m_pSomeStruct
We can instantly tell ourselves by the Hungarian Notation that this is a pointer to some kind of structure, which is also a member variable of a class or structure.
Look how much we can instantly figure out merely by glancing at it without sifting through comments or other documentation!
Regardless if it is considered old skool or not, I'd recommend every coder adopt, or continue, this practice.
The main thing that has changed over the years is that the software stack has grown deeper, and today there are many more people working at higher abstraction levels where a lot is handled for them.
But under the covers, little has changed. Someone must write and understand code all the way down to the hardware. You can bet that people writing kernels and drivers think a lot about space and speed. Or write some code for one of the low cost micro-controllers that are in virtually all electronic devices and you won't have the luxury of a large software stack.
There are very likely *more* people writing low level code today than 20 years ago. Only the percentage of programmers writing low level code is declining because there are many orders of magnitude more programmers writing higher level code.
I worked for a time in what are now called 'embedded systems'. Our prototype equipment had eprom (eraseable programmable read only memory). The burner was roughly square, covered with sockets for different kinds of eproms. You had to find the socket that matched your eprom. You erased the roms by putting them in a box with a strong ultraviolet light. You could tell the eraseables because they had a little window to let in the UV.
Do people still use those old fashioned hardware debuggers with the adapter that had pins for the socket on the board where the CPU would go, so it could capture the signals on all the pins?
In theory, theory and practice are the same; in practice they're different. (Yogi Berra & A. Einstein)
These historical artifacts of programming haven't gone away, they're still here. Runtime libraries and OO languages hide a lot of the complexity from users. There's still plenty of lists, and memory management code hanging around in the C++ runtime libraries. As a Compiler developer for DSPs we still encounter a lot of these problems. Memory is sparse, and applications need to have a minimal footprint. On some processors we need to do pointer math to calculate pointers. And while we do provide C++ support and an abridged C++ runtime library, you would be amazed at the number of users who stick to assembler and C. They are insistent that C++ is slower (which it can be if you get lost in certain parts of the language) and far more memory hungry (which is certainly is if you pull in large sections of the runtime library. But it can drastically reduce time to market. DSP's are the little brothers to your desktop CPUs that most people will be programming for. Because they're smaller with more constraining power requirements the processors are still playing catch-up to your PC. Multi-core processors are in some ways still an emerging technology. And the languages and tools used on them are steps behind too. Not to mention the conditioning of a lot of DSP developers (One of our senior chip designers (just retired) always designed his chips for assembler - "no one programs a DSP in C".)..
I got my start on System 7, and I'm grateful for it. You see, with a fixed size heap and no memory protection, you learned to be very, very careful about memory leaks and corruption, because your program could do very bad things if you weren't. I'm a better developer for it.
Back in my day we didn't even have computers we programmed on stone tablets with a chisel and hammer and we liked it!
Back around 1980, the most common piece of self modifying code was to implement a 16 bit index read/write/goto instruction in the Apple ]['s (and Atari and C64) 6502 processor.
The processor has an 8 bit index register but to allow it to access more than 256 byte addresses, you could either create 256 versions of the operation (each one accessing a different 256 byte address block in memory) or put the function in RAM and modify the instruction that selected the 256 byte address block.
Sorry, I know longer have the code and my 6502 manuals are packed away, but I'm sure somebody out there remembers and has an example.
myke
Mimetics Inc. Twitter
...magic numbers. I did a lot of coding in assembly and machine code for old CPUs like the 6502 where miniscule available memory made these a necessity. You young whippersnappers whine about having only 4 Gig in your computer, try doing work with only 4 Kilobytes! And that was expanded from the original 1K. now get off my lawn!
Chaos maximizes locally around me.
Regular employment.
Let me tell you a true story to illustrate why I think people should still learn that stuff.
ACT I
So at one point I'm in a room with what looks like two particularly unproductive Wallys. Though it's probably unfair to call both Wally, since at least one looks like the hard working kind... he just makes as much progress as a courier on a treadmill.
So Wally 1 keeps clicking and staring at the screen all week and spewing things like "Unbelievable!" every 5 minutes. My curiosity gets the better of me and I ask what's happening.
"Look at this," goes Wally 1, and I indeed move over to see him toiling in the debugger through a Hashtable with String keys. He's looking at its bucket array, to be precise. "Java is broken! I added a new value with the same hash value for the key, and it just replaced my old one! Look, my old value was here, and now it's the new one!"
"Oh yes, we had that bug too at the former company I worked for," chimes in Wally 2. "We had to set the capacity manually to avoid it."
I clench my teeth to stop myself from screaming.
"Hmm," I play along, "expand that 'next' node, please."
"No, you don't understand, my value was here and now there's this other key there."
"Yes, but I want to see what's in that 'next' node, please."
So he clicks on it and goes, "Oh... There it is..."
Turns out that neither of them had the faintest fucking clue what a hash table is, or for that matter what a linked list is. They looked at its hash bucket and expected nothing deeper than that. And, I'm told, at least one of them had been in a project where they actually coded workarounds (that can't possibly do any difference, too!) for its normal operation.
ACT II
So I'm consulting at another project and essentially they use a HashMap with string keys too. Except they created their own key objects, nothing more than wrappers around a String, and with their own convoluted and IMHO suboptimal hash value calculation too. Hmm, they must have had a good reason, but I ask someone.
"Oh," he goes, "we ran into a Java bug. You can see it in the debugger. You'd add a new value whose key has the same hash value and it replaces yours in the array. So Ted came up with an own hash value, so it doesn't happen any more."
Ted was their architect, btw. There were easily over 20 of them merry retards in that project, including an architect, and neither of them understood:
A) that that's the way a hash table works, and more importantly
B) that it still worked that way even with Ted's idiotic workaround. It's mathematically impossible to code a hash there which doesn't cause the same collisions anyway, and sure enough Ted's produced them too.
ACT III
I'm talking to yet another project's architect, this time a framework, and, sure enough...
"Oh yeah, that's the workaround for a bug they found in project XYZ. See, Java's HashMap has a bug. It replaces your old value when you have a hash collision in the key."
AAARGH!
So I'm guessing it would still be useful if more people understood these things. We're not just talking abstract complaints about cargo-cult programming without understanding it. We're talking people and sometimes whole teams who ended up debugging into it when they had some completely unrelated bug, and spent time on it. And then spent more time coding "workarounds" which can't possibly even make any difference. And then spent more time fixing the actual bug they had in the first place.
A polar bear is a cartesian bear after a coordinate transform.
There is no practical difference between OO code and structured code. The article assumed structured code means goto and gosub, but any Real Programmer knows that procedures (which are just gosubs by name rather than address) are still structured programming.
So what's OO? Each class is just a bunch of functions and procedures, with one entry point and one exit point for each - your standard structured programming methodology. The fact that there are different classes makes no difference. Calls between classes don't change the nature of a class any more than pipes between programs change the nature of programs.
I wasn't impressed by other claims, either. Garbage collection is still a major headache in coding, which is why there are so many debugging mallocs and so many re-implementations of malloc() for specialist purposes. Memory leaks are still far, far too common - indeed they're probably the number 1 cause of crashes these days.
Pointer arithmetic? Still very very common. If you want to access data in an internal database quickly, you don't use SQL. You use a hash lookup and offset your pointer.
Sorts? Who the hell uses a sort library? Sort libraries are necessarily generic, but applications often need to be efficient. Particularly if they're real-time or HPC. Even mundane programmers would not dream of using a generic library that includes sorts they'll never refer to in, say, an e-mail client or a game. They'll write their own.
One of the reasons people will choose a malloc() like hoard, or an accelerated library like liboil is that the standard stuff is crappy for anything but doing standard stuff. This isn't the fault of the glibc folks, it's the fault of computers for not being infinitely fast and the fault of code not being absolutely identical between tasks.
The reason a lot of these rules were developed was that you needed to be able to write reusable code that also had a high degree of correctness. Today, you STILL need to be able to write reusable code that also has a high degree of correctness. If anything, the need for correctness has increased as security flaws become all the more easily exploited, and the need for reusability has increased as code bases are often just too large to be refactored on every version. (Reusability is just as important between versions as it is between programs - a thing coders often forget, forcing horrible API and ABI breakages.)
The reason that software today is really no better, stability-wise, than it was 15-30 years ago is that new coders think they can ignore the old lessons because they're "doing something different", only to learn later on that really they aren't.
It's a small world and it smells funny; I'd buy another if it wasn't for the money; Take back what I paid (SoM)
x = x xor y y = x xor y x = x xor y Now you know!
Hmm (let's assume C++, or something close to it): int x = 4; int & y = x; x = x xor y; y = x xor y; x = x xor y;
Oops. x starts at 4, and ends up with 0. Bad swap algorithm.
And a few more examples of cargo-cultism, from people who were untrained to understand what they're doing, but someone thought it was ok because the Java standard library does it for them anyway.
1. The same Wally 1 from the previous story had written basically this method:
Then he called it like this, to try to get around an immutable field in an object. Let's say we have an object called Name, which has an immutable String. So you create it with that string and can't change it afterwards. You have a getName() but not a setName() on it. So he tried to do essentially:
I understand that he worked a week on trying to debug into why it doesn't work, until he asked for help.
2. From Ted's aforementioned project:
So they used the wrapper classes like Integer or Character all over the place instead of int or char. This was back in Java 1.3 times too, so there was no automatic boxing and unboxing. The whole code was a mess of getting the values boxed as parameters, unboxing them, doing some maths, boxing the result. Lather, rinse, repeat.
I ask what that's all about.
"Oh, that's a clever optimization Ted came up with. See, if you have the normal int as a parameter, Java copies the whole integer on the stack. But if you use Integer it only copies a pointer to it."
AAARGH!
A polar bear is a cartesian bear after a coordinate transform.
The biggest "new" headache that will probably end up in such an article 20 years from now is web "GUIs", A.K.A. HTML-based interfaces. Just when I was starting to perfect the art of GUI design in the late 90's, the web came along and changed all the rules and added arbitrary limits. Things easy and natural in desktop GUI's are now awkward and backassward in a browser-based equivalent.
Yes, there are proprietary solutions, but the problem is that they are proprietary solutions and require users to keep Flash or Active-X-net-silver-fuckwhat or Crashlets or hacky JimiHavaScript up-to-date, making custom desktop app installs almost seem pleasant in comparison, even with the ol' DLL hell.
On a side note, I also came into the industry at the tail end of punched cards (at slower shops). Once the card copy machine punched the holes about 1/3 mm off, making them not read properly, but on *different* cards each pass thru. It's like including 0.5 with binary numbers, or 0.4999 and 0.5001 with a quantum jiggle.
Good Times
Table-ized A.I.
Oops. Someone can't XOR. Bad post.
A lot of RISC machines do support it, it's required and mandatory if only to load new exception handler routines, boot loaders, program loaders, etc. Even for debuggers you've got to drop a trap instruction at the breakpoints. Of course, if you never leave user mode then you may not ever have to worry about it.
I don't expect the average programmer to have to deal with this, cache flushing, pipeline synchronization, etc. But definately a lot of embedded programmers need to know it, and operating system writers.
Even in a debugger I've modified assembler code in place. It's a handy trick if your re-link and re-download can take 15-30 minutes.
Coding in BCPL (http://en.wikipedia.org/wiki/BCPL)on 64k TRIPOS (http://en.wikipedia.org/wiki/TRIPOS) machines with no memory protection was probably the worst experience for me.
BCPL effectively had no types and memory management was very c like.
If memory got tight, which it always did, you needed to needed to split your application into pieces and get the code to page sections of itself in and out of memory.
If you didn't initialise a variable it was probably equal to zero. If you used it as a pointer then your were probably smashing all over the bottom of memory.
The stand alone debugger was loaded at the bottom of memory so you'd now smashed that to pieces and you were probably left to debug by walking through memory by pushing buttons of the front of the machine, reading the hex of memory locations and trying to de-assemble and decompile the memory one word at a time
Now I use Eclipse - luxury - don't miss those days at all.
You're missing the point---it breaks when one of the variables is a reference to the other.
It's a neat algorithm, but the case in which it fails just goes to show that these skills aren't irrelevant. Yes, you should know what a reference is. Using your compiler and libraries as a crutch for your lack of understanding leads to unpleasant bugs.
Althought I will not miss some of the mentioned techniques, the rest made the programming fun for me. And I miss them a lot.
You don't need long division in normal life. Regardless of if you are in a math heavy career or not, you aren't going to waste your time doing it by hand, you'll use a calculator which is faster and more accurate. However, you need to learn it. You need to understand how division works, how it's done. Once you learn it, you can leave it behind and automate it, but it is still important to learn. An understand of higher level math will likely be flawed if basic concepts aren't learned properly.
x |= y |= x |= y
Back in the day ...
I did my PhD work on a university-wide mainframe which cam equipped with a seriously dubious scheduling algorithm and a whole lot of quota restrictions on job queues, job output queues, cpu usage etc. The critical parameter was the five-minutes-cpu per job limit. The only way to get around this was the self-resubmitting job which, when it got to 4:30 minutes cpu, would write its state to disc and resubmit itself using the written state as input. The fun, of course, was avoiding creating a rabbit job which would reproduce itself uncontrollably bringing the entire university's theoretical research program to a grinding halt. Oh happy days.
it's hardcoded into my brain
*DrugCheese rants*
Do you remember releasing your application and the best way to solve a bug was to restart the entire frikin machine? ..oh, we still do that?
Yeah, except that's not XOR it's plain OR
This is one of my favourite quotes:
"The First Rule of Program Optimization: Don't do it. The Second Rule of Program Optimization (for experts only!): Don't do it yet." - Michael A. Jackson
That being said, when I hit the experts only situation I can usually get 2 orders of magnitude improvement in speed. I just then have to spend the time to document the hell out of it so that the next poor bastard who maintains the code can understand what on earth I've done. Especially given that all too often I am this poor bastard.
1)Variables change type. And then you have to rename everything. Its a pain
.3 seconds
Visual studio -> Right click on any variable -> rename -> type new name -> click ok
eta
You might not use VS, but really, any good IDE should have this feature. Don't get down on tha' hung' because you are using a shoddy IDE.
HA! I just wasted some of your bandwidth with a frivolous sig!
As a python user I've always been confused by this one
x,y = y,x
Is the easiest way to do it surely?
Puzzle Daze is now my job
Do people still use those old fashioned hardware debuggers with the adapter that had pins for the socket on the board where the CPU would go, so it could capture the signals on all the pins?
As of... crap, I guess it's 8 years ago now... these were still in use, but phasing out, at least in my experience. Modern processors supported a JTAG test interface that allowed in-circuit debugging via a serial connection directly into the CPU itself. Not sure what the current state is; I recall being able to do some things with the 68k ICE that were not supported on the JTAG interface -- mostly related to detailed state of registers in the CPU. I don't recall whether this was an inherent limitation of the interface or a quirk of a cheap implementation of the interface.
Don't miss this.
In the embedded world many of those items are still required skills, however I will admit they are not as important as they once were.
On the other hand I often worry how little my windows programming colleagues understand about the mechanics of computers especially when it comes to such things as parallel programming and network stacks.
Choose your allies carefully, it is highly unlikely you will be held accountable for the actions of your enemies
When I learnt Cobol we had to write out the code onto coding sheets for someone else to type it in. You had to get your characters in the right place in the 80 columns otherwise it wouldnt compile for the next day argggggggh.
On a long enough timeline. The survival rate for everyone drops to zero. Chuck Palahniuk, Fight Club, 1996
Ahh RSX11M -- happy days :-)
I wouldn't want to go back to the days of trying to cram everything into a 16 bit address space with no virtual memory support but.... having to create trees and manually specify which parts of a program could swap when was an interesting and useful education.
Going back a few years (OK 25+ -- ouch that makes me feel my age!) I remember working on a system which had a tool to manipulate key data files and effectively had one module per screen. To fit it in we had to capture data and store it in a root node and then allow the module to swap out and let the next screen's one swap in. (There was a bit more to it than that, but I'm simplifying for brevity)
It could be a right pain, trying to work out why something wasn't picking up the right values, only to discover an execution scenario where a vital chunk was swapped out when least expected.
Having multiple trees in the same program -- now that was 'fun' -- lost art nowadays with big address spaces, loads of memory, automated virtual memory management as a "don't worry about it - it just works" feature.
(Cue IT version of Monty Python's four Yorkshire men sketch....)
It should be noted that the performance is considerably worse for that particular variable swap on the majority of architectures.
XOR is a rarely used operation outside of encryption and compression, so most architectures aren't designed to execute the XOR operation very quickly. The performance is focused elsewhere.
However, if your target architecture does have optimized XOR operations, go for it.
... I have one word : wParam.
I rest my case.
In Win32, wParam went from 16-bit to 32-bit, but still stayed as wParam. Now in Win64, wParam is a 64-bit quantity.
wParam my foot.
What, no mention of the Duff's Device, possibly the neatest and at the same time the ugliest C hack ever? Here's the (modernized) code for those too lazy to click on the links - What's really scary is that it's perfectly ANSI/ISO C compliant:
void dsend(char *to, char *from, int count)
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
This works because of the way switch statement is defined in C - the case labels inside are really just labels (even though they cannot be targets of a goto statement), and so constructs can flow around and between them almost freely, with only the same limitations as for normal goto labels.
Unfortunately (or fortunately?), this has survived in C++, but not in any other language from the curly-braces family that I know of.
Code that generates code is occasionally necessary, but code that actually modifies itself locally, to "improve performance", has been obsolete for a decade.
WTF? One of the most used technology in the Real-World [TM] uses exactly this technique to achieve outstanding speed, often surpassing static compilation.
Java's JIT compiler modifies, rearranges, at runtime, the bytecode, so that, amongst other, the most often visited paths are executed faster. You may say "oh but it's bytecode, not native" but anyway the set of supported instruction on, say, a Core 2 Duo is just "faked" on top of non-accessible instructions.
Java's JIT compiler does modify code at runtime and it's not anywhere near obsolete. It's a technology that pretty much powers the Real World [TM]. Think about modifying bytecode the next time you do a non-cash money transaction.
The problem is your narrow view on were self-modifying code can take place. You're not forced to modify the very next (or previous) instructions to qualify for self-modifying code. Sure, modifying code may imply *one* cache miss, but what if it's done to then later on avoid thousands of branches?
That's what the JIT does. Think about it next time you make a non-cash payment. Because I can guarantee you at some point there's Java JIT involved in the process.
It is well known that Michael Schumacher is NOT much of a car nut when it comes to the mechanics. How many world championships did he win? Oh, more then ANYONE ELSE?
You need to know about the network stack if it is your job to know about the network stack. If it isn't, you don't need to know about it. What good is it for someone who writes an music codec to know about the network? Parallell programming notepad?
MMO Quests are like orgasms:
You may solo them, I prefer them in a group.
As a python user I've always been confused by this one
Is the easiest way to do it surely?
Yes, but you should be aware that the system is creating (at least) one temporary behind the scenes to hold those values while they're being swapped over. Which isn't to say don't write that! The method you use has the strong advantage of being really easy to get right, as opposed to that idiotic xor "idiom"...
"Little does he know, but there is no 'I' in 'Idiot'!"
Now you know!
That's a terrible way of doing it! It guarantees that the compiler and processor can't do anything smart with reordering variable accesses since the values are now officially dependent on each other, despite not really being.
"Little does he know, but there is no 'I' in 'Idiot'!"
Problem was this never worked out perfectly. Somebody would AddRef too many times or Release too many times meaning the object leaked or died while still in use. Releasing early meant a crash occured somewhere else since other consumers had an invalid pointer. Even with smart C++ pointers you always got some situation with inout parameters or thrown exceptions or two objects referencing each other or some unsafe cast or even third party code where things got screwed up. Sometimes it could take hours and hours to find leaks.
While C# and Java don't alleviate all of these issues (objects can still hold strong references), they do basically scrape all of this crap from the developer's plate meaning less and more readable code. Reference counting and garbage collection is automatic and just works. There are even weak reference classes for cases where two classes might need to reference each other.
Of course garbage collection is its own issue but IMO far, far, far better than what preceded it. The biggest issue with GC is it needs tuning, and some novice programmers make stupid mistakes such as creating objects in a loop rather than reusing them - the kind of thing that means 10,000 transient objects suddenly leap into and out of existence giving the GC a hernia. IMO it wouldn't be a bad thing for Java and .NET devs to learn C++ just to understand some of the underlying issues their language is mostly protecting them from.
CORAL allows you to declare and use variables with whitespace within them. This arguably makes for more pleasant-looking code than when you use underscores or CamelCase (and is a real help for dyslexic programmers).
What's not so cool is that the compiler ignores whitespace so that
Variable number 1
Variable number1
Variablenumber 1
and
Var iablenum ber1
are all equivalent. All it takes is some inconsistent naming and you become unable to use search functions which don't ignore whitespace (I couldn't even tell you if grep has a switch for that - the system I programmed with CORAL didn't run Unix and its search command was brain-dead).
Oh, and bitwise operations were allowed, but bits were numbered from the most significant downwards !
One of the most horrible jobs I ever did was porting a system written in CORAL into C. If it hadn't been for modern IDE's and debuggers, I reckon I'd still be doing it !
Begone foul beast !
Squirrel!
Or integer arithmetics in general. Ever written a function to compute a square root or a binary logarithm using only integer? Fun stuff.
You forgot to mention that the code section on Windows is now read-only.
When you change it to read/write, there is a factor 2 penalty (the code runs 2 times slower), even when not using self-modifying code.
A lot of this still comes up in video coding and working with complex rendering environments. ... even cards. *cough* (although more in a "this object has to be X size sense rather than 80-column limits) ... but then I've only been coding since the 80s so what do I know ;)
Event if you teach them what hash tables and linked lists are, many developers still write functions that are 500 lines long that could be 20 lines. Some people I have worked with don't even grasp the concept of a subroutine.
But of course they now know about patterns and javadoc, so they put a
* This is a FooBar singleton factory (See the Design Patterns book)
*/
On top of their stinking mess and feel smart.
When I got my Apple][ clone (ca. 1980) it came with basic and that's it. I knew assembly language programming, and the computer manual came with the processor instruction set (or I got that some other way). Anyway, I wrote a macro assembler in basic, so that I could write applications that were faster than possible with the basic interpreter. That was good fun :-)
A JIT compiler is a compiler, self-modifying code is usually not.
although sometimes its as a replacement for this code:
static bool flag = true; /* do something once only */
if (flag) {
flag = false;
}
Could you explain the motivation for this part?
First good answer.
Calling arbitrary locations in the system ROM to call utility functions (which were usually designed to be called to implement BASIC commands, the most commonly-used one being "LOAD").
The idea that there might be a new version of the machine produced in future that _had different software in its ROM_ just never occurred. Thus leaving machine vendors with the annoying situation that they were unable to upgrade their systems, because half the software out there had dependencies on the precise implementation of their ROM-loaded software.
or just SWAB x,y
The tyrant will always find a pretext for his tyranny - Aesop
Comment removed based on user account deletion
In the 1970's, on IBM 370's, you could have virtual memory if you created your own swap tables. This required a lot of thinking about program structure and execution order. You had to figure out if a routine was in use or not, and swap it in and out of memory. If someone added a call to a routine while it was swapped out, the program would dump core.
Worse, sloppy programmers would assume that the contents of non global variables would be there from one call of a routine to the next. That would be true - as long as the routine wasn't swapped out. So, changing the swap tables was likely to cause subtle bugs, as counters or indices suddenly got reset to random numbers. Since the swapping would generally depend on what the program was doing, it might pass simple check cases, but go weird some of the time on extended runs.
I can remember literally spending weeks debugging such problems in previously working code.
On TRS-80's Mod I & III's this technique was used to emulate the sprite functionality available at the time on the Commodore. You'd predefine arrays of strings (limited to 255 chars- max string length in BASIC) that contained combinations of Extended ASCII (>127) characters which corresponded to the desired 5x3 pixel mappings and control codes to reposition the cursor while outputting the string. This way you could use the BASIC PRINT statement instead of the graphics SET() & RESET() statements. This resulted in enough of a peformance boost that you could write playable games in BASIC rather than Assembly.
As people become less and less knowledgeable about the basics, we will end up with more and more bloated and unstable code.
How many people today even know what microcode is? Let alone used it to push bits around registers directly? Understanding at that level gives you an appreciation of what is going on that is lost when you see the computer as a black box you toss prepackaged widgets at.
---- Booth was a patriot ----
Given my druthers I'd step back five to ten years and keep each component to a single page.
Many of the articles points are cute, valid and bring back happy memories of making hard problems go on the old day's slow boxes, but they don't compare to the overhead of writing current code in today's production environments. (How about Vista being more bloated and slow than XP?)
Think of the chain of single point failures when you are dependent on thousand's of other programmer's commercial objects performing EXACTLY as documented.
These chain breaks are common... what can you do if you trust and are betrayed by someone else's slow or buggy object? And the verbosity of declaring hundreds of properties to display, say, a simple web page are preposterous.
What if you have a demanding app, or just don't need the overhead of every marketing guy's punch list of features slowing down your code?
Perhaps we were better off, and more productive somewhere between manual memory management and pointers in C... and today.
"Knowing everything doesn't help..."
Manual multithreading
All the time. select() is your friend, learn it.
Unless the embedded platform you're working with doesn't even have select() or WaitForMultipleObjects(), and you have to write your own version.
"Hey, we had a crash 42 hours into the run, can you take a look?"
Any computing task expected to run more than about an hour will need to have some sort of checkpointing.
x = x + y
y = x - y
x - x - y
Much less CPU intensive.
x, y = y, x
I must be pretty old. I remember when you could only multiply by using a loop and adding to the register the necessary amount of times.
Seven puppies were harmed during the making of this post.
any non-epic-fail IDE will at the least tell you variable types when you mouse over them
But with the template calls that pervade the C++ standard library, it's harder to make a C++ IDE non-epic-fail. What does std::stlp_string <std::w_char, std::char_traits> mean to a programmer in a hurry?
Several years ago, I disassembled the output of gcc -O2 (on an RS/6000) for swaps through a temp variable, and swaps using xors. Turns out that the peephole optimizer recognized both patterns and replaced them with the same machine-specific fastest way.
here's an easier one ...
You have a wheelbarrow full of mulch and "Jackass" comes by in his shopping cart and dumps the load. You now have to pick up everything and continue. Till he does it again. It really slows down work.
That's a neat little trick.
Not that I'd ever use it because it's not very readable, but I'll try to remember it next time I need to impress someone :-)
This might be okay if you are SO constrained you can't afford one register's worth of temp space, but if you're into performance, this is 4-8x slower than using a temp variable, in every language I've tried it on. Run your own benchmarks, see what I mean. Also, don't obfuscate your code, just to be "clever".
Back in 1988/89, while I was working for a small HW/SW company in Berlin, I inherited code from four different "groups" of people:
1. From one external developer who was supposed to write some embedded stuff in C:
He delivered the code and went on holidays to the caribbean (I am not exaggerating). Imagine, this was the time without email and mobile phones. Actually, I didn't inherit it directly but a colleague. And someone else had given the contract to that external person without asking us.
Anyway, we had to install it on a mission-critical power amplifying thingy (I don't know what it was, I'm a software guy). It just didn't work... Then my colleague, who didn't know C, asked me if I could have a look at the code because he didn't really understand it (he was an assembler coder only). The code looked like this: ... ... (you get the picture here) ... ... ... ...
#define BEGIN {
#define END }
#define THEN {
#define IF if
#define WHILE while
#define FOR for
#define FUNCTION
#define PROCEDURE void
#define REAL float
FUNCTION DOSOMETHING (REAL in, INT out)
BEGIN
FOR (...) BEGIN
END
IF (...) THEN
ELSE
END
END
--- I started crying
2. It was from a physicist who worked there as programmer for a while and left. He developed in Turbo Pascal.
The code was really nicely structured, he had written a really nice guide how to structure and write code. It was readably without any comment - perfect.
The one thing... it was the most inefficient code I had ever seen and it used some tricks of Turbo Pascal 3, which drove me really crazy when I tried to bring it forward to TP4/TP5 (was it OVLs? I dunno anymore)
3. Two external guys developed another piece of software as a new module for (2) above. They insisted that they should use Turbo-C. I don't know who said "yes", but after a while I received their code to integrate it as a separate module (actually separate exe) to our package.
Integration was no problem, it worked, everything was fine. Except when a client asked for some changes. I had to dive into the code and try doing some changes... Well, ... it was object-oriented C - no, it was NOT C++, it was their own flavour of object-oriented C they had developed...
--- I started crying again.
4. The last example was actually an operating system we developed for a small self-developed computer based on C-64 (it was for QS-systems for car manufacturers; this piece of hardware had built-in analogue and digital measurement-device inputs and so on). The code was beautiful, it was fast, efficient and ... readable. It did everything we wanted and I understood everything. It was faster than a C64 and so on...
I really loved that "OS"... the drawback?? He was such a genius that he became an alcoholic and had to leave the company and we couldn't continue developing the stuff anymore...
Throughout my 27 years of computing, the worst thing that happened to me was inheriting software from various different sources at the same time. But other than that, all the other stuff I can only say: "Been there, done that" (yes, even punch-cards, PDP/11s, Vixens... ahm, VAXens, etc. -- no, no magnetic cylinders)
my first enterprise application was HAND WRITTEN on coding sheets and handed to a data entry operator who typed it in and i had to wait 3-5 days for a compilation report and code listing printouts. Needless to say the first report was mostly syntax errors (bad handwriting, smudges and data entry errors). Then i had to hand in modifications to be made on new sheets - these would only take a day or 2 to come back!
a few years later i worked on a PRIME mini and had to submit all C applications into a queue for compilation - sometimes 30 minutes but on high load days, it could take 8 hours - just to compile the damn code! again i pick up the results in the form of a printout.
but now, even to this day, i still get a huge kick out of compiling, building, linking (including generating and optimizing) 1 million+ lines of code right on my desktop in seconds.
Let's talk assembler for a minute, many of the young whipper snappers, don't know that a GUI like visual studio that creates its own language (CLR) to then be recoded into assembler then into binary, is like all languages, really, at the core.
Yet how many can code in assembler, even I have trouble keeping any resemblance of the code, and
need to brush up every now and then, so if 20 years go by, no one will even know what assembler is, which is sad, because we still have to know it essentially, as it is pre-binary, and the only thing
to stop us from needing to recognize 0 and 1 patterns to communicate with our computer.
How about that total absence of respect back in the days where programmers were seen as interchangeable faceless drones (aka Code Monkeys) in a "fad industry" that everyone just assumed would go away with the hula-hoop and the Rubik Cube?
"You and I learned C when it was programmers, not compilers, which had to be intelligent." --- Terry Lambert
You're missing the point---it breaks when one of the variables is a reference to the other.
It's a neat algorithm, but the case in which it fails just goes to show that these skills aren't irrelevant. Yes, you should know what a reference is. Using your compiler and libraries as a crutch for your lack of understanding leads to unpleasant bugs.
If "one of the variables is a reference to the other", you don't have two variables, you have ONE.
Tell us, bright boy, how to swap two values in one variable. There's a Nobel Prize in Mathematics riding on your answer.
Geez, you went all the way around the circle from smartass to dumbass.
Is quicksort still all the rage? For my time critical stuff I use heap-sort because it's execution time is O(n*log(n)) whereas quicksort is worst case O(n*n) but typically O(n*log(n)). I've always wondered about this because most quicksorts are usually faster in practice than heapsort, but that's an average. Can anyone clarify this for me?
One old school programming thing was learning how to multiply in 6502 (C64 and others) machine language which didn't have a multiply instruction available. It involved doing bitwise math instead of costly addition loops.
Since I was doing "undercover" programming (i.e. programming at night when I was supposed to sleep ;-), I discovered pretty quickly that the beast was making noise, a lot of different ones.
I quickly learned the sound of a running program, the noise of an error, and the sound of a few number being displayed as well.
I then kept this habit since, learning the sound of a properly running computer, and being able to tell when the beast is trashing, and so on.
Of course, with all those new-fangled virtual hosts, I'm missing a lot of cue about the system I'm working with. This is sad, my friend, really sad.
[Pruneau
Parroting quicksort is useless. But learning to write a sorting algorithm is a gateway to very fundamentally useful topics, such as recursion. It's not so much that the particular example (sort) is useful, it's that it teaches tricky but fundamentally useful ways of thinking.
[Ego]out
We had to walk FIFTEEN MILES to get to our compiler, uphill both ways, in the snow!
You just keep seeing it, programmers who only deal with high-latency and/or low bandwidth applications on modern PCs writing op-eds about how "old school" programming techniques which involve actually understanding how computers work have become obsolete. The article makes a handful of valid points, but taken as a whole is completely off base.
For perspective I'm a 29 year old software engineer working in the aerospace industry. In the embedded world, many of these techniques, and especially the need to understand them is not going away any time soon, and I'd wager it is the same anywhere that hardware capacity is not much greater than the load imposed by the problem.
The biggest problems I've seen in my current project are inexperienced developers cramming "modern" programming idiom like multithreading into a program where a much simpler idiom like a state machine with select() would get the job done. Oh, and function pointers, they might have their place in some instances, but imo they are the modern equivalent of goto, potentially useful but used improperly they can have horrible effects on the readability of code.
For a second opinion, I'll put on my amateur hardware hacker hat and laugh in the author's face. I'm assembling a BCD clock project running on a 16MHz microcontroller and I'm coding everything in bare C. OS? Libraries? What are those?
Final note to the author. If you explicitly limit the domain of your claims to the sub-set of the industry that you actually work with, you'd have a heck of a lot less people heckling you about over-generalization. Then again you might just not care.
My introduction to "multithreaded" code was on the Atari 6502 platform. I know it wasn't threads, but 25 years later it still feels like them.
The vertical blank interrupt (as the television monitor scanning beam moved back into position) happened 60 times per second. In that brief amount of time, the 6502's normal instruction flow was interrupted and you could execute just a few instructions.
This is where you read the joystick, played music, and did housekeeping. Of course, you didn't have time to do all of these in a single VBI so you'd develop a chain or round-robin of routines to run in the VBI.
Lots of registers were off-limits in the VBI, taking too long could cause all kinds of weird effects, many memory addresses were overwritten without warning, side effects everywhere, and if you failed to return properly the game was over -- time to reset.
Simpler times...
Get off my lawn.
Nice, though the last line should doubtless read:
x = x - y
No?
Um, I'm pretty sure quicksort is still the go-to sort simply because it's the implementation that's built into almost every single programming environment. Then again honestly, I'd say that from the point of view of a pragmatic programmer... it doesn't matter. There's a built-in fuction (whether it's qsort() in the C standard library, or Arrays.Sort() in Java, or whatever) that will take your array and return it, sorted. If your app runs too slow and you profile it and it turns out the speed problem is in the sorting AND you can't find a better algorithm that doesn't depend so much on sorting... THEN you look at optimising it. Never forget the two cardinal rules of optimising:
1) Don't optimise.
2) (Experts only:) Optimise later.
Or as I once read it eloquently expressed:
1) Make it work.
2) Make it work right.
3) Make it work fast.
Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
I'll tell one coding "technique" I certainly don't miss at all: the Lotus 1-2-3 macro. My first job after college was to finish development of such a macro, which had been started by the (non-programmer) owner of the consulting company that hired me. This multi-thousand line macro was the production scheduling system for a major transformer manufacturing plant. Back then a macro was simply a recorded collection of keystrokes with some control structures around it to do loops and branches and so on, nothing like the relatively well-structure VB macros of today.
Amazingly, this thing actually worked. When the guy who hired our company to write it got a job with a competing manufacturer, he even hired us again to do the same thing all over again. We were determined to learn from the past and figure out how to write more structured and maintainable code the second time again. Unfortunately we were also determined to do it as a Lotus 1-2-3 macro again. I am so glad I quit that job before having to finish V2.0 of the Mother of all Macros.
Hand sorting algorithms - check
Custom linked lists - check
Custom GUI interfaces - check
Spaghetti code (w/ GOTOs) - check
Manual threading/TSR apps - check
Self modifying code - check
Custom memory management - check
Punch cards - nope (phew!)
Text only dev tools - check
Custom math and date logic - check
Hungarian notation - check (Grrrrr!)
Strange optimization techniques - check
Forced patience - check
I guess 12 out of 13 isn't too bad. Still, reading that article started to make me feel old. Thankfully, I got a small boost from having never had to use punch cards!
Yes. Although I tend to do most debugging on a Linux based hardware emulator I wrote, at times when debugging hardware issues, we still dig out our old Arium and plug it into the boards.
Note that STL swap(T&,T&) handles this case just fine, even if you try to use it on the same variable. Claim you'd never be so stupid if you'd like, but if anyone else uses your code, they will likely be annoyed if your custom swap(T&,T&) function fails so badly without warning.
And if you use that today, the maintenance programmers will kick your ass.
A slashdotter who didn't build his own computer is like a Jedi who didn't build his own lightsaber.
This is really an age-old question that has effected other fields long before it has effected programming. The question I refer to is, of course, what is worth learning when training in the field (a.k.a. what are the essentials for an undergraduate degree)? If you look at mathematics curricula today, for instance you'll find the regular gambit of calculus courses along with a smattering of higher-level courses such as numerical analysis, abstract algebra, and (at some undergraduate universities) topology. These topics were not decided upon overnight... but over many years of teaching this material, it was determined that these formed the most valuable basis for other material that may interest the student.
Today, most professional mathematicians specialize in a very specific topic for their graduate degrees and only have a "cursory" understanding of higher mathematics outside of their field of expertise. I think this is the direction computer science is heading. There are far too many languages, specializations, and paradigms already and that number is only growing by the day.
This isn't an entirely bad thing - it means accelerated development of given topics - but it also leads to narrow-mindedness and an inability to connect concepts from different but related fields of study with concepts from one's own field. Pick your poison folks...
Ok, now do it with strings!
Life is wet, then you dry.
This isn't always a bad thing. The one situation where integer representations really win is in angular calculations. If you represent the angles 0-360 degrees as 0 to 2^n bits, you can forget about overflows and just use the integer operators to perform addition and subtraction and throw away the carry flag. Finding the shortest distance between two angles is also an easy thing to calculate. If you use 32 bits to represent an angle, you can achieve accuracies to around 1/1,000,000th of a degree. Hell, even 16 bits lets you work down to around 1/200th of a degree.
Be wary of 'upgrading' angular-integer code to utilise hardware floating-point units - the extra bounds checking required might negate potential speed increases.
Squirrel!
But by and large, the article is right -- there's a vast majority of places where these just don't matter anymore.
IMHO, they are still relevant for a number of reasons:
1) Not everything is coded in Java. I do a lot of embedded programming, bootloader code, and the like. You have to be able to knock out a ring buffer manually or you simply can't do this kind of work. It's great when I get to do Java/C# and all this stuff is already there, but more often than not I'm fiddling around in the bootloader or in driver source and it's all C to the wire. I know that colors my experiences though. But without the drivers and the bootloader, Java has nowhere to live in the first place.
2) Even when you do have them premade, somebody had to put those functions in the libraries for you to use. From a certain point of view, the tiny minority of asm guys you mention are the vast majority - their code is copied over and over and over, every time you link to their work.
3) Knowing what's under the hood makes you a better driver. There's your obligatory car analogy since we're doing it old school in this thread.
Weaselmancer
rediculous.
The input file for MCNP5 (http://en.wikipedia.org/wiki/Monte_Carlo_N-Particle_Transport_Code)used for nuclear engineering uses a punch card format. Each line in the input file is refered to as a card, and the file is called the input deck. The lines are limited to the same restrictions as a regular punch card, and the program will spit out errors if you do not create the input file properly.
Please stop using "m_" as a prefix to a member variable of a class.
Jeez, we all know it's a member variable.
This falls under the same category as Hungarian notation. Stupid and useless.
Although it's generally true that what I initially think is a compiler bug is almost always my fault, I definitely run into actual compiler or library bugs on occasion.
- I was working on some low-level boot loader assembly code, and found that "call esp" was not working as documented. It turns out that most modern Intel CPUs have a bug they haven't bothered fixing where it jumps to the value of ESP *after* pushing the return address. AMD processors don't do that, which is why it worked for me >_<
- I found that a memcpy() in our Win32 "vectored exception handler" was corrupting memory. The code that caused the exception was a memmove() that had decided to go in reverse to ensure a proper copy. It turns out that Win32's exception handler stub wasn't clearing the x86 direction flag before calling exception handlers, a violation of the Win32 calling convention. Compilers assume that the direction flag is clear at the start of a function, so a constant-sized memcpy() will frequently get inlined as simply "rep movsd".
- We found an extremely obscure bug in Visual C++'s compiler where it incorrectly zero-extended a pointer to 64 bits when it should have sign-extended as per the C standard. A global declaration like this was being used:
int var;
long long extended = (long long) &var;
If the base address of a 32-bit program is >= 0x80000000, as occurs in NT device drivers, the extension to 64-bit will be zero-extended instead of sign-extended. This differs from what happens in local variables.
In fact, getting this right is impossible with the relocations available to Win32 images and object files. The compiler should've thrown an error because it can't do the requested operation. (In C++ it could, but would have to make it a constructor.)
"Screw Sun, cross-platform will never work. Let's move on and steal the Java language." - Visual J++ Product Manager
I had a punch card assignment as well (circa 1979 because "in the real world, everybody uses punch cards") but my preferred machine was a TTY. Part of it was because none of the engineering students knew the thing existed. It was in the basement of the dorm and it was mostly a commuter school. But it also meant I could get printouts any time I wanted. The tubes only had line editors anyway, so the teletype machine wasn't a big step backward anyway.
===== Murphy's Law is recursive. =====
He's right. At least the simple methods should be taught along with examples of when those fail, skim over some more advanced tools, and teach how to find the right tool for the job. Otherwise you'll encounter pinball machines that don't accept coins for a while because they're doing a bubble sort of all the games ever played (stored on a 500GB disk) so they can show the top score.
Well, I'll agree that often there is hubris involved. But sometimes they do have to optimize, though. Like, they hit deadline, and their code runs slower than a sleepy snail on sandpaper. With Slow cast on it.
The client says, "this has to run at least 5 times faster or else."
And nobody involved has more clue than building a cargo-cult program. In many cases nobody involved even understands the O notation.
Enter desperately googling for ideas that they aren't even qualified to understand why they're bad ideas, or why the VM doesn't work that way.
The "for an int Java copies the whole value on the stack, but for Integer it copies only a pointer" mis-optimization I mentioned in the other message, was born out of such an emergency. Their code ran like a 3-legged pig on greased ice, the client was unsatisfied, and they just had to try something before they bring in the consultants. Of course, the real bottleneck was how they used the database. But they hadn't figured that out.
So they scratch their head and go, "let's use a cache." Which they promptly invalidate when they write something, and they're doing a read-compute-update loop there, so it really brings nothing except more complexity and more overhead.
So their architect comes up with that... great idea ;) Let's replace every single int with an Integer, and every char with a Character at that, 'cause his half-knowledge made him infer that it would be somehow faster. They actually spent two weeks doing that change to their heap of code. Of course, it still doesn't run any faster. Luckily, not measurably slower either, because that's not the bottleneck anyway.
Now what?
Someone googles some more and comes upon such a "clever trick". The page is from the 90's, though, and that trick stopped working after Java 1.0.
Lather, rinse, repeat.
A polar bear is a cartesian bear after a coordinate transform.
The big problem with Windows applications today isn't so much garbage collection as it is bloated applications.
Those advanced coding platforms might wipe the snot from your nose, but they still can't build tight code.
E.g. Firefox with a number of add-ons consumes 123K of memory. Google Chrome - 19K. And lets not talk about the top RAM consumer on my system, iTunes.
I once came across some code that consisted of many unrelated branches of nested loops spread across several source files. Everything was done in a loop, including calls to other routines (also implemented in loops).
After puzzling over this for quite a while, I finally realised that this was an old Fortran programmer's way of implementing threads. The termination condition for these loops was typically seme kind of flag-signaled event, or a time expiring, whereupon the program would bubble back out of all the loops, then descend down some other path into some other inner loop to service the event.
It was really beautiful Fortran code, very clean and organized. Sadly, he wasn't using Fortran anymore, but Ada, which supports threads in the language...
My dad has told me stories of punch cards. I'm obviously a bit newer than that. I'm not a programmer, never was (except for Basic on my Apple II), but had to take some programming classes in college. Fortran, Cobol, and oh my, machine & assembly. What a mess that was. One project was to write a clock in machine. At one point my clock was running backwards because I misaddresed two registers, or some such. 2 years ago I had to take a Java class for my masters, Even though I read /. and vaguely know what's going on, wow I was blown away with all the crap that libraries hold now. You know that decimal to roman notation that all newbies have to write? After doing that, I contemplated how I would have done that in basic 25 years ago. No way I could have made it pretty.
Feel free to make fun of me- I've never been a good programmer, which is why I went into engineering!
Vote monkeys into Congress. They are cheaper and more trustworthy.
Performance isn't the only reason for writing self-modifying code. For instance, the Motorola 68k had instructions for doing I/O to special hardware ports numbered 0-255. I had to write OS-level routines in C to do in(port) and out(port,byte). Because of the way the addressing modes worked, there was actually no possible way to implement this without writing self-modifying code. My boss didn't like it when he first saw it, but once he studied the addressing modes, he agreed that it was necessary. One worry was that the code would already be in cache before you modified it, so IIRC we did some jumps back and forth to make sure that the cache would get refreshed.
Find free books.
The author of the article mentioned that boxes of punched cards inevitably got dropped, and had to be restored to their proper order. Since many programmers ignored the sequence numbers in cols 72-80, or forgot them, a simple quick-fix trick was to draw a diagonal stripe across the top of the deck with a magic marker.
Mel Kaye was a real person, and here's his signature to prove it.
In the embedded world, of course, you still get to use most of these tricks. Not to mention all sorts of low-level hardware hacks. I've written about that here before, but what I find really interesting is that despite the general trend toward ever-faster and more powerful embedded processors, there's at least one new core out there that's SMALLER than its 1970s predecessors.
Freescale's (relatively) new RS08 core is absolutely Spartan. IIRC, the directly addressable memory is 256 bytes. Everything beyond that requires paging. No index register, but it's emulated with a couple of RAM locations. Some single-byte instructions act on only a 4 or 5 bit address space. The advantage of all this minimalism is that the parts can cost under 50 cents each.
So not only is there still some need for all of the old tricks, but there are opportunities to learn NEW ugly hacks! I'm sure someone can come up with something suitably mind-blowing with an addressable index register and a processor architecture that supports self-modifying code.
just because you're trying to optimize the performance of your system doesn't make it a real-time one. If it were a real-time system, missing a timing window would result in your data being incorrect, not just tardy.
Ah, the missing piece of O().....it talks about the driving factors, but not the actual forumla as it relates to performance....
n*log(n) + 15
n*log(n) + 4000000
That's how two algorithms can be seemingly different in performance......and for that matter, why Bubble Sort can still be somewhat relevant. Depending on your value of n, the most performant algorithm can change....and as you describe, border cases matter, too.
The thing that pisses me off most about people who mindlessly follow Hungarian notation, even more than the ones who use hungarian notation in typeless languages, and that's the ones who don't realize that "sz" means "zero-terminated string" and tag all their strings in languages where the string is a first class opaque data type with "sz".
Didn't Jacquard invent multi-threaded code???
Quicksort can be made to avoid the O(n*n) pretty easily (randomly selected pivot goes a long, long way very cheaply), and in my experience it tended to be around 2x as fast for typical data. Of course, I haven't measured in a while.
On deeply pipelined yet highly parallel machines, if you're sorting lots of small things, a modified merge sort combined with loopless sorting networks for the "leaves" (ie. subdivide only down to, say 16 elements and use a sorting network for the rest) can actually work better because of the inherent parallelism across the bushy parts of the tree.
Program Intellivision!
This is true. There are certain pathological cases where Quicksort is O(n*n). Some tweaks have been done to the basic algorithm to remove as many as possible of these, but they are still there.
If you know that your data is, or is close to, one of these pathological cases, then you will want to use another algorithm.
I think that more important than knowing how to bang out various sorts on demand is knowing the characteristics of each one and when to use them.
For example, if I only have 10 items to sort you might be better off using a simple O(n*n) sort rather than a complex O(n*log(n)) sort. If you know that your data is very nearly sorted, then a pass or two of a bubble sort may be all you need (this is one of the pathological cases for quicksort). If you know nothing about your data, then quicksort is probably a good start. And learn about your data.
un-ALTERED reproduction and dissimination of this IMPORTANT information is ENCOURAGED
I remember a case study I read when I was a young'un about a particular data set, where the person writing the article settled on bubblesort, because a fast machine-language implementation of bubblesort was quicker than the quickest implementation of quicksort that the author was able to come up with.
In the end, the fastest algorithm _in practice_ *is* the fastest algorithm, and should be used.
Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
Oh, and I forgot... heap sorts do have one place where they truly shine: Priority queues. The incremental O(log(n)) update to rectify the heap after an insert makes it a perfect candidate for priority queues.
Of course, if you didn't take data structures and basic algorithms, how would you know this? :-)
I guess if someone gave you a bag of higher level algorithms (STL and Boost come to mind in the C++ world) documented with big-Oh on all the pieces, you could still get by quite well without knowing the lower level details. Somebody implemented STL and Boost though.
Program Intellivision!
I'm one of the few people who actually likes a simple form of hungarian notiation. I understand the arguments against, and I understand the "apps hungarian" was the original intent, as a poster below notes, but I would still rather also see the type information. There's nothing wrong with additional information, as long as you still get all the other information you need, and as long as it's not confusing. Allow me to explain:
1)Variables change type. And then you have to rename everything. Its a pain
This was never an issue for me. The vast majority of the time a variable is constrained to a single file, for the rest of the times, I still have multiple-file replace tools.
2)The extra information it gives you is minimal. I want to know what data is in a variable, not the language type used to hold it.
Ah, yes. The information might be minimal, but it still saves me the pain in the ass to go look at the declaration if I need to. Yes, I know IDEs will give you that information automatically these days, but often I just ssh into a machine and do less on the source file (for this reason, I also limit my source lines to 80 characters).
I agree that what data is in the variable is much more important, and I also include that in the variable name.
If the name of the variable is firstName, I don't need it to be called lpcstrzFirstName, I know it's a string.
True, and lpcstrz is more information than I would need to know with just a glance, so I wouldn't use it. If I really need to know if it's a long pointer or some other type of string, that happens so infrequently that it's not too bad to go look at the definition. However, is it a member variable for the class or a local variable within the function only? sFirstName would instantly tell me the string is local m_sFirstName would tell me it's a member of the class, without me having to go check.
And the language type is rarely interesting- I want to know that the variable outsideTemp holds degrees farenheit, not that it's an integer
Yes, but why are you so resistant to knowing both? I don't know if outsideTemp holds F, C, or K. I also don't know if it's an integer or a float, or for that matter, I don't know if it's a string representation of the temperature. I don't know if it's local or a member of the class. But if I name it, m_fOutsideCelsiusTemp, you get everything.
3)It makes searching the code for a variable that much more annoying, because they all start with freaking 'i' and 'p'.
Yes, but who cares what it starts with? If you're looking for the variable that holds outside temperature, you're not going to guess at the type or whether it's in F or C. You're just going to type grep -inH "temp" *.c, and vary the query depending on what comes up.
Warning: Opinions known to be heavily biased.
And as N -> infinity, they are roughly equivalent. That's what big-O notation is about (not big O's, as you probably guessed...). It's about how the algorithm _scales_ as input sizes increase.
Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
I'm not a programmer, though I've enjoyed coding in various languages for hobby and as a student...
Pointers can burn in helllllll... other than that, I'm kind of torn - I always enjoyed coding things as small and efficient as I was able, and even reusing variables for something else once I was done with them to avoid taking up ONE MORE BYTE of precious RAM, haha... on the other hand, when I first played with VB.NET I was enamoured with IntelliSense and the way I could just think up and idea for a program and then bang it out like I had an assistant handing me the right tools from my toolbox instead of wasting time chasing forgotten semicolons, or trying to make sure I was always using the right operators to refer to memory pointers, or in some cases, just trying to kludge a method to get some data passed to a function and back.
So there are things to be said for both program efficiency and programmer efficiency, and it really depends on the project. I still think it would be fun to go into firmware or PLC programming some time to take advantage of small, efficient, reliable code on a limited set of resources though. There is a certain beauty to an elegant solution that can be more easily understood and appreciated by others on a smaller project.
I started programming on my C64 when I was seven years old. Commodore Magazine had reams of printed code to type in every month, and I was fascinated to learn how it all hung together to make a working program.
In high school, I noticed a BASIC programming class offered, and I took it knowing I was in for an easy A. (I was a bored D student with an A+ brain, so that class appealed to me.) The first program was the usual HELLO WORLD deal with a few extras thrown in for interest. I coded it in the way I'd learned how. It was fast and easy.
The next day, I found my printed program hanging up on the door with the words "NEVER DO THIS" in red ink blazing at the top. I brought it to my teacher and demanded an explanation. The code worked, it ran, it gave the desired output. What gave?
Turns out the class was in *Structured* BASIC, which I'd never encountered before.
My Teacher, Mister Maier, told me that it was obvious that I knew what I was doing with BASIC programming concepts, but learning how to structure my code wouldn't be significant work for me. I was already learning PASCAL and other structured languages anyway; it was easy to apply those ideas to BASIC, which I'd always thought of in terms of line numbers and GOTOs.
Mr Maier offered to make me his aide: I wouldn't have to do all the boring programs and I would have unfettered access to the computer lab. In return, I would help him in class by assisting students debug their code and get ideas about how to tackle problems.
His was a brilliant solution. He elevated the challenge level of the class to meet my skills. He showed insight and trust in *me*, which was something teachers didn't generally feel inspired to do around me previously. He gave me the opportunity to TEACH, and that kept my investment in the class surprisingly high. Suddenly my expertise in this esoteric arena began to pay off.
Because of that, I became more invested in my own formal education, my confidence skyrocketed, I went on to college (which surprised everyone), graduated with honors, and pursued a Masters in Education.
Mr Maier literally changed my life, and I have spaghetti coding practices to thank for opening that door.
That, and a damn fine teacher.
Hell, we seem to be obsessed by them. We romanticize them. We justify using crappy obsolete technology by the fact that it caters to old-school techniques. We seem to think that if it weren't for the old-school techniques, there wouldn't be any opportunity to improve our software using hard work and ingenuity.
"Our software sucks because we use Python, which doesn't offer any scope for hard work and ingenuity. Man, if we wrote in assembler or Perl, we could rock out with some wicked cool code and give our software the hard, nasty edge we need to kick ass!"
No, no, the opportunities for hard work and ingenuity are sitting right in front of us while we daydream about how awesome we would be if the world hadn't gone all soft and candy-assed on us.
Haven't seen old school programmers have this much fun arguing since the days of rec.arts.programming and rec.arts.software.engineering.
Why bother
While TFA accurately points out that in most modern IDEs, you can point at a variable and see what data type it is, thus making Hungarian notation not needed, I disagree that Hungarian is deprecated.
Few points:
If I am reading code, I don't want to stop and point at variables to see what types they are.
If I am doing a code review of printed out code or emailed code, I don't even have an IDE.
And perhaps the most important: If I see something named pSomeValue but it holds data values itself instead of a pointer value (something you can usually see fairly easily in code when being misused) then I question it immediately and often find bugs right away. If on the other hand it was named "SomeValue" that wouldn't be the case. I may actually think it's being used correctly.
Hungarian notation is as much as it ever was, one of the signatures of a programmer who knows what he's doing. (Or she!)
Most working programmers don't have the balls to get their heads down and learn about the tools they use every day. They start and then go "gosh, that's hard", which soon translates into "hey, I don't need this academic bullshit to write a program!" and goes downhill from there as they struggle and squirm once they run into a real problem.
It's rather pathetic, and sad. They like to look down at "college" programmers, and rightly, because many "college" programmers are so inept with actual tools that they can't get the connection between implementation and getting an "A" in their Data Structures class. But, the fact is that there are indeed "college" programmers who /also/ know what they're doing-- and, generally speaking, smoke amateurs for lunch.
For real professionals, there's just no excuse.
This article is amusing, but it also encourages the mediocre. Not understand sorting algorithms, some of the most insightful things you can do with a computer, simply because we know a couple of them are faster in most cases? Don't make me laugh. Ditto not being able to understand the actual "goto debate", etc.. Basically, there /ARE/ some things for which actual understanding is not optional and will make a big difference in how you program. If it doesn't matter for you, and you think typing "sort()" is good enough, then you take a big piss off a cliff and be content with your own pig snot.
If anything, use this article for suggestions during your next interview: you'll separate wheat from chaff in a hurry.
(Note: I certainly don't miss writing TSRs, though. ;-P)
Why, why, why do people get SO offended when you tell them they have to learn computers to be good at computers?
Because you're essentially attacking them.
What if I responded to you by saying: "I'm sorry, but if you don't understand how flatly attacking people's qualifications for their job is insulting and threatening, you shouldn't be having this discussion. You simply don't have the interpersonal skills to articulate this kind of thing in a manner that would be productive, let alone persuasive."
Get your dander up at all?
And please don't hide behind the "I was just stating a fact, if the shoe fits, wear it." There are lots of good ways to say what you're trying to get at that are probably even closer to the truth.
Which is that really don't have to learn *everything* about computers in order to be good at computers. It is certainly an underlying truth that the more you know, the better you are as a developer. But it's entirely possible to be a reasonably productive developer without knowing everything... as long as your abilities are matched to what you need to accomplish. And there's more or less a curve of task difficulty to go along with a curve of developer abilities.
I don't know very much about building compilers. Some people would say that makes me a mere dilettante of a software developer. That's a rash overstatement. It's absolutely true I would be a *better* developer if I knew more about these things, and certain problem domains would be more open to me, but there's a huge problem space that really doesn't require this knowledge. This works the other way, too: I probably know more about Linear Algebra and Discrete Mathematics than many developers and even some CS majors (studied Math in school) and I'm familiar with the Logic Programming paradigm (written full programs in Prolog). These things make me a better developer, particularly for some problem domains, but it certainly doesn't mean anyone who doesn't know these things is a simple hack.
I think implementing hashes and other primitives that are now part of libraries/languages falls in this category. Being able to implement them is certainly a *demonstration* that you've mastered certain skills. The contrapositive doesn't necessarily follow. Not ever having implemented them -- in particular because you've never had to -- doesn't necessarily imply that you lack the ability to solve that class of problem.
And in fact, it might demonstrate a certain stripe of wisdom: there's a limited amount of time and a pretty much infinite supply of problems. What do you spend time learning how to do?
Tweet, tweet.
Thanks for the trivia, but how do we actually know you can ship code on time, and within budget?
Dude, you're totally harshing the buzz here. You don't obfuscate your code to be "clever". You obfuscate your code because you can. Also, have you ever worked on an 8-bit embedded controller? Sometimes you don't have an extra byte for that temp variable. On the other hand, they've gotten so cheap and low power that those problems are a thing of the past.
hmpf hmpf, indeed code self modification without explicit synchronisation has never been supported in a backward compatible way on the x86 familly. It is even a way to distinguish between 8086 and 80286 (or is it between 80286 and 80386 ?)
You also makes a lot of assumptions on conditions in which the code will self-modify ; it can indeed be interresting even on modern architectures if the code does not change very often, for example to enable / disable traces in a kernel.
And it _is_ also possible on common RISC machines (you also have to use explicit synchronisation like on x86)
y = x + y - (x = y)
When you're aiming for realtime photon mapping, you start with a ray tracer that's already fairly optimised. Then you make the photons work. Then you optimise some more.
Someone pays you to implement realtime photon mapping? Are they hiring? O.o
Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
x = x + y
y = x - y
x - x - y
The XOR implementation is not subject to overflow problems. (And the typo has already been noted by scipero.)
Much less CPU intensive.
That depends on the CPU. For example, on old x86 CPUs, XOR AX,AX was faster than MOV AX,0; generally, XOR was faster than MOV, ADD, and SUB.
- T
I generally agree. Most of us have at least some part of us that likes to learn by digging around in stuff rather than just reading the dry docs (no pun intended). Sometimes poking around will lead us to make mistakes, and sometimes it improves our knowledge. A worse-case outcome, like the hash example, is perhaps not representative of the net advantages of poking around. Sometimes you have to take a step back to move 2 steps forward.
Table-ized A.I.
One of the things this article ignores completely is the area of embedded programming -- and there is still a LOT of it going on. There are still a ton of NEW projects being done based on 8051 and 6800 series derivatives -- and those are just two of the major architectures.
Even if you are not specifically doing embedded programming per se, the more you know about the basic architecture of your system the more you can help the compiler take advantage of it.
For instance, on the vast majority of processors comparing a register to zero is typically a VERY low cost operation. Furthermore, many processors have hard coded instructions that both decrement a register, compare it to zero, and branch if it is not. If you enable the most aggressive optimizations on some compilers they will attempt to do loop re-ordering (often with disastrous results) and do sometimes succeed. HOWEVER, more often than not there are chunks of code inside the loops that prevent effective re-ordering from occurring. If you are aware of your processors underlying architecture and try to intentionally write most of your loops as count down to zero in the first place, you make life much easier for the compiler and allow it to make more efficient code as a result. This is just one small case.
Also, as far as hand optimizations go, I still do it quite often -- even at the raw assembly level. With visual inspection and manual adjustment I have proved time and time again that I can do a MUCH better optimizing job than the Keil compiler can. I can also typically get some gains on ColdFire/Freescale stuff as well, just not quite as drastic.
I have worked on many projects over the years and seen more bad programmers than I care to admit -- and the most recent/youngest batch has both some of the best and worst I've ever seen (with far more of the later than the former). This is not their fault, it is the fault of what the university's are teaching them. At one company I used to work for (and this was a BIG company with over a hundred thousand of employees worldwide) our local HR department had a standing policy to NOT hire Computer Science graduates for permanent programming positions unless they had interned with us first. Basically, all the CS grads had far to many theoretical and inefficient/unreliable programming ideas to unlearn to be useful.
Also, there are many cases where even hand manipulation at the raw binary code level is still needed and useful. Although most projects I deal with now, thankfully, use flash for code space, a few still do use OTP (one time programmable) parts. It has not been that many years ago that I had to spend the better part of 2 months figuring out a way to "overburn" a set of parts by finding a safe location where I could turn existing instructions into NOP's followed by a branch to a new chunk of code at the end of our programmed space (when re-programming/overburning an OTP you can still turn a 1 to a 0 but not the other way around -- thankfully, the architecture we were using considered 00 as a NOP and we had left all the unprogrammed space as FF's). And yes, this is a very extreme example, but it allowed me to find a fix that allowed us to use over 35 THOUSAND mis-programmed parts that otherwise would have had to be tossed (and these parts cost in the $12 range EACH).
Similarly, on some large volume consumer products, manual code optimizations, low level coding, and hand tweaking is still the norm -- for a very simple reason: it saves money. In almost every case, it is still almost always cheaper to use the micros with less onboard flash and hand optimizing the code often allows us to keep code just below the threshold of the next size part. Similarly, on one project I was on we had 3 engineers spend 6 months hand writing a custom DSP algorithm that allowed us to remove a filter circuit whose component cost was on the order of $0.15 USD (yes, 15 cents). The management team was utterly thrilled over this as the volume for the circuit in question was way over 1 million units (you do t
Arrays.sort in Java uses mergesort rather than quicksort for objects because quicksort has one big downside: it's not stable. For primitives this doesn't matter, but for objects which might already be sorted on one field it could do in some use cases.
If you knew about sorting and had actually learnt it you would know that Quicksort is the default because is is usually the quickest and most efficient method, but on some datasets it can be slow or inefficient, and there are "better" sorting methods for that data ...
You do not need to optimise quicksort (the version you are using is probably already more optimised than you can make it...), you need to optimise your use of it ... i.e. use it where it is appropriate, and use something else where it is not!
Puteulanus fenestra mortis
If you knew about sorting and had actually learnt it
Because obviously from my statement that "most library implementations of 'sort' are quicksort based", I know nothing about sorting. Yeah. >.>
All rules of programming (without exception) can be replaced (for a sufficiently intelligent entity) with a simple rule of "don't be fucking retarded and do something suboptimal" - but that doesn't work so well as a directive to humans.
Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
Yes, I've worked with the "optimise later" sort. Try this out: Phase I of the project sets up Hardware and Memory requirements, based on average memory usage for similar projects. Phase II sets up the infrastructure with whatever crap is easiest to through together, no optimization. Phase III get to write the program with 50% less memory than normal, and faster response. With less money, usually, and less people.
a few people have mentioned that you tend to not write sorting algorithms and data structures in this day and age. but it's still critical that they be understood so a developer can understand the trade-offs for selecting a particular library and also the requirements needed to interface effectively with the chosen algorithm. C++'s STL library does a very good job about being somewhat "in your face" about time complexity. Java's collection api, which is also a well designed library, is much less in your face about time complexity and the various trade-offs. Since I sling java code for a living, i'll whine about Java collections. #1. I have solved performance problems with large datasets simply by re-implementing the hashcode method on objects. When explaining that a hashtable (or hashmap in java parlance) degrades to linear time if the hash distribution isn't sufficiently sparse, i'm reminded that these concepts are new to the folks implementing systems on which multibillion dollar businesses are run. #2. explaining to folks why you have to override both hashcode and equals if you want to expect your stuff to work is equally as evasive as well as the following: #3. pointing out that you should re-put a stored object into a hashtable after you mutate because the hashcode may no longer be accurate. #4. explaining why when you iterate over a keyset for a hashmap things aren't in order, but when you use a treemap you are. #5. choosing a linkedlist for queue-like operations, choosing a treemap for natural-ordering, choosing a hashmap for good average performance. it would be nice if when these problems are encountered and unwound a lightbulb would go off and they said "oh! i remember this from CS-xxx class" but instead it's like.. "oh you're such a geek i bet you don't get laid much." i think we get in trouble when we attempt to make hard things easy. most un-zen-like.
>Honorable mention: Implementing a linked list or hash table yourself
In a lot of languages, you're going to implement your own linked lists if you want to have efficient in-order (LIFO or FIFO) and out-of-order removal.
With the C++ STL list template you can stash an iterator in each object and it isn't a problem. WIth Java, Perl, and probably C# that's not an option.
There are fewer situations where an off-the-shelf hash table implementation won't do the trick although they exist.
>Manual multithreading and multitasking
Most programmers can't make systems with multiple interacting locks work right. Thorough testing with contemporary tools is not possible, so customers are often the ones to find problems. For instance, I regularly crashed a SPARC running Solaris to the ROM monitor when doing a simple user space pthreads.
Modeling tools like spin/promela exist to prove locking schemes are correct although locking is often a leaky abstraction and you can't guarantee the implementation tracks the model.
Pre-emptively scheduled kernel threads don't perform and scale well enough for many applications. The C10K paper would be the first popular reference here. Non-premptive user space threads are a partial solution but still have a large cache and TLB footprint.
Consequently, most smart people building reliable and performant systems software end up with message based concurrency control systems. With popular language support (Java, C++, perl, not Erlang or Haskell) lacking appropriate functionality and popular libraries (libevent) being incomplete and not working well in multi-core environments this is usually home brew (Google's kilim might be a good contemporary example). That's manual multi-threading and multi-tasking.
A contemporary definition of "performant" should be on the order of 1M operations per second per node.
>Honorable mention: Non-WYSIWYG editing platforms. Some of us remain comfortable with vi/emacs, command-line compile options or .nroff for documentation formatting, but initially we programmers didn't have a choice.
I'll take vi and a text markup language over WYSIWYG editor any day. Multiple cut buffers make re-arranging text much faster. All of the meta-data is visible with the markup language and can be manipulated with the standard QWERTY keyboard while the WYSIWYG editor doesn't make all of it visible and requires traversing a maze of menus to make sense of it.
With my roff and html resumes I've never had a problem getting bullets at the right indentation level on the first try. With Microsoft Word or Open Office I've had to do little dances with paragraph deletions because traversing the menu structure didn't always do the trick.
This disregards productivity loss due to the temptation to play around with the layout instead of trusting the macro package to do the right thing.
> Honorable mention: Writing your own utilities to search for where you'd used functions or procedures and where you'd called them.
A coding standard makes that painless. Search for ^function( for the definition. Search for white space function( for use. Within a file an editor like vi will jump to the next reference with one key.