2006年12月15日

执行./startup.sh,或者./shutdown.sh的时候,爆出了Permission denied

关于LINUX权限-bash: ./startup.sh: Permission denied

<script type="text/javascript"></script><script type="text/javascript"></script>

在执行./startup.sh,或者./shutdown.sh的时候,爆出了Permission denied,

其实很简单,就是今天在执行tomcat的时候,用户没有权限,而导致无法执行,

用命令chmod 修改一下bin目录下的.sh权限就可以了

如chmod u+x *.sh

在此执行,OK了。

posted @ 2014-12-30 10:26 liujg 阅读(273) | 评论 (0)编辑 收藏

submit()和onsubmit()的区别(转)

2011-03-16 10:34

最近在开发中遇到了表单提交前验证的问题,用一个普通的button按钮代替submit按钮,
在提交前触发这个button的onclick事件,在其事件中触发form的submit事件。问题出现了:
以下是出现相关代码:
<form action="http://www.baidu.com/s?wd=this.form.submit%28%29%3B&cl=3" method="post" name="form1" onsubmit="return alert('已提交!'); return false;"> 
    <table align="center" width="420px" cellPadding="2" cellSpacing="1" bgcolor="#A4B6D7"    style="word-wrap:Break-word;">                
        <tr style="cursor: hand;background:#d7e3f6" > 
            <td width="20%" align="right">条型码</td> 
            <td><input style="width:90%" type="text" name="GOODSNUM"   size="30"  maxlength="8" ></td> 
        </tr> 
        <tr> 
            <td align="center" colspan="2"> 
                <input type="button" name="save" value="保存" onclick="if((confirm('确定要提交吗?'))) this.form.submit();"/> 
            </td> 
        </tr>  
    </table> 
</form> 


却发现并没有触发form的onsubmit方法,而是直接提交了。奇怪了,难道没有这种方式无法结合form的onsubmit方法吗?
仔细想了想,既然this.form表示form这个对象,那么肯定能获取到form的属性和方法的
,就改成this.form.onsubmit();  成功!
我又查了查手册,原来submit的方法是这样解释的:
  The submit method does not invoke the onsubmit event handler. Call the onsubmit event handler directly. When using Microsoft® Internet Explorer 5.5 and later, you can call the fireEvent method with a value of onsubmit in the sEvent parameter.

意思是说submit这个方法是不触发onsubmit时间的,如果想要触发它,需要调用
fireEvent方法。尝试一下:this.form.fireEvent('onsubmit');哈哈,果然也成功!不过这样不是多此一举吗?呵呵!

就这个小问题也搞了我将近一个小时,不过为了以后不为这个问题烦恼,这也是值得的。
this.form.submit(); //直接提交表单
this.form.onsubmit(); //调用form的onsubmit方法
this.form.fireEvent('onsubmit'); //同上,
     PS:又学到了fireEvent这个方法,

2.onsubmit()与submit() :

<sCript>
funCtion fun()
{
   alert("form_submit");
}
</sCript>

<form onsubmit="fun()">
<input type="submit" id="aaa" value="submit">   <!--能弹出form_submit-->
<input type="button" id="bbb" value="onCliCk_submit" onCliCk="doCument.forms[0].submit()">
<!--
表单会提交,但是不会运行fun() 原因是 onsubmit事件不能通过此种方式触发(在IE环境)
直接用脚本doCumetn.formName.submit()提交表单是不会触发表单的onsubmit()事件的
-->
    <input type="button" id="bb1" value="onCliCk_onsubmit" onCliCk="doCument.forms[0].onsubmit()">

<!--会触发fun()参数-->
</form>

posted @ 2011-09-28 15:11 liujg 阅读(321) | 评论 (0)编辑 收藏

doGet()和doPost()的区别(转)

service()是在javax.servlet.Servlet接口中定义的, 在 javax.servlet.GenericServlet 中实现了这个接口, 而 doGet/doPost 则是在 javax.servlet.http.HttpServlet 中实现的, javax.servlet.http.HttpServlet 是 javax.servlet.GenericServlet 的子类. 所有可以这样理解, 其实所有的请求均首先由 service() 进行处理, 而在 javax.servlet.http.HttpServlet 的 service() 方法中, 主要做的事情就是判断请求类型是 Get 还是 Post, 然后调用对应的 doGet/doPost 执行.

