程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 用Rails創建高質量Web應用

用Rails創建高質量Web應用

編輯:關於JAVA

越來越多的企業開始選擇Rails作為Web應用的框架。Rails曾經還主要是一些輕公司的選擇,但今天一些“重”企業(比如保險、金融等行業的企業)也開始把Rails納入內部應用甚至外部應用的考慮范圍。我最近服務過的客戶是國外某大型保險公司,該公司就選擇了Rails來創建他們的保險銷售網站。

選擇Rails的原因,是因為它快速構建的能力,是因為它是Web開發的DSL。但是否選擇了Rails就代表了高效開發?是否在Rails上創建的Web應用就一定是高質量的?答案是否定的。從我參與過的幾個Rails項目來看,質量可謂是參差不齊,開發速度也是判若雲泥。而開發的效率低下的原因,則常常是應用本身質量的低下和設計的拙劣。

在本文中,我將逐一討論幾個影響Web應用質量的因素。同時,我們也可以從中領悟到Rails為創建高質量的Web應用所做的努力、它的各種設計給我們的啟示,以及Rails 3的改進所代表的意義和趨勢。

MVC

我們都知道Rails是一個MVC架構模式的Web框架,MVC各部分的職責也很清楚。但問題在於我們是否真的遵循了MVC架構模式做到了各部分職責的明確分離?是否遵循了單一職責的原則?

在大多數代碼裡面,這種混沌不清的狀態存在於model和controller之間:controller承擔了太多本應由model承擔的職責。其中比較典型的例子是內嵌(多)對象表單。比如,Album和Photo之間是一對多的關系,我們要創建一個含有多個photo的album。在 Rails 2.3之前,我們可能會寫出類似的代碼:

AlbumsController
  def create
   album = Album.new params[:album]
   album.photos << Photo.new params[:album][:photo]
   ...

如果是一個涉及更多種類型對象的表單,這裡的代碼可能會更加復雜。但在AlbumsController裡面,我們真正想關心的只是Album的創建,而不是Photo或其它關聯對象的創建。而且從Album的角度看,創建過程中photo跟其它attributes沒有區別,應該得到一致地處理。

Rails 2.3之後,我們就可以很簡單地達到這個目的。在Album裡面做這樣的聲明:

class Album < ActiveRecord::Base
  ...
  accepts_nested_attributes_for :photos
end 

然後,controller中的代碼就可以被簡化為:

AlbumsController
  def create
   album = Album.new params[:album]
   ...

從這個例子中可以看到Rails在推進MVC三部分之間職責明確上所做的努力和進步。很多人可能會說,我們的代碼沒有這樣的問題。但MVC三部分之間職責開始模糊,往往出現在業務邏輯變得復雜之後。我們應該經常審視我們的代碼,做到真正的職責單一。

REST

現今的互聯網應用已經很難是一個獨立的個體,互聯網應用之間的交互越來越多。所以,建立REST架構風格的互聯網應用變得越來越重要。Rails的 router很好地支持了REST風格的外部接口設計。Rails 3所做的一個很大改進就是router的改進,以強調REST風格的接口設計。

REST也讓我們以資源的角度看待應用中的數據,我們的代碼設計因此也產生了一些變化。當需要增加一個invoice的PDF文件下載功能的時候,我們一般會向InvoicesController添加這麼一段代碼:

InvoicesController
  def download_pdf
   ...
   send_data(generate_pdf(@invoice), :type => 'application/pdf')
  end

這段代碼至少存在兩個問題:第一個問題,就是我們前面所述的職責明確問題。PDF的generate屬於Invoice而不是controller 的職責,所以我們應該把PDF生成的邏輯移到Invoice內部。第二個問題,則是語義是否恰當的問題 。如果我們以資源的角度看待Invoice的話,PDF跟HTML或者XML一樣,只是Invoice的另一種表現形式而已。而表現一個資源,在show action中處理最為恰當。

重寫之後,代碼如下:

