Wednesday, November 30, 2005

Shape Code After Language II

If you shape your code after language then you can read your code aloud, and what is easy to say, it is easy to code.

In the previous post I mentioned how code shaped after language could be hard for other developers to read, and how easy it was for users.  It looked like there was almost an inverse relationship between developer skill and code understanding :-)

Seriously, I think that as developers we have got used to a contrived way of writing code.  Sometimes we get confortable in our own ways, with the language crutches and patterns that we have built.  We are so used to our way of doing things that we cannot even conceive there can be a better way.  I have been watching the whole java vs ruby diatribe on The Server Side and it amazes me how much java programmers were defensive in their ways.  When they looked at ruby they couldn't see simplicity, just a threatening difference.  I must say that those strong reactions played not a little role in my switching over to ruby.

While reading the bizarre Why's Poignant Guide to Ruby, I stumbled on this passage from chapter 3:

My conscience won't let me call Ruby a computer language. That would imply that the language works primarily on the computer's terms. That the language is designed to accommodate the computer, first and foremost. That therefore, we, the coders, are foreigners, seeking citizenship in the computer's locale. It's the computer's language and we are translators for the world.

But what do you call the language when your brain begins to think in that language? When you start to use the language's own words and colloquialisms to express yourself—Say, the computer can't do that. How can it be the computer's language? It is ours, we speak it natively!

We can no longer truthfully call it a computer language. It is coderspeak. It is the language of our thoughts.

Read the following aloud to yourself.

5. times { print "Odelay! " }

Yep, do just as Why  says:  read the code aloud.

If you don't focus just on the code deep structure (language, syntax, objects, variables), but choose to focus on the meaning, you will see that the intent of the code is transparent.

When you decide to start focusing on the human-oriented semantics of the language, a strange thinks happen - you will see that you will  start focusing more on what things you want rather than on how you want to do them.  Initially you are likely to experience it as frustration.  You will feel like you cannot say what you want in simple human terms.  You will want to go back to code to get stuff done, rather than trying to find the right words to compose some prose.

If you manage to overcome this feeling, maybe just setting some time aside to try to do it as well as you can, you will see that the way you use human words is changing the shape of your code.  If you want to be understood by humans you will tend to talk in terms of intents, of things that you want, without going much in detail about how to do them.  These details can be sketched out later on if necessary by exploding chosen parts of the code.

Let me show you a simple example example: choose those houses that cost less than 200'000 euros from a text file
An instinctive approach could be something like:
The intent first approach goes more like this:


Iteration I

choose_those_houses_that_cost_less_than_200000_euros_from_a_text_file

we say what we mean.  we start from the intent and we express it via human language


Iteration II

choose_houses_that_cost_less_than_200000_from(a_text_file)

we removed the "those", which didn't  add much to the intent, and we say that the text file is a variation point.  we have also implicitly expressed the intent of being able to change file.  Note that we haven't expressed the intent of changing the selection criteria.

Also, I'm playing at home here.  I am not going to specify that I am dealing in euros.


Iteration III

choose_houses_that_cost_less_than 200000, from=a_text_file

I dropped the brackets on arguments since I have decided that I am going to do this in Ruby.  I have also created another variation point for the selection threshold.  The second parameter is prefixed by a 'from='.  What am I doing there?  Suppress your coding habits and just read it aloud skipping the punctuation: choose houses that cost less than 200000 from a text file

It can't get much cleare than this, can it?  This is what I call prose - code transliteration without punctuation and symbols.

By the way, the 'from='  is a trick that consists in the implicit creation of the local variable 'from' before passing its value to the function.  You could have also used a hashmap in Ruby.  Something like: :from => a_text_file.  That would have been more in the Ruby Way kind of style, but for today I want it my way.


Iteration IV

choose_houses_that_cost_less_than 200000, from_the=textfile("houses.txt")

reading it aloud: choose houses that cost less than 200000 from the text file "houses.txt"
I have added the possibility of specifying the file name.  I have also accomodated the longer file specification, changing the sentence from
 
from=a_text_file

to

from_the=textfile("houses.txt")

'from' has become 'from_the' for no other reason than it sounds better this way when close to its neighbourgs.

This intent could have also been specified as:

choose_houses_that_cost_less_than 200000, from_the_textfile="houses.txt"

but in this case I just took the opportunity to wrap up a bit of meaning (the concept of textfile) and keep it outside the function that I am calling.  What is the return value of textfile("houses.txt") ?  It could be the text of the file.  It could be a File object.  It could be a parsified collection of houses.  Or maybe it's just a string.. maybe it's just returning the argument itself! Who cares? The overall meaning is clear.  We are not refactoring code, we are doing intent refactoring here.

