Wednesday, January 27, 2016

How not to be a better programmer

Trying to reuse code is near the top of reasons why big projects fail. The problem is that while the needs of multiple users of a module may sound similar, they are often different in profound ways that cannot be reconciled. Trying to make the same bit of code serve divergent needs is often more complex and buggy than multiple modules written from the ground up for each specific need.

Yes, we adhere to code cleanliness principles (modularity, cohesion) that makes reuse easier. Yes, we should reuse code when the needs match close enough. But that doesn't mean we should bend over backwards trying to shove a square peg through a round hole, and the principle that all pegs/holes are the same.

Give variables/methods clear names

Programmers hate to read other code because the variable names are unclear. Hence the advice to use "clear names" that aren't confusing.

But of course, programmers already think they are being clear. No programmer thinks to themselves "I'm going to be deliberately obtuse here so that other programmers won't understand". Therefore, telling them to use clear names won't work, because they think they already are doing that.

The problem is that programmers are introverts and fail to put themselves in another's shoes, trying to see their code as others might. Hence, they fail to communicate well with those other programmers. There's no easy way to overcome this. Those of us who spend a lot of time reading code just have to get use to this problem.

One piece of advice is to make names longer. Cryptographers write horrible code, because they insist on using one letter variable names, as they do in mathematics. I've never had a problem with names being too long, but names being too short is a frequent problem.

One of the more clueful bits of advice I've heard is that variable names should imply their purpose, not the other way around. Too often, programmers choose a name that makes sense once you know what the variable is, but tells you nothing about variable if you don't already know what it is.

Don't use magic numbers or string literals

Wrong. There are lots of reasons to use magic numbers and literals.

If I'm writing code to parse external file-formats or network-protocols, then the code should match the specification. It's not going to change. When checking the IP (Internet Protocol) header to see if it's version 4, then using the number '4' is perfectly acceptable. Trying to create a constant, such as enum {IpVersionFour = 4}; is moronic and makes the code harder to read.

While it's true that newbie programmers often do the wrong kind of magic numbers, that doesn't apply to good programmers. I see magic numbers all the time in Internet code, and they almost always make the code easier to understand. Likewise, I frequently see programmers bend over backwards to avoid magic numbers that makes the code harder to read.

In short, if you are an experienced programmer, ignore this dictum.

Don't be afraid to ask for help

Oh, god, the horror. Engineering organizations are divided into the "helper" and "helpee" sections. The "helpees" are chronically asking for help, to the point where they are basically asking better programmers to finish and debug their code for them.

Asking for help is a good thing if, when reading a book on a technical subject (networking, cryptography, OpenCL, etc.) , you want the local expert in the subject to help overcome some confusion. Or, it's good to ask for help on how to use that confusing feature of the debugger.

But stop asking for others to do your work for you. It's your responsibility to debug your own code. It's your responsibility to be an expert in the programming language you are using. It's your responsibility for writing the code, unit tests, and documentation.

If you see some buggy or messy code, fix it

No, no, no, no.

This advice only makes sense in modules that already have robust unit/regression that will quickly catch any bugs introduced by such cleanups. But if the code is messy, then chances are the tests are messy to.

Avoid touching code that doesn't have robust tests. Instead, go in and write those unit tests. Unstable code prone to bugs can remain so when the tests are robust. The tests act as a safety net, preventing bugs from appearing.

Only once the unit/regression tests are robust can you start doing arbitrary cleanups.

Share knowledge and help others

This is bad for several reasons.

When programmers don't complete their code on schedule (i.e. the norm), one of their excuses is that they were helping others.

Engineering organizations are dominated by political battles as engineers fight for things. This often masquerades as "sharing knowledge", as you help others understand the power of LISP over C++, for example.

As pointed out above, the lazy/bad programmers will exploit your good nature to shift their responsibilities onto you. That's toxic bad.

The upshot is this. You have a job to complete your code on schedule. Only once you have done that do you have done that, then you've got time to become a subject matter expert in something (networking, crypto, graphics), and have time to share your expertise on these subjects with other.

Conclusion

Beware anything that boils programming down to simple rules like "don't use magic numbers". Code is more subtle than that.

The way to become a better programmer is this: (1) write lots of code, (2) work on big projects (more than 10kloc), (3) spend more time reading open-source. Over time, you'll figure out for yourself what to do, and what not to do.

16 comments:

It's true about fixing messy code that's not yours. Once I was working on a huge code base and I kept fixing small bits of messy code here and there and then I found my self "fixing" everything. Then just stopped and thought that I'm not being paid enough to do this sh*t.

That idea you put out is quite a good one, just code unit test and let it fail and have the owner of the code fix it. Then again if anyone will ever code those tests is another story.

I like your comment on cleaning up messy code. It is better indeed to write some good unit tests before starting the cleanup. These tests will often reveal that the messy code has some "quirks" that the callers are counting on, or may even reveal some bugs that were taken for granted already.Writing the tests will also force you to refactor the code a bit already, and you'll gain some understanding of the code. So all good things!

How not to be a better programmer. Give variables/methods clear names? mmmmm interesting. Don't repeat yourself (reuse code)? So you should re use code to be better? You obviously don't understand how to use a negation operator. But hey don't ask me for help because that would be bad right?

I've worked with programmers who intentionally used obscure variables names. Their rational is "job security". Said genius's company is barely still in business, having gone from a multi-billion $$ company to a laughingstock. Said wonder was promoted to "Architect".

