天天看點

開發,從需求出發 · 之一 所見即所得

從需求出發并不是一句空話,在開發過程中也是如此。

從需求出發,實質上是暗合了極限程式設計和測試驅動開發的一些思想。

鑒于網站開發是一個比較流行的方向,我打算從一個網站開始,闡述一下自己對“需求驅動開發“的了解,并将其引申到一個更廣泛的領域。

首先,我們假設一個需求:

我們需要實作一個類似google的網站,使用者通過web浏覽器通路,在首頁輸入框中查詢,傳回搜尋的結果。

效果如下圖所示:

開發,從需求出發 · 之一 所見即所得

STEP 0,通過eclipse建立一個web項目:sitefromscratch,檔案結構如圖所示:

開發,從需求出發 · 之一 所見即所得

在WebRoot下新增一個jsp檔案:

<%@ page pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
    <h1>search it!</h1>
    
    <form action="" method="get">
    	<input type="text" value="site from scratch" name="keywords" />    
    	<input type="submit" value="search!" />
    </form>
    
    <table  bordercolor="grey" >
    	<tr>
    		<td>result 1</td><td>something..................</td>
    	</tr>
    	<tr>
    		<td>result 1</td><td>something..................</td>
    	</tr>
    	<tr>
    		<td>result 1</td><td>something..................</td>
    	</tr>
    	<tr>
    		<td>result 1</td><td>something..................</td>
    	</tr>    	    	    	
    </table>
    
  </body>
</html>
           

通過浏覽器通路 /sitefromscratch/search1.jsp 可以得到和目标一緻的效果。

STEP 2,通過界面所展示的内容,我們可以大緻估計出所需要的資料以及其格式,讓我們加入少量的代碼實作同樣的效果:

<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%!
class Result {
	String title;
	String content;
	public Result(String title, String content) {
		this.title = title;
		this.content = content; 
	}
} 
%>
<%
	String keywords = "site from scratch";
	
	List results = new ArrayList();	
	results.add(new Result("result 1", "something.................."));
	results.add(new Result("result 2", "something.................."));
	results.add(new Result("result 3", "something.................."));
	results.add(new Result("result 4", "something.................."));
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
    <h1>search it!</h1>
    
    <form action="" method="get">
    	<input type="text" value="<%=keywords %>" name="keywords" />    
    	<input type="submit" value="search!" />
    </form>
    
    <%
    if(!results.isEmpty()) {
    %>
    <table  bordercolor="grey" >
    	<%
    	for(int i = 0; i < results.size(); i++) {
    		Result result = (Result)results.get(i);
    	%>
    	<tr>
    		<td><%=result.title %></td><td><%=result.content %></td>
    	</tr>    	
    	<%
    	} 
    	%>    	    	    	
    </table>    
    <%
    } 
    %>
    
  </body>
</html>
           

這裡,我們構造了一批僞資料(同時建立了一個類),并通過對應的執行邏輯,得到了完全一緻的展示效果。

STEP 3,現在,該把送出、查詢、結果展示的流程走通了:

<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%!
class Result {
	public String title;
	public String content;
	public Result(String title, String content) {
		this.title = title;
		this.content = content; 
	}
} 

public List search(String keywords) {
	List results = new ArrayList();	
	results.add(new Result("result 1", "something.................."));
	results.add(new Result("result 2", "something.................."));
	results.add(new Result("result 3", "something.................."));
	results.add(new Result("result 4", "something.................."));
	
	return results;
}
%>
<%
	String keywords = request.getParameter("keywords");
	if(keywords == null) keywords = "";
	
	List results = search(keywords);	
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
    <h1>search it!</h1>
    
    <form action="" method="get">
    	<input type="text" value="<%=keywords %>" name="keywords" />    
    	<input type="submit" value="search!" />
    </form>
    
    <%
    if(!results.isEmpty()) {
    %>
    <table  bordercolor="grey" >
    	<%
    	for(int i = 0; i < results.size(); i++) {
    		Result result = (Result)results.get(i);
    	%>
    	<tr>
    		<td><%=result.title %></td><td><%=result.content %></td>
    	</tr>    	
    	<%
    	} 
    	%>    	    	    	
    </table>    
    <%
    } 
    %>
    
  </body>