doGet:处理GET请求 doPost:处理POST请求 doPut:处理PUT请求 doDelete:处理DELETE请求 doHead:处理HEAD请求 doOptions:处理OPTIONS请求 doTrace:处理TRACE请求 通常情况下,在开发基于HTTP的servlet时,开发者只需要关心doGet和doPost方法,其它的方法需要开发者非常的熟悉HTTP编程,因此这些方法被认为是高级方法。 而通常情况下,我们实现的servlet都是从HttpServlet扩展而来。 doPut和doDelete方法允许开发者支持HTTP/1.1的对应特性; doHead是一个已经实现的方法,它将执行doGet但是仅仅向客户端返回doGet应该向客户端返回的头部的内容; doOptions方法自动的返回servlet所直接支持的HTTP方法信息; doTrace方法返回TRACE请求中的所有头部信息。 对于那些仅仅支持HTTP/1.0的容器而言,只有doGet, doHead 和 doPost方法被使用,因为HTTP/1.

下边是CSDN里边的一些讨论:
1.doGet和doPost的区别,在什么时候调用,为什么有时doPost中套用doGet
2.提交的form     method=Post就执行DOPOST,否则执行GOGET 套用是不管method是post还是get都执行dopost方法
3.get:你可以通过URL传参数。
http://www.csdn.net/index.asp?user=1234    , Post不行  
4.你的表单提交都有方法的,如果提交为get就调用get方法,用post就调用post方法.  
    get显示你传过去的参数,post则不显示.
5.通常的写法:先用doGet(),然后在doPost()中调用doGet(),这样就万无一失了
6. 简单的说,get是通过http     header来传输数据,有数量限制,而post则是通过http     body来传输数据,没有数量限制。
7.还有一点:get和post提交的数据量是不一样的.  
    get好像最多只能在url后跟64K(?具体多少忘记了),  
    post好像没这个限制,至少我post过5M以上的文本    
    还有url刷新时get好像可以不用重复提交原来提交的数据,  
    而post则会说内容已提交,想刷新请再提交.

posted @ 2011-05-24 23:58 liujg 阅读(464) | 评论 (0)编辑 收藏

转载 Vim 基本用法

这是我总结的一些基本用法,可能对初用者会有帮助,独乐乐不如众乐乐,是吧!

说明:以下黑色为vi和vim均有的一般功能,而红色为Vim(Vi Improved)所特有功能。Vim一般的Unix和Linux下均有安装。
 三种状态
Command: 任何输入都会作为编辑命令,而不会出现在屏幕上,任何输入都引起立即反映
Insert: 任何输入的数据都置于编辑寄存器,按ESC,可跳回command方式
Escape: 以“:”或者“/”为前导的指令,出现在屏幕的最下一行,任何输入都被当成特别指令。
 离开vi
:q! 离开vi,并放弃刚在缓冲区内编辑的内容。
:wq 将缓冲区内的资料写入磁盘中,并离开vi。
:x 同wq。
(注意—— :X 是文件加密,一定要与:x存盘退出相区别)
 进入输入模式
a (append) 由游标之后加入资料。
A 由该行之末加入资料。
i (insert) 由游标之前加入资料。
I 由该行之首加入资料。
o (open) 新增一行於该行之下供输入资料之用。
O 新增一行於该行之上供输入资料之用。
 删除与修改
x 删除游标所在该字元。
X 删除游标所在之前一字元。
r 用接於此指令之后的字元取代(replace)游标所在字元。如:ra将游标所在字元以 a 取代之。
R 进入取代状态,直到《ESC》为止。
s 删除游标所在之字元,并进入输入模式直到《ESC》。
S 删除游标所在之该行资料,并进入输入模式直到《ESC》。
 光标的移动
m<a-z> 设置书签<a-z>
‘<a-z> 移至书签<a-z>处
0 移至该行之首
$ 移至该行之末。
e 移动到下个字的最後一个字母
w 移动到下个字的第一个字母。
b 移动到上个字的第一个字母。
^ 移至该行的第一个字元处。
H 移至视窗的第一行。
M 移至视窗的中间那行。
L 移至视窗的最后一行。
G 移至该文件的最后一行。
+ 移至下一列的第一个字元处。
- 移至上一列的第一个字元处。
:n 移至该文件的第 n 列。
n+ 移至游标所在位置之后的第 n 列。
n- 移至游标所在位置之前的第 n 列。
<Ctrl><g> 显示该行之行号、文件名称、文件中最末行之行号、游标所在行号占总行号之百分比。

(Vim) 光标移动基本用法小解:
(这只要组合上边的功能就可以明白了,不用再一一讲解了吧!)
ge b w e
← ← ---→ --→
This is-a line, with special/separated/words (and some more).
←- ←-- -----------------→ ---→
GE B W E

 视窗的移动
<Ctrl><f> 视窗往下卷一页。
<Ctrl><b> 视窗往上卷一页。
<Ctrl><d> 视窗往下卷半页。
<Ctrl><u> 视窗往上卷半页。
<Ctrl><e> 视窗往下卷一行。
<Ctrl><y> 视窗往上卷一行。
 剪切、复制、删除
Operator + Scope = command
 Operator