I think there may be a misunderstanding of the intent of the article. The way I read it is that a lot of the advice we get (DRY for example) is good advice typically, but there are places and times when it is inappropriate and even dangerous. If we use this advice dogmatically, without thinking about the problem at hand, we will inevitably end up in some trouble.

Even his parting advice, learn coding by reading and writing code, is subject to the same problem. Code tells you what it is doing, but rarely why. Relying only on reading code to learn code misses an entire problem of translating requirements into code.

I disagree with most of what you say... Let me explain point by point.

- I constantly reuse my code, there's not a single project where I have not reused bits I wrote somewhere else. Plus, if you extrapolate a bit nearly every single programmer I know copy-paste some code found on stack overflow or codeproject! Your “don’t reuse code” advice is nonsense!

- Clear naming... Well if you ever had to maintain a piece of software written by a young arrogant and ignorant version of yourself 15 years ago and that person used to not care about naming and commenting then you would have never written such things. I guess you don’t maintain code, do you?

- Magic number... well if you have two or three fair enough, if you need to talk to some low level api that uses 50 structs and 2500 constants using magic numbers is pretty stupid.

- Asking for help... Ok! That’s mad! you are totally wrong! you see it from your narrow-minded self-centred point of view. What is important in a business is the overall productivity of a team not the hyper-performance of a diva genius!

- If you see some messy code fix it. Maybe the only point that I find mildly justified... obviously you would only do it if you had a decent set of unit tests. But thinking about it nothing stops you writing unit tests while fixing.

- Share knowledge and help others. Of course Yes! that is what any professional seasoned developer should do every day! Lazy or bad programmers don't tend to be employed for very long anyway... so if you work in a decent company, you tend to have decent colleagues and it is helping the business to make sure that knowledge is shared. Not sharing is ridiculous. If you were my employee I would sack you pretty quickly.

Your conclusion is pretty dumb as well... at 10kloc project is NOT a big project... That's what my team guys write in a week... anyway Kloc is one of the most idiotic metrics you can use. It seems that you do not understand development, coding is not only about being a top coder, a good software engineer understands real life problems and how to translate them in abstract machine instructions. A good software engineer will help architecturing a solution for coders who can’t conceptualise problems easily. They spend most of their time sharing their knowledge, fixing the messy code of jo-blogg-graduate, refactoring the unreadable code of some genius, and making sure that this code can be reused in the next project he will lead.

I have been a paid developer of some eighteen years experience, unpaid for about twenty, much of the first half on massive legacy systems - think of Siemens South Africa's national spread of several SAP instances. Then in the later years doing more work on writing new code, but including horrors such as VB6 and VBA from Access conversions to .NET.

As a purist, and developer of new projects, this post names me nauseous, but as the proverbial Pragmattic programmer, currently working on a Web Forms code base grown over about ten years, being the Marval IT support desk operation, some of your points warmed my heart.

1. Don't reuse code: This often involves engineering that code to cater for all the different use cases of calls into it, and it involves code changes that slow down the check-in review process, and our check-ins - on task completion - must be reviewed and ready for test build within minutes. Personally I would never copy code, but I have learned also that once it exists, and works, do not refactor it. Only do that when you have to fix the same defect in all instances of that code.

2. Variable names etc: I use one letter jobs for loops, but longer ones for counters, Count. I really don't care what you call your local variables inside properly sized methods, but class level variables must be explanatory even if not pretty.

3. Magic numbers: I hate them but when one line of code checks "if sc > 5", I will repeat that if I must check sc as well. It is there and you can see the value of sc's max easily.

4. Messy code: DO NOT fix it if it works. It wastes time and again hinders the code review process, and that new code, if done, had bloody better be perfect.

Yea, I kind of see some logic and fact problems here. If you google for the "Top 10 Reasons Why Projects Fail" or something similar, I doubt you will hit upon the given reason. Then, "Don't be afraid to ask for help" ... but don't give it if your in the middle of some task. Ho hum - most I know are working on multiple tasks that overlap, so per this advice we have a kind of paradoxical situation.

Anyway, reading between the lines, this rings a bell. The nutcases who try to put everything into some kind of established design pattern. Design patterns are mostly good for educational assignments and rudimentary structures. In real world programming - there is too much complexity to try to fit things into some pre-established design pattern. We create mostly one-shot design patterns all the time and go on our merry way - far more interested in being keenly observant and reactive to real requirements and problems than to some dogma [yet no doubt those pre-conceived notions serve, I suppose, as a kind of guiding light.]

Rule #4: Whenever possible use only single letter variables as parameters to functions (remember Rule #3) this will create functions that are alphabet soup. Ex. DoIt(a,b,c,d,e,f,g,h,i,j,k,l)

Rule #5: Go as deep as you can. A colleague and I traced a function call down 25 levels and did not hit bottom. By then (remember Rule #2) we had no idea what it was doing.

Rule #6: Never do anything once that you can do twice. Search a list to find an item, if item not found, search the list again to find the end.

Rule #7: Create as many duplicate functions as possible with different names. We found not 1, but 5 different memory management routines that did the same thing as a Microsoft memory function. We were told it was done because Microsoft's memory function did not work. Which we thought was going to be a surprise to the millions of other programmers that were using it. Possibly these guys didn't know how to use it.

Rule #8: Pretend disk is memory. Create functions that search disk files as though you were searching memory. Sure it is slow, but you can tell the client you are versed in virtual memory techniques.

Rule #9: Log all error messages to disk. Including "Disk Full".

Rule #10: At startup, hide the cursor (this was back in 80 column text screen days) by moving it off the screen. Display error messages there.