1. 程式人生 > >什麼是超程式設計?

什麼是超程式設計?

臨下班的緊急任務

時鐘指向6點半,張大胖今天不太忙,想著今天終於可以早點兒下班了。

收拾好東西準備離開的時候,領導佈置了一個新任務,張大胖很無奈,哀嘆一聲,老老實實地坐下來。

新任務看起來非常簡單:從一個CSV檔案中讀取資料,形成Java物件,然後對外提供一個API,讓別人呼叫。

這個CSV檔案叫做employee.csv, 張大胖開啟這個CSV檔案,裡邊的內容一看就懂。

name,age,level

Andy,25,B7

Joe, 22, B6

張大胖的API就需要返回一個List<Employee>,很自然,Employee類長這個樣子:

public class Employee{
    private String name;
    private String age;
    private String level;
    ......
}

class中的每個欄位和csv檔案的“表頭”的“列名”保持一致。

這樣簡單的任務對張大胖來說是小菜一碟,他寫了一個EmployeeParser,專門解析CSV檔案,形成Employee物件,半個小時不到就收工了,趕緊下班!

還沒來得及溜走,又被領導叫住了:“大胖,那個CSV檔案新加了一個欄位,叫做salary ,快把你的程式改一下啊!”

name,age,level,salary

Andy,25,B7,3000

Joe, 22, B6,2500

張大胖極不情願地坐下來,給Employee類增加了一個salary的欄位,又修改了EmployeeParser類,增加對這個欄位的解析。

然後又聽到領導在喊:“又加了一個欄位,叫做tax !”

沒轍,繼續修改Employee類和EmployeeParser類吧。 這一次修改完,領導終於放他走了。

 

模板:用程式來生成程式

等了兩趟車,終於在西二旗擠上了13號線,張大胖心裡一直在想:明天保不齊還要增加欄位,這真是讓人厭煩的重複勞動啊。大家都說,Don't repeat yourself, 我這怎麼才能減少重複呢?

關鍵點就在於,那個Java類的欄位要和CSV的表頭的列名做對應,CSV變化了,Java類的欄位以及解析的方法都要做相應得修改才可以。

對了,能不能根據CSV的列名自動地生成那個Employee類啊,這樣問題不就解決了嗎? CSV變化, Employee類跟著變化,多好!

CSV的“列名”經過讀取,可以變成一個Java 的List ,例如["name","age","level"], 如何寫一段程式碼,把這個List變成一個Employee Class呢?

張大胖聚精會神,在地鐵上想了一路,完全無視地鐵上那擁擠的人群和汙濁的空氣。

快要下車時,他靈機一動,可以用模板技術嘛,比如velocity模板,定義一個employee.vm :

public class Employee{    
    #foreach ($field in $headers)
        private String $field;         
    #end    
    ##其他程式碼略
}

然後再寫一個程式碼生成器,讀取employee.csv的“表頭”,形成List,把List傳遞給這個employee.vm模板,就可以輸出Java類了:

寫成具體的程式碼就是這個樣子:

VelocityEngine ve = new VelocityEngine();

...初始化引擎的程式碼略...

Template template = ve.getTemplate("employee.vm");   

VelocityContext context = new VelocityContext();

List<String> headers = readCSVHeaders();

context.put("headers",headers);

Writer writer = new PrintWriter(new FileOutputStream(
                new File("C:\\Employee.java")));           

//把headers變數傳遞給模板
template.merge(context, writer);
writer.flush();    

(友情提示:可左右滑動)

(碼農翻身注:這裡做了簡化只關注了Empployee的欄位,還需要處理getter/setter方法,尤其是也需要通過模板的方法生成EmployeeParser,用來形成Employee物件。此外還有資料型別的問題。)

在小區對面的田老師紅燒肉吃了一份蓋飯以後,張大胖立刻投入到程式的編寫中來,一邊寫一邊想:我這是用程式來生成程式啊!

 

超程式設計

第二天,領導果然要加新的欄位了,張大胖心中暗自佩服自己的自知之明,調出昨晚寫的“寶貝”執行了一下,不到一秒鐘,新的Employee和EmployeeParser就生成了。

下午的時候,張大胖洋洋得意地給Bill展示自己的工作成果,Bill說:“不錯啊,都開始超程式設計了!”

“超程式設計?”

“對啊,你不是用程式來生成程式嘛,這就是一種超程式設計。”