d 剪切
y 复制。
p 粘帖,与 d 和 y 配和使用。可将最后d或y的资料放置於游标所在位置之行列下。
c 修改,类似delete与insert的组和。删除一个字组、句子等之资料,并插入新建资料。
 Scope
e 由游标所在位置至该字串的最后一个字元。
w 由游标所在位置至下一个字串的第一个字元。
b 由游标所在位置至前一个字串的第一个字元。
$ 由游标所在位置至该行的最后一个字元。
0 由游标所在位置至该行的第一个字元。
 整行动作
dd 删除整行。
D 以行为单位,删除游标后之所有字元。
cc 修改整行的内容。
yy 使游标所在该行复制到记忆体缓冲区。
 取消前一动作(Undo)
u 恢复最后一个指令之前的结果。
U 恢复游标该行之所有改变。
(vim) u 可以多次撤消指令,一次撤消一个操作,直至本次操作开始为止。
(vim) Ctrl+r 可以恢复撤消前内容,按多次可恢复多次。
 查找与替换
/字串 往游标之后寻找该字串。
?字串 往游标之前寻找该字串。
n 往下继续寻找下一个相同的字串。
N 往上继续寻找下一个相同的字串。
% 查找“(”,“)”,“{”,“}”的配对符。
s 搜寻某行列范围。
g 搜寻整个编辑缓冲区的资料。
:1,$s/old/new/g 将文件中所有的『old』改成『new』。
:10,20s/^/ / 将第10行至第20行资料的最前面插入5个空白。
(vim)
/字符串 后边输入查询内容可保存至缓冲区中,可用↑↓进行以往内容选择。
另外:将光标移动在选定单词下方按*,则可以选中此单词作为查询字符,可以避免输入一长串字符的麻烦。
 (vim) 大小写替换
首先用按v开启选择功能,然后用↑↓←→键来选定所要替换的字符,若是小写变大写,则按U;反之按u;
如果是选择单词,则可以在按v后,按w,最后按U/u,这样就可以将字符随意的改变大小写了,而不用删除后重新敲入。

 资料的连接
J 句子的连接。将游标所在之下一行连接至游标该行的后面。
 环境的设定
:set all 可设置的环境变量列表
:set 环境变量的当前值
:set nu 设定资料的行号。
:set nonu 取消行号设定。
:set ai 自动内缩。
:set noai 取消自动内缩。
(vim)
:set ruler 会在屏幕右下角显示当前光标所处位置,并随光移动而改变,占用屏幕空间较小,使用也比较方便,推荐使用。
:set hlsearch 在使用查找功能时,会高亮显示所有匹配的内容。
:set nohlsearch 关闭此功能。
:set incsearch 使Vim在输入字符串的过程中,光标就可定位显示匹配点。
:set nowrapscan 关闭查找自动回环功能,即查找到文件结尾处,结束查找;默认状态是自动回环

 ex指令
 读写资料
:10,20w test 将第10行至第20行的资料写入test文件。
:10,20w>>test 将第10行至第20行的资料加在test文件之后。
:r test 将test文件的资料读入编辑缓冲区的最后。
:e [filename] 编辑新的文件。
:e! [filename] 放弃当前修改的文件,编辑新的文件。
:sh 进入shell环境,使用exit退出,回到编辑器中。

:!cmd 运行命令cmd后,返回到编辑器中。
 删除、复制及搬移
:10,20d 删除第10行至第20行的资料。
:10d 删除第10行的资料。
:%d 删除整个编辑缓冲区。
:10,20co30 将第10行至第20行的资料复制至第30行之后。
:10,20mo30 将第10行至第20行的资料搬移至第30行之后。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jsufcz/archive/2009/02/11/3875956.aspx

posted @ 2011-05-03 14:25 liujg 阅读(239) | 评论 (0)编辑 收藏

OERR: ORA-12519


OERR: ORA-12519报错  
1、查询数据库当前的连接数:
select count(*) from v$process;
2、查询数据库允许的最大连接数:
select value from v$parameter where name = 'processes';
3、修改数据库允许的最大连接数:
alter system set processes = 300 scope = spfile;
4、重启数据库:
shutdown immediate;
startup; 

 


文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/7_databases/oracle/oraclejs/20091103/181042.html

posted @ 2011-01-30 13:36 liujg 阅读(546) | 评论 (0)编辑 收藏

原形设计模式,搞不懂

今天下载了个设计模式看,prototype模式就两页纸,看过了也没看出来它到底做什么,比较郁闷。我就不清楚那个copy方法到底做了什么?没有copy方法不行吗?


定义:

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. //通过拷贝创建新的对象跟通过继承创建有什么区别呢?
Prototype 模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的

细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象

通过请求原型对象拷贝它们自己来实施创建。

如何使用?

因为Java 中的提供clone()方法来实现对象的克隆(具体了解 clone()按这里),所以

Prototype 模式实现一下子变得很简单.

以勺子为例:

