ITPUB??ì3
ITPUB论坛 » 项目过程 » 敏捷软件开发:原则、模式与实践

标题: [精华] 敏捷软件开发:原则、模式与实践
离线 chncaesar
初级会员



精华贴数 0
个人空间 0
技术积分 6 (122884)
社区积分 0 (652885)
注册日期 2005-10-14
论坛徽章:0
      
      

发表于 2007-8-16 02:05 
下了。多谢


只看该作者    顶部
离线 lawer-bbc
版主


精华贴数 2
个人空间 0
技术积分 17086 (53)
社区积分 2195 (515)
注册日期 2007-1-12
论坛徽章:107
现任管理团队成员管理团队2007贡献徽章会员2007贡献徽章ITPUB新首页上线纪念徽章  
      

发表于 2007-8-16 09:37 


QUOTE:
最初由 chncaesar 发布
下了。多谢

感谢光顾


__________________
If you don't know where you're going, any road will do.If you don't know where you are, a map won't help.
E-mail:max656798@21cn.com
只看该作者    顶部
离线 lawer-bbc
版主


精华贴数 2
个人空间 0
技术积分 17086 (53)
社区积分 2195 (515)
注册日期 2007-1-12
论坛徽章:107
现任管理团队成员管理团队2007贡献徽章会员2007贡献徽章ITPUB新首页上线纪念徽章  
      

发表于 2007-8-19 23:49 
容器外的JSP页面测试技术

Jsp测试技术

开发web应用程序最恼人的一点就是想要测试的话你就必须向将其部署好。当然,并不是所有部分都这样。如果你是经过了精心的设计的话,你可以在Java程序中测试业务逻辑。你可以在应用服务器不运行的情况下测试数据访问、接口以及存储过程。不过如果是测试GUI的话(由Jsp所产生的HTMl),你就必须向将其部署,然后才可能测试。

很多的团队求助于Sellenium,Mercury或是其他的一些工具通过web server来测试GUI。然而,即使是页面的内容不变但样式变了得情况也会让测试变得脆弱不堪。其他的团队使用Cactus解决这种脆弱性,或是用HtmlUnit、HttpUnit这样原始的工具来监测web应用程序所生成的HTML。对于这些问题,我会在另一系列的blog之中来谈论。

本文之中我会介绍一种简单易行的技术,它使用JUnit或是HtmlUnit来测试Jsp页面,并且完全脱离容器。这项技术的优势也在此。

你不必一定保持容器的运行,甚至存在。你可以在选择特定的webserver之前就测试你的Jsp。

你不必在每次修改后重新部署,因而编辑/编译/测试的过程会更迅速。

你可以使用测试优先开发的方式来持续的构建Jsp。

容器外测试Jsp技术之所以并不盛行是因为Jsp在设计上就运行于容器内的。设计者从未过多的想过容器外运行的可能。因此由Jsp编译器的所生成代码往往依赖于容器所提供的诸多组件。即使是生成Jsp代码的工具也假定了你已经有一个成功部署的web应用程序在运行。因此,为了在容器外运行,你就要开发出相应的这些工具和组件。
依赖管理的抱怨

为什么这么多框架和工具的设计者们总期望你生活在他们提供的狭小世界中?为什么我必须先构建出完整的web应用才能编译Jsp?为什么这些东西一定要运行在容器中?信息隐藏早在10年前就已经是优秀软件设计的基本信条了。我们这个行业何时才能认真对待它?
编译Jsp

测试Jsp的第一步是将其编译为servlet。实现这一步,我们还需要先将Jsp转换成Java格式。Apache提供了一个叫做Jasper的工具,我们调用Jasper为MyPage.jsp创建一个Java格式的源文件MyPage_jsp.java。然后,你就可以使用你最喜欢的IDE编译这个文件成Servlet。

可惜Jasper并非是设计用在命令行中使用的,或者说并不是完全这样设计的。但Jasper确有一个main函数用来处理命令行参数,而且通过调用java org.apache.jasper.JspC就能够轻易调用它了。不过,Jasper期望它所运行的环境与容器环境是保持一致的。你要确保classpath中有了很多apache的Jar文件,而且它要能找到web应用程序的web.xml。它还需要能够找到包含web应用程序Jar以及TLD文件等的WEB-INF目录。简而言之,Jasper需要能找到一个完整的web应用程序。

