Signs that you're a bad programmer
sites.google.com
sites.google.com
On style, though, I think the document is trash. Some of what I say is probably even a bit ad hominem.
Most of the good programmers I know don't spend time worrying about bad programmers. They just write good code and cleanup bad code when they see it.
If someone feels compelled to write up an essay about bad programmers, he probably spends a lot of time dealing with bad programmers.
That experience should lead to humility, not arrogance. If you deal with a lot of bad programmers, you might not be as good as you think you are. After all, you work at the same place on the same project as a bunch of bad programmers. They where hired under the same standards as you.
To me, the document just seemed like arrogant posturing. An attempt at crafting a "negative identity" (define a group by vilifying the outsider). The best way to "be", and to be recognized as, a good programmer is to write good code. All the other stuff is just horse shit.
The problem is though the hiring process for programmers is often completely broken. Measuring good programmers is also broken in many companies, so bad programmers tend to just stay there instead of improving, or being fired.
I think being a good programmer isn't too hard if you have the talent, taste, time etc. Being recognized as a good programmer, and rewarded as such is harder.
I have been fortunate to never work with a bad programmer, but I have worked with inexperienced programmers that were learning as they went. I was not hired under the same standard or for the same reason they were.
But on the whole, the article also leaves a sour taste in my mouth due to the superiority complex that cuts through it.
Good programmers won't effect much change at companies that have broken hiring practices. Rather move somewhere where your skills will be appreciated.
Being a good team member who improves the process of your team by working to everyone's strengths is ... excellent.
Oddly enough, I think that at least some managers are more likely to recognize the latter than the former and often rightfully so.
I should cancel my appointment with the psychoanalyst :-)
Writing is also a catharsis. You write to discover what you think and improve yourself.
But perhaps Isaac Asimov spent a lot of time dealing with robots...
That is why he included "remedies" for each condition. If you implement all of these remedies, you are very likely to write good code, and thus be a good programmer.
The title leads to the structure of the article, which is really about pitfalls to avoid if you want to be a good programmer. And for some, the negative tone might really be necessary. Remember, incompetent people are often unaware of their own incompetence. But if someone has the humility to read an article titled "Signs that you're a bad programmer" and honestly look for things he can identify in himself, he is on the right track.
In my opinion, the remedies transform this from an attempt at vilifying outsiders, into a potentially valuable tool for becoming a better programmer.
The idea behind any essay about bad programming has merit. I read the points in essay like this through two filters: is it a valid point and am I guilty of it. Most of the points in this essay are valid. The one about pointers isn't so relevant unless you live in C but the others, sure. The bit about knowing your platform well - that made me think about some things I've been working on.
The essay made me think and maybe checksum myself just a bit. For that, I like it.
To be fair to the article's author, he does mention that understanding references is analogous to understanding pointers. They aren't the same thing (his own words) but they're close enough to matter. I confess I originally thought exactly as you did: "What? This only applies to languages like C!" until I read a little more closely. It was a tricky sentence, and I would have appreciated it more if he were to have made the two a little more obvious. It's always easier to judge in retrospect, though.
The problem I have is that I agree with you and the original individual who stated that the article seemed arrogant. On the one hand, it made some really good points including MANY that I have been guilty of at some point or another.
The biggest beef I have with the article at large is that some of the issues are language-specific. For instance, not using methods/fields "correctly" is a pretty big problem--unless you're using a language that doesn't have properties (hello, PHP). I absolutely love properties, but whenever I've had to maintain or write PHP (yeah, I know), I tend to create public fields and use them as a pseudo-property rather than writing getter/setter methods. One, since it's a scripting language, getter/setters are superfluous for most simple applications and the fact that it's a dynamic language with an extremely stupid typing system sort of mitigates the benefit of writing extra code. I'm also not sure whether there's a performance penalty with accessor and mutator methods (maybe someone can clue me in here) in PHP.
So, am I a bad programmer because I don't use standard paradigms for small scripts written in an arguably awful language? Maybe so. Maybe more so since I actually write in that language from time to time! But, the point of this minor tangent is that some of the article's points don't effectively apply in all languages either due to deficiencies or oversights in the language specification.
I do appreciate your point about reading the article through two filters. I'd like to add a couple of others: does it matter (in the context of the language you're using) and is the point of contention a mistake that could be made due to scheduling pressure or deadlines? To some extent, I'm sure we could blame a specific degree of bad code on ridiculous deadlines (and I think the author pointed that out, too!).
Because otherwise, Google 2009 == GeoCities 1999, and that would be unthinkable.
Empires rise and fall fast on the net. Google is now a mish-mash of offerings which get uneven attention from their internal talent. 'Sites' itself is a successor to the soon-to-be-shuttered Google Page Creator (which unlike Sites allowed custom CSS and JS). A few key people leave, a couple bad quarters -- 'Sites' could get the axe too.
searching through their help center, they won't even tell you what the limit is, but it seems to be pretty low. just. wow.
A bad programmer won't understand or take into consideration the limits of the target platform.
A good example is posting an article to a host with limited bandwidth.
But we have absolutely no problem serving high traffic sites (millions of pageviews per month) for free, it hardly costs us a thing.
Overheard statement "You know when I was in college I learned C. I just can't get the hang of these objects, so I try to avoid them". The guy who said this is now 34, and has programmed python for 4 years.
A guy who would copy and paste 100 line blocks of code, and then occasionally thing "oh yeah, i need functions", and then finish out the current function by passing 12 parameters of state to current_function_2(). If you combined the func, func_2 and factored out the 100 line blocks it would be reverted with a commit: "stop making this so complicated".
There was one guy who insisted that using templates was only for web frameworks, to use them to write system/config files (done frequently due to 3rd party daemon limitations) was an abuse of the templating system, and we should just use inline prints. The program was to be called with prog > /etc/conffile.
One guy who decided that threads were so tricky, that he was the arbiter of thier use. My idea of a job queue and worker threads was "too abstract and hard to debug", so we stuck with the "tried and true" one thread per job method, all spawned at once.
I could go on, but you're all prolly bored now :)
tl;dr: i worked with the guys the article was written about
While some people really don't get it I'm pretty sure that most people could learn how to program with some competence.
The only metric I've found to measure programmer competence reliably is how big a project you can manage to complete. Some people get stuck at around 100 lines, others in the thousands or tens of thousands, some can keep their rudder straight across 100's of thousands of lines.
The latter takes extreme self discipline and organization.
Gzip-bytes of source FTW.
Clever trick to measure the real information content of code using gzip too.
Implicit in the notion of completing a project is the certainty that the project manager will change their mind, usually after you have written 95% of it. If you copied & pasted those hundred thousand lines, you will have to change something in at least 100 different places. At that point, you'll give up in despair, proclaim the change impossible, and have the PM cancel the project in a fit of frustration. Thus, not complete.
LOC actually sorta makes sense in this context.
That is what logic tells you, but I've seen smarter people than me, which couldn't get it even after years of trying. (I helped a few with labs while studying.)
You shouldn't have too much grit.
You characterization about handling program size fits well with my own development. Interesting point.
But beyond the practical, what you're really measuring is the ability to break down a large problem, solve individual pieces, reason about how those interact, and iterate. That is the critical intellectual muscle that makes programmers reliably great.
I remember a few projects I abandoned when their size outgrew my ability to comprehend them. It took multiple attempts with new methods to break through certain complexity barriers. I could not write a program longer than about 500 lines until I had learned the practical use of subroutines and data structures. I could not write a program longer than a few thousand lines without a sense of taste when it came to objects and modules. And I could not write a program of significant size until I reliably turned out code that could be comprehended at a glance after months or weeks away, as my own throughput guaranteed it might be that long between visits to disparate parts of the codebase.
This went on for weeks, maybe even months.
Then a friend told me the magic words: "structured programming". I went to the library and read a book (I forgot the title, I think it was by Wirth).
Within 3 days I had it up and running.
Anyone who has written code in C can probably cite an anecdote where exactly this was the case, be it from a compiler bug or as fallout from a memory corruption bug.
Maybe we should add "does not understand the von Neumann architecture" to the list of signs that you're a mediocre programmer... On second thought, let's not -- the piece is bad enough to begin with. The "alternative careers" sections are just odious, and the whole thing breathes an air of inconstructiveness.
Granted this usually only affects runtime speed, but in some circumstances things can get worse (multithreaded programs are particularly susceptible to such optimizations, especially if they're not written correctly - yes, the real problem may be that you've written your code wrong, but there are very real situations where the presence of dead code can change the optimization path taken enough so that it either works or doesn't, which is a very real effect).
And that's even before we take into account compiler bugs, which just make things worse.
I get worried when I see someone take a mainstream language (C#, Java, Python) and try and twist its idioms into whatever their favorite language is (lisp, the ML family, Perl, etc). Or if someone wants to be on the bleeding edge and use the latest language syntax/library trick. This is a pain because
- Someone new to the codebase may not have as much familiarity with all these nuances of the language. For example, I've seen crazy things done with C++ templates. I don't think there was ever a justifiable reason for doing any of them.
- The esoteric, cutting edge features often have bugs and/or don't have great tool support. Fact of life is that the more some feature gets used, the more the bugs found and fixed. If you're on the bleeding edge, you're going to hit weird language/runtime bugs which you need not have inflicted on everyone.
- Debugging. Often, the only thing you have after a weird bug is a crash dump and caffeine. You want to give yourself all the chances you can of tracking down that issue. Esoteric language features rarely lead themselves to great crash dump debugging. I'm not suggesting writing C code but the closer the mapping from code you write to x86/64 code, the easier it is for you
One of the examples of bad technique is:
Homebrew "Business Rule Engines"
Can anyone give examples of the sorts of things the author means by this? What sort of systems have people seen, and why is it necessarily a bad thing?Don't worry. In the Valley, people from MIT don't have a good reputation as great programmers. For some reason, their education is a lot of 'theory' and less practice.
Apart from Standford\Caltech, Some of the best schools that produce great programmers are your average state school.
I have a theory behind this, which may be correct. Learning to be a great programmer takes years, and school is just the start. People that do well enough go to something like MIT, think they are already very smart, and don't try hard when they come out.
But if you are hungry, and smart, and just happen to go to your average state school, you probably will do better, as you probably are humbler to begin with.
It seems when the rubber meets the street, in startups, building products, that's where the great programmers are made, and lisp is not a requirement to know to be a great programmer, or actually build great products. Having an idea of what functional languages are is a must, knowing them is not.
Even fricken DeVry will help you with that if you are already motivated.
This is not to say that any of these people are bad at what they do, they just have bad habits that they need to walk off, but first someone needs to tell them!
That's a very astute observation. I work and have worked with many MIT people, and even TA'd an MIT course. The "all nighter" hazing ritual culture was very prevalent. To this day, my friends who went to MIT prefer to work 24 hour shifts, late nights, and on weekends - even though they've been out of school for a decade. In addition, MIT had a culture of boot-camp style negative reinforcement. "You suck, are not smart enough and are lazy, work harder" was the driving attitude. (at least it was in the late 1990s)
In contrast, when I moved to the west coast, all the Stanford people I met were more about basking in their obvious awesomeness and mastering time management. Get the project done early, schedule everything around ultimate frisbee team and rock climbing club, go to the outdoor concert on Friday night and still make it up to Tahoe for the weekend.
While I have great respect for these institutions and the aptitude required to succeed within a difficult program, it's not a guarantee of quality.
For those who are curious: given these were linux systems, d-bus was the right answer for the former, and something like RabbitMQ or OpenAMQP, or even JMS solutions would be the proper answer to the latter.
It's easy in retrospect, but Postgres predates JMS etc by a loooong way - who's to say that wasn't the best solution at the time?
[1] Affordable being defined as: this particular company could pay for it without going out of business.
These problems are called "lazyness leaks". This is why he still cares about order of evaluation.
I (a Haskeller) interviewed there, and ended up in an long debate with their CTO Yaron Minsky poking at warts in each other's pet languages and patterns of thought -- in retrospect we were trolling each other pretty hard just as a natural reflex.
If Google can't figure out a way to monetize popular content targeted at a professional technical audience, something's very wrong.
The best idea I've seen for such situations is to let people trying to view the page buy an instant traffic upgrade on the site's behalf; I'd chip in 100x the costs to serve my single hit if I knew it would restore visibility for me and some others.
The help link is also uncharacteristically inept for Google -- it doesn't lead to an explanation of pageview limits, and searching [pageview limit exceeded] in the help area turns up only user questions, no official article on the topic. The third hit is this unanswered gem, from a popular site with AdSense that keeps going down due to the limits:
http://www.google.com/support/forum/p/sites/thread?tid=08f19...
However, it seems that there is a little bit of a self-diagnosis problem. For example, if you write "voodo code" (great description), unless there is some one to tell you, how do you know? And if you write voodo code, do you know what idempotent means? There is maybe a bit of the "Blub" effect here.
Perhaps between "Symptoms" and "Remedies" there needs to be "Diagnosis" or some hints at self-evaluation or tools for self-assessment.
I've seen articles like this before, but they're usually super-parochial, and I've never seen one that offered 'remedies' (much less the hilarious 'alternative careers' in the last section).
We are sorry, but this site has exceeded its page view limit at this time. Please try again later. For more information, see Google Sites help.
> (Functional) Manually caching the results of a deterministic function on ... Haskell
You still have to explicitly memoize at times in Haskell.
> refactor his old code with the goal of reducing its instruction count by 10:1 or more
What a loser! I go into my old code with a goal of 10000:1.
> Recursive subroutines that concatenate/sum to a carry-along output variable
This can be justified.
> Using strings/integers for values that have (or could be given) more appropriate wrapper types in a strongly-typed language
Not always worth it.
> Unit Testing, which you use at design time.
No, I don't.
> You don't use whitespace or indentation
If I'm not using whitespace, what am I supposed to indent with?
This is one of the best arguments for why anyone should bother to learn Haskell (or likewise language)
(Haskell simply does call-by-need: square (x+1) evaluates to (x+1)*(x+1), but there's still only one (x+1) that gets evaluated.)
Yes, the author is more arrogant than desirable in his delivery but don't let that blind you to the good stuff. Some of the teaching analogies, in particular, I thought were good.
I've always understood high cohesion to be related to low coupling. Also, writing util classes before you need them (where low cohesion may be necessary) is a nothing more than a time sink. Do something when you need it, not before. You are not omniscient and will not be able to account for everything no matter how hard you try.
1. Inability to determine the order of program execution
Symptoms
a = 5 b = 10 a = b
print a You look at the code above and aren't sure what number gets printed out at the end"
The answer is not always obvious. If the above code were in Java and print a was running in a different thread then it very well print 0, since a or b could be 0. JMM can really bite you if you don't understand within-thread as-if-serial semantics.
5. Lisp is opaque to youI don't think this makes me stupid, or a bad programmer. I think it means I have different tastes and limited time. Learning LISP feels like learning assembly: a great exercise, but there are other things I'm more interested in doing - like building things I enjoy with my limited free time.
That being said, maybe I am stupid, or a bad programmer. I don't think not being into LISP means this is the case. I like building things people use, more than I like writing elegant code for code's sake.
"so if it's opaque to you...,you probably shouldn't be programming"
So your saying that someone should not program if they haven't learned basic lisp ( 5 )?
Or are you suggesting that there should be some time limit... like if you program for X months but don't understand it you are forced to quit.
But.. further... What is the purpose of forcing these people to quit?
And.. What is the purpose of belittling these people?
To put it another way: if you call yourself a programmer, and sell your skills as a programmer, but can't mentally decompose "(+ 2 3)" into an AST (basically the first, and easiest, step to understanding), even after Lisp's general syntax is explained to you, then you aren't actually a programmer.
"you can't take a guess at what "(+ 2 3)" means"
I can see your meaning if it was explained to someone and they still didn't grok it. But I would still kinda feel like encouraging them.
I don't think you're a bad programmer-- but if you're making a distinction between "building things people use" and "writing elegant code", prepare to have your mind blown by SICP.
Then I've spent some time learning LISP, practising it, trying to implement more and more complex algorithms in it and while I'm still not a pro, I'm starting to grasp the basics. And it made me a better programmer, even though at my daytime job I work as a C#/.NET programmer, the little LISP knowledge I have heavily influenced the quality of my code and I think for the better.
I think the point behind such statements is the point, that Lisp and especially Haskell today are very, very advanced languages with very, very advanced and abstract concepts (best example: Monads.). If you 'Learn Haskell', or 'Learn Lisp', the speaker will usually mean: Learn these abstract and advanced concepts. Learn to love homoiconic languages such as Lisp, Factor, and learn to love the mathematic backgrounds in haskell, with its Monads, Types, Monoids and whatsoever. If you have groked those concepts, you will usually make a serious step on the ladder of good programmers.
However, as 'Learning Lisp', or 'Learning Haskell' are just a way to say to learn these very advanced concepts, it can be very possible that one knows these concepts by heart already. So, if you know functional programming, the most interesting part about lisp reduces to macros, pretty much. (I know, I will be bashed, because there will be one or two interesting features I forgot, because I did not venture into the LISP-Land too much, because the madness of interpreters and such was too big for me, but the point still stands.)
# (OOP) Writing lots of "xxxxxManager" classes that contain all of the methods for manipulating the fields of objects that have little or no methods of their own
I guess they keyword 'lots' make this ok, but having plain objects (scruct like), passed around in the application, and having some managers is fine. eg. if you have an application that passes around contacts, you can have a ContactsManager, taking care of all the messy bussines (local peristence, remote persitence, deleting, etc., while contacts remain simple objects. ActiveRecord pattern goes the other way, where this logic is placed in the objects. Both are fine.
# (Relational) Treating a relational database as an object store and performing all joins and relation enforcement in client code
--I guess all those programmers in the "NOSql" movements are idiots. (they are not, some of these people are really smart). Don't take this statement too seriously.
# Re-inventing or laboring without basic mechanisms that are built-into the language, such as events-and-handlers or regular expressions
--I think regular expressions are evil, obscure, and performance is highly depended on the implementation. Practical example, checking if an email address is valid. While some people go all the way crazy with regular expression, All I do is check that there is an '@', or a '+', at least one '.', and minimum/maximum lengths. You don't regular expressions for that, as no matter how smart you think your expression is, there is a great chance somebody that edits it will fuck it up just by changing a character on it.
# Re-inventing classes and functions that are built-into the framework (eg: timers, collections, sorting and searching algorithms) *
Done many times. If you are doing mobile, most of platform implementation (at least in J2ME) just suck ass. You can't rely on them. Heck, in my previous company we did even font rendering, thread worker queues, etc. as the scheduler was unreliable, etc.
#Recursive subroutines that concatenate/sum to a global variable or a carry-along output variable
--For some problems carry-along variable are needed.
eg, in a binary search tree, trying to print only the nodes in a certain level. printTreeLevel(tree, 2) -- will print only the nodes at level two
print(node, level)
if level == 0
print node.data
else
printTreeLevel(node.left, level - 1)
printTreeLevel(node.right, level - 1)
# Writing business-logic functions with tragically compromising side-effects, such as updating a user interface or performing file I/O--I guess it depends on the definition of what "business-logic" is. This guy thinks it more as the transactional/model part and not a controller. For some apps, the business logic is the main controller. Plus, "bussines logic" is more of a managerial speak anyways.
#Homebrew "Business Rule Engines"
-- Maybe I am not old enough, but I had to look it up what a "Business Rule Engine" is. Seems stuff from the Dot Com era/enterprise world.
#Code that tries to prevent an exploit from working by searching for the exploit's signature
--Haha. That's the whole Antivirus industry. I agree with him, and I don't have antivirus software, but it seems that those companies made millions by breaking this rule.
Recursive subroutines that concatenate/sum to a global
variable or a carry-along output variable
Wow...I didn't see this in the article. That is downright boneheaded. Is accumulator-passing style forbidden for a reason? It's a cheap way to make functions tail-recursive---much cheaper than CPS!You know about tail recursion, but I'm trying to get at the uses of accumulators that have nothing to do with tail recursion.
That and I got fed up with listing all of the exceptions to every rule.
"5. 'Bulldozer code' that gives the appearance of refactoring by breaking out chunks into subroutines, but that are impossible to reuse in another context (very high cohesion)"
I disagree with this. The purpose of breaking out code in to subroutines isn't only for code reuse. If you don't break out code in to subroutines, at some point your code is going to become unwieldy and difficult to read.
I try to stick to the rule of having each subroutine consist of no more than a page of code (so that each subroutine can be seen all at one time, without needing to scroll). Often that leads me to break up the subroutine in to many other subroutines. This improves readability greatly and makes it much easier to reason about the code. If any of the subroutines I break my code out in to happen to be reusable, that's just icing on the cake, not a necessity.
I think what the article is suggesting, though, is not just that the subroutines would be "reusable" as in useful if called from another part of the program.
He's suggesting that the contents of each subroutine should be decoupled from each other - so not sharing global state, depending on strange changes made in other seemingly unrelated methods, etc.
The property of "I could reuse this routine if I needed to" implies low cohesion and therefore (probably) more maintainable code.
Steve Yegge argued something related to this topic that I don't think I like much, and I don't think you'd agree with it either. He claimed that junior programmers tend to write shorter methods because they're not used to fitting large chunks of complex code in their heads: http://steve-yegge.blogspot.com/2008/02/portrait-of-n00b.htm...
As someone who has seen 2000-line long C++ methods written by "senior developers", I still think short clearly named methods are better. :)
I do agree that you should not (depending on language) write sub of everything. ex int add(int a, int b) {return a+b;} //Junior model
I've seen plenty of bad code where a task is broken down arbitrarily, creating sets of procedures like foo, begin_foo, execute_foo, actually_do_foo, foo_part_two, etc. This seems to be a particular problem with OOP, where tasks have to be handed off from object to object, down the chain of responsibility.
Anyway, long story short when I see a huge stack chain, general they are either do anemic objects and just hacking up the big controller procedure into a bunch of little functions or they are doing OO work flow, either of which is not an appetizing prospect to support or fix. Evey once and a while you will get someone that is doing factory patterns everywhere which can create a lot of deep dives down the stack to figure out that the 17 methods deep stack finally results in one method creating a object.
P.S. Private subroutines do not have to be reusable public ones should be. The author did not specify. so I hope and assume he was talking about public subroutines.
What I gathered from the original article however, is a focus on those guys who will decompose long_function(params) into short_func1(params), the last line of which is return short_func2(params, 12 state params); .
http://en.wikipedia.org/wiki/Cohesion_(computer_science) http://en.wikipedia.org/wiki/Coupling_(computer_science)
WARNINGS := -Wall -W -Wunused-parameter -Wmissing-declarations
WARNINGS += -Wstrict-prototypes -Wmissing-prototypes -Wsign-compare
WARNINGS += -Wconversion -Wshadow -Wcast-align -Wparentheses
WARNINGS += -Wsequence-point -Wdeclaration-after-statement -Wundef
WARNINGS += -Wpointer-arith -Wnested-externs -Wredundant-decls
WARNINGS += -Werror -Wdisabled-optimization -pedantic
CFLAGS += $(WARNINGS)
Note: -pedantic is pretty good if you really wanna learn how to write code that will work properly.
Also: Treating Warnings as Errors is a good thing. You'll learn a lot, and the most important thing will be: handle all warnings. Do not release code until it builds clean.
http://kerneltrap.org/node/6591
An unrelated example where gcc (and icc) emit needless warnings about integer types is this:
int x = foo();
unsigned y = (x == 0);
Don't get me wrong, I'm in favor of cranking up the warning levels, but there is a reason why not everything is included in -Wall (yes, "uninitialized" is included, I know).I work on SIL-4 rated systems for life-protection applications. Absolutely ZERO TOLERANCE for warnings is a requirement in this job, and every single time it has been addressed, the warning gave us a clue how to fix something. Whether it was the code that needed fixing, or the compiler - either way, the alert is there. Ignore at your own peril.
If you think you are good, you are not. If you think you have to improve, chances are you will become better.
(Life is baby steps; ask a baby...)