raganwald
Wednesday, October 04, 2006
  Why are local variables bad?
Steve Yegge wrote a terrific post about refactoring, "Transformation," in March of 2006. My two cent review is that the most valuable part is his discussion of how the Blub community has become so enamored of push-button refactoring at the line of code level that they have lost sight of what refactoring is supposed to accomplish at the design level.

Many Blub programmers won't even try a language that doesn't have so-called refactoring support in their IDE. Given the choice between dynamic metaprogramming and refactoring, they choose refactoring without even knowing exactly what dynamic metaprogramming is.

And they are blissfully unaware that the 'refactorings' in their editors are just a few of the refactorings suggested in Martin Fowler's seminal book on the topic. Furthermore, there is more than one way to accomplish the goal of well-designed software, and some languages, (especially folding languages), provide much more powerful tools for clean software design than micro-refactoring.

All that leads me to a question posed on programming.reddit.com:

Why are local variables considered bad?

I'll simplify things. Mutable local variables are bad. And here's why:

Mutable local variables mean that within a method execution you have changes of state. That makes it very difficult to change anything without breaking things.
Those are my principles, and if you don't like them... well, I have others.
Groucho Marx

For Eclipse fans, consider what happens if you take a random chunk in the middle of a method with mutable local variables and try to use the automatic refactoring to make a new private method.

If you have lots of immutable local variables, they have to be passed to your new method as parameters. That's not so bad. But if you have mutable local variables, you have to do some back flips and sit ups to ensure that any changes to those variables are propagated to the rest of your method.

This is a tip-off that the mutable local variables have introduced a lot of complexity to the structure of the method. Unless they make it dramatically shorter than a variable-free version of the same method, they should be removed.

(Update): What kind of complexity are we talking about? We are talking about lots and lots of dependencies between the lines of code in the method. Those dependencies are what make the code brittle: if you try to move something or change it, you break some other code that is depending on its side effects. The extra dependencies have introduced coupling, which is a code smell.

Refactoring: Improving the Design of Existing Code

The book that touched off a revolution in software design. Incredibly, most programmers think that refactoring is just about the dozen or so menu items in their IDE. This book discusses over a hundred refactorings, organized into themes. It goes beyond mere transformations to explain what makes good code and how to recover from bad decisions.
The extra coupling manifests itself in many ways, not just in the difficulty of moving code around. You also have problems changing lines of code. This manifests itself in regressions: every time you fix a problem or add some functionality, do you find yourself having to fix a bunch of bugs created by your change?

Let's be clear: we don't say mutable local variables are bad because it's hard to refactor methods with mutable local variables. We say mutable local variables are bad because they intrduce complex dependencies and coupling between the lines of code in a method.