張大胖沒想到的工作居然就是高大上的“超程式設計”,更高興了。

“還有,如果把CSV檔案看成資料庫的表,程式碼生成器自動生成的EmployeeParser不就相當於DAO嗎?Employee 不就是和資料表對映的Domain物件嗎? 你的程式碼實現了Object-relational mapping !”

就是啊,我怎麼沒想到,雖然距離真正的O/R Mapping還很遠,但思想是一致的,大神就是厲害,看透了本質,張大胖暗想。

可是Bill很快給它潑了一盆冷水:“不過這種用模板生成的方式還是有些‘低階’,每次CSV檔案有變化,都需要執行一下程式碼生成器才可以。”

“那怎麼辦?”

“其實吧,這個Employee的類沒有必要在編譯期存在,如果能在執行時動態地生成就行了。”

執行期動態生成? 張大胖有點懵。

“對於Java語言來說,執行期在記憶體中動態生成一個Class,還是有難度的,你需要透徹理解Java Class的檔案格式,還需要在底層用ASM這樣的東西去操作Java位元組碼。”

“檔案格式和位元組碼?就是那些0xCAFEBABE,iload ,iadd, putfield,invokespecial ? ”  張大胖看過虛擬機器的書,知道有很多位元組碼,但是操作它們形成符合要求的類,實在是難以想象。

Bill 笑道:“你可以用動態語言,比如Ruby,超程式設計很強大,實現你這個功能簡直是小菜一碟。”

Bill很快就寫出了一段程式碼:

#在記憶體中建立一個名稱為Employee的類
klass = Object.const_set("Employee", Class.new) 

names= ...讀取csv檔案第一行,形成陣列,如 ["name","age","level"]...

#對這個記憶體中的類進行"手術"
klass.class_eval do
    #現在 name,age,level...變成了這個Employee類的欄位!
    attr_accessor *names    
    #再定義一個Employee類的建構函式    
    define_method(:initialize) do |*values|  
        names.each_with_index do |name, i|  
        instance_variable_set("@" + name, values[i])  
    end
    end 
end 

(友情提示:可左右滑動)

張大胖沒有學過Ruby , 看到這裡更懵了。

Bill看到張大胖發呆的樣子,說道:”經過上述處理,記憶體中建立了一個類,如果把它的原始碼展示一下,你就明白了。”

#動態生成的類
class Employee
  #動態生成的屬性,類似與java的getter方法
  def name
    @name
  end
  #動態生成的屬性,類似java的setter方法
  def name=(str)
    @name = str
  end
  def age
    @age
  end
  def age=(str)
    @age = str
  end
  def level
    @level
  end
  def level=(str)
    @level = str
  end
  #動態生成的建構函式
  def initialize(*values)
      @name = values[0]
      @age = values[1]
      @level = values[2]
  end
end
#一個使用Employee類的例子
p = Employee.new("andy","22","B6")

(友情提示:可左右滑動)

(碼農翻身注:對CSV檔案內容的讀取沒有包括在其中。)

張大胖明白了,這個類是由資料驅動,動態生成的,CSV的header 中有多少欄位,這個類就會生成多少個屬性。

和自己的程式碼生成器比較了一下,Ruby寫的這段程式碼更加精煉,不需要模板,沒有所謂程式碼生成器,或者說,程式碼生成器和生成的類已經合二為一了。

即使是CSV檔案發生了變化,也不需要額外執行程式碼生成器,只需要執行那段Ruby程式碼就行。

Bill問道:“怎麼樣,超程式設計不錯吧?”

張大胖說道:“嗯, 這Ruby的超程式設計能力很強大啊,可惜的是,我們的專案都是Java的,這動態的指令碼語言Ruby沒法直接使用,如果是微服務,對外提供的是HTTP的API,我可以學學Ruby,單獨寫個Ruby專案。”

Bill說:“其實吧,程式語言中,超程式設計能力最強大的還屬LISP,在LISP當中,程式和資料的表現形式是一致的,造就了它無以倫比的超程式設計能力,LISP程式可以像操作資料一樣操作程式碼。 有人甚至說,LISP根本不是程式語言,它是程式設計元語言,專門為了生成程式而生。”

張大胖聽得雲裡霧裡,黯然道:“不知道你在說什麼,太抽象了!等我學學LISP以後再回來和你討論吧。”