如果事情更糟的话,除非是与TOMCAT的调用方式保持完全一致,否则某些特定的Jasper版本(我用的是tomcat 5.5.20)存在一些bug,它生成的代码会有一些错误。

第一点要做的虽然繁琐但还算简单,你需要创建好正确的目录以及文件结构,然后在Ant(Classpath更容易控制)中调用Jasper。第二点就需要一定的研究和测试才能让它跑起来。以下就是能成功运行的ant文件。JspC的调用出现在最后一个任务中。

<project name="Library" default="compile" basedir=".">

  <property environment="env"/>

  <property name="build.home" value="${basedir}/build"/>

  <property name="build.war.home" value="${build.home}/war"/>

  <property name="build.classes.home" value="${build.home}/classes"/>

  <property name="build.jar.home" value="${build.home}/jars"/>

  <property name="catalina.home" value="${env.CATALINA_HOME}"/>

  <property name="dist.home" value="${basedir}/dist"/>

  <property name="web.home" value="${basedir}/web"/>



  <path id="compile.classpath">

    <fileset dir="lib">

      <include name="*.jar"/>

    </fileset>

    <pathelement location="${catalina.home}/common/classes"/>

    <fileset dir="${catalina.home}/common/endorsed">

      <include name="*.jar"/>

    </fileset>

    <fileset dir="${catalina.home}/common/lib">

      <include name="*.jar"/>

    </fileset>

    <pathelement location="${catalina.home}/shared/classes"/>

    <fileset dir="${catalina.home}/shared/lib">

      <include name="*.jar"/>

    </fileset>

  </path>



  <target name="clean">

    <delete dir="${build.home}"/>

    <delete dir="${dist.home}"/>

  </target>



  <target name="compile">

    <mkdir dir="${build.classes.home}"/>

    <javac srcdir="${src.home}" destdir="${build.classes.home}" excludes="**/*Test.java">

      <classpath refid="compile.classpath"/>

    </javac>

  </target>



  <target name="jar" depends="compile">

    <mkdir dir="${build.jar.home}"/>

    <jar jarfile="${build.jar.home}/application.jar" basedir="${build.classes.home}" includes="**/application/**/*.class" />

  </target>



  <target name="dist" depends="jar">

    <copy todir="${build.war.home}">

      <fileset dir="${web.home}"/>

    </copy>



    <copy todir="${build.war.home}/WEB-INF/lib">

      <fileset dir="${build.jar.home}" includes="*.jar"/>

    </copy>



    <mkdir dir="${dist.home}"/>

    <jar jarfile="${dist.home}/${app.name}.war" basedir="${build.war.home}"/>

  </target>



  <target name="jsp" depends="dist">

    <delete dir="${basedir}/testjsp"/>

    <java classname="org.apache.jasper.JspC" fork="true">

      <arg line="-v -d ${basedir}/testjsp -p com.objectmentor.library.jsp -mapped -compile -webapp ${build.war.home}"/>

      <arg line="WEB-INF/pages/patrons/books/loanRecords.jsp"/>

      <classpath>

        <fileset dir="${catalina.home}/common/lib">

          <include name="*.jar"/>

        </fileset>

        <fileset dir="${catalina.home}/server/lib">

          <include name="*.jar"/>

        </fileset>

        <fileset dir="${catalina.home}/bin">

          <include name="*.jar"/>

        </fileset>

        <fileset dir="${build.war.home}/WEB-INF/lib">

          <include name="*.jar"/>

        </fileset>

        <pathelement location="/Developer/Java/Ant/lib/ant.jar"/>

      </classpath>

    </java>

    <jar jarfile="${build.jar.home}/jsp.jar" basedir="${basedir}/testjsp"

         includes="**/jsp/**/*.class"

      />

  </target>

</project>

当然,你要让所有标准文件以及目录都在${build.war.home}之下以确保工作。如果你在你的Jsp之中使用了自定义tag的话,还要确保所有相应的TLD文件都在你的TLD目录之中。