public abstract class AbstractSpoon implements Cloneable
{

    String spoonName;

    public void setSpoonName(String spoonName) {this.spoonName = spoonName;}

    public String getSpoonName() {return this.spoonName;}

    public Object clone()

     {

        Object object = null;

        try {
            object = super.clone();

        } catch (CloneNotSupportedException exception) {

            System.err.println("AbstractSpoon is not Cloneable");

        }

        return object;
    }

}

有两个具体实现(ConcretePrototype):

public class SoupSpoon extends AbstractSpoon

{

    public SoupSpoon()
     {

        setSpoonName("Soup Spoon");

    }

}

public class SaladSpoon extends AbstractSpoon

{

     public SaladSpoon()

     {
         setSpoonName("Salad Spoon");

     }

}

调用 Prototype 模式很简单:

AbstractSpoon spoon = new SoupSpoon();

AbstractSpoon spoon = new SaladSpoon();

posted @ 2007-11-29 16:23 liujg 阅读(299) | 评论 (0)编辑 收藏

java 关闭IE

本文代码来自以下连接。
http://www.developer.com/java/other/article.php/10936_2212401_3Introduction to the Java Robot Class in Java
代码简单说明:可以在1024*768的屏幕分辨率下关掉一个最大化的IE窗口。


import java.awt.*;
import java.awt.event.*;

/**this class will close an maxmimum IE window in the 1024*768's screen resolution's machine.*/
public class Robot04{
   public static void main(String[] args)
                             throws AWTException{
  Robot robot = new Robot();
  robot.mouseMove(1005,10);
  robot.delay(2000);
  robot.mousePress(InputEvent.BUTTON1_MASK);
  robot.delay(2000);
  robot.mouseRelease(InputEvent.BUTTON1_MASK);
   }//end main

}//end class Robot04

这个程序的GUI版本。
Robot04GUI.java
/**
 * Robot04GUI.java
 * create by kin. 2004/11/07.
 * Please enjoy this.
 */

import javax.swing.*;
import javax.swing.event.*;

import java.awt.event.*;
import java.awt.*;

/**Robot04's GUI version.*/
public class Robot04GUI extends JFrame {
 
 private JButton b = new JButton("Close IE");
 
 public Robot04GUI() {
  super("Close IE");
  getContentPane().add(b,BorderLayout.CENTER);
  b.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
    try {
     new Robot04().main(new String[]{}); 
    } catch (Exception ex) {
     ex.printStackTrace();
    }
   } 
  });
 } 
 
 public static void main(String[] args) {
  Robot04GUI r = new Robot04GUI();
  r.setSize(200,200);
  r.setVisible(true);
 } 

posted @ 2007-10-12 10:06 liujg 阅读(570) | 评论 (0)编辑 收藏

看了下java核心技术中的代理,还是很晕

需要记住的东东:
   1.代理类是在程序运行过程中创建的,一旦创建就变成了常规类,与虚拟机种的任何其他类没有什么区别.
  2.所有的代理类都扩展于Proxy类,一个代理类只有一个实例变量--调用处理器,它定义在Proxy的超类中,为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中.
   3.所有的代理类都覆盖了Object中的toString,equals和hashCode,如何所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke.Object中的其他方法clone,getClass没有被重新定义.

感觉就是把原来的方法,拿到代理类里面执行,在执行前后可以加入自己的代码而已,spring的IOC就是这样的.
例子:

import java.lang.reflect.*;
import java.util.*;

public class PorxyTest {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Object[] elements = new Object[1000];
  
  for (int i = 0; i < elements.length; i ++) {
   Integer value = i + 1;
   Class[] interfaces = value.getClass().getInterfaces();
   InvocationHandler handler = new TraceHandler(value);
   Object proxy = Proxy.newProxyInstance(null, interfaces, handler);
   elements[i] = proxy;
  }
  Integer key = new Random().nextInt(elements.length) + 1;
  int result = Arrays.binarySearch(elements, key);
  
  if (result >= 0)
   System.out.println(elements[result]);
 }

}

class TraceHandler implements InvocationHandler {
 private Object target;
 public TraceHandler(Object t) {
  target = t;
 }
 
 public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
  System.out.print(target);
  System.out.print("." + m.getName() + "(");
  if (args != null) {
   for (int i = 0; i < args.length; i ++) {
    System.out.print(args[i]);
    if (i < args.length - 1) {
     System.out.print(",");
    }
   }
  }
  System.out.println(")");
  return m.invoke(target, args);
 }
}

posted @ 2007-05-24 14:57 liujg 阅读(384) | 评论 (0)编辑 收藏

java程序员的5个好习惯()

http://today.java.net/pub/a/today/2006/08/24/five-habits-of-highly-profitable-developers.html


Five Habits of Highly Profitable Software Developers Five Habits of Highly Profitable Software Developers

