Software Development

10 programming habits that should be more common

Book learning will take you only so far. In the trenches, you need to develop sensible, effective habits like these.

You learn lots of things when you go to college and get a computer science degree or read a how-to-program book. Unfortunately, a number of good programming habits are either untaught or take a good amount of practice to turn into a way of life. Here are 10 such habits you should cultivate to become a better programmer.

1: Learn your source control process

You know that thing users like to do, where they pick up your application for the first time and get angry because it doesn't do things exactly the way they want -- and then they refuse to learn how the program works? You're left scratching your head wondering why they didn't bother to learn the application, which would have saved them all sorts of problems.

Well, a lot of developers do that too, when it comes to working with source control. Every source control system is a bit different and has a workflow that maximizes the value you get from it. Learn that workflow! It may take some time, research, and practice, but trying to fight it by making your source control repository look like the ugly mess you are used to creating on your local hard drive defeats the purpose. You might as well just have good backups for your local system and call it a day.

2: Go with obvious variable naming

This one comes up all the time: developers just give variables uninformative names and think it doesn't matter. Well, it does matter any time someone else comes in to look at the code. I can't speak for all developers, but I have yet to meet one who is typing for eight hours a day straight. Adding a few keystrokes for better variable names (especially when modern IDEs will autofill it for you) will make a measurable different in productivity. Stop making excuses and use better variable names.

3: Use interfaces, not classes when possible

I violate this one all the time, and I know it. Whenever possible, code to an interface instead of a class. For example, if your class needs to return an enumeration of Integers, there's no need to return a List<int>; return an IEnumerable<int>. This allows recipients to do what they want without needing to recast or transform the results into a class that meets their needs.

4: Assume the worst from your peers

I'd love to think that everyone I worked with was as good, if not better than me at writing software. In some jobs, it was usually true; but in others, it wasn't. I did learn, though, that you have to write your code as if someone with six months of experience (or someone with lots of experience who just is not very good) is going to be working on it next, because it may very well be true. What that means is that you can't afford to write "fancy" or excessively "elegant" code. The next person to look at it could end up making a mess because he or she doesn't understand it. I've discovered that writing code is like cooking: To please everyone, you can't go for the escargot or liver pate; you need to be serving steak and potatoes. Keep your code basic and straightforward, and those coming behind you to maintain it will be able to do their job.

5: Assume the worst from your users

There is a bell curve of tech savvy with our users. We tend to miss the mark on usability and hit the lower end of the bell, but that still leaves about 10% - 20% of users in the dark. Aiming for the lowest common denominator means just that. With a few exceptions, you have to assume that the person using your application will never look at documentation, attend training, or otherwise seek out how to use your application.

6: Document the reason for a change

All too often, there will be a change in an application but no documentation of why the change was made. A few months later, someone will notice that the change conflicts with another requirement and demand to know why it was made, and no one will actually know. The next thing you know, everyone will be losing a lot of time trying to figure out where the change came from and why. By documenting the purpose of the change (and who the requester is), you'll be saving everyone a big headache down the road.

7: Explain the purpose of algorithms

In the same vein, any algorithm should have a clear explanation of why it was chosen. For example, I support a CRM system that has a nonintuitive formula for applying discounts to purchases. No one can quite explain why this specific formula is in place, but it is not working in some scenarios. We are not quite sure how to change it, because the reason for its current form is unknown. All it would have taken was a simple comment that said, "We do it like this because of consideration CYZ," and everything would be fine.

8: Provide contextual help

We all know that users tend to gloss over training materials. At the same time, it's important to provide a variety of contextually available help in your application. While users won't read the manual, they are likely to click the giant question mark next to the input control they don't understand. Put your help access as close to the UI controls as possible with targeted text to make it easy for users to get the help they need. Also, remember to use terms and phrases your users will understand, not internal jargon.

9: Perform cross-platform testing

If you are writing Web applications, it is important to test them on a variety of platforms. You will want to check different browsers on different platforms. Sounds like a hassle, right? It is. Luckily, there are some tools to help with that. Check out the offerings from Sauce Labs to make it a bit easier to cross-platform test your Web applications.

10: Keep the users in mind

A lot of business requirements don't help the users at all, and some may even annoy them. These requirements may be non-functional but are designed to satisfy someone in the marketing or sales departments. A great example is a registration page that asks for a lot more information (and makes it mandatory) than most users feel comfortable giving. While I understand the motivation (making their jobs easier), developers often underestimate how much users dislike it --while overestimating users' desire to get the benefits of registration. Remember that there are few unique propositions in this industry, and you do not have the luxury of turning users away. Fight on behalf of your users to make the best application possible.

What would you add?

What other good practices do you think are underused? Do you have any pet peeves you'd add to this list?

About

Justin James is the Lead Architect for Conigent.

23 comments
WesLong@WesWare.com
WesLong@WesWare.com