def show
  ...
  respond_to do |format|
   format.html
   format.pdf { render :pdf => @invoice.pdf }
  end
end 

重寫之後的代碼不僅更符合REST的風格,而且更加簡潔優美。

JavaScript

隨著RIA的普及以及HTML5時代的即將來臨,JavaScript的江湖地位正在與日俱增。從Google的一些應用就可以看出業界對於 JavaScript態度的一些變化。比如Gmail,它提供了在無JavaScript支持環境下的普通版本和有JavaScript支持的全功能版本 ──這是一種漸進式增強的設計理念。但隨後幾年推出的Google Doc,已經完全放棄了對無JavaScript環境的支持。從這些變化可以看出,JavaScript已經是Web應用的“必需品”。甚至有人把 JavaScript稱為當今最重要的編程語言,從某種意義上這種說法也不過分。

很久以來,我們一直以“腳本”的態度看待JavaScript。程序員對JavaScript的重視程度很不夠,業界對程序員的JavaScript能力要求也不高。現在,必須做出這種態度的轉變。

Rails 3所做的很大一個改進就是:Unobtrusive JavaScript(非侵入式的JavaScript),以實現對HTML和JavaScript代碼的分離。比如:

<%= link_to "delete", album_path(@album), :method => :delete, :confirm => "Are you sure?"%>

在Rails 3之前,它生成的代碼應為(代碼進行了省略):

<a href="/albums/1" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; ...

可以看到,生成的HTML內嵌了大量的JavaScript代碼,這是一種不好的做法。Rails 3所做的其中一個改變,就是分離HTML和JavaScript代碼,生成的HTML中內嵌的JavaScript代碼消失了:

<a href="/albums/1" data-confirm="Are you sure?" data-method="delete" rel="nofollow">delete</a>

那麼JavaScript代碼到哪裡去了?它們都被放到了一個叫做Rails.js的文件中。

跟服務端MVC要求職責分離一樣,這個原則也應該體現在客戶端的代碼上。HTML、CSS和JavaScript應該職責明確地各自負責數據、顯示和行為。同時,這種分離也對程序員的JavaScript能力提出了更高的要求。

性能

從一個請求(Request)的數據傳輸角度看,數據一般會經歷從數據庫到服務器,最後到客戶端這麼一個過程(可能還有其它層次)。數據離客戶端越近,響應速度肯定越快。因此,緩存是提升性能的一大利器。

而客戶端緩存是離用戶最近的地方。關於客戶端緩存的一條原則是:不要緩存動態HTML頁面,但永久緩存一切其它文件類型。Rails對靜態文件的處理很好地體現了這條原則。比如下面這段代碼:

stylesheet_link_tag("application")

它生成的HTML是:

<link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css"/>

其中application.css?1232285206的後綴是這個文件的時間戳。那麼客戶端就可以放心地永久緩存這個靜態文靜。因為文件一旦更新,客戶端就會認為這是一個新的請求,即會去獲取最新的文件。

Rails還在其它很多方面提供了簡便的方法使性能優化變得簡單,比如服務端緩存機制等。但大多數時候,性能問題源自於我們自己的實現或者設計問題。比如對於Active Record Query Interface的的濫用,多數時候性能問題都可以通過完善數據庫查詢來得到很大的改進。對於數據庫查詢,我們應該經常關注幾個問題,比如:獲取的數據結果集中是否有大量無用數據,數據庫查詢次數是否可以減少等等。

用戶體驗

用戶體驗是Web應用非常重要的元素,Rails作為Web開發的DSL在這方面也有很多關注。 在Web應用中我們經常遇到的一個問題是:submit button(提交按鈕)被多次點擊。如果沒有被恰當處理,就會引起表單的多次提交。用Rails的form helper方法可以很簡單地避免這個問題:

submit_tag "Submit", :disable_with => "Submitting..." 

代碼中的:disable_with選項的作用是:在button被點擊之後把它disable掉,並且把button的文字替換成“Submitting”。一個簡單的選項帶來了顯而易見的好處:不僅避免了多次點擊的問題,而且顯式地告訴了用戶表單正在被提交當中。