</html>
           

這裡,我們構造了一個 List search(String keywords)方法,将業務邏輯和頁面展示分離開來,分别置于在jsp檔案中分離的區塊。

STEP 4,接着,為了保持頁面的簡潔,我們把定義的類和方法提取出來,用包組織起來:

開發,從需求出發 · 之一 所見即所得
package cn.com.sitefromscrath.entity;

public class Result {
	
	public String title;
	public String content;

	public Result(String title, String content) {
		this.title = title;
		this.content = content;
	}
}
           
package cn.com.sitefromscrath.service;

import java.util.ArrayList;
import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public class SearchService {

	public List search(String keywords) {
		
		List results = new ArrayList();
		results.add(new Result("result 1", "something.................."));
		results.add(new Result("result 2", "something.................."));
		results.add(new Result("result 3", "something.................."));
		results.add(new Result("result 4", "something.................."));

		return results;
	}

}
           

不出意料,SearchService.java 和 Result.java的代碼就是直接從search.jsp中copy過去的。

值得一提的是,我們新增了一個BeanFactory類,作為工廠模式的一個實作,它簡單的通過指定的ID所對應的類傳回産生的執行個體。

如下所示:

package cn.com.sitefromscrath;

import cn.com.sitefromscrath.service.SearchService;

public class BeanFactory {
	
	public static Object getBean(String id) {
		if("searchService".equals(id)) {
			return new SearchService();
		}
		
		throw new RuntimeException("cannot find the bean with id :" + id);
	}

}
           

雖然它現在看來顯得畫蛇添足了一些,但是在我之後的展開論述中,它将占有很重要的位置。

現在,jsp檔案的内容看起來簡潔多了:

<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="cn.com.sitefromscrath.service.SearchService"%>
<%@page import="cn.com.sitefromscrath.BeanFactory"%>
<%@page import="cn.com.sitefromscrath.entity.Result"%>
<%
	String keywords = request.getParameter("keywords");
	if(keywords == null) keywords = "";
	
	SearchService searchService = (SearchService)BeanFactory.getBean("searchService");	
	List results = searchService.search(keywords);	
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
    <h1>search it!</h1>
    
    <form action="" method="get">
    	<input type="text" value="<%=keywords %>" name="keywords" />    
    	<input type="submit" value="search!" />
    </form>
    
    <%
    if(!results.isEmpty()) {
    %>
    <table  bordercolor="grey" >
    	<%
    	for(int i = 0; i < results.size(); i++) {
    		Result result = (Result)results.get(i);
    	%>
    	<tr>
    		<td><%=result.title %></td><td><%=result.content %></td>
    	</tr>    	
    	<%
    	} 
    	%>    	    	    	
    </table>    
    <%
    } 
    %>
    
  </body>
</html>
           

STEP 5,聽說MVC是個很高科技的東西,我們也來實作一下:

開發,從需求出發 · 之一 所見即所得

新增一個servlet,作為 控制層 Controller

package cn.com.sitefromscrath.web;

import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.com.sitefromscrath.BeanFactory;
import cn.com.sitefromscrath.service.SearchService;

public class SearchServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		String keywords = request.getParameter("keywords");
		if(keywords == null) keywords = "";
		
		SearchService searchService = (SearchService)BeanFactory.getBean("searchService");	
		List results = searchService.search(keywords);	
		
		request.setAttribute("keywords", keywords);
		request.setAttribute("results", results);

		request.getRequestDispatcher("/search5.jsp").forward(request, response);
	}

}
           

新增一個jsp檔案,search5.jsp,作為視圖層 Viewer。