要注意的是,在ant文件中调用Jspc的命令行,而不是使用Tomcat所提供的JspC的Ant Task。因为我发现当你有自定义tag的时候它无法正确运行。也许我犯了糊涂,或者JspC中确实有bug。不过我所发现的唯一能让Jasper生成正确代码的方式是从命令行调用它,并明确的传递Jsp文件路径作为命令行的参数!如果你依靠它的Ant Task或是使用命令行来搜索所有web应用中的Jsp进行编译的话,它就会生成错误的代码。(请参阅这篇blog)

现在我们有了Java文件,让我们来分析一下它。首先,请看下面的Jsp文件。

<%@ page import="com.objectmentor.library.utils.DateUtil" %>

<%@ page import="com.objectmentor.library.web.controller.patrons.LoanRecord" %>

<%@ page import="java.util.List" %>

<%

  List loanRecords = (List) request.getAttribute("loanRecords";

  if (loanRecords.size() > 0) {

%>

<table class="list" id="loanRecords">

  <tr>

    <th>ID</th>

    <th>Title</th>

    <th>Due date</th>

    <th>Fine</th>

  </tr>

  <%

    for (int i = 0; i < loanRecords.size(); i++) {

      LoanRecord loanRecord = (LoanRecord) loanRecords.get(i);

  %>

  <tr class="<%=i%2==0?"even":"odd"%>">

    <td><%=loanRecord.id%>

    </td>

    <td><%=loanRecord.title%>

    </td>

    <td><%=DateUtil.dateToString(loanRecord.dueDate)%>

    </td>

    <td><%=loanRecord.fine.toString()%>

    </td>

  </tr>

  <%

    }

  %>

</table>

<%

  }

%>

下面则是Jasper所生成的代码。

package com.objectmentor.library.jsp.WEB_002dINF.pages.patrons.books;



import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import com.objectmentor.library.utils.DateUtil;

import com.objectmentor.library.web.controller.patrons.LoanRecord;

import java.util.List;



public final class loanRecords_jsp extends org.apache.jasper.runtime.HttpJspBase

    implements org.apache.jasper.runtime.JspSourceDependent {



  private static java.util.List _jspx_dependants;



  public Object getDependants() {

    return _jspx_dependants;

  }



  public void _jspService(HttpServletRequest request, HttpServletResponse response)

        throws java.io.IOException, ServletException {



    JspFactory _jspxFactory = null;

    PageContext pageContext = null;

    HttpSession session = null;

    ServletContext application = null;

    ServletConfig config = null;

    JspWriter out = null;

    Object page = this;

    JspWriter _jspx_out = null;

    PageContext _jspx_page_context = null;



    try {

      _jspxFactory = JspFactory.getDefaultFactory();

      response.setContentType("text/html";

      pageContext = _jspxFactory.getPageContext(this, request, response,

                  null, true, 8192, true);

      _jspx_page_context = pageContext;

      application = pageContext.getServletContext();

      config = pageContext.getServletConfig();

      session = pageContext.getSession();

      out = pageContext.getOut();

      _jspx_out = out;



      out.write('\n');

      out.write('\n');

      out.write('\n');



  List loanRecords = (List) request.getAttribute("loanRecords";

  if (loanRecords.size() > 0) {



      out.write("\n";

      out.write("<table class=\"list\" id=\"loanRecords\">\n";

      out.write("  <tr>\n";

      out.write("    <th>ID</th>\n";

      out.write("    <th>Title</th>\n";

      out.write("    <th>Due date</th>\n";

      out.write("    <th>Fine</th>\n";

      out.write("  </tr>\n");

      out.write("  ");



    for (int i = 0; i < loanRecords.size(); i++) {

      LoanRecord loanRecord = (LoanRecord) loanRecords.get(i);



      out.write("\n");

      out.write("  <tr class=\"");

      out.print(i%2==0?"even":"odd");

      out.write("\">\n");

      out.write("    <td>");

      out.print(loanRecord.id);

      out.write("\n");

      out.write("    </td>\n");

      out.write("    <td>");

      out.print(loanRecord.title);

      out.write("\n");

      out.write("    </td>\n");

      out.write("    <td>");

      out.print(DateUtil.dateToString(loanRecord.dueDate));

      out.write("\n");

      out.write("    </td>\n");

      out.write("    <td>");

      out.print(loanRecord.fine.toString());

      out.write("\n");

      out.write("    </td>\n");

      out.write("  </tr>\n");

      out.write("  ");



    }



      out.write("\n");

      out.write("</table>\n");



  }



    } catch (Throwable t) {

      if (!(t instanceof SkipPageException)){

        out = _jspx_out;

        if (out != null && out.getBufferSize() != 0)

          out.clearBuffer();

        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

      }

    } finally {

      if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);

    }

  }

}

最后的抱怨

这个类为什么要声明为final呢?如果我想创建一个测试的stub派生类呢?为什么有人会觉得生成类如此不可冒犯以至于我都无法覆写它。



仔细读过这段代码你就会发现,要想使用这个servlet的实例我们需要HttpServletRequest以及HttpServletResponse的实例。

更仔细研读一下我们就会发现servlet将所有的HTML写到JspWriter的实例中,而JspWriter是从PageContext中获得的。如果我们能够创建一个JspWriter的mock up的版本来保存所有的这些HTML,再为PageContext创建一个mock up的版本来派送mock JspWriter,那么我们就能在我们的测试中访问这些HTML了。

幸运的是,Tomcat的设计人员把JspWriter的创建放入到了JspFactory的工厂类中。而这个工厂类是可以覆写的!这就意味着我们可以在servlet之中获得我们自己的JspWriter类而不用改变servlet。需要的就是下面这段代码。

  class MockJspFactory extends JspFactory {

    public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse, String string, boolean b, int i, boolean b1) {

      return new MockPageContext(new MockJspWriter());

    }



    public void releasePageContext(PageContext pageContext) {

    }



    public JspEngineInfo getEngineInfo() {

      return null;

    }

  }

现在,我们需要的是mock Jspwriter。为了便于展示,我用了下面的:

MockJspWriter

package com.objectmentor.library.web.framework.mocks;



import javax.servlet.jsp.JspWriter;

import java.io.IOException;



public class MockJspWriter extends JspWriter {



  private StringBuffer submittedContent;



  public MockJspWriter(int bufferSize, boolean autoFlush) {

    super(bufferSize, autoFlush);

    submittedContent = new StringBuffer();

  }



  public String getContent() {

    return submittedContent.toString();

  }



  public void print(String arg0) throws IOException {

    submittedContent.append(arg0);

  }



  public void write(char[] arg0, int arg1, int arg2) throws IOException {

    for (int i=0; i<arg2; i++)

      submittedContent.append(String.valueOf(arg0[arg1++]));

  }



  public void write(String content) throws IOException {

    submittedContent.append(content);

  }



  // lots of uninteresting methods elided.  I just gave them

  // degenerate implementations.  (e.g. {})

}

无需关心那些我省略掉的未实现方法,我认为只需要关心那些足够使得我的测试得以运行的方法即可。对于剩下的,我只会使用其退化实现。

我的IDE对于创建这些mock类非常有帮助。它能够自动化的构建方法原型,并为那些接口或是抽象类所需要实现的方法给出退化的实现。

同样的用类似方法创建出MockPageContext,MockHttpServletRequest以及MockHttpServletResponse类。

MockPageContext

package com.objectmentor.library.web.framework.mocks;



import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import java.io.IOException;

import java.util.Enumeration;



public class MockPageContext extends PageContext {



  private final JspWriter out;

  private HttpServletRequest request;



  public MockPageContext(JspWriter out) {

    this.out = out;

    request = new MockHttpServletRequest();

  }



  public JspWriter getOut() {

    return out;

  }



  public ServletRequest getRequest() {

    return request;

  }

  // lots of degenerate functions elided.

}



MockHttpServletRequest

package com.objectmentor.library.web.framework.mocks;



import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.security.Principal;

import java.util.*;



public class MockHttpServletRequest implements HttpServletRequest {



  private String method;

  private String contextPath;

  private String requestURI;

  private HttpSession session = new MockHttpSession();

  private Map parameters = new HashMap();

  private Map attributes = new HashMap();



  public MockHttpServletRequest(String method, String contextPath,

                                String requestURI) {

    super();

    this.method = method;

    this.contextPath = contextPath;

    this.requestURI = requestURI;

  }



  public MockHttpServletRequest() {

    this("GET");

  }



  public MockHttpServletRequest(String method) {

    this(method, "/Library", "/Library/foo/bar.jsp");

  }



  public String getContextPath() {

    return contextPath;

  }



  public String getMethod() {

    return method;

  }



  public String getRequestURI() {

    return requestURI;

  }



  public String getServletPath() {

    return requestURI.substring(getContextPath().length());

  }



  public HttpSession getSession() {

    return session;

  }



  public HttpSession getSession(boolean arg0) {

    return session;

  }



  public Object getAttribute(String arg0) {

    return attributes.get(arg0);

  }



  public String getParameter(String arg0) {

    return (String) parameters.get(arg0);

  }



  public Map getParameterMap() {

    return parameters;

  }



  public Enumeration getParameterNames() {

    return null;

  }



  public void setSession(HttpSession session) {

    this.session = session;

  }



  public void setParameter(String s, String s1) {

    parameters.put(s, s1);

  }



  public void setAttribute(String name, Object value) {

    attributes.put(name, value);

  }



  // Lots of degenerate methods elided.

}



MockHttpServletResponse

package com.objectmentor.library.web.framework.mocks;



import javax.servlet.ServletOutputStream;

import javax.servlet.http.*;

import java.io.*;

import java.util.Locale;



public class MockHttpServletResponse implements HttpServletResponse {

  // all functions are implemented to be degenerate.

}

有了这些mock对象,现在我就可以创建一个loanRecords_jsp的servlet实例并且开始调用它!我的头一个测试用例就像下面这样:

  public void testSimpleTest() throws Exception {

    MockJspWriter jspWriter = new MockJspWriter();

    MockPageContext pageContext = new MockPageContext(jspWriter);

    JspFactory.setDefaultFactory(new MockJspFactory(pageContext));

    HttpJspBase jspPage = new loanRecords_jsp();

    HttpServletRequest request = new MockHttpServletRequest();

    HttpServletResponse response = new MockHttpServletResponse();



    jspPage._jspInit();

    jspPage._jspService(request, response);



    assertEquals("", jspWriter.getContent());

  }

就像预期的一样,测试失败了。这是因为还有些内容还没补充上,不过所剩无多。如果你仔细的看过Jsp文件,你就会发现它调用了request.getAttribute(“loanRecords”)并且期望返回一个List。但因为目前的测试并未为这样的属性赋值,从而导致了代码抛出了异常。

要想成功让servlet输出HTML,我们还需要加载这个属性。然后,我们就可以使用HtmlUnit来解析此HTML并且编写相应的单元测试。

HtmlUnit非常的容易使用,尤其是在测试所产生的像是本例这样的web pages上。我这里还有篇文章详细的介绍了它。

下面就是最终测试加载属性的测试,它通过htmlunit来检测HTML,并且做出正确的判断:

package com.objectmentor.library.jspTest.books.patrons.books;



import com.gargoylesoftware.htmlunit.*;

import com.gargoylesoftware.htmlunit.html.*;

import com.objectmentor.library.jsp.WEB_002dINF.pages.patrons.books.loanRecords_jsp;

import com.objectmentor.library.utils.*;

import com.objectmentor.library.web.controller.patrons.LoanRecord;

import com.objectmentor.library.web.framework.mocks.*;

import junit.framework.TestCase;

import org.apache.jasper.runtime.HttpJspBase;



import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import java.util.*;



public class LoanRecordsJspTest extends TestCase {

  private MockPageContext pageContext;

  private MockJspWriter jspWriter;

  private JspFactory mockFactory;

  private MockHttpServletResponse response;

  private MockHttpServletRequest request;

  private WebClient webClient;

  private TopLevelWindow dummyWindow;



  protected void setUp() throws Exception {

    jspWriter = new MockJspWriter();

    pageContext = new MockPageContext(jspWriter);

    mockFactory = new MockJspFactory(pageContext);



    JspFactory.setDefaultFactory(mockFactory);

    response = new MockHttpServletResponse();

    request = new MockHttpServletRequest();

    webClient = new WebClient();

    webClient.setJavaScriptEnabled(false);

    dummyWindow = new TopLevelWindow("", webClient);

  }



  public void testLoanRecordsPageGeneratesAppropriateTableRows() throws Exception {

    HttpJspBase jspPage = new loanRecords_jsp();

    jspPage._jspInit();



    List<LoanRecord> loanRecords = new ArrayList<LoanRecord>();

    addLoanRecord(loanRecords,

                  "99",

                  "Empire",

                  DateUtil.dateFromString("2/11/2007"),

                  new Money(4200));

    addLoanRecord(loanRecords,

                  "98",

                  "Orbitsville",

                  DateUtil.dateFromString("2/12/2007"),

                  new Money(5200));



    request.setAttribute("loanRecords", loanRecords);



    jspPage._jspService(request, response);



    StringWebResponse stringWebResponse = new StringWebResponse(jspWriter.getContent());

    HtmlPage page = HTMLParser.parse(stringWebResponse, dummyWindow);

    HtmlElement html = page.getDocumentElement();



    HtmlTable table = (HtmlTable) html.getHtmlElementById("loanRecords");

    List<HtmlTableRow> rows = table.getHtmlElementsByTagName("tr");

    assertEquals(3, rows.size());



    assertEquals("even", classOfElement(rows.get(1)));

    assertEquals("odd", classOfElement(rows.get(2)));



    List<HtmlTableDataCell> firstRowCells = rows.get(1).getCells();

    assertEquals(4, firstRowCells.size());



    List<HtmlTableDataCell> secondRowCells = rows.get(2).getCells();

    assertEquals(4, secondRowCells.size());



    assertLoanRecordRowEquals("99", "Empire", "02/11/2007", "$42.00", firstRowCells);

    assertLoanRecordRowEquals("98", "Orbitsville", "02/12/2007", "$52.00", secondRowCells);

  }



  private String classOfElement(HtmlTableRow firstDataRow) {return firstDataRow.getAttributeValue("class");}



  private void assertLoanRecordRowEquals(String id, String title, String dueDate, String fine, List<HtmlTableDataCell> rowCells) {

    assertEquals(id, rowCells.get(0).asText());

    assertEquals(title, rowCells.get(1).asText());

    assertEquals(dueDate, rowCells.get(2).asText());

    assertEquals(fine, rowCells.get(3).asText());

  }



  private void addLoanRecord(List<LoanRecord> loanRecords, String id, String title, Date dueDate, Money fine) {

    LoanRecord loanRecord = new LoanRecord();

    loanRecord.id = id;

    loanRecord.title = title;

    loanRecord.dueDate = dueDate;

    loanRecord.fine = fine;



    loanRecords.add(loanRecord);

  }



  private class MockJspFactory extends JspFactory {

    private PageContext pageContext;

    public MockJspFactory(PageContext pageContext) {

      this.pageContext = pageContext;

    }



    public PageContext getPageContext(Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse, String string, boolean b, int i, boolean b1) {

      return pageContext;

    }



    public void releasePageContext(PageContext pageContext) {

    }



    public JspEngineInfo getEngineInfo() {

      return null;

    }

  }

}

上述的测试确保了所生成的HTML中表格中的每一行都具有正确的内容。这项测试确实能够测出是否存在这样的表格,并且判断出是否表格的每一行是按照正确的顺序来展现的。同时,它也确保了每一行的相应style。测试忽略了此外的表单以及语法部分。
结论

这篇发表在此的技术能够用来测试几乎所有目前我们所见过的web页面,并且脱离容器,也无需web server的运行。相对来说,它也比较容易去设置,并且非常易于扩展。有了它,你就可以快速的进行编辑、编译、测试的周期性迭代,并且你也能遵循测试驱动开发的原则了。

(原文链接网址: http://blog.objectmentor.com/articles/category/testing-guis; Robert C. Martin的英文blog网址: http://blog.objectmentor.com/)

作者简介:Robert C. Martin是Object Mentor公司总裁,面向对象设计、模式、UML、敏捷方法学和极限编程领域内的资深顾问。他不仅是Jolt获奖图书《敏捷软件开发:原则、模式与实践》(中文版)(《敏捷软件开发》(英文影印版))的作者,还是畅销书Designing Object-Oriented C++ Applications Using the Booch Method的作者。Martin是Pattern Languages of Program Design 3和More C++ Gems的主编,并与James Newkirk合著了XP in Practice。他是国际程序员大会上著名的发言人,并在C++ Report杂志担任过4年的编辑。


__________________
If you don't know where you're going, any road will do.If you don't know where you are, a map won't help.
E-mail:max656798@21cn.com
只看该作者    顶部
离线 liusu1350
初级会员



精华贴数 0
个人空间 0
技术积分 26 (43210)
社区积分 3 (20257)
注册日期 2005-5-25
论坛徽章:0
      
      

发表于 2007-8-28 09:18 
好东西
支持下
最近在找这方面的资料


只看该作者    顶部
离线 intelren1
初级会员



精华贴数 0
个人空间 0
技术积分 16 (64635)
社区积分 0 (1529279)
注册日期 2007-8-31
论坛徽章:0
      
      

发表于 2007-8-31 11:21 
为什么解压缩的时候说文件被破坏,解压不成功呢?


只看该作者    顶部
离线 lawer-bbc
版主


精华贴数 2
个人空间 0
技术积分 17086 (53)
社区积分 2195 (515)
注册日期 2007-1-12
论坛徽章:107
现任管理团队成员管理团队2007贡献徽章会员2007贡献徽章ITPUB新首页上线纪念徽章  
      

发表于 2007-8-31 14:51 


QUOTE:
最初由 intelren1 发布
为什么解压缩的时候说文件被破坏,解压不成功呢?

文件是否都下载了?文件号是否连续?


__________________
If you don't know where you're going, any road will do.If you don't know where you are, a map won't help.
E-mail:max656798@21cn.com
只看该作者    顶部
离线 lawer-bbc
版主


精华贴数 2
个人空间 0
技术积分 17086 (53)
社区积分 2195 (515)
注册日期 2007-1-12
论坛徽章:107
现任管理团队成员管理团队2007贡献徽章会员2007贡献徽章ITPUB新首页上线纪念徽章  
      

发表于 2007-9-2 00:33 
还有你的解压缩工具的版本是否偏低?


__________________
If you don't know where you're going, any road will do.If you don't know where you are, a map won't help.
E-mail:max656798@21cn.com
只看该作者    顶部
离线 zhanghong00708
骑个破自行车就是跑


来自 北京
精华贴数 0
个人空间 0
技术积分 208 (8819)
社区积分 1 (42945)
注册日期 2007-8-7
论坛徽章:1
设计板块每日发贴之星     
      

发表于 2007-9-6 15:57 
顶下这本书不错


__________________
人类最愚蠢的行为在于忘记常识!!!!!!!!!!!!
oracle技术群:37383097
只看该作者    顶部
离线 lawer-bbc
版主


精华贴数 2
个人空间 0
技术积分 17086 (53)
社区积分 2195 (515)
注册日期 2007-1-12
论坛徽章:107
现任管理团队成员管理团队2007贡献徽章会员2007贡献徽章ITPUB新首页上线纪念徽章  
      

发表于 2007-9-7 10:26 


QUOTE:
最初由 zhanghong00708 发布
顶下这本书不错

感谢支持


__________________
If you don't know where you're going, any road will do.If you don't know where you are, a map won't help.
E-mail:max656798@21cn.com
只看该作者    顶部
离线 lawer-bbc
版主


精华贴数 2
个人空间 0
技术积分 17086 (53)
社区积分 2195 (515)
注册日期 2007-1-12
论坛徽章:107
现任管理团队成员管理团队2007贡献徽章会员2007贡献徽章ITPUB新首页上线纪念徽章  
      

发表于 2007-9-9 15:05 
当心:工具的美貌

我这周在芬兰的弗罗茨瓦夫(译者注:Wroclaw)为一位客户作咨询。(那是过去叫布雷斯劳(译者注:Breslau)的德国城镇,而现在发VRAHT-swahf的音。)这周末我拜访了这里的考古博物馆,就在城镇的广场旁。当我观看这博物馆的时候,我被那些创造箭、钩、斧子、罐、篮子和所有其他日常生活中的物品的远古人类的细心所震撼了。这些物品肯定曾花去几小时、几天、或是几周去创建;而且那个时代的人民也认为所花费的那些时间是值得的。

       我端详了一支箭几分钟时间。这支箭是完全的实用主义风格。它没有各种装饰。而它却那么的显得工艺高超而且美观。杆只是个木棒,但它的直挺和整齐却无疑是经过悉心挑选的。箭头是粗糙的石英做的,而它的轮廓修整的与箭杆相协调而箭头与箭杆也稳固地切合。这种修整紧凑、规则、而又精确的。整个的物件给我留下了深刻的印象。一些人花费了很大的艰辛来打造它。一些人是很细心的。

       而更令人印象深刻的还是一个古老的织布机。这个设备使用树枝制造的。就像那支箭一样,它未经修饰而且表面粗糙。而这又是个了不起的设计品,并且经过缜密的思索而成。对我来说这只需花上几分钟时间就能明白它是如何运行的。从便利的线绳,到那能交替的分离线绳以至于它不会在织物穿梭之中而拖拉缠绕的灵巧的线杆。这个设备是远古手工艺技术和工程上的杰作。

       我也被使用这台织布机所需的小心谨慎所震撼。仅仅是去装配好这些线绳不花上一天时间也要好几小时。所有其他的线绳必须要与线杆通过一条长度刚刚好的宽松绳子相连,以至于当织布者拉动线杆的时候,那些线会被剩余的线绳拉开。这一定要小心谨慎而避免让线绳缠绕。

       我从这次经历获得的精神是:工艺打造不能有任何的粗心!用另一个方式来说,你无须修饰就可以展示出伟大的工艺品。工艺品不在意外表的华丽,在意的是内在的美;实用的美。

       我从这些远古设备中学习到的和为之震撼的地方是,如果他们能花费如此巨大的心思在打造和使用这些手工艺品上的话,那我们这些软件工程师也能做到。作为一个软件咨询师我看到了非常多的代码。我经常被这中间的大量代码构造的不经意所惊骇。好像那些作者是因为太忙于达到工具的成功而放弃了细心。而那些箭、织布机和那么多博物馆中的物品却告诉我的是工艺的成功是细心的结果。

       如果我们是软件专家的话我们就要关注我们的工艺,关注我们的工艺制品。我们不能丢下一堆代码然后修修补补直到他们大体上能运行,而应该注意确保代码是经过认真思考的,认真书写的,认真测试的,而且认真的写文档的。

       我所带的绿色护腕(译者注:green wrist-band,一种优秀的敏捷人的荣耀,可参见Martin之前的blog)告诉我测试优先(译者注:TEST FIRST,一种敏捷开发的优秀方法)是谨慎的专家态度的标志。它代表了我对我自己和我的专业所下的承诺,我不会对我的代码粗心。



作者小注:

弗罗茨瓦夫太棒了!

弗罗茨瓦是个漂亮的边陲城市,与苏联镇压和控制的时期相比,它现在恢复得非常棒。城市的广场华丽、生机勃勃、而且繁荣。大学正在培养出一批批新的聪敏敢为的毕业生。商业正在形成,有很多革新的建筑。简而言之,食美物兴。



(原文链接网址:http://www.butunclebob.com/ArticleS.UncleBob.TakingCare; Robert C. Martin的英文blog网址: http://www.butunclebob.com/ArticleS.UncleBob)

译者注:Robert C. Martin是Object Mentor公司总裁,面向对象设计、模式、UML、敏捷方法学和极限编程领域内的资深顾问。他不仅是Jolt获奖图书《敏捷软件开发:原则、模式与实践》(中文版)(《敏捷软件开发》(英文影印版))的作者,还是畅销书Designing Object-Oriented C++ Applications Using the Booch Method的作者。Martin是Pattern Languages of Program Design 3和More C++ Gems的主编,并与James Newkirk合著了XP in Practice。他是国际程序员大会上著名的发言人,并在C++ Report杂志担任过4年的编辑。


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1010912


__________________
If you don't know where you're going, any road will do.If you don't know where you are, a map won't help.
E-mail:max656798@21cn.com
只看该作者    顶部
相关内容


CopyRight 1999-2006 itpub.net All Right Reserved.
北京皓辰广域网络信息技术有限公司. 版权所有
E-mail:Webmaster@itpub.net
京ICP证:010037号 联系我们 法律顾问