by Robert J. Miller
08/24/2006

Software developers who have the ability to create and maintain quality software in a team environment are in high demand in today's technology-driven economy. The number one challenge facing developers working in a team environment is reading and understanding software written by another developer. This article strives to help software development teams overcome this challenge.

This article illustrates five habits of software development teams that make them more effective and therefore more profitable. It first will describe the demands the business team puts on its software development team and the software they create. Next it will explain the important differences between state-changing logic and behavior logic. Finally, it will illustrate the five habits using a customer account scenario as a case study.

Demands Placed on Developers by the Business

The business team's job is to determine what new value can be added to the software, while ensuring that the new value is most advantageous to the business. Here, "new value" refers to a fresh product or additional enhancement to an existing product. In other words, the team determines what new features will make the business the most money. A key factor in determining what the next new value will be is how much it will cost to implement. If the cost of implementation exceeds the potential revenue, then the new value will not be added.

The business team demands that the software development team be able to create new value at the lowest possible cost. It also demands that the software development team have the ability to add new value to a product without having the product's implementation costs increase over time. Furthermore, every time the business team requests new value, it demands that value be added without losing any existing value. Over time, the software will accrue enough value that the business team will demand documentation to describe the current value the software provides. Then the business team will use this documentation to help determine what the next new value will be.

Software development teams can best meet these demands by creating easy-to-understand software. Difficult-to-understand software results in inefficiencies throughout the development process. These inefficiencies increase the cost of software development and can include the unexpected loss of existing value, an increase in developer ramp-up time, and the delivery of incorrect software documentation. These inefficiencies can be reduced by converting the business team's demands, even if complex, into simple, easy-to-understand software.

Introducing Key Concepts: State and Behavior

Creating software that is easy to understand starts by creating objects that have state and behavior. "State" is an object's data that persists between method calls. A Java object can hold its state temporarily in its instance variables and can persist it indefinitely by saving it into a permanent data store. Here, a permanent data store can be a database or Web service. "State-changing methods" typically manage an object's data by retrieving it and persisting it to and from a remote data store. "Behavior" is an object's ability to answer questions based on state. "Behavior methods" answer questions consistently without modifying state and are often referred to as the business logic in an application.

Case Study: CustomerAccount object

The following ICustomerAccount interface defines methods an object must implement to manage a customer's account. It defines the ability to create a new active account, to load an existing customer's account status, to validate a prospective customer's username and password, and to validate that an existing account is active for purchasing products.

public interface ICustomerAccount {
//State-changing methods
public void createNewActiveAccount()
throws CustomerAccountsSystemOutageException;
public void loadAccountStatus()
throws CustomerAccountsSystemOutageException;
//Behavior methods
public boolean isRequestedUsernameValid();
public boolean isRequestedPasswordValid();
public boolean isActiveForPurchasing();
public String getPostLogonMessage();
}

Habit 1: Constructor Performs Minimal Work

The first habit is for an object's constructor to do as little work as possible. Ideally, its constructor will only load data into its instance variables using the constructor's parameters. In the following example, creating a constructor that performs minimal work makes the object easier to use and understand because the constructor performs only the simple task of loading data into the object's instance variables:

public class CustomerAccount implements ICustomerAccount{
//Instance variables.
private String username;
private String password;
protected String accountStatus;
//Constructor that performs minimal work.
public CustomerAccount(String username, String password) {
this.password = password;
this.username = username;
}
}

A constructor is used to create an instance of an object. A constructor's name is always the same as the object's name. Since a constructor's name is unchangeable, its name is unable to communicate the work it is performing. Therefore, it is best if it performs as little work as possible. On the other hand, state-changing and behavior method names use descriptive names to convey their more complex intent, as described in "Habit 2: Methods Clearly Convey Their Intent." As this next example illustrates, the readability of the software is high because the constructor simply creates an instance of the object, letting the behavior and state-changing methods do the rest.

Note: The use of "..." in the examples represents code that is necessary to run in a live scenario but is not relevant to the example's purpose.

String username = "robertmiller";
String password = "java.net";
ICustomerAccount ca = new CustomerAccount(username, password);
if(ca.isRequestedUsernameValid() && ca.isRequestedPasswordValid()) {
...
ca.createNewActiveAccount();
...
}

On the other hand, objects with constructors that do more than just load instance variables are harder to understand and more likely to be misused because their names do not convey their intent. For example, this constructor also calls a method that makes a remote call to a database or Web service in order to pre-load an account's status:

//Constructor that performs too much work!
public CustomerAccount(String username, String password)
throws CustomerAccountsSystemOutageException {
this.password = password;
this.username = username;
this.loadAccountStatus();//unnecessary work.
}
//Remote call to the database or web service.
public void loadAccountStatus()
throws CustomerAccountsSystemOutageException {
...
}

A developer can use this constructor and, not realizing it is making a remote call, end up making two remote calls:

String username = "robertmiller";
String password = "java.net";
try {
//makes a remote call
ICustomerAccount ca = new CustomerAccount(username, password);
//makes a second remote call
ca.loadAccountStatus();
} catch (CustomerAccountsSystemOutageException e) {
...
}

Or a developer can reuse this constructor to validate a prospective customer's desired username and password and be forced to make an unnecessary remote call since these behavior methods (isRequestedUsernameValid(), isRequestedPasswordValid()) don't need the account status:

String username = "robertmiller";
String password = "java.net";
try {
//makes unnecessary remote call
ICustomerAccount ca = new CustomerAccount(username, password);
if(ca.isRequestedUsernameValid() && ca.isRequestedPasswordValid()) {
...
ca.createNewActiveAccount();
...
}
} catch (CustomerAccountsSystemOutageException e){
...
}

Habit 2: Methods Clearly Convey Their Intent

The second habit is for all methods to clearly convey their intent through the names they are given. For example, isRequestedUsernameValid() lets the developer know that this method determines whether or not the requested username is valid. In contrast, isGoodUser() can have any number of uses: it can determine if the user's account is active, determine if the requested username and/or password are valid, or determine if the user is a nice person. Since this method is less descriptive, it is more difficult for a developer to figure out what its purpose is. In short, it is better for the method names to be long and descriptive than to be short and meaningless.

Long and descriptive method names help developer teams quickly understand the purpose and function of their software. Moreover, applying this technique to test method names allows the tests to convey the existing requirements of the software. For example, this software is required to validate that requested usernames and passwords are different. Using the method name testRequestedPasswordIsNotValidBecauseItMustBeDifferentThanTheUsername() clearly conveys the intent of the test and, therefore, the requirement of the software.

import junit.framework.TestCase;
public class CustomerAccountTest extends TestCase{
public void testRequestedPasswordIsNotValid
BecauseItMustBeDifferentThanTheUsername(){
String username = "robertmiller";
String password = "robertmiller";
ICustomerAccount ca = new CustomerAccount(username, password);
assertFalse(ca.isRequestedPasswordValid());
}
}

This test method could have easily been named testRequestedPasswordIsNotValid() or even worse testBadPassword(), both of which would make it hard to determine the precise intention of the test. Unclear or ambiguously named test methods result in a loss of productivity. The loss in productivity can be caused by an increase in ramp-up time used to understand the tests, the unnecessary creation of duplicated or conflicting tests, or the destruction of existing value in the object being tested.

Finally, descriptive method names reduce the need for both formal documentation and Javadoc comments.

Habit 3: An Object Performs a Focused Set of Services

The third habit is for each object in the software to be focused on performing a small and unique set of services. Objects that perform a small amount of work are easier to read and more likely to be used correctly because there is less code to digest. Moreover, each object in the software should perform a unique set of services because duplicating logic wastes development time and increases maintenance costs. Suppose in the future, the business team requests an update to the isRequestedPasswordValid() logic and two different objects have similar methods that perform the same work. In this case, the software development team would spend more time updating both objects than they would have had to spend updating just one.

As the case study illustrates, the purpose of the CustomerAccount object is to manage an individual customer's account. It first creates the account and later can validate that the account is still active for purchasing products. Suppose in the future, this software will need to give discounts to customers who have purchased more than ten items. Creating a new interface, ICustomerTransactions, and object, CustomerTransactions, to implement these new features will facilitate the ongoing goal of working with easy-to-understand software:

public interface ICustomerTransactions {
//State-changing methods
public void createPurchaseRecordForProduct(Long productId)
throws CustomerTransactionsSystemException;
public void loadAllPurchaseRecords()
throws CustomerTransactionsSystemException;
//Behavior method
public void isCustomerEligibleForDiscount();
}

This new object holds state-changing and behavior methods that store customer transactions and determine when a customer gets its ten-product discount. It should be easy to create, test, and maintain since it has a simple and focused purpose. The less effective approach is to add these new methods to the existing ICustomerAccount interface and CustomerAccount object, as seen below:

public interface ICustomerAccount {
//State-changing methods
public void createNewActiveAccount()
throws CustomerAccountsSystemOutageException;
public void loadAccountStatus()
throws CustomerAccountsSystemOutageException;
public void createPurchaseRecordForProduct(Long productId)
throws CustomerAccountsSystemOutageException;
public void loadAllPurchaseRecords()
throws CustomerAccountsSystemOutageException;
//Behavior methods
public boolean isRequestedUsernameValid();
public boolean isRequestedPasswordValid();
public boolean isActiveForPurchasing();
public String getPostLogonMessage();
public void isCustomerEligibleForDiscount();
}

As seen above, allowing objects to become large repositories of responsibility and purpose makes them harder to read and more likely to be misunderstood. Misunderstandings result in a loss in productivity, costing the business team time and money. In short, it is better for objects and their methods to be focused on performing a small unit of work.

Habit 4: State-Changing Methods Contain Minimal Behavior Logic

The fourth habit is for state-changing methods to contain a minimal amount of behavior logic. Intermixing state-changing logic with behavior logic makes the software more difficult to understand because it increases the amount of work happening in one place. State-changing methods typically retrieve or send data to a remote data store and, therefore, are prone to have problems in the production system. Diagnosing a system problem within a state-changing method is easier when the remote call is isolated and it has zero behavior logic. Intermixing also inhibits the development process because it makes it harder to unit test the behavior logic. For example, getPostLogonMessage() is a behavior method with logic based on the accountStatus's value:

public String getPostLogonMessage() {
if("A".equals(this.accountStatus)){
return "Your purchasing account is active.";
} else if("E".equals(this.accountStatus)) {
return "Your purchasing account has " +
"expired due to a lack of activity.";
} else {
return "Your purchasing account cannot be " +
"found, please call customer service "+
"for assistance.";
}
}

loadAccountStatus() is a state-changing method that loads the accountStatus's value from a remote data store:

public void loadAccountStatus()
throws CustomerAccountsSystemOutageException {
Connection c = null;
try {
c = DriverManager.getConnection("databaseUrl", "databaseUser",
"databasePassword");
PreparedStatement ps = c.prepareStatement(
"SELECT status FROM customer_account "
+ "WHERE username = ? AND password = ? ");
ps.setString(1, this.username);
ps.setString(2, this.password);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
this.accountStatus=rs.getString("status");
}
rs.close();
ps.close();
c.close();
} catch (SQLException e) {
throw new CustomerAccountsSystemOutageException(e);
} finally {
if (c != null) {
try {
c.close();
} catch (SQLException e) {}
}
}
}

Unit testing the getPostLogonMessage() method can easily be done by mocking the loadAccountStatus() method. Each scenario can then be tested without making a remote call to a database. For example, if the accountStatus is "E" for expired, then getPostLogonMessage() should return "Your purchasing account has expired due to a lack of activity," as follows:

public void testPostLogonMessageWhenStatusIsExpired(){
String username = "robertmiller";
String password = "java.net";
class CustomerAccountMock extends CustomerAccount{
...
public void loadAccountStatus() {
this.accountStatus = "E";
}
}
ICustomerAccount ca = new CustomerAccountMock(username, password);
try {
ca.loadAccountStatus();
}
catch (CustomerAccountsSystemOutageException e){
fail(""+e);
}
assertEquals("Your purchasing account has " +
"expired due to a lack of activity.",
ca.getPostLogonMessage());
}

The inverse approach is to put both the getPostLogonMessage() behavior logic and the loadAccountStatus() state-changing work into one method. The following example illustrates what not to do:

public String getPostLogonMessage() {
return this.postLogonMessage;
}
public void loadAccountStatus()
throws CustomerAccountsSystemOutageException {
Connection c = null;
try {
c = DriverManager.getConnection("databaseUrl", "databaseUser",
"databasePassword");
PreparedStatement ps = c.prepareStatement(
"SELECT status FROM customer_account "
+ "WHERE username = ? AND password = ? ");
ps.setString(1, this.username);
ps.setString(2, this.password);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
this.accountStatus=rs.getString("status");
}
rs.close();
ps.close();
c.close();
} catch (SQLException e) {
throw new CustomerAccountsSystemOutageException(e);
} finally {
if (c != null) {
try {
c.close();
} catch (SQLException e) {}
}
}
if("A".equals(this.accountStatus)){
this.postLogonMessage = "Your purchasing account is active.";
} else if("E".equals(this.accountStatus)) {
this.postLogonMessage = "Your purchasing account has " +
"expired due to a lack of activity.";
} else {
this.postLogonMessage = "Your purchasing account cannot be " +
"found, please call customer service "+
"for assistance.";
}
}

In this implementation the behavior method getPostLogonMessage() contains zero behavior logic and simply returns the instance variable this.postLogonMessage. This implementation creates three problems. First, it makes it more difficult to understand how the "post logon message" logic works since it is embedded in a method performing two tasks. Second, the getPostLogonMessage() method's reuse is limited because it must always be used in conjunction with the loadAccountStatus() method. Finally, in the event of a system problem the CustomerAccountsSystemOutageException will be thrown, causing the method to exit before it sets this.postLogonMessage's value.

This implementation also creates negative side effects in the test because the only way to unit test this getPostLogonMessage() logic is to create a CustomerAccount object with a username and password for an account in the database with an accountStatus set to "E" for expired. The result is a test that makes a remote call to a database. This causes the test to run slower and to be prone to unexpected failures due to changes in the database. This test has to make a remote call to a database because the loadAccountStatus() method also contains the behavior logic. If the behavior logic is mocked, then the test is testing the mocked object's behavior instead of the real object's behavior.