In all seriousness, why is this necessary? If an array implements IEnumerable, and its structure uses the least overhead (as opposed to List, for example), why would returning IEnumerable be any better? The only advantage I can see is that if you were to use a LINQ expression to return a result, and the user ended up not using the result (due to incorrectly-nested If/Else structures), then you wouldn't execute the query. The HUGE potential problem with that approach is that using the deferred execution model of LINQ could have unpredictable results if a parameter of the LINQ query was a property of a reference type that changed before the query was executed. All that being said, I keep seeing respected journals insist on returning IEnumerable, rather than T[]. Can someone please go into that? Thank you. BTW - AMEN on "Don't use Global Variables."

johnm7
johnm7

I have had to fix code where the call stack looked like: fna (..., x, y,...) fnb(..., x, y, ...) fnc(..., x, y, ...) fnd(..., x, y, ...) fnd(..., y, x, ...) // grumble #1 about nonstandard parameter ordering. fne(..., x, y, ...) fnf(..., x, y, ...) fng(...) // does not have x or y passed in. fnh(...) // does not have x or y passed in, but USES x and y. These were placed in global variables in fnd. The code at the bottom had to be called with a different call stack, and this bug took quite a while to figure out.

DFO_REXX
DFO_REXX

I'd add two things. 1) Indent source code appropriately to separate sections of code (hard to show in this format). That is, indent all lines of a loop, say, two spaces in from the edge. And yes, I know some compilers (COBOL, e.g.) don't like that... don't do it there. 2) Separate groups of like code ("paragraphs") by a blank line (if the compiler allows it) or an empty comment line (if it doesn't). In short, I tend to write programs as I would write English: in paragraphs, sentences, and with clarity of purpose. At the top of programs, stubs, etc. I indicate their general purpose (e.g. "This program is an edit macro called by the XYZ program to align input data into columns."). Part of this description is a change log, including the initials of the changer, the date, and a description of the change including why the change was made (e.g. "Changed input variable name from 'butter' to 'margarine' as many programs using this stub call the variable 'margarine'"). For each "paragraph" (group of code with a unified purpose) I spell out what the paragraph does. On occasion, if I needed a kludge to get the program to work (or found a really elegant solution), I will either put alternate source code for that process in the comments section or describe the other possibilities. An example might be "Although I used 'parse var' here, you could also use 'left' or 'substr'; I used parse because it's more efficient." Since comments don't compile they don't add to the program's size or harm its performance, so go nuts (except, perhaps, in interpreted languages, then you should group them). In the bad old days, variables (outside of COBOL) were limited to 8 characters; there's no longer any excuse (outside assembler-type languages) not to use descriptive variable names. I don't think you should go overboard (e.g. "input_field_1_from_program_xyz" isn't a good variable name), but be descriptive so the meaning is clear. The one exception I follow is in very short loops, where the counter is only a few lines away from the end of the loop; in those cases I simply use "I" because I've been using "I/J/K" naming conventions for loop counters for 30 years (force of habit). An example of each follows. Say "Enter the three user numbers." Do I = 1 to 3 Say "Enter User Number" I Pull UserNumber.I End I /* Read the set of input commands and execute them as appropriate. If the command is null then leave the loop. */ Do Forever Pull InputCommand If InputCommand = "" Then Leave : : End /* of Do Forever for input commands */

minstrelmike
minstrelmike

I encourage pseudocode for the entire program (procedural languages). GetInput(); Process(); DoOutput(); is the basic but then you write function names that make sense until you are down to just writing the actual functions in code: Process()==> AddFudgeFactor(); CalculateAverages(); SavePlotPoints2Array(); It forces you to think clearly about each step and once you get down the actual programmed step, you're done and it's been fully analyzed as you go along.

sukumarraju
sukumarraju

ASP.NET application GridView allows end users to navigate between paged data, when the user SORTS data on particular page and navigate to another page/few pages, is it expected functionality to see the data as the user sorted initially on that particular page? I think its too intelligent behaviour you can expect from an end user.

hectorj102
hectorj102

Include comments where appropriate, especially if you take a unique approach to a problem. Nothing as frustrating as looking at someone else's code after the fact (sometimes years after) and trying to figure out why the did some obscure tidbit the way they did and what function it serves.

Tony Hopkinson
Tony Hopkinson

Unit test, batch test, manual test script, ifdef debug, automation test, doesn't matter. Modularise, use interfaces, keep your encapsulation. Any code you can't unit test is legacy code the second it compiles. If there's code police tool for your environment (e.g. FxCop for .Net), use it. And from the start, not turn it on the last day and discover 1400 problems you don't have time to fix. Course you could be working somewhere where the code and the environment it runs in never changes, so once you've got it right, job done....... :(

stewart.noakes
stewart.noakes

The stuff from Sauce labs is really good and the people there are very helpful. Over the last couple of years they've really made some great things happen there. Another top tip, is using an on demand, virtual QA team from companies such as uTest. www.uTest.com. Through this approach you can select testers on each browser/OS/hardware combination that you want to see tested. You can also get into geographical variation and social demographics for your test team too. What this helps to do is to create a giant test lab environment at a very low cost. From a time and cost point of view, it's often quicker and cheaper to take this approach - even early in the lifecycle - than to build too many tools up front.

