存档

文章标签 ‘java’

Java方法不应超过15行

2013年3月20日 没有评论

大多数人都会说,方法不能太长,但也不能定死,要具体问题具体分析。再追问一下,有人会说,不超过200行,100行,50行,30行都有。另有人说,面向对象风格的可以短些,面向过程风格的可以长些。也有人说,一个方法不超过一屏幕就行(姑且不论显示器大小,字体大小和分辨率问题)。

先摘录一段Martin Fowler《重构》P110-P111 中的一段话:

人们有时会问我,一个函数多长才合适?在我看来,长度不是问题,关键在于函数名称和函数本体之间的语义距离(semantic distance)。如果提炼动作(extracting)可以强化代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。

说了直白点,函数名是干什么,函数体是怎么干。当2者不是那么能容易分辨出来时,就需要提炼出函数来,让代码更好懂。

既然大师都说了,长度不是问题了吗,那干吗还要追究函数长度规范呢?
其实大师讲的是对于懂得编程之道的人而言,不用关心长度,因为小函数是必然的结果。但在开发实践中,适当的函数长度限制值,可以给予我们警示,为什么这个方法写长了?哪里不对劲了?
对于这个问题我的回答是:Java函数不应超过15行。
为什么定这个数呢?
一方面,这个是基于多年开发实践的总结。15行其实已经是一个比较宽松的标准,开发人员稍加培训就可以实际贯彻,而符合这个标准的函数也能达到良好的可读性。

另一方面,这是基于对函数复杂度和代码行数之间关联关系的认识。
我采用一个简单的数学公式:

方法复杂度 >= 代码行数^2 (该公式参考于 gerald m. weinberg 《质量·软件·管理(第1卷)——系统思维》)

以下为了简化,只以最低增长方式,平方数进行计算。
以一个函数的行数,从1行到20行为例,见下图:

java

当函数行数到达10行以上,函数复杂度就开始大幅度攀升。
当代码行数达到20行时(复杂度400),其复杂度已经是14行时(复杂度 196)的2倍。
因此我将代码规范定在15行。

下面我们运行此公式,来分析一下提炼小函数的好处。
例如,有1个100行的函数,分解为11(含自身)个10行的函数。
原复杂度= 100^2=10000。
分解后复杂度= 11×(10^2)= 1100。
分解后的复杂几乎是分解前的 1/9,几乎降低了1个数量级。这就解释了为什么长方法分解之后,可维护性大幅度提高。

也就无怪乎有同学表示维护1000行以上的函数,有种生不如死的感受。

 

分类: java, 项目管理 标签:

位、字节、字符、汉字的关系

2013年3月16日 没有评论

1 bit 二进制数据
1 byte = 8 bit
1 字母 = 1 byte = 8 bit
1 汉字 = 2 byte = 16 bit
1. bit:位
一个二进制数据0或1,是1bit;

2. byte:字节
1 byte = 8 bit

3. 一个英文字符占一个字节;
1 字母 = 1 byte = 8 bit

4. 一个汉字占2个字节;
1 汉字 = 2 byte = 16 bit

5. 标点符号
A>. 汉字输入状态下,默认为全角输入方式;
B>. 英文输入状态下,默认为半角输入方式;

C>. 全角输入方式下,标点符号占2字节;
D>. 半角输入方式下,标点符号占1字节;

故:汉字输入状态下的字符,占2个字节 (但不排除,自己更改了默认设置);
英文输入状态下的字符,占1个字节 (但不排除,自己更改了默认设置);

 

老美在发明电脑时,肯定以自己的英文字母-即他们自认为的字符为最小的存储计量单位,于是也就有了不规范的1字符=1byte, 岂不知还有我们伟大的汉字计量单位,NND,一个汉字也是一个字符,我们的1汉字字符就等于2byte,后来,他们可能意识到这个尴尬的问题,于是又标榜为:一个字母为一个标准字符,去球吧,谁整天没事说个字符还“标准字符”,所以啊,个人认为:字符,不能用于标准的计量单位。

分类: java 标签: , , ,

HttpServletResponse的addHeader与setHeader区别

2013年3月8日 没有评论

setHeader(name,value) :如果Header中没有定义则添加,如果已定义则用新的value替换原用value值。

addHeader(name,value) :如果Header中没有定义则添加,如果已定义则原有value不改变。

分类: java 标签: , ,

Servlet线程安全问题

2013年3月5日 1 条评论

由于Servlet默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全问题。然而,很多人编写Servlet程序时并没有注意到多线程安全的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题。

Servlet的多线程机制
 
  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。

servlet线程池

这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

  Servlet的线程安全问题

  Servlet的线程安全问题主要是由于实例变量使用不当而引起的,这里以一个现实的例子来说 阅读全文…

分类: java 标签: , ,

Java程序员应该知道的10个eclipse调试技巧

2013年3月4日 2 条评论

在开始前给大家提3点建议!
  不要使用System.out.println作为调试工具
  把所有涉及到的组件日志级别激活并使用
  使用日志分析器来读取日志