Habit 5: Behavior Methods Can Be Called in Any Order

The fifth habit is to ensure that each behavior method provides value independent of any other behavior method. In other words, an object's behavior methods can be called repeatedly and in any order. This habit allows the object to deliver consistent behavior. For example, CustomerAccount's isActiveForPurchasing() and getPostLogonMessage() behavior methods both use the accountStatus's value in their logic. Each of these methods should be able to function independently of the other. For instance, one scenario can require that isActiveForPurchasing() be called, followed by a call to getPostLogonMessage():

ICustomerAccount ca = new CustomerAccount(username, password);
ca.loadAccountStatus();
if(ca.isActiveForPurchasing()){
//go to "begin purchasing" display
...
//show post logon message.
ca.getPostLogonMessage();
} else {
//go to "activate account" display
...
//show post logon message.
ca.getPostLogonMessage();
}

A second scenario can require that getPostLogonMessage() is called without isActiveForPurchasing() ever being called:

ICustomerAccount ca = new CustomerAccount(username, password);
ca.loadAccountStatus();
//go to "welcome back" display
...
//show post logon message.
ca.getPostLogonMessage();

The CustomerAccount object will not support the second scenario if getPostLogonMessage() requires isActiveForPurchasing() to be called first. For example, creating the two methods to use a postLogonMessage instance variable so that its value can persist between method calls supports the first scenario but not the second:

public boolean isActiveForPurchasing() {
boolean returnValue = false;
if("A".equals(this.accountStatus)){
this.postLogonMessage = "Your purchasing account is active.";
returnValue = true;
} else if("E".equals(this.accountStatus)) {
this.postLogonMessage = "Your purchasing account has " +
"expired due to a lack of activity.";
returnValue = false;
} else {
this.postLogonMessage = "Your purchasing account cannot be " +
"found, please call customer service "+
"for assistance.";
returnValue = false;
}
return returnValue;
}
public String getPostLogonMessage() {
return this.postLogonMessage;
}

On the other hand, if both methods derive their logic independently of each other, then they will support both scenarios. In this preferred example, postLogonMessage is a local variable created exclusively by the getPostLogonMessage() method:

public boolean isActiveForPurchasing() {
return this.accountStatus != null && this.accountStatus.equals("A");
}
public String getPostLogonMessage() {
if("A".equals(this.accountStatus)){
return "Your purchasing account is active.";
} else if("E".equals(this.accountStatus)) {
return "Your purchasing account has " +
"expired due to a lack of activity.";
} else {
return "Your purchasing account cannot be " +
"found, please call customer service "+
"for assistance.";
}
}

An added benefit of making these two methods independent of each other is that the methods are easier to comprehend. For example, isActiveForPurchasing() is more readable when it is only trying to answer the "is active for purchasing" question as opposed to when it is also trying to set the "post logon message". Another added benefit is that each method can be tested in isolation, which also makes the tests easier to comprehend:

public class CustomerAccountTest extends TestCase{
public void testAccountIsActiveForPurchasing(){
String username = "robertmiller";
String password = "java.net";
class CustomerAccountMock extends CustomerAccount{
...
public void loadAccountStatus() {
this.accountStatus = "A";
}
}
ICustomerAccount ca = new CustomerAccountMock(username, password);
try {
ca.loadAccountStatus();
} catch (CustomerAccountsSystemOutageException e) {
fail(""+e);
}
assertTrue(ca.isActiveForPurchasing());
}
public void testGetPostLogonMessageWhenAccountIsActiveForPurchasing(){
String username = "robertmiller";
String password = "java.net";
class CustomerAccountMock extends CustomerAccount{
...
public void loadAccountStatus() {
this.accountStatus = "A";
}
}
ICustomerAccount ca = new CustomerAccountMock(username, password);
try {
ca.loadAccountStatus();
} catch (CustomerAccountsSystemOutageException e) {
fail(""+e);
}
assertEquals("Your purchasing account is active.",
ca.getPostLogonMessage());
}
}

Conclusion

Following these five habits will help development teams create software that everyone on the team can read, understand, and modify. When software development teams create new value too quickly and without consideration for the future, they tend to create software with increasingly high implementation costs. Inevitably, bad practices will catch up to these teams when they have to revisit the software for future comprehension and modification. Adding new value to existing software can be very expensive if the software is difficult to comprehend. However, when development teams apply these best practices, they will provide new value at the lowest possible cost to their business team.

 


posted @ 2006-12-15 17:27 liujg 阅读(793) | 评论 (0)编辑 收藏

<2006年12月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

文章分类

文章档案

相册

收藏夹

boddiy

搜索

最新评论

阅读排行榜

评论排行榜