Rails提供了很多便捷的方法,讓提升用戶體驗變得非常容易。作為程序員,我們也應該對用戶體驗有更多關注,比如如何設計更好的交互來避免AJAX所帶來的種種用戶體驗問題等等。

安全

Web應用面臨著很多安全隱患,比如Session定置(Session Fixation)、跨站請求偽造(CSRF)和日志信息洩露(Logging)問題。在Rails中我們可以用簡單到只有一行代碼的方式來避免這些安全問題。下面是各安全隱患以及對應策略。

Session定置

攻擊者通過某種方式強制用戶使用他所掌握的Session ID,在用戶登錄之後攻擊者即可使用此Session ID竊取用戶的信息。解決方案:

reset_session 

在登錄邏輯中添加此段代碼,以在登錄之前重置session,這樣便可以防止攻擊者通過Session Fixation攻擊來獲得用戶信息。

跨站請求偽造

CSRF是指在頁面中注入一些惡意代碼或者鏈接──指向用戶使用的其它站點,比如站點A。當用戶訪問被污染的頁面時,如果剛好站點A仍處於有效認證期,則用戶在站點A的數據就會被侵犯。解決方案:

protect_from_forgery :secret => "123456789012345678901234567890..."

此代碼會在非get請求中添加一個security token,如果token不一致,則請求將失敗。這種方式可以有效防止CSRF,當然前提是我們正確地使用了HTTP method。

日志信息洩露

默認情況下,Rails會把所有的請求信息都記錄在日志文件中。那麼攻擊者就可以通過竊取日志文件,以得到一些秘密信息,比如登錄密碼、信用卡信息等等。解決方案:

filter_parameter_logging :password

這行代碼就可以過濾那些不希望被日志文件記錄的信息,比如password等,從而避免通過日志來洩露敏感信息。

Web應用還面臨著很多其它安全問題,比如SQL注入,XSS等等。我們應該更多關注Web應用所面臨的安全問題,並盡可能避免。何況,在Rails中要避免大多數問題,方法都很簡單。

業務模型

最後一個問題雖然與Rails甚至技術的關系並不大,但是卻關系到一個Web應用質量的最關鍵問題:創建的Web應用是否符合業務模型。我們曾經在一個電子商務應用的開發過程中遇到這麼一個問題:整個購買流程的最後一步是支付頁面,用於完成支付並生成收據的PDF文件。產品交付之後,客戶開始抱怨支付頁面的性能問題:響應時間超過了容忍度。於是我們試圖改進支付頁面的性能,但因為支付頁面涉及的邏輯和業務實在過多,性能提升很困難。

但當我們重新審視支付頁面的業務邏輯時,我們發現這個頁面其實包含了兩部分功能:支付和PDF文件的生成。而從業務角度看,PDF文件的生成不屬於支付過程,而是支付完成之後的邏輯。而且,並不是所有的用戶都需要生成PDF,強制在支付的同時生成PDF是一種資源的浪費。所以,我們把支付頁面進行了拆分:把生成PDF文件的功能移到了支付成功頁面,而且只有在客戶點擊相應鏈接之後才會生成PDF。簡單的改動之後,不僅性能問題得到了解決,而且應用也更加符合真實的業務流程。

當我們遇到問題或者舉步維艱之時,停下來思考一下:我們對業務的理解是否出現了問題,我們的設計是否出現了問題。很多時候我們都可以在這裡找到答案。重新思考業務邏輯或者重新設計之後,實現可能會簡單很多,甚至也許問題本身都已經不復存在了。

小結

以上談到的各個元素關注了代碼質量、用戶體驗、性能、設計等等問題。也許這些並沒有涉及到什麼高深的技術問題,但在一個項目中,我們大多數時候面臨的都不是高深的技術難題,而是這些平常的點點滴滴。一個高質量的Web應用,正是從這些點點滴滴開始。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved