Tuesday, November 17, 2009

Gratuitous Context

I have been working on code that oscillates between cryptic abbreviations and absurdly long names in functions. Sadly, I cannot reproduce it here for you, or I would. The problems with cryptic abbreviations I have hammered before, so my opinion is well-known (stp bng stngy nd us sm stnkng' vwls, k?). I don't talk enough about gratuitous context.

A name needs to be meaningful in context. The context of a class includes its namespace, the context of a method or class variable includes its class, the context of a method argument includes its method name, and the context of a local variable includes the method that encloses it. This context is cumulative as you navigate namespaces to classes to methods to method inners.

One thing that really is annoying is to see Grouping.Group.MakeGroup(string GroupName) with local variables that all include the prefix "Group". This is gratuitous context. This kind of naming actually hurts the readability of a program.

In a previous article I mentioned three concerns that will affect naming. In this case, we see that a local variable will be close to its point of declaration, and that there are very few variables in play. In this context, all the names are used fairly frequently and never outside of the method. All of these forces drive us to small, easily-recognized names.

Sticking the word "group" on the front of every name not only is unnecessary, it causes the eye to search farther into each word to tell the variables apart. While this is hardly a strenuous effort, it is enough of an effort to cause the reader to exercise extra care in reading instead of taking in the code in a glance.

The contextual prefix also means that the auto-completion of your editor/ide/whatever will pop up a list of names and yours is unlikely to be the first candidate. With a list of similar names, you must exercise additional care to be sure you do not accidentally pick the wrong variable from the list. This is becoming a fairly common type of programming error these days. Usually a pair programming partner will be able to tell that you have picked the wrong variable even if you do not notice it yourself, but this is again wasted effort.

In naming, a long name is not automatically better than a short name. Clarity is the goal. Sometimes longer names are harder to differentiate, which makes them less clear than shorter names, and a long bad name is worse than a short bad name.

For more on this topic, please see Chapter 2 of Clean Code, or the less polished version at my older blog and/or the earlier article the Long and Short of Naming.

Friday, October 23, 2009

Factors Driving Naming

As I lay awake in bed after about 4 hours of sleep, it suddenly dawned on me that we've not given enough thought to why we need good names and when we need good names. My blanket statement of position is strong enough: code that is hard/slow to understand is hard/slow to change reliably. When we use better (not just longer) names, we find our code can be easier for others to understand.

We can vet our naming system by pair programming. If we and our partners cannot devise a way to make the code more readable, then it is probably readable enough for now. By using TDD we can create executable specifications that further explain why the code might have an odd turn or surprising detail. Further, when a complex problem has a simple and generic solution, the tests show that we've covered all the bases (or that we haven't). Between the clear and obvious tests and the clear and obvious code we can largely eliminate the need for comments and frequent vertical line breaks and flowerboxes. Code + Test can be clear enough.

There seem to be three factors that drive the need for clarity in naming.
  • Distance from declaration. We approximate this with "scope". An iteration variable in a list comprehension is created and used all in one expression, and need not carry the context that would be needed for a class name from a distant package/namespace or (God forbid!) a global boolean variable. We need to contextualize things that will be used far from their point of declaration. Things used near declaration/initialization need not carry as much context via naming.

  • Number of names that are "in play" in a given routine will drive the need to create clear distinctions between the objects they name. In a method like Math.Min(int x, int y) there is not much we need to know about x and y. They are just two ints, and we want to know which is lesser. But if we have a function that is manipulating 11 variables in 4 lines then we start to have a problem with variable density. People with particularly strong math skills don't feel the need as much because they learned earlier to deal with extremely economical notation, but that knack is more a rite of passage than a readability ideal to be propagated. In a name-crowded space, it is simply harder to differentiate one thing from another and people may type 'r' meaning 'k'. As my colleague Vadim points out, it is more important here to have an easy visual distinction between names (something overly long names may hurt more than help).

  • Infrequency of use drives to longer names. The less a name is used in any given context, the more it must describe its own purpose. Conversely, a name that is used repeatedly in many contexts becomes familiar to developers, and having a long name merely makes it tedious to read and easier to confuse with similar long names.
I have long approximated these issues with a rule (from James Grenning, I think) that the length of a name should be in direct proportion to its scope. My colleague Vadim has challenged the simple rule but I was not really ready to think past it until a recent amicable disagreement with Bob Martin on naming, followed by a serendipitous period of activity-free nocturnal wakefulness.

Now I think I'm seeing naming as trade-offs between these forces. As always, I am interested in counterpoint and comment.

Tuesday, October 20, 2009

Code That Makes You Feel Good

There are clever code constructs in the world. Some of them are quite useful in the right circumstances. Some, like Duff's device or the Curiously Recurring Template Pattern might prompt a double-take or some serious study to comprehend. Some are are even simpler to understand, like the Visitor design pattern, yet puzzling them out and making an implementation that actually works releases some endorphins. You feel accomplished for working through a difficult code device. Actually having N-levels of nested lambdas feels like the work of a wizard when it actually works and gives a result you are looking for. Clever and inobvious operator overloading can make some weird code work, and make you feel pretty good. A bizarre bit of code can feel like a real accomplishment. I was listening to a Stack Overflow broadcast a while back and heard one of the participants praising an obscure code trick as being "elegant", while the other corrected him that it was a kind of a hack.

Dirty tricks may stretch your brain in new ways and teach you things you would not otherwise have learned about your programming language or environment. Learning strange devices and code patterns can open you mind to new ways of thinking about software. These puzzles rightfully make you feel good about your growing skills. That "eureka!" endorphin rush is prized by mental athletes and rightfully so.

I've built things that made me feel proud for the wrong reasons.

Being "clever" is not strictly a bad thing. Being someone who loves code means loving a good puzzle. The problem is that the rush of figuring out or implementing puzzle-based code feels like an accomplishment and a triumph, when in reality it may be opulent self-indulgence.

Certainly there is plenty of code in the world that suffers more from a lack of brilliance, being unintentionally (even unknowingly) stuffed to the brim with implementation complexity. Programming stupidly tends to produce more code than necessary, and more complicated code as one kludge covers for the deficiencies of another kludge and the patches pile up on each other. Not thinking about the code, not learning to write code that is efficient in effect and parsimonious in quantity, not considering side-effects and weaknesses, not programming *well* is a horrible thing. We are told that 80% of all people believe that their work is "above average." That means nearly 40% of us are wrong about our work, and also not in a place to even recognize it. We all need to learn more about this crazy trade.

Yet the vast bulk of code is more workaday stuff, and would benefit more from from clarity and simplicity than from ego-stroking brilliance. We don't need very many Duff's Device implementations or clever metaclass madness or obscene table-based subsystems that totally obscure the flow of a program with levels of indirection through void pointers. There are things that we *can* do but should not do, and yet these things feel like implementation triumph. We put them together, and by golly they actually work! They may even be performant, yet even our better coworkers are loath to maintain the code.

The problem is not that we need to dumb-down the code. The problem is that we need to simple-up the code. If we can turn our mental resources to find ways to eliminate opulence and duplication and create more amazingly simple code, we can eventually get a reputation for writing code that attracts our coworkers and fluidly accepts change. That beats a reputation for writing obscure, difficult, and fancy code any day.

My dream is that my coworkers will see code I've worked on and breathe a sigh of relief. "Oh," they'll say, "it's Ottinger code. No problem." This echoes Ward Cunningham's comment to Uncle Bob (quoted in Clean Code), that truly clean code does pretty much what you expect it to do.

I think that Abraham Lincoln's stated goal is still a worthy pursuit for the software professional.

Wednesday, October 14, 2009

Sorry Way to do Business

I have been thinking a lot about Esther Derby's work on performance assessments and wanted to jot this down.

Competition is a good thing when it pushes people forward, but anyone who has ever played Sorry can tell you that the game is in is the relative progress you make with respect to others on the board. You can succeed by getting better movement cards than other people (being lucky) or by denying success to the other players (opportunistic sabotage). If you can block forward progress and send people back to the start then you have a little more control of the game board. The trick becomes making sure you can move between safe spaces without sacrificing too many pieces while wiping out the opponents from behind.

Relative success in a company is, I think, intended to be reached by propelling yourself forward. What I have seen in my 30+ working years is that you can also move ahead more surely by hopping between "safe spaces" and denying success to your coworkers. I've seen people who were excellent at that game. They take a larger slice of a smaller pie, which affords them the prestige and perks of a winner.