<%@ page pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="cn.com.sitefromscrath.entity.Result"%>
<%
	String keywords = (String)request.getAttribute("keywords");	
	List results = (List)request.getAttribute("results");	
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>
  
  <body>
    <h1>search it!</h1>
    
    <form action="" method="get">
    	<input type="text" value="<%=keywords %>" name="keywords" />    
    	<input type="submit" value="search!" />
    </form>
    
    <%
    if(!results.isEmpty()) {
    %>
    <table  bordercolor="grey" >
    	<%
    	for(int i = 0; i < results.size(); i++) {
    		Result result = (Result)results.get(i);
    	%>
    	<tr>
    		<td><%=result.title %></td><td><%=result.content %></td>
    	</tr>    	
    	<%
    	} 
    	%>    	    	    	
    </table>    
    <%
    } 
    %>
    
  </body>
</html>
           

web.xml中配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<servlet>
		<servlet-name>SearchServlet</servlet-name>
		<servlet-class>cn.com.sitefromscrath.web.SearchServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>SearchServlet</servlet-name>
		<url-pattern>/search</url-pattern>
	</servlet-mapping>
	
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	
</web-app>
           

現在,讓我們再來run一次,http://localhost:8080/sitefromscratch/search 

binggo,效果跟目标無差!

——————————————————————————————————————————————————————————

嗯,到這裡,我們到底達到了什麼目的?資料還是假的啊,有這個必要嗎?有這個必要嗎?

開發,從需求出發 · 之一 所見即所得

看看,葛大爺已經被拍了一臉血了,我希望您還沒到這地步。

到了這一步,我們其實完成了跟前端頁面制作人員的握手協定:

您寫js的也好,html5的也好,随便整,我傳回的資料格式和内容您也看見了,就這樣,格式不會變,内容也不會出錯。資料怎麼嵌,那是您的事兒,俺就不伺候了。

關于”内容不會出錯“這句,補充一點兒,這是指的前端頁面人員(也許是你自己兼任)無需啟動一大堆複雜的應用程式,比如mysql、memcache,就能調試自己的html或者js代碼,同時,也避免了其他異常(資料庫down了,資料表毀壞了,網絡斷了,memcache連接配接不上了等等等等)對前端開發的幹擾。

特别是debug階段,如果你不能確定哪些是正确的,你就無法找到錯誤的。

很多程式員在debug排查某個問題的時候,最常犯的錯誤就是迷失在一大堆的子產品中間,找不到出路,造成這種情況的根本原因就在于:在當事人看來,每個子產品都是可疑的,不确定的。要麼猜要麼一個個查,精力和時間就此白白浪費。

而對于後端開發人員來說,現在面臨的就隻剩一個任務:讓下面的類方法傳回真實的業務結果吧。

package cn.com.sitefromscrath.service;

import java.util.ArrayList;
import java.util.List;

import cn.com.sitefromscrath.entity.Result;

public class SearchService {

	public List search(String keywords) {
		
		List results = new ArrayList();
		results.add(new Result("result 1", "something.................."));
		results.add(new Result("result 2", "something.................."));
		results.add(new Result("result 3", "something.................."));
		results.add(new Result("result 4", "something.................."));

		return results;
	}

}
           

我們将在下一章讨論這個問題。

WEB開發那些事兒
第一部分:從需求出發
所見即所得 這裡從一個靜态html頁面說起,逐漸抽離出展示層面和資料層面的東西。
造飛機的工廠 這裡主要說的是工廠方法。當然,工廠不是目的,而是結果,需求才是源起。
春天在哪裡 這裡開始扯到了spring和《兒歌三百首》
春天在這裡 對spring的吐槽
麥克斯韋妖 讓我們充當一次麥克斯韋妖,探測和控制單個子產品/方法的工作
扒皮MVC MVC模式的得失,開發的時候,别因為迷失才過程裡爾忘記了我們的目的。
第二部分:拿起筆來做刀槍
序言 拿起筆來做刀槍,開發路上當闖将
再造一個dom4j 标題說明了一切
再造一個spring 标題說明了一切
再造一個jsp 标題說明了一切,這裡的 jsp 不是 java server pages,而是java sign pages :)
再造一個struts 标題說明了一切
再造一個lucene 标題說明了一切,lucene的原理隻需要一句話說清楚,這個就是我喜歡他的原因
再造一個hibernate 其實我在意的是HQL如何映射到多種sql查詢語言上
Final Fantasy 最終的成品