If you are relying on your hard won coding instincts this will drive you crazy.  Just deep breathly and read the code aloud.


Iteration V

chosen_houses = houses_that_cost_less_than 200000, from_the=textfile("houses.txt")
puts chosen_houses

I have added the code to keep my selection into a variable and the code to print out this selection to screen.
Initially I wrote:

chosen_houses = choose_houses_that_cost_less_than 200000, from_the=textfile("houses.txt ")

but I din't like it.  It must be due to the repetition of 'choose'. chosen equals to what we choose.  It's not bad, but it's not great either.  The language is still young and we can mould it to what appeases our ear.  Generally speaking, when I find two similar words in a row (chosen and choose) I try to keep one and drop the other.  In this case "chosen_houses" becomes simply "houses".

The call to the function "houses_that_cost_less_than" simply return houses.  It returns what it states: houses.

When I print it out I use the ruby method "puts".  I don't like it a lot.  I would have rather used:

print chosen_houses

or

chosen_houses.display

but 'puts' is truly ubiquitous in Ruby and this time I felt that complying with the confortable habit of just typing those four easy letters was the more expressive thing to do.


Iteration VI

def houses_that_cost_less_than( a_threshold, houses)
    houses.select { |house| house.price < a_threshold }
end

chosen_houses = houses_that_cost_less_than 200000, from_the=textfile("houses.txt")

puts chosen_houses

Now there is some meat!  I have defined what I mean by that function, and I have said it in Ruby's own terms.  We can't go much lower level than that.  Fortunately Ruby is very legible, and this code is expressive even in its raw form.  If I read it aloud it is: [from] houses select [an] house [when] house.price < a threshold

In a different language it would have been much less legible.  When you read a chunk of code, it should convey its meaning - its intent - in seconds.  If it doesn't it needs intent refactoring.  A readable, easily digestible chunk of code, is called Stanza, just like the stanzas of a poem.

Note that by writing that code in such a way that it is expressing its intent in the simplest way, we have also implicitly taken a design decision.  'houses' is an enumerable object containing house elements that have a price.


Iteration VII

def houses_that_cost_less_than( a_threshold, houses)
    houses.select { |house| house.price < a_threshold }
end

def houses_file(filepath)
    File(filepath).readlines.collect { |line| house_from line }
end

chosen_houses = houses_that_cost_less_than 200000, from_the=houses_file("houses.txt")

puts chosen_houses

I have decided to make textfile("houses.txt") into houses_file("houses.txt") .  I was pushed to do this by the decision of returning a set of houses.  I wanted to make it slightly clearer without compromising the expressiveness of the code.  This is another important trade-off.  It's not like language-level syntax has no importance, it's just that it should come second after expressiveness.  In this case this change also akes the intent clearer, by specifying that the file is a special kind of file, a house file.

We have also defined the insides of this function.  Once again it was so simple to express it in ruby that it was pointless to detail it further: [from the] File [in] filepath read lines [and] collect [every] line [by getting an] house from [the] line

This is a bit more complex, but if you do Ruby it's clear at a glance.  Note that I say File(filepath) and not File.new(filepath).

I don't like constructors.  Constructors are for objects, not for people.  And we don't want to see objects, we want to see intents.  We want a File made from a filepath, we are not interested in creating objects.  I don't need a "new", I just need to say that I want a File.  When I read I don't want to see that File is an object that gets constructed there and the "new" just obscures the meaning.  In Ruby it's pretty easy to define an implicit constructor so that the 'File(filepath)' syntax works without any problem

We have also defined a 'house_from' that is going to parse a line and produce a 'house' object.


Iteration VIII

def houses_that_cost_less_than( a_threshold, houses)
    houses.select { |house| house.price < a_threshold }
end

def
house_from(text)
    city, address, price = text.split(",")
    return House(city,address,price)
end

def houses_file(filepath)
    File(filepath).readlines.collect { |line| house_from line }
end

chosen_houses = houses_that_cost_less_than 200000, from_the=houses_file("houses.txt")

puts chosen_houses

We have defined house_from where we split the text along commas and get the information that we use to define a 'House'.  We could have done it all in a single operation:

    return House *text.split(",")

but we would have lost the meaning of the fields extracted from the text line.  House is another implicit constructor.


Iteration IX

class House
    attr_accessor :
city, :address, :price

    def initialize(city, address, price)
        @city=city; @address=address; @price=price
    end
end

in this iteration we conclude by definining the House in simple terms.



Comments: Post a Comment

Links to this post:

Create a Link



<< Home

This page is powered by Blogger. Isn't yours?