But it's a smaller pie they're making. Denying success to your coworkers should be an absolute obscenity inside a business. To consider the company achieving less so that you can beat that other salesman, manager, etc. to the perks is without merit. People who play this game are Sorry coworkers.

Now, the problem with it is that a Sorry boss will look for the Sorry workers, and will admire those "smart enough" to play this Sorry game well. He will admire the sharper gamesman and will not mind promoting him... to a point. Eventually the Sorry employee will start denying success to his Sorry boss, once he has some contacts higher in the Sorry company.

Don't work for a Sorry company. Loving your job means never having to say you're sorry for working there.

Monday, October 5, 2009

The Fast Food Chicken Dilemma

One question for you:
What if someone told you that you were going to have to serve chicken less well-done in order to make it through the lunch rush?


Let's say you are working in a fast-food restaurant that is attempting to keep up with Chick-Fil-A and KFC and Brown's Fried Chicken. Speed and quality are important, but you are a fast food drive-up, so the audience is a little forgiving.

Now you find out that the other local drive-up chicken joints are selling more chicken than you are on Sundays. The management suspect the product is not being prepared fast enough. They remind you that quality is important, but sometimes you just have to ship the product if you want to compete, no?

You are only the fry cook. Your manager makes three times as much as you do, drives a much nicer class of car, dresses better, probably has more years of management experience than you have fry cook experience. He probably knows what he's talking about, and he can certainly fire you even if he doesn't.

You know what it means to under-cook chicken. Your steady customers will never forget one bout with food poisoning, and will quit showing up if they feel your food isn't safe. Food safety has legal ramifications. Yet, you have got to keep up in the fried chicken wars if you want to survive in this industry.

Will you cook product ahead of time, and let it sit under the lights? Will you operate a larger number of fryers? Experiment with some new techniques (pressure-cook, then fry)? Would you use smaller portions of chicken? Would you skip the breading stage of the cooking? Increase or decrease the batch size and chicken piece mix? Mess with the heat? Trim a few minutes off the cooking timer and hope for the best?

Knowing that there is a real business problem and that you are only a fry cook, what will you do?

Finally, consider that you're talking about software, not chicken.

Saturday, October 3, 2009

Define Leadership for Me

I was invited out to breakfast with a good friend who works in HR. Between sips of coffee and bites of Tabasco-soaked potatoes, Tom mentioned that he has been to leadership seminars that spent a lot of time on fuzzy ideas like "finding the hidden leader inside yourself" but even give a definition of "leadership". That took us in a new direction. I realized that in making up my list of the qualities of a good leader, I didn't even consider the definition of leadership.

This week in Twitter, @marick brought up questions about the meaning and nature of leadership also. Now I can't escape the meme rattling around in the back of my head. It's a bit like when you have three bars of a song in your head and it won't stop repeating until you hear the song in its entirety. What is leadership? It is clearly a kind of skillfulness, but what skill is it? What set of skills define leadership?

My short list of "attributes of a good leader" was easier to come up with:
  • Strong
  • Benevolent
  • Effective
  • Politically savvy


Those are attributes of a good leader, but are not definitions of leadership. This is my quandry today. I would love to hear your best quotes, thoughts, links, definitions, and questions.

Here are some thought-starters:
  • Is it about being personally driven to get work done?
  • Is it wisdom?
  • Is it brashness/courage?
  • Is it the ability to drive people?
  • Is it the ability to draw people?
  • Is it the ability to make people work more?
  • Is it about accomplishment of feelings?
  • Is it about being the superior predator?
  • Intimidation?
  • Gentleness?

Tuesday, September 29, 2009

Tragedy of the Doubtful Solution

The Background

I (re)tell a story frequently about a place I worked in the 90s. There was a piece of code with an absurd cyclomatic complexity score, running literally hundreds of lines in length, and being called from myriad places in the code base.

The code was written to check to see if two ranges were overlapping. Being written in a poor 4GL, it took four parameters representing the starting and stopping dates of two time ranges. Such a simple task for the code to be so horrible and lengthy. In C it would look rather like this:

bool ranges_are_overlapping(int a, int b, int c, int d){
}

It quickly became clear to the most casual reader that the ranges were a..b and c..d. Well, sort of. The code was defensive and was written with the understanding that the range-defining arguments could be somewhat unordered:

if ( (a<b) && (c<d) ) {
// blah blah
}
else if ( (a==b) && (c<d) ){
// blah blah
}
else if ( (a>b) && (c<d) ){
// blah blah
}
else if ( (a<b) && (c==d) ) {
// blah blah
}
else if ( (a==b) && (c==d) ){
// blah blah
}
else if ( (a>b) && (c==d) ){
// blah blah
}
else if ( (a<b) && (c>d) ) {
// blah blah
}
else if ( (a==b) && (c>d) ){
// blah blah
}
else if ( (a>b) && (c>d) ){
// blah blah
}

Now, of course, even once we square away the range markers, there are a whole host of possibilities. A could be less than, equal to, or greater than C, and B could likewise be greater than, less than, or equal to D.

if ( (a<c) && (b<d)) {
// Lets call this "inner block"
if ( b > c ) {
}
else if ( b == c ) {
}
else if ( b < c) {
}
// And on we go ....
}
else if ( (a<c) && (b==d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a<c) && (b>d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a==c) && (b<d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a==c) && (b==d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a==c) && (b>d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a>c) && (b<d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a>c) && (b==d)) {
// cut/paste/edit "inner block" from above
}
else if ( (a>c) && (b>d)) {
// cut/paste/edit "inner block" from above
}

Of course, each permutation of the range start and end relationship would require a full repetition of the comparisons of the range starts and ends complete with cut-n-paste-n-edit inner blocks. We do not attempt to recreate the entire mess here.

I bet the programmer responsible for this routine wrote more lines of code that day than anyone else on the team. What he lacked in efficiency, he made up in diligence.

The code was correct in its results, but it was huge and slow and tedious to desk-check. It had no tests, automated or otherwise.

I replaced it with the rather ordinary and obvious solution:


int left_start = min(a,b);
int left_end = max(a,b);

int right_start = min(c,d);
int right_end = max(c,d);

bool result = true;
if( ( left_end < right_start) || (right_end < left_start) ) {
result = false;
}
return result;

Straighten out the begin/end so you don't need a Cartesian explosion of if statements, and then look at the ranges. When either range ends before the other starts, there is no overlap. Otherwise, you have overlap. It is hardly a miracle of profound logic.

Young Tim actually had to work that out on paper. It worked great, was much smaller and simpler, and ran much faster than the ugly monstrosity that was there before.

The reason I'm writing this is not to express that I'm revolted by bad code or to brag that I replaced it with good code. It is not to ridicule diligent-yet-misguided junior programmers. The point of this blog is what happened next.

What Happened Next

I was called into the boss' office. Someone noticed my code improvement! He showed me the old code and the new, both printed out in neat stacks on his desk. I grinned and said "Yes, it's much faster now."

He didn't smile back. He frowned.

He told me that it was clear from the original code that the author had thought through all of the possible scenarios and had accounted for them. Mine, on the other hand, showed no such diligence. I clearly had not put any real effort into my work. My answer was too small. Even though they couldn't make it fail in testing (yet), he knew that I must have left something out. As a result of disbelief in my abilities, he was rolling back my change.

I was upset that a small working algorithm was about to be tossed out and a messy steaming pile of code put back in. I was more upset with the accusation that I did not think through the cases, and that (despite all evidence to the contrary!) I had written an insufficient piece of code.

I tried to defend my solution, even pulling out my scrap paper with all the overlap scenarios on it, but it was too late. The decision was made. I and my solution were obviously inferior. Chastised, I returned to work at my desk.

I began to lose interest in the company where I had previously intended to spend my entire career. In time I left and have had a good time of it.

The Payoff

Why blog about it in 2009? Because my TDD associates have blogged and tweeted about how, in TDD, the code becomes more generic as the tests become more specific.

If the Tim of 1994 had known about TDD, he would have built up a large and specific base of tests covering all the cases represented by the old code. His tests would have demonstrated that he'd thought through all the permutations, and the simpler solution might have been fielded. Tim would have been saved a moment of humiliation, and that poor application would have gotten a boost in performance.

TDD would have made the code better, and it would have improved the experience I had there. It would have given me visible evidence of my thinking. With a body of unit tests, there is proof that we've thought things through. An oblique, small solution cannot provide that on its own.

Young programmers: consider this advice. The more elegant solutions you devise will need a body of proof if you are to survive clue-challenged technical managers. If you don't do TDD for the sake of the code, do it for yourself.