1.条件断点

 条件断点

  如果你不知道如何添加断点,只需点击左边面板(行号前面)断点即被创建。在调试界面中,“断点”视图会把所有被创建的断点列出来。我们可以给它加一个布尔条件,也就是说,该断点会被激活并且如果布尔条件为真,就会执行该断点,否则将会跳过往下执行。

  2.异常断点

 异常断点

  在断点视图中,有一个J!标记按钮!我们可以使用该按钮来添加一个Java异常断点。例如,我们想让程序在遇到空指针异常(NullPointerException)时,仍然能继续调试,那么我们可以使用该按钮来添加一个异常断点!

  3.监视点

 监视点

  这是一个非常好的功能,当选定的属性访问或修改程序时,程序会停止执行并允许进行调试。在Outline视图中选择一个类变量并从上下文菜单中选择切换监视点,属性监视点将会被创建,在断点(Breakpoints)视图中会把所有监视点用列表的形式显示出来。

  4.评估/检查 阅读全文…

分类: java 标签: , ,

java中byte转换int时为何与0xff进行与运算

2013年3月3日 没有评论

先看代码:

public static String bytes2HexString(byte[] b) {
 String ret = "";
 for (int i = 0; i < b.length; i++) {
 String hex = Integer.toHexString(b[ i ] & 0xFF);
 if (hex.length() == 1) {
 hex = '0' + hex;
 }
 ret += hex.toUpperCase();
 }
 return ret;
}

上面是将byte[]转化十六进制的字符串,注意这里b[ i ] & 0xFF将一个byte和 0xFF进行了与运算,然后使用Integer.toHexString取得了十六进制字符串,可以看出:b[ i ] & 0xFF 运算后得出的仍然是个int,那么为何要和 0xFF 进行与运算呢?直接 Integer.toHexString(b[ i ]);,将 byte 强转为 int 不行吗?答案是不行的。

其原因在于:
1.byte的大小为 8bits 而 int 的大小为 32bits ;
2.java的二进制采用的是补码形式 ;

在这里先温习下计算机基础理论:

byte是一个字节保存的,有8个位,即8个0、1。
8位的第一个位是符号位,也就是说0000 0001代表的是数字1 ,1000 0001代表的就是-1 。
所以正数最大位0111 1111,也就是数字127;负数最大为1111 1111,也就是数字-128 。

上面说的是二进制原码,但是在java中采用的是补码的形式,下面介绍下什么是补码(补码就是原码按位取反再加1,下边的解释有些抽象。):

1、反码:
一个数如果是正,则它的反码与原码相同;
一个数如果是负,则符号位为1,其余各位是对原码取反;

2、补码:利用溢出,我们可以将减法变成加法
对于十进制数,从9得到5可用减法:
9-4=5 因为4+6=10,我们可以将6作为4的补数
改写为加法:
9+6=15(去掉高位1,也就是减10)得到5.

对于十六进制数,从c到5可用减法:
c-7=5 因为7+9=16 将9作为7的补数
改写为加法:
c+9=15(去掉高位1,也就是减16)得到5.

在计算机中,如果我们用1个字节表示一个数,一个字节有8位,超过8位就进1,在内存中情况为(100000000),进位1被丢弃。

⑴一个数为正,则它的原码、反码、补码相同
⑵一个数为负,则符号位为1,其余各位是对原码取反,然后整个数加1

- 1的原码为 10000001
- 1的反码为 11111110
+ 1
- 1的补码为 11111111

0的原码为 00000000
0的反码为 11111111(正零和负零的反码相同)
+1
0的补码为 100000000(舍掉打头的1,正零和负零的补码相同)
Integer.toHexString的参数是int,如果不进行&0xff,那么当一个byte会转换成int时,由于int是32位,而byte只有8位这时会进行补位,例如补码11111111的十进制数为 -1 ,转换为int时变为 11111111111111111111111111111111,好多1啊,呵呵!即 0xffffffff,但是这个数是不对的,这种补位就会造成误差。

和0xff相与后,高24比特就会被清0了,结果就对了。

重要信息:

Java中的一个byte,其范围是-128~127的,而Integer.toHexString的参数本来是int,如果不进行&0xff,那么当一个byte会转换成int时,对于负数,会做位扩展,举例来说,一个byte的-1(即0xff),会被转换成int的-1(即0xffffffff),那么转化出的结果就不是我们想要的了。

而0xff默认是整形,所以,一个byte跟0xff相与会先将那个byte转化成整形运算,这样,结果中的高的24个比特就总会被清0,于是结果总是我们想要的。

分类: java 标签:

如何成为强大的java程序员?

2013年3月2日 1 条评论

很多人已经有了几年的开发经验,但还是没有充分发挥自己的潜力,本文总结了十种程序员无法意识到自己潜力的原因,意在让更多程序员发掘出自己的潜力,从而成为强大的developer。

1. 太害怕学不会新的工具、语言和框架

一般的程序员会墨守他们最喜欢的工具,而不希望学习新的,因为他们认为,离开了那些语言和工具,多年的经验就会付诸东流。而强大的程序员会拥抱那些挑战和机会,积极地学习新的工作方式。

2. 直到特性“完成”的时候才会提交。(但永远都不会完成!)

他在MarkedUp公司中把这种行为叫做“囤积提交(commit hoarding)”。有些程序员没有足够的信心来承受团队中其他成员的批评和审查,因此会把自己的工作藏起来,直到“完成”状态才提交。

这种开发者会损害团队中其他人员的生产力,因为团队看不到他每天的成果,而且他也不会在正常开发的过程中寻求帮助,这样就会造成很多“最后一分钟”的缺陷,从而让交付延迟。而强大的程序员会知道,代码并不是他们自己,因此会把代码经常自信地呈现在其他团队成员的眼前,获得批评和建议。

3. 只是“知其然”会很危险

在微软最近在C# 5.0中引入的async和await关键字为例,这两个关键字会让创建和管理异步调用变得很容易,但是也会造成上下文切换、对共享资源进行多线程访问的成本,仅仅对此有基本了解的程序员会盲目地使用这些特性,把所有I/O调用都封装成C#中的Task对象,这会创建出危险的、不可预测的而且非常难以测试的代码。

好的开发者不仅“知其然”,而且会了解为什么这么做以及应该在什么样的条件下使用。

4. 分析瘫痪(Analysis paralysis)

分析瘫痪是指在程序开发初期进行系统分析,常因为太过执着于控制所有可能的变化和意外,而造成大量时间的浪费,裹足不前。这是一种很经典的问题,会影响很多一般的程序员。它通常是由过度分析造成的,但是Aaron认为其根本原因在于不敢做出坏的决定。一般的程序员会担心犯错,只想一次成功。

而强大的程序员不会害怕,他们会编写很烂的代码,对其进行单元测试,如果认为无法达到目的,就会在45分钟之内把它抛弃。强大的程序员会积极地限制用来研究的时间,因为他们知道那是个陷阱——看起来是有效的,但经常都无效。

5. 没有对工具和开发过程投入

阅读全文…

分类: java 标签:

解决HttpClient:org.apache.http.ProtocolException异常

2013年3月2日 没有评论

现象:用httpclient发送http请求时,客户端返回:

org.apache.http.client.ClientProtocolException
 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:822)
 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754)
 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732)
 ……
Caused by: org.apache.http.ProtocolException: Content-Length header already present
 at org.apache.http.protocol.RequestContent.process(RequestContent.java:67)
 at org.apache.http.protocol.ImmutableHttpProcessor.process(ImmutableHttpProcessor.java:108)
 at org.apache.http.protocol.HttpRequestExecutor.preProcess(HttpRequestExecutor.java:174)
 at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:462)
 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
 ... 27 more

原因:查看httpclient中RequestContent类的process方法,看到当body非空时,会自动加上Content-Length请求头及其对应值,不需要自己手动加上它。源码如下:

</pre>
public void process(final HttpRequest request, final HttpContext context)
throws HttpException, IOException {
if (request == null) {
throw new IllegalArgumentException("HTTP request may not be null");
}
if (request instanceof HttpEntityEnclosingRequest) {
if (request.containsHeader(HTTP.TRANSFER_ENCODING)) {
throw new ProtocolException("Transfer-encoding header already present");
}
if (request.containsHeader(HTTP.CONTENT_LEN)) {
throw new ProtocolException("Content-Length header already present");
}
ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity();
if (entity == null) {
request.addHeader(HTTP.CONTENT_LEN, "0");
return;
}
// Must specify a transfer encoding or a content length
if (entity.isChunked() || entity.getContentLength() < 0) {
if (ver.lessEquals(HttpVersion.HTTP_1_0)) {
throw new ProtocolException(
"Chunked transfer encoding not allowed for " + ver);
}
request.addHeader(HTTP.TRANSFER_ENCODING, HTTP.CHUNK_CODING);
} else {
request.addHeader(HTTP.CONTENT_LEN, Long.toString(entity.getContentLength()));
}
// Specify a content type if known
if (entity.getContentType() != null && !request.containsHeader(
HTTP.CONTENT_TYPE )) {
request.addHeader(entity.getContentType());
}
// Specify a content encoding if known
if (entity.getContentEncoding() != null && !request.containsHeader(
HTTP.CONTENT_ENCODING)) {
request.addHeader(entity.getContentEncoding());
}
}
}

解决方法:remove掉Content-Length后,不再抛异常,请求正常返回。

分类: java 标签: ,

错误解决:Some projects cannot be imported because they already exist in the workspace

2013年2月27日 没有评论

eclipse导入项目时出现:Some projects cannot be imported because they already exist in the workspace

解决方法如下:

取消勾选:”copy projects into workspace”,再重新导入即可

分类: java 标签: ,

maven中jar包冲突的解决办法

2013年2月21日 没有评论

把冲突的jar包exclusion即可

<dependency>  
  <groupId>org.hibernate</groupId>  
  <artifactId>hibernate</artifactId>  
  <version>3.2.5.ga</version>  
  <exclusions>  
    <exclusion>  
      <groupId>cglib</groupId>  
      <artifactId>cglib</artifactId>  
    </exclusion>  
  </exclusions>  
</dependency>  
分类: java 标签: ,