Friday, September 21, 2007

Code Voyeur

I like perusing public code repositories, be they CVS/SVN/Mercurial/Git whatever. I'm not sure if this is some kind of sick code voyeurism fetish but I think it's kind of healthy in degrees. It's a good place to pick up tricks. Kind of like doing the weekly ruby quiz but looking at the code before you know what the task is and seeing how closely you think they correlate to the spec.

Scott Hanselman is also on the kick of reading other peoples code to become a better developer, could it be the path to enlightment?

It dawned upon me that at work I have rarely had the need to tread outside my projects. All this time i've been searching far and wide for great code when I had a veritable treasure trove of unread code right under my nose.

We have a VB contractor doing some work for us and I caught wind that someone asked him to do it in C# ASP.NET. Wow. Might not be so bad I thought. So I had a look in SVN and came across this:


 1     public static string Proper(string StringIn) {
 2       string Temp = StringIn;
 3       if (Temp.CompareTo(String.Empty) != 0) {
 4
 5         char[] Delimiter = { ' ' };
 6         string[] Words = Temp.Split(Delimiter);
 7         Temp = String.Empty;
 8
 9         for (int i = Words.GetLowerBound(0); i <= Words.GetUpperBound(0); i++)
10           if (Words[i].Trim().CompareTo(String.Empty) != 0)
11             Temp += Words[i].Substring(0, 1).ToUpper() +
12                     Words[i].Substring(1).ToLower() + " ";
13         return Temp;
14       }
15       else
16         return String.Empty;
17     }
18


Apart from the upper case local variable names, in-efficient string concatenation and re-inventing the wheel, the dude did alright. Absolute respect to him, he's smart, he got it working, but it is obvious that C# is not his native tongue. That is textbook VB in C#.

Here is something roughly equivalent I came up with that produces the same output (don't lambast me for not getting the current culture, "en-AU" does weird shit that "en" doesn't) :)


1     using System.Globalization;
2     public static string TitleCase(string str) {
3       return String.IsNullOrEmpty(str) ? String.Empty
4              : new CultureInfo("en").TextInfo.ToTitleCase(str.ToLower());
5     }


It's not his fault really. He was asked to code in a language he wasn't familiar with and he got by. But what he did was speak English in down-town Nairobi.

Now I don't know what my point is but just look at the API's if you are using an unfamiliar language. I'm not shit-hot with C#, give me C, Java or Ruby and I've got a fighting chance. The thing is I'd assume that .NET would provide a way to title case a string. Instead of re-inventing the wheel I did a google search and came across the TextInfo class. You think he would have twigged that if VB has StrConv there might be something vaguely similar in C#.

Moral of the story, if you're asked to code in a language you are not familiar with, knock it back or make it clear it might not look pretty. But i guess who doesn't like getting paid?

Tuesday, September 18, 2007

Meta-Meta-Meta Programming

Now this isn't particularly clever or something you should even really consider doing but i've wasted my time so now you don't have to :). L and I got a lovely new bookshelf a few weeks ago and I noticed my beloved copy of The Pragmatic Programmer eyeing me off in the corner, so I started to thumb through it.

The astute amongst you that have read it would remember their example on meta-programming that uses Perl to take an ordinary text file and generate Pascal and C source code from it. I thought i'd update it a bit and use Ruby to generate Java, and, well Ruby.

I did it in a similar way to the prag guys i.e. the "proper" way but then I got a little bit curious about the notion of source that generates source that generates source. Plus, I hadn't really dug into Ruby meta-programming and this seemed like something harmless to play with.

Seeing we all love Shoes, let's have a look at a file called Shoe.txt that in pretty much plain English, models a shoe.


1 C Shoe
2 M   brand          String        
3 M   colour         String        
4 M   size           int        7    
5 M   isTrendy       boolean    true    
6 M   scent          Scent        
7 E

For clarification, the beginning of each line identifies what it is modelling:

C is the name of the class
M is a member of the class, specifying it's name, type and default value
E signals the end of the class.

Pretty straight forward, eh.


So how do we turn that into:

Shoe.Java


 1 class Shoe {
 2   private String brand = null;
 3   public String getBrand {
 4     return brand;
 5   }
 6   public void setBrand(String brand)
 7     this.brand = brand;
 8   }
 9
10   private String colour = null;
11   public String getColour {
12     return colour;
13   }
14   public void setColour(String colour)
15     this.colour = colour;
16   }
17
18   private int size = 7;
19   public int getSize {
20     return size;
21   }
22   public void setSize(int size)
23     this.size = size;
24   }
25
26   private boolean isTrendy = true;
27   public boolean getIstrendy {
28     return isTrendy;
29   }
30   public void setIstrendy(boolean isTrendy)
31     this.isTrendy = isTrendy;
32   }
33
34   private Scent scent = null;
35   public Scent getScent {
36     return scent;
37   }
38   public void setScent(Scent scent)
39     this.scent = scent;
40   }
41
42
43 }



and Shoe.rb?


1 class Shoe 
2   attr_accessor :brand, :colour, :size, :isTrendy, :scent
3   def initialize 
4     @colour = nil
5     @size = 7
6     @isTrendy = true
7     @scent = nil
8   end
9 end

Well, there is the smart way i.e. the pragmatic way, or there is the meta-meta-meta programming way.

language_generator.rb


 1 langs = %w(ruby java)
 2 class LangGen 
 3 end
 4
 5 langs.each do |lang|
 6   LangGen.class_eval <<-LETS_DANCE
 7     $first = true
 8     $init = ""
 9  
10     def #{lang}_class_start(name)
11       out = "class " + eval(\"name.chomp\") + ' '
12       out << "{" if '#{lang}' == 'java'
13       out << "\n"
14       out  
15     end
16   
17     def #{lang}_class_end
18       out = ""
19       out << "\n}" if '#{lang}' == 'java'
20       if '#{lang}' == 'ruby'
21         out << $init
22         out << "  end"
23         out << "\nend"
24       end
25       out
26     end
27   
28     def #{lang}_members(name, type, value)
29       out = ""
30       if '#{lang}' == 'java'
31         value ||= 'null'
32         out << "  private " + eval(\"type\")+' '+eval(\"name\")+' = '+
33                   eval(\"value\")+";\n"
34       end
35   
36       out << "" if '#{lang}' == 'ruby'
37       out
38     end
39   
40     def #{lang}_accessors(name, type, value)
41       accessors = ""
42       if '#{lang}' == 'java'
43         accessors << "  public " + eval(\"type\")+
44                         " get"+eval(\"name.capitalize\")+" {\n"
45         accessors << "    return "+eval(\"name\")+";\n"
46         accessors << "  }\n"
47         accessors << "  public void set"+eval(\"name.capitalize\")+ '('+
48                         eval(\"type\")+ ' '+ eval(\"name\")+")\n"
49         accessors << "    this."+eval(\"name\")+" = "+eval(\"name\")+";\n"
50         accessors << "  }\n\n"  
51       end
52       if '#{lang}' == 'ruby'
53         value ||= 'nil'
54         if $first
55           $init << "\n  def initialize \n"
56           accessors << "  attr_accessor :" + eval(\"name\")  
57         end
58         accessors << ", :" + eval(\"name\") unless $first
59         $init << '    @'+ eval(\"name\")+' = '+eval(\"value\")+"\n" unless $first
60         $first = false
61       end
62       accessors
63     end    
64  
65   LETS_DANCE
66 end
67
68 gen = LangGen.new
69 langs.each do |lang|
70   File.open('Shoe.txt').each do |line|
71     if line =~ /^C/
72       print gen.send("#{lang}_class_start".to_sym, line.gsub(/^C\s+/,''))  
73     end
74     print gen.send("#{lang}_class_end".to_sym) if line =~ /^E/ 
75     if line =~ /^M\s+(\w+)\s+(\w+)\s+(\w+)?/
76       name, type, value = $1, $2, $3
77       print gen.send("#{lang}_members".to_sym, name, type, value)
78       print gen.send("#{lang}_accessors".to_sym, name, type, value)
79     end
80   end
81   printf("\n"+'*'*50 + "\n")
82 end