Mark Miller
Mark Miller

In all the projects I worked on, I never saw this done in the sense that I think you mean. I didn't do it particularly often. I'd document my changes in the code, but it was functional: "[date] - Changed A to do B." I didn't say why. I typically documented what a customer requested when I'd check in code to source control. That was my way of doing it. At one place I worked, the solution we used was Lotus Notes (this was in the '90s). We asked our customers to e-mail requested changes, rather than discuss them in detail over the phone, because with a phone call there was no record of who said what, when. We filed these e-mails under project categories in Notes so that we and other employees could retrieve them and understand why changes were made. I never knew if this worked, because I was typically the only employee who would look up what I had filed. I think this was the same for the other employees. We were the most familiar with our own material. As far as other practices, this is not from me, but I'm sure someone would suggest, "Write unit tests for your code," as far as TDD. Isn't this pretty much expected now?

mattohare
mattohare

I'm not saying make classes for the sake of it. However, it is a nice organisation tool to keep the right code, fully tested, in the right place.

Tony Hopkinson
Tony Hopkinson

though with List, I managed recently because I'd used IEnumerable. I had private List _MyThings; Public IEnumberable MyThings{get{return_MyThings;}} Lots of other code depends on MyThings, some of it needed to do a look up by an ID property (Guid) of MyThing. I found I was building the dictionary from MyThings, in several places, which was irritating..... So I built in once in my class, Changed Mythings to Public IEnumberable MyThings{get{return_MyThings.Values;}} and added a FindMyThingById(Guid argId) function. Took about five minutes, broke nothing, if I'd have exposed List, I would have been in a world of hurt. Imagine the pain if this was Dll you were already using it several places... In fact you probably couldn't make the change, and then for the life time of the code you'd have to deal with the consequences of the flaw in the design. (Sound familiar :( ) There will be change, design by interface put's you in control, by implementation is anarchy.

Tony Hopkinson
Tony Hopkinson

Some one who doesn't know waht I mean by a Bloom isn't a dummy, just means they haven't done much work in the steel industry. COme up with some design standards, document and police them. Jargon busters, top level diagrams. It's not about asuming your audience are dummies, it's about not being a dummy and assuming your audience knows what you mean.

Tony Hopkinson
Tony Hopkinson

Got to look at all these recomendations and get to the why of them, then look at how to get the same thing fromthe environment you are working in. Doc comments in Visual studio, no limit on variable name length, so use descriptive ones. Change reasons should/could be be delegated to check in's in source control etc. Prettyfying, neither here nor there, if you have a good IDE witha good compae tool, though choosing a standard can help. The key was and is and always will be the code should be comprehensisble. The problem I have with going mad on comments, even in environments where other options are limited, is you double your maintenance burden, or you end up with out of date comments, which is worse than none.

Tony Hopkinson
Tony Hopkinson

Program to interfaces. Stub the implementation and then fill it in. OO is just a framework for where you put your procedures. Good old top down design...

Jeep16
Jeep16

While coding some things seem obvious, but if you do not document the code, algorithm or change and someone calls on you five years later - well you will wish you wrote a little note in the code.

Justin James
Justin James

I talked to them a number of years ago when they were first starting up, I really liked the value proposition. J.Ja

SnoopDoug
SnoopDoug

One technique that I've seen/used that works well with updates is to use the bug system as the repository of the history/reasoning behind a change request. Then, in a code comment, add something like "Changed farkle to doohickey per Change Request #12345".

WesLong@WesWare.com
WesLong@WesWare.com

If I read it right, you added a function to your class with a Guid parameter. You implemented proper object-oriented design by adding the function to your class rather than re-writing it in the calling objects. I don't see where the MyThings was the critical factor. You could have easily had a LINQ expression: public MyThing FindMyThingById(Guid argId) { return this._liMyThings.SingleOrDefault( thing => thing.Guid==argId); } I still don't see what is gained by exposing IEnumerable as opposed to an array. What am I not understanding?

stewart.noakes
stewart.noakes

Justin, that's cool - did you use the uTest approach in the end? what results did you get? I've been using the approach for a couple of years now and it has moved / changed and refined quite a bit. My first ever release went out globally to over 23K people. That was a bit noisy as a test cycle!!! I find It goes a lot smoother now :)

Tony Hopkinson
Tony Hopkinson

I was using Dictionary because I was FindingById a lot, not to mention Public MyThing FindMyThingById(guid argId) { return _mythings[argId]; } is a heck of a lot simpler.... Mythings was the critical factor because everything was using it. If I'd have exposed it as a concrete type such as a List, and then wanted to change it for whatever reason... Another way to look at it, is if I'd wanted to acess the entire functionality of List for any consuming object to use, then it's basically a global variable, how would you know what had changed it, can you imagine threading it? Limiting the exposure of the implementaion with IEnumerable gives you more scope for encompassing change.

Justin James
Justin James

The projects that I've worked on where it would be possible didn't have the budget, unfortunately. :( J.Ja

Editor's Picks