我生平首次參加馬拉松培訓。馬拉松培訓最有趣的方面——實際上,也是惟一的方面——就是提高不 斷疊加所帶來的影響。有時,我為了提高效率而進行專門為了改進身體條件而設計的長短跑。有時,在跑 步過程中,我學習避免小的錯誤,避免重復多余的姿勢(多余的姿勢對單個步幅沒有太大影響,但卻會在 整個 26.2 英裡的跑步過程中浪費能量或傷害到我)。我每周都有提高,可每周之間的區別並不顯著。但 是一個訓練計劃周期過後,我會從最初只能跑 4 英裡提高到能跑 26.2 英裡。軟件開發也與此類似。如 果持續進行小的改進,消除多余的重復,您就會不斷地累積提高,從而在今後的每個項目中都會做得更好 。
在這篇包含兩部分的文章中,我把重點放在 Ruby on Rails 搭建上,這是一個能夠在早期開發階段削 減重復的 Rails 特性。第 1 部分介紹 Rails 搭建的限制和 Streamlined,Streamlined 是個代碼生成 器,它高效地應用了元編程技術來消除更高層次上的重復。第 2 部分將進一步深入 Streamlined 的元編 程模型及其定制特性。
低級重復與高級重復
在整個 跨越邊界 系列中,我介紹了通過降低重復和提高效率實現反復改進的語言和框架:
具備諸如 duck typing 這類特性的編程語言,通過使用更少的類型定義、減少純粹為了支持編譯器所 需要的代碼數,能夠提高靈活性和減少重復。
框架試圖通過處理核心任務(例如持久性或事務)來提高效率和消除重復,這樣就不必為每個新的應 用程序編寫代碼。
Ruby on Rails 通過利用公共規范消除重復配置,允許框架推斷您的意圖,而不是強迫您配置應用程 序特性(例如應用程序中特定的數據庫表名稱和列名稱)。
就像所有高效的語言和框架必須做的那樣,這些措施都把重點放在每個步驟上,或低級重復上。但是 一旦搭建了一個有效的基礎,就可以把目標放得更高。Rails 的搭建特性試圖通過公共應用程序類型(數 據庫支持的 Web 應用程序)消除重復。
多數數據庫支持的 Web 應用程序,幾乎要為系統中每個主要的表都提供執行 CRUD 操作(創建、讀取 、更新和刪除)的用戶界面。 搭建這些用戶界面應當自動進行,而不應當重復。 Rails 通過 搭建開始 消除這種重復,搭建是一個特性,可以根據數據庫表集合的內容構建默認的 CRUD 界面。使用 Rails,只 用幾個簡單步驟,就可以從頭開始構建一個搭建完整的應用程序。如果一直跟隨 跨越邊界 系列,那麼以 前就看過這些步驟。這次,我再把這些步驟簡要介紹一下:
輸入 rails trails 生成編排山地摩托車賽道的 Rails 應用程序。
用選中的數據庫引擎創建叫作 trails_development 的數據庫,並修改 trails/config/database.yml ,以反映選中的配置。
切換到 trails 目錄,,生成模型和控制器:輸入 ruby script/generate model Trail (如果在 UNIX 上運行,可以省略 ruby) 生成叫作 Trail 的模型,輸入 script/generate controller Trails 生成叫作 TrailsController 的控制器。
把文件 db/migrate/001_create_trail.rb 編輯成清單 1 那樣:
清單 1. 初始遷移
class CreateTrails < ActiveRecord::Migration
def self.up
create_table :trails do |t|
t.column :name, :string
t.column :difficulty, :string
t.column :description, :text
end
end
def self.down
drop_table :trails
end
end
把文件 app/controllers/trails_controller.rb 編輯成像清單 2 一樣:
清單 2. TrailsController 中的搭建
class TrailsController < ApplicationController
scaffold :trail
end
輸入 rake migrate,運行遷移。
用命令 script/server 啟動服務器,並把浏覽器指到 localhost:3000/trails/list。
現在就已經得到了一個簡單的能夠工作的帶有數據庫支持的 Web 應用程序,可以進行基於 CRUD 的每 個操作,如圖 1 所示。可以看到主屏幕列出了每個項目和相關的圖片,提供了 Ajax 窗口用來創建、讀 取、更新和刪除項目。
圖 1. 簡單的 Rails 應用程序
到現在,只付出了很少努力,就到達了一個可以把應用程序開發帶到更高檔次的地步。Rails 演示人 員總會展示搭建功能,因為它是如此之炫,而且對於調試和在匆忙之間為客戶做演示,都極為有用。可以 通過代碼生成器生成搭建 —— 在這個示例中輸入了 script/generate scaffold Trail Trails —— 或 者在控制器中指定 scaffold 元編程標記。每種方式都有自己的用途。
添加關系
搭建確實有一些明顯限制:它不處理關系,也沒有利用優秀的 Rails Web 服務或 Ajax 支持。為了說 明這些限制,要創建帶有模型、視圖和控制器的 Location。Location 與 Trail 之間存在一對多關系。 搭建並不能協助該關系的管理。
創建 location 的模型(script/generate model Location)和控制器(script/generate controller Location Locations)。就像對 TrailsController 所做的那樣,把 scaffold :location 添加到 location_controller.rb。要把 Location 和 Trail 編織在一起,兩者間需要多對一關系,所以 把 belongs_to :location 添加到 Trail,把 has_many :trails 添加到 Location,如清單 3 所示:
清單 3. trail.rb 和 location.rb 間的關系
class Trail < ActiveRecord::Base
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :trails
end
把 db/migrate/002_create_locations.rb 編輯成清單 4 那樣:
清單 4. locations 表的遷移
class CreateLocations < ActiveRecord::Migration
def self.up
create_table :locations do |t|
t.column :city, :string
t.column :state, :string
end
add_column "trails", "location_id", :integer
end
def self.down
drop_table :locations
remove_column "trails", "location_id"
end
end
輸入 rake migrate 運行遷移。(要查看關於遷移的更多內容,請參閱 跨越邊界: Rails 遷移。)
一下子就有了這麼多設置。現在可以深吸一口氣,總結以下到目前為止構建的內容:
有了一個針對賽道的數據庫表和另一個針對地點的數據庫表。
有了 Ruby 模型對象,對象之間還有 Rails 關系。
模型現在在賽道和地點之間有多對一關系。
有了處理模式中的變化的策略,也可以收回目前為止兩個主要模式變化中的任何一個。
有了原始用戶界面。
雖然可能想添加一些驗證,但模型對象是適合生產應用的第一級 Rails 對象。許多 Rails 模型對象 之所以簡單,是因為屬性都是用元編程動態添加的。為了演示現在的關系,通過控制台添加一些數據。輸 入 script/console 啟動控制台,並輸入清單 5 中的命令:
清單 5. 把數據添加到賽道和地點
>> trail = Trail.new
=> #<Trail:0x2446168 @attributes={"name"=>nil, "location_id"=>nil,
"description"=>nil, "difficulty"=>nil}, @new_record=true>
>> trail.name = "Hermosa Creek"
=> "Hermosa Creek"
>> trail.difficulty = "easy"
=> "easy"
>> trail.description = "22 miles of mostly downhill singletrack."
=> "22 miles of mostly downhill singletrack."
>> trail.save
=> true
>> location = Location.new
=> #<Location:0x240d1c4 @attributes={"city"=>nil, "state"=>nil}, @new_record=true>
>> location.city = "Durango"
=> "Durango"
>> location.state = "Co"
=> "Co"
>> location.trails << trail
=> [#<Trail:0x2446168 @errors=#<ActiveRecord::Errors:0x2411c9c @errors={},
@base=#<Trail:0x2446168 ...>>, @attributes={"name"=>"Hermosa Creek", "id"=>1,
"location_id"=>nil, "description"=>"22 miles of mostly downhill singletrack.",
"difficulty"=>"easy"}, @new_record=false>]
>> location.save
=> true
>> hc = Trail.find 1
=> #<Trail:0x147c588 @attributes={"name"=>"Hermosa Creek", "location_id"=>"1",
"id"=>"1", "description"=>"22 miles of mostly downhill singletrack.",
"difficulty"=>"easy"}>
>> hc.location
=> #<Location:0x6cc2f8 @attributes={"city"=>"Durango", "id"=>"1", "state"=>"Co"}
清單 5 向數據庫添加了一條賽道和一個地點,由從 trails 中的 location_id 列指向 locations 中 的 id 列的外鍵管理。模型對象足夠健壯,可以作為應用程序的構建基礎。但是,視圖就是另一回事了。
關系問題
把浏覽器指向 http://localhost:3000/trails/show/1,看到圖 2 所示的屏幕:
圖 2. Rails 搭建沒有關系字段
在這裡看不出 trail 和 location 之間的關系。還會注意到,搭建非常原始:它沒有圖片、沒有 Ajax、沒有公共標頭或側欄,也沒有任何現代 Web 頁面中常見的修飾。但重要的是通過 搭建,只花了幾 分鐘就得到了一個相對復雜的應用程序。您可能並不指望這個簡單特性能夠生成健壯的代碼,但是現在您 可以把您的期望值抬高一點。
雖然搭建代表著對多數 Web 開發框架技術水平的顯著提高,可它仍然有提高的余地,也應當如此。但 是如果在此基礎上構建,您會發現獲益極多。這就像是從 13 英裡開始馬拉松訓練,而不是從 4 英裡開 始。
搭建,像許多元編程技術一樣,就是個運行時代碼生成器。Rails 社區中的有些人認為搭建是有局限 的,認為搭建還沒有豐富到可以處理多數應用程序。其他人則認為搭建很好用,搭建的質量才是基本問題 。這完全取決於應用程序的性質。如果正在構建一個重復的模式,那麼會從構成搭建基礎的元編程技術得 到巨大收獲。如果模板是充分可調整、充分豐富的,那麼在框架中就能在更高層次上減少重復。現在開始 介紹 Streamlined。
Streamlined:強化的搭建
自從 Rails 出現以來,各種形式的和各種大小的 Rails 插件一直在提升所有應用程序開發的抽象程 度。像登錄生成器這樣的組件允許生成安全性。其他插件使得在 Rails 中處理 Web 服務更容易。 Streamlined 以其產品級質量的應用程序生成器超越了搭建。與使用搭建時一樣,您可能需要擴展生成的 代碼,但初始的應用程序從它本身來說,其功能性令人驚訝。
請下載初始 alpha 版本的 Streamlined .gem 文件(參閱 參考資料)。切換到保存 .gem 的目錄, 並輸入 gem install streamlined。所需要的所有內容都會自動安裝。如果出現問題,可以通過 streamlined 的博客得到優秀的支持,也有商業支持可選。
現在是把 Streamlined 投入實踐的時候了。首先,輸入 script generate streamlined location trail,運行 Streamlined 生成器。當提示是否替換 locations 和 trails 控制器時,回答 y。
把浏覽器指向 http://localhost:3000/locations/list 查看圖 3 中的結果:
圖 3. 默認的 Streamlined 應用程序
可以立即讓 Streamlined 生成一個更完整的應用程序。把 Streamlined 列表與 圖 1 中的列表比較 。區別是驚人的:
默認應用程序處理關系,單擊其中一個 Edit 鏈接就可以看到。在下一節會看到更多關於關系的內容 ,在這篇文章的第 2 部分中甚至會更多。
應用程序更好地運用樣式表,並生成更復雜的樣式表。 Streamlined 運用各種技術,例如在表格周圍 使用 <div>s,使得每個頁面元素更容易進行樣式處理。Streamlined 的 alpha 版本的樣式處理有 限,但是預期未來的版本會突破這個限制。
應用程序在左側有默認的導航側欄,在頂部有菜單和標頭。這些菜單有更完整的默認行為,而且能夠 定制。
表格每一行都有代表編輯、顯示或刪除行的圖片,應用程序還有額外的圖片代表創建新條目、導出 CSV 以及把整個表導出為 XML。
這個頁面看起來更像默認應用程序,而不太像不完整的搭建。這正是 Streamlined 的亮點。在深入之 前,先對 Streamlined 的工作方式做個簡單描述。
先決條件
要使用 Streamlined,先要有一個可以工作的數據庫模式、一個使用經典 Rails 工具和規范(在這篇 文章中已經見到)的模型。然後,用 script/generate streamlined model1, model2, 等等命令生成 Streamlined 界面。Streamlined 觀察 Rails 的命名規范,並在處於開發模式時頻繁地重新裝入應用程 序對象,這樣只要刷新浏覽器,就可以看到最新的代碼變化。
像 Rails 搭建一樣,Streamlined 是個元編程框架,用元數據構建默認應用程序,構建的程序可以用 各種方式定制。框架查詢兩個元數據源:活動記錄模型和每個模型對象的定制元數據文件。默認情況下, Rails 從活動記錄內捕獲到足夠的元數據,構建復雜的用戶界面。活動記錄查詢數據庫表,獲得表中數據 之外的信息,並且維護您所提供的其他信息,例如主鍵、關系、字段、字段類型、字段大小。 Streamlined 利用所有這些信息來提供默認應用程序,但是要調整應用程序,框架還需要更多數據。 Streamlined 提供了額外的元數據來源。
快速查看 trails/app 下的目錄,可以看到 Rails 的常見目錄:models、controllers、views 和 helpers。但是還有第五個目錄可用:streamlined。就是在這裡指定額外的元數據。streamlined 目錄中 四個文件快速列表說明了問題:
location.rb 和 trail.rb 包含同名模型的詳細定制信息。
streamlined_relationships.rb 包含活動記錄中指定的關系的更多信息,例如 Streamlined 要如何 呈現關系。
streamlined_ui.rb 包含全局用戶界面問題的配置信息,例如是否創建頭、尾以及左側導航欄。
Streamlined 立刻組合了代碼生成(它生成可以修改的代碼)和真正的元編程(它使用 Ruby 語言在 運行時把代碼動態地添加到應用程序)開始工作。Streamlined 生成日後可能要修改的靜態內容和頁面。 例如,生成器直接把樣式表和圖片復制到您的項目。可以用真正的元編程或代碼生成來創建視圖,視圖可 能需要修改,也可能不需要修改。
特性
通過操作這個默認應用程序,可以對它提供了多少特性有些感覺。左側的導航側欄擁有針 對每個所指定模型的鏈接——針對本文的模型就是賽道和地點。單擊鏈接,會進入每個模型的 主頁面。標頭有管理域內對象的一套默認鏈接,有上下文敏感幫助,還有關於頁面。
在進入表格 數據區時,會看到更為復雜的功能。有充當記錄過濾器的文本框。要查看它的工作方式,請單擊 + 鏈接 添加新賽道,並輸入一些數據。然後,在主窗口輸入 Her。將看到列表被調整成只有字段中包含指定文本 的條目。也可以單擊任意一個列名,根據這一列對列進行排序。
繼續操作下去,肯定會注意到優 秀的 Ajax 功能。在這裡的 CRUD 設置中使用 Ajax 的最大好處是在一個主屏幕上就能提供管理表所需要 的全部內容,只有很少的彈出框(用來編輯、顯示和刪除)。Ajax 支持更豐富的用戶體驗、更簡潔的應 用程序路徑和更好的用戶反饋。
最後看看關系管理。請單擊左側側欄上的 Locations 鏈接。然後 單擊 + 圖片,添加新地點(試著添加 Moab,Utah)。單擊賽道下的 Edit,並選擇應當屬於這個地點的 賽道。請注意 Streamlined 默認記錄了屬於每個地點的賽道的數量。這個默認行為已經非常豐富了,但 是我在第 2 部分還要用更復雜的優化對它進行定制。
與 Java 框架比較
目前為止,最流 行的 Java™ 框架都不生成 搭建,更不用說應用程序了。部分原因是在這個領域在根本上缺少驅動 創新的競爭。Ruby on Rails 正在改變這種局面。而且,可以假設,在 Web 框架發展了八年之後,應當 有人已經構建出了類似的東西。
應用程序生成器在 Java 環境中一直沒有成功。它們有一個重要 的問題:過多地依賴代碼生成器,但在元模型上,卻缺乏能夠對代碼生成進行補充的堅實的元編程框架。 這類框架可以提供短期的生產力提升,但是不能在長時間內持續改進。生成的代碼通常太脆弱和復雜。除 非有足夠的能力在每次代碼生成之間定制代碼,否則時間一長就會失去生產力。Streamlined 確實支持代 碼生成,但只支持應用程序中不變的那些部分,或者應用程序中簡單的可變部分——例如視圖 和樣式表,而這些內容開發人員可以容易地修改和維護。
有兩個看起來想正確地混合代碼生成和 元數據的 Java 框架,它們是 RIFE 和 JMatter(請參閱 參考資料)。我在這個系列中已經多次討論過 RIFE,但是 JMatter 是新的。JMatter 框架擁有開源許可,也有商業報價。JMatter 基於 Hibernate 和 Swing,它允許根據元編程模型迅速地開發非常復雜的應用程序。Eitan Suez 這位 Java 圈中著名的發言 人構建了 JMatter,以幫助快速地啟動一項針對醫療實踐的兩層客戶/服務器應用程序的 Java 開發。在 將近兩年的特化之後,JMatter 驚人地強壯,而且它的特性很容易與 Rails 和 Streamlined 對抗。如果 Jmatter 中的變化步伐能趕上 Ruby 社區的技術水平,那麼它今後還會存在。
結束語
在這 篇文章中,我介紹了 Rails 搭建、它的限制以及稱作 Streamlined 的替代品。Streamlined 搭建得更完 整,但到目前為止,它仍然還是搭建。在第 2 部分中,您將獲得圍繞 Streamlined 的元編程模型的更詳 細討論,還將學習如何定制應用程序的關鍵部分。在這之前,您可以放飛思維、大量實踐,繼續跨越邊界 。