Immutable local variables, such as the local variables you use to cache certain results, are not bad. (I should say, "in my opinion," but this is my blog. Note the words "my" and "blog," establishing that we're talking about me and opinions, not everyone and especially not widely accepted irrefutable facts).

As a matter of fact, I can prove that they are not bad. Okay, "proof" is an ambitious word given that there is no widely accepted proof that 1+1=2. I'll demonstrate. Here's some code with an immutable local variable:

my_var = SomeModelClass.find :all
if my_var.empty?
:empty
else
my_var
end

Let's transform that code into:

(lambda do |my_var|
if my_var.empty?
:empty
else
my_var
end).call(SomeModelClass.find :all)

We've transformed the immutable local variable into a parameter for a lambda (or closure, or anonymous proc object, whichever name you prefer). Parameters are not harmful. And since this transformation can be automatically performed for any immutable local variable, I claim that immutable local variables are also not harmful.

Cool discovery, hunh? I should be famous. Oh wait, someone on the phone from the 1970s. The Lisp people want their let macro back. And they're right. People having been using let for more than thirty years to write concise code without the side effects and state changes associated with mutable local variables. Consider instead:

(let ((my_var (find SomeModelClass 'all)))
(if
(empty? my_var)
'empty
my_var))

That's simpler structurally than using one of Ruby's procs. So what do we conclude?

First, refactoring is cool, but it's a tool, not an objective. The important thing is the objective, well-designed software. Second, read the book. Really read it, cover to cover.

Update: This is exactly why languages with more powerful abstractions are more important than adding push-button variable renaming to less powerful languages. Where's the button that refactors a method with lots of mutable variables into one with no mutable variables? No button? Okay, until you invent one, don't you want a language that makes it easy to write methods and functions without mutable local variables? (Attention Java programmers: Yes you do, and I have proof. Do you like the new for syntactic sugar in Java 1.5? Its biggest benefit is that it makes loops that used to have a mutable variable look just like an iterator block with an immutable parameter in Ruby or Smalltalk. Think about it.)

Labels:

 

Comments on “Why are local variables bad?:
Perhaps they're all reading "Renaming: Keeping the Design of Existing Code"?

Immutable local variables are not bad, actually I think the alternative (no free variables) is an excercise in pain.

But immutable local variables are, just like globals, method overloading and class heirarchies, something you're better off doing in limited quantities.

If you go on a free variable diet, you will leave a better and longer life.
 
As a purely knee-jerk reaction... I think this idea is good in principle, but it is fundamentally incompatible with the languages most programmers use.

Mutable local state is the hallmark of imperative programming; elimination of mutable state is, more or less by defintion, functional programming. There's a reason why Lisp programmers have "had it right" for decades and Java programmers still use state - and it has nothing to do with the fact that Lisp users are more enlightened ;-)


In principle, functional languages tend to produce better code anyways (remember - knee-jerk and subjective here) because the elimination of state allows precisely the kind of refactorings you allude to. However, shifting entirely towards a functional style in a procedural language is misery.

I know you didn't say it, but I think we all read between the lines: the big obsessors over automatic refactoring are Java folks. And let's face it, Java's a bass-ackwards language; it doesn't even have decent closures. (Even Microsoft got that right as of 2.0 revisions of the .Net languages, folks...)

I feel bad for anyone who has to code in Java in the first place, but trying to write in a functional style in Java would just be murderous. As such, while I fully agree with your ideas in principle, I just don't think they're going to the right audience.

That said, minimizing state is definitely practical and not too hard, even though eliminating it is almost impossible to do conveniently in any imperative language. And I *absolutely* agree that most refactoring-weenies need to actually read Fowler properly.
 
elimination of mutable state is, more or less by defintion, functional programming

I think you're arguing absolutes.

Object oriented programming is, at its heart, all about state. if you write objects without state, you are basically using objects as namespaces.

I'm not suggesting that.

Consider, instead, the principle of confining your state changes to objects, not assignment to local variables.

I'm arguing in favour of a certain level of granularity for managing state, not for eliminating state.

A pedant can argue that state change is state change, so who cares about the difference between object state change and local variable state change?

While this is true in theory, in practice people have a whole library of design tastes and patterns and guidelines for "good" object design. It's easy to see when an object has a logical purpose and changes state in a straightforward way.

Local variables... not so much so. It seems that every mutable local variable introduces a smidgen of MS Basic-like thinking in even the most experienced programmer.

Summary: if you want to program with changes of state, do so at a coarser level of granularity, in objects.

Trying to write in a functional style in Java would just be murderous. As such, while I fully agree with your ideas in principle, I just don't think they're going to the right audience.

There are a wide range of Java programmers in the world. Axiomatically, nobody who reads my blog drinks the Java kool-aid.
 
OK, I see what you're driving at. I agree that encapsulation of state in an object is a solid principle, at least as far as the OO realm goes. I'd also attach the proviso that "encapsulation" mandates genuinely hiding state concerns from clients of the class when possible; simply going from foo=bar; to object.SetFoo(bar); is not sufficient.

I guess this falls under what I referred to as "minimizing state" before; state isn't actually going away so much as be confined as much as possible. The point is to prevent state from affecting things outside the smallest possible scope (which may not necessarily correspond to a specific lexical scope in the code itself, mind). In OO-land, this is often done best using encapsulation, provided the design is done right.


I guess I haven't thought in a way that promoted local mutable state in so long that I've more or less forgotten what it looks like :-) I actually trawled a good chunk of my code looking for examples I could use for discussion, and virtually everywhere my only use of local variables essentially amounts to caching. While such caching is of course isomorphic to elimination of local variables, that transformation is ugly and gross in imperative languages. It tends to be far more pleasant in more abstract languages that "understand" functional concepts.
 
Forth
 
While I normally use locals as if they were immutable, there are times when I really do need variables that can be changed. I am not against new syntax that says certain variables are/are not immutable.

The extract method/mutable locals seem to be a problem specific to Java. Languages that allow you to pass values by reference handle it cleanly.

Refactor by DevExpress does have a tool to take a local and split it into several "immutable" varaibles. (I used quotes becuase they are really immutable, they just look like they are.)
 
The extract method/mutable locals seem to be a problem specific to Java. Languages that allow you to pass values by reference handle it cleanly.

I think you'll agree that passing parameters by reference trades one set of dependency/coupling challenges for another.

Clearly you can move code around a little easier, but that doesn't change the underlying coupling concern that is eliminated when you redesign your code to minimize the use of mutables.

These things are not absolutes, of course.
 
Great post.

I code in Java almost exclusively (oh noes!) and I can tell you that as my code evolves I always go for removal of method-local mutable variables because it allows for coarser *reusability* (pluggable objects) -- the whole point of OO to begin with. It introduces (a possible) tradeoff with concurrency, but it can also be easier to deal with concurrency at a coarser level.

The problem with this style of programming in Java is that it leads to lots of wrapper objects which make large systems composed this way seem very impenetrable at first. Until you drink the kool aid of course.
 
"That's simpler than using one of Ruby's procs".

I beg to differ. Let's leave aside the fact that the parentheses are nested 4 deep there. I can forgive that as a characteristic of Lisp, a language with much to outweigh that.

The main problem I have with the Lisp definition is that value used for the parameter is textually within the definition. Clearly the Ruby version, where the lambda is followed by its call, the extraction of the lamboda to a method is textually simpler. One could, as a first step write

def method(param)
lambda do |x|
...
end.call(param)
end

and later remove the lambda.
Or one could just nest the methods in the first place:

%> cat !$
cat test_nested_def.rb
def do_this
def this_or_empty(x)
if x
return x
else
return :empty
end
end
puts
this_or_empty(nil).inspect
puts this_or_empty("full").inspect
end

do_this
%> ruby !$
ruby test_nested_def.rb
:empty
"full"
%>

My indents are lost, and I can't use PRE tags to display the code.
 
It's all black-n-white, isn't it?
Myself, I'm heading towards an OO-implemented functional design with a clean cut between entities ("dumb data") and activities (functions). Most of both worlds, I'd think.
 
Here's another viewpoint:

I've read up a bit on this local variable issue and the reason (supposedly) that local variables are bad is because:

"Mutable local variables mean that within a method execution you have changes of state. That makes it very difficult to change anything without breaking things."

Now, I'm of the belief that if you think logically enough about your app AND you profoundly UNDERSTAND your app, then local variables are not a big deal to implement, and quite frankly, if a dev is not capable of making changes to an app without breaking it, then the developer is not a very good one anyway...

Every app is different and you cannot go through your development life thinking that mutable local variables are bad or shouldn't be used. They have their place in programming.

What the article suggested to me is that the more mutable local variables are contained within an app, the more closely one pays attention to the CYCLOMATIC COMPLEXITY rating of the app.

I don't agree with the position that something shouldn't be implemented because of fear of not being able to handle the impact of it under change.

If you understand what you are working with, then one can handle change states as well.

Quite frankly, I believe that global variable or form variables are much more tricky to deal with in an app than any local variable because at least your local variables are confined within the procedure in which they are written.

Global and form variables have much more complexity tied to them when they are being accessed from multiple procedures and functions....

--Brice Richard, writing in the Joel on Software discussion forums
 
Hmmm, excellent post - I did in fact run into this exact problem while playing with a section of "Big Companies" code. I was attempting to clean up a bit of code dealing with the creation and download of Quicken and MS Money files. It has a mutable local state variable controlling some of the sections of the code. It was HARD to get the whole thing re-organized to make that variable go away, as its existance made the WHOLE thing very very brittle.
 
I can understand why people like to have automated functionality to pull code blocks into functions, rename variables, and so forth. But even if I had these functions available in my editor (Notepad), I'd hardly use them as they wouldn't be worth the effort in the languages and programming style that I use. I tend to use ML, Python, and C, and to use the functional idioms whenever possible; it seems like most of what I do is calling existing functions or writing and debugging new functions, and only occasionally grouping functions into "classes." Now I've used Java a fair amount and I'd say that refactoring is useful in Java because Java programming requires maintaining lots of complicated class hierarchies and interrelationships between classes (everything is a noun...), so much of the effort in Java goes into moving classes and methods around and changing how interfaces and classes and class hierarchies all couple together in a "web." If one's programming looks like a spiderweb then it's only natural that one would like to have refactoring; however, this doesn't necessarily mean that refactoring is useful (or more useful than search and replace at any rate) for languages in general.
 
Connelly:

I think we're in agreement:

we don't say mutable local variables are bad because it's hard to refactor methods with mutable local variables. We say mutable local variables are bad because they introduce complex dependencies and coupling between the lines of code in a method.

The goal is to eliminate the spiderweb of dependencies. Difficulty of refactoring in Java is a symptom, not a root problem, because the refactoring itself is not a goal, but a means to the end of simple code.
 
okay -- i haven't read all the comments, but i do like what i've read so far.

i've wanted to coin the term 'refunctoring' for automatable code modifications that preserve intent while removing stateliness, and removing unneeded side-effects.

lb (secretGeek.net)
 
I have to agree- but I'd follow up and say, as bad as mutable local variables are, mutable object-scope and mutable global variables are even worse, as they encourage complicated dependencies between widely separated pieces of code. Not to mention being a lurking source of hideous to debug race conditions in multithreaded code.

I'd also like to agree with the poster who said that this was in direct contradiction to how popular languages today work. You're both right- it stinks but it's popular.
 
"nobody who reads my blog drinks the Java kool-aid" - No, but some of us have to hold our nose and choke it down from time to time.
 




<< Home
Reg Braithwaite


Recent Writing
Homoiconic Technical Writing / raganwald.posterous.com

Books
What I‘ve Learned From Failure / Kestrels, Quirky Birds, and Hopeless Egocentricity

Share
rewrite_rails / andand / unfold.rb / string_to_proc.rb / dsl_and_let.rb / comprehension.rb / lazy_lists.rb

Beauty
IS-STRICTLY-EQUIVALENT-TO-A / Spaghetti-Western Coding / Golf is a good program spoiled / Programming conventions as signals / Not all functions should be object methods

The Not So Big Software Design / Writing programs for people to read / Why Why Functional Programming Matters Matters / But Y would I want to do a thing like this?

Work
The single most important thing you must do to improve your programming career / The Naïve Approach to Hiring People / No Disrespect / Take control of your interview / Three tips for getting a job through a recruiter / My favourite interview question

Management
Exception Handling in Software Development / What if powerful languages and idioms only work for small teams? / Bricks / Which theory fits the evidence? / Still failing, still learning / What I’ve learned from failure

Notation
The unary ampersand in Ruby / (1..100).inject(&:+) / The challenge of teaching yourself a programming language / The significance of the meta-circular interpreter / Block-Structured Javascript / Haskell, Ruby and Infinity / Closures and Higher-Order Functions

Opinion
Why Apple is more expensive than Amazon / Why we are the biggest obstacles to our own growth / Is software the documentation of business process mistakes? / We have lost control of the apparatus / What I’ve Learned From Sales I, II, III

Whimsey
The Narcissism of Small Code Differences / Billy Martin’s Technique for Managing his Manager / Three stories about The Tao / Programming Language Stories / Why You Need a Degree to Work For BigCo

History
06/04 / 07/04 / 08/04 / 09/04 / 10/04 / 11/04 / 12/04 / 01/05 / 02/05 / 03/05 / 04/05 / 06/05 / 07/05 / 08/05 / 09/05 / 10/05 / 11/05 / 01/06 / 02/06 / 03/06 / 04/06 / 05/06 / 06/06 / 07/06 / 08/06 / 09/06 / 10/06 / 11/06 / 12/06 / 01/07 / 02/07 / 03/07 / 04/07 / 05/07 / 06/07 / 07/07 / 08/07 / 09/07 / 10/07 / 11/07 / 12/07 / 01/08 / 02/08 / 03/08 / 04/08 / 05/08 / 06/08 / 07/08 /