Mysql恢复一个表中删除的数据

在开发过程中,经常会因为误操作将数据或者表都删除。假如没有有效的数据备份,客户数据丢了那就损失惨重。今天教给大家一种比较”传统”数据恢复方法,通过Mysql的binlog进行恢复。

原理

在Mysql中,执行的一切SQL语句都会以二进制形式存储在binlog文件中,那么我们就可以将删除之前的所有sql重新执行一次,来达到数据恢复的目的。binlog目录在 {mysql}/data/  如下图:

1F966D80-EFF4-4F12-9C66-4E78C7BCC930PS:文件个数取决于数据库大小,一般是按照同等大小切割的。

假设

假设我今天将一个表名为 foo 的数据不小心删除了,现在来进行恢复。

具体步骤

1.使用mysqlbinlog将binlog转换为sql文件.

../bin/mysqlbinlog mysql-bin-m.000001 --base64-output=decode-rows --verbose > bin.00001.sql

这个命令是将第一个binlog文件  mysql-bin-m.000001 执行过的SQL进行解析,并保存在bin.00001.sql中。使用mysqlbinlog还可以过滤数据库或者开始结束时间(回滚到误操作之前),如:

../bin/mysqlbinlog mysql-bin-m.000001 
--database=test 
--base64-output=decode-rows 
--start-datetime="2004-12-25 11:25:56"
--stop-datetime="2005-12-25 11:25:56"
--verbose > bin.00001.sql

执行成功后会在当前目录下发现一个新的文件:bin.00001.sql

2 过滤出需要恢复的SQL

由于bin.00001.sql 中包含了全部的SQL,因此我们可以使用grep 过滤出需要恢复的sql语句,-wr 表示完全匹配”foo”,  避免误过滤数据。

grep -wr "foo" bin.00001.sql > redo.sql

这个时候查看下redo.sql 是否还需要处理,一般需要删除被截断的DDL,如:

CREATE TABLE `foo` (

然后需要在结尾处增加一个分号,方便导入到数据库中.

awk '$0=$0";"'redo.sql > redo.sql

可以看到每个语句结尾处都自动加上了分号。

3 将SQL文件 source到数据库中.

use test;
source {mysql}/data/redo.sql

执行后查看 affect rows 是否与恢复前一样,简单验证。

总结

通过mysqlbinlog恢复文件适合数据量较小的数据库,如果binlog文件特别大,处理时间会较长。另外如果文件特别多需要循环处理,可以根据项目自己定制一个脚本。

 参考:

https://dev.mysql.com/doc/refman/5.0/en/mysqlbinlog.html#option_mysqlbinlog_start-datetime
http://stackoverflow.com/questions/6924823/how-to-recover-just-deleted-rows-in-mysql

 

TOMCAT – SEVERE: Context [] startup failed due to previous errors

问题

最近在开发中碰到一个tomcat启动后部署程序失败,访问所有的URL全部都是404,但是在日志中没有显示任何关于错误的堆栈信息,仅仅显示:

Sep 07, 2015 7:30:01 PM org.apache.catalina.core.StandardContext startInternal
SEVERE: Error listenerStart
Sep 07, 2015 7:30:01 PM org.apache.catalina.core.StandardContext startInternal
SEVERE: Context [] startup failed due to previous errors
Sep 07, 2015 7:30:01 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8180"]
Sep 07, 2015 7:30:01 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8189"]
Sep 07, 2015 7:30:01 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 13378 ms

也不知道到是什么具体原因,通过万能的google搜索一番,终于发现问题所在。

原因

由于项目使用的是logback作为日志输出,因此没有使用的logging.properties 配置文件。但是tomcat使用的依然是传统log4j配置方式。因此日志没有打印出来。

解决方法

在 WEB-INF/classes 目录下增加文件logging.properties,写入如下信息:

org.apache.catalina.core.ContainerBase.[Catalina].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].handlers = java.util.logging.ConsoleHandler
总结

系统日志是解决问题的首要途径,只要查看到了异常信息才能进一步解决问题。因此设置好日志级别显示后重新启动,查看日志异常信息。基本上原因可以归结为,造成tomcat无法启动的错误,例如:

  1. 项目配置文件出错,例如: web.xml
  2. 缺少依赖的class文件.
  3. Spring容器无法启动,部分Bean创建失败,具体的原因很多,可以指定一些
  4. Tomcat 配置参数错误,如果修改了conf/ 下或者 bin/ 下的文件.

希望给大家带来帮助,如果有问题请回复在评论中.

解决C3P0获取数据库连接hang住问题

最近对服务器进行压力测试后发现吞吐一直上不去,查看线程发现大多数线程卡在了获取数据库连接,起初我觉得是数据库连接不够了,后台调到了100依然没有解决问题.

"1658227@qtp-25018827-56" - Thread t@321  
   java.lang.Thread.State: WAITING  
      at java.lang.Object.wait(Native Method)  
      - waiting on <103344b> (a com.mchange.v2.resourcepool.BasicResourcePool)  
      at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1315)  
      at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:557)  
      at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:477)  
      at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:525)  
      at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:128)  
      at org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource(DefaultSqlSessionFactory.java:72)  
      at org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession(DefaultSqlSessionFactory.java:32)  

我们对系统进行压力测试QPS在200左右,理论上20个数据库的连接基本够用了,但是当持续一段时间后连接池的所有线程都在busy状态.通过研究c3p0官方文档,终于发现事情的真相:

numHelperThreads 

Default: 3
c3p0 is very asynchronous. Slow JDBC operations are generally performed by helper threads that don’t hold contended locks. Spreading these operations over multiple threads can significantly improve performance by allowing multiple operations to be performed simultaneously.

 

numHelperThreads 是C3P0内部辅助的线程数量,将参数调整大后会显著提高连接池效率。在我最近开发的项目中,通过测试发现,单点tomcat QPS 从设置这个参数前的50 暴增到800。

在spring的连接池配置中改为 10.

 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
 destroy-method="close">
 ....
 <property name="numHelperThreads">
 <value>10</value>
 </property>

 </bean>

问题解决,基本没有出现获取连接池等待的情况,系统吞吐量直线上升.

在vim下golang的语法高亮显示(golang学习)

配置语法高亮显示

在目录~/.vim/syntax/ 下创建文件 go.vim:(如果syntax不存在请先使用mkdir syntax进行创建)

" Vim syntax file
" Language: Go
" Maintainer: David Daub <david.daub@googlemail.com>
" Last Change: 2009 Nov 15
" Version: 0.1
"
" Early version. Took some (ok, most) stuff from existing syntax files like
" c.vim or d.vim.
"
"
" Todo:
" - very much

" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
 finish
endif


" A bunch of useful Go keywords
syn keyword goStatement select
syn keyword goStatement defer
syn keyword goStatement fallthrough range type
syn keyword goStatement return

syn keyword goClause import package
syn keyword goConditional if else switch
syn keyword goBranch goto break continue
syn keyword goLabel case default
syn keyword goRepeat for
syn keyword goType struct const interface func
syn keyword goType var map
syn keyword goType uint8 uint16 uint32 uint64
syn keyword goType int8 int16 int32 int64
syn keyword goType float32 float64
syn keyword goType float32 float64
syn keyword goType byte
syn keyword goType uint int float uintptr string

syn keyword goConcurrent chan go

syn keyword goValue nil
syn keyword goBoolean true false

syn keyword goConstant iota

" Builtin functions
syn keyword goBif len make new close closed cap map

" According to the language specification it is not garanteed to stay in the
" language. See http://golang.org/doc/go_spec.html#Bootstrapping
syn keyword goBif print println panic panicln

" Commants
syn keyword goTodo contained TODO FIXME XXX
syn match goLineComment "\/\/.*" contains=@Spell,goTodo
syn match goCommentSkip "^[ \t]*\*\($\|[ \t]\+\)"
syn region goComment start="/\*" end="\*/" contains=@Spell,goTodo

" Numerals
syn case ignore
"integer number, or floating point number without a dot and with "f".
syn match goNumbers display transparent "\<\d\|\.\d" contains=goNumber,goFloat,goOctError,goOct
syn match goNumbersCom display contained transparent "\<\d\|\.\d" contains=goNumber,goFloat,goOct
syn match goNumber display contained "\d\+\(u\=l\{0,2}\|ll\=u\)\>"

" hex number
syn match goNumber display contained "0x\x\+\(u\=l\{0,2}\|ll\=u\)\>"

" oct number
syn match goOct display contained "0\o\+\(u\=l\{0,2}\|ll\=u\)\>" contains=goOctZero
syn match goOctZero display contained "\<0"

syn match goFloat display contained "\d\+\.\d*\(e[-+]\=\d\+\)\="
syn match goFloat display contained "\d\+e[-+]\=\d\=\>"
syn match goFloat display "\(\.[0-9_]\+\)\(e[-+]\=[0-9_]\+\)\=[fl]\=i\=\>"

" Literals
syn region goString start=+L\="+ skip=+\\\\\|\\"+ end=+"+ contains=@Spell

syn match goSpecial display contained "\\\(x\x\+\|\o\{1,3}\|.\|$\)"
syn match goCharacter "L\='[^\\]'"
syn match goCharacter "L'[^']*'" contains=goSpecial


hi def link goStatement Statement
hi def link goClause Preproc
hi def link goConditional Conditional
hi def link goBranch Conditional
hi def link goLabel Label
hi def link goRepeat Repeat
hi def link goType Type
hi def link goConcurrent Statement
hi def link goValue Constant
hi def link goBoolean Boolean
hi def link goConstant Constant
hi def link goBif Function
hi def link goTodo Todo
hi def link goLineComment goComment
hi def link goComment Comment
hi def link goNumbers Number
hi def link goNumbersCom Number
hi def link goNumber Number
hi def link goFloat Float
hi def link goOct Number
hi def link goOctZero Number
hi def link goString String
hi def link goSpecial Special
hi def link goCharacter Character


let b:current_syntax = "go"
设置自动文件检测

设置完语法高亮后,希望每次打开以 .go结尾的文件都自动开启,需要在目录 ~/.vim/ftdetect/ 下创建另一个 go.vim ,内容如下:

au BufRead,BufNewFile *.go set filetype=go
默认开启语法高亮

最后编辑文件 ~/.vimrc 将语法高亮开启:

syntax enable
syntax on
filetype on

编辑一个go文件,查看效果:

golang.vim

linux下安装go(golang学习)

最近在学习go语言,下面简单记录以下linux下安装go语言的过程,免得下次忘记.

下载

找到适合自己版本的标准go包,两个下载地址(没有翻墙情况下请使用百度网盘下载):

下载得到文件: go1.0.3.linux-386.tar.gz

安装
$tar xzvf go1.0.3.linux-386.tar.gz -C /usr/local/
配置

打开使用vi ~/.bashrc文件,在底部加入以下内容:

export GOROOT = /usr/local/go
export PATH=/usr/local/go/bin:$PATH

好了,大功告成。下面尝试一下go是否可以正常工作。

jason@localhost ~> go version
go version go1.4.2 linux/amd64
jason@localhost ~> go env
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH=""
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
CXX="g++"
CGO_ENABLED="1"

java.io.IOException: invalid constant type: 18 问题解决

今天在项目里发现一个少见异常如下:

Caused by: java.lang.RuntimeException: java.io.IOException: invalid constant type: 18
 at javassist.CtClassType.getClassFile2(CtClassType.java:203)
 at javassist.CtClassType.subtypeOf(CtClassType.java:303)
 at javassist.CtClassType.subtypeOf(CtClassType.java:318)
 at javassist.compiler.MemberResolver.compareSignature(MemberResolver.java:247)
 at javassist.compiler.MemberResolver.lookupMethod(MemberResolver.java:119)
 at javassist.compiler.MemberResolver.lookupMethod(MemberResolver.java:96)
 at javassist.compiler.TypeChecker.atMethodCallCore(TypeChecker.java:704)
 at javassist.expr.NewExpr$ProceedForNew.setReturnType(NewExpr.java:243)
 at javassist.compiler.JvstTypeChecker.atCallExpr(JvstTypeChecker.java:146)
 at javassist.compiler.ast.CallExpr.accept(CallExpr.java:45)
 at javassist.compiler.TypeChecker.atVariableAssign(TypeChecker.java:248)
 at javassist.compiler.TypeChecker.atAssignExpr(TypeChecker.java:217)
 at javassist.compiler.ast.AssignExpr.accept(AssignExpr.java:38)
 at javassist.compiler.CodeGen.doTypeCheck(CodeGen.java:241)
 at javassist.compiler.CodeGen.atStmnt(CodeGen.java:329)
 at javassist.compiler.ast.Stmnt.accept(Stmnt.java:49)
 at javassist.compiler.CodeGen.atStmnt(CodeGen.java:350)
 at javassist.compiler.ast.Stmnt.accept(Stmnt.java:49)
 at javassist.compiler.CodeGen.atIfStmnt(CodeGen.java:404)
 at javassist.compiler.CodeGen.atStmnt(CodeGen.java:354)
 at javassist.compiler.ast.Stmnt.accept(Stmnt.java:49)
 at javassist.compiler.Javac.compileStmnt(Javac.java:568)
 at javassist.expr.NewExpr.replace(NewExpr.java:206)
 at org.powermock.core.transformers.impl.MainMockTransformer$PowerMockExpressionEditor.edit(MainMockTransformer.java:427)
 at javassist.expr.ExprEditor.loopBody(ExprEditor.java:211)
 at javassist.expr.ExprEditor.doit(ExprEditor.java:90)
 at javassist.CtClassType.instrument(CtClassType.java:1289)
 at org.powermock.core.transformers.impl.MainMockTransformer.transform(MainMockTransformer.java:74)
 at org.powermock.core.classloader.MockClassLoader.loadMockClass(MockClassLoader.java:243)
 ... 29 more
Caused by: java.io.IOException: invalid constant type: 18
 at javassist.bytecode.ConstPool.readOne(ConstPool.java:1023)
 at javassist.bytecode.ConstPool.read(ConstPool.java:966)
 at javassist.bytecode.ConstPool.<init>(ConstPool.java:127)
 at javassist.bytecode.ClassFile.read(ClassFile.java:693)
 at javassist.bytecode.ClassFile.<init>(ClassFile.java:85)
 at javassist.CtClassType.getClassFile2(CtClassType.java:190)
 ... 57 more

发现javassist包出现这个奇怪的错误。通过分析,发现javassist 是一个动态生成字节码的开源库,由于我使用的是jdk1.8.

原因:javassist 3.18以下的版本不支持在JDK1.8下运行,详情点击.

解决方法有两个:

1 使用JDK1.7或者以下版本.

2 升级javassist 到3.18或以上版本.

利用maven生成一个可执行的jar文件

最近要在远程服务器调试一段小程序,需要将项目导出一个可执行的jar放到服务器上.  利用maven的插件可以很容易完成.

 

下面做一个简单总结, 以免下次忘记:

1 首先创建一个maven项目:

package

 

2 创建一个含有main方法的类:
package main;

public class Boot {

 public static void main(String[] args) {
 System.out.println("Boot.main start");
 }
}

3 为了模拟真实场景, 在pom里添加一些依赖:
<?xml version="1.0" encoding="UTF-8"?>
<dependencies>
   <dependency>
      <groupId>commons-httpclient</groupId>
      <artifactId>commons-httpclient</artifactId>
      <version>3.1</version>
   </dependency>
   <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.3.6</version>
   </dependency>
   <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.5</version>
   </dependency>
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
   </dependency>
</dependencies>
4 重点来了, 配置maven的插件, 将main函数当作jar文件的执行入口,同时包含所以依赖的jar. 方便直接在服务器运行.
<?xml version="1.0" encoding="UTF-8"?>
<build>
   <finalName>debug</finalName>
   <plugins>
      <!-- Set a compiler level -->
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>2.3.2</version>
         <configuration>
            <source>1.6</source>
            <target>1.6</target>
         </configuration>
      </plugin>
      <!-- Make this jar executable -->
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
         <configuration>
            <archive>
               <manifest>
                  <mainClass>main.Boot</mainClass>
               </manifest>
            </archive>
         </configuration>
      </plugin>
      <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <configuration>
            <descriptorRefs>
               <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
         </configuration>
      </plugin>
   </plugins>
</build>
 6 进入项目目录后执行打包命令:
mvn assembly:assembly -DdescriptorId=jar-with-dependencies
 7 进入target目录可以直接执行jar文件:
java -jar debug-jar-with-dependencies.jar

可以看到结果打印出:

result

 

样例代码下载

使用mockito测试静态方法

今天开始一个新项目里边有使用到mockito做UT,发现普通方法都OK,但是测试静态方法时候遇到点小问题,下面做一个小总结。避免下次继续犯错。

首先引入maven依赖:

 <dependencies>
<dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-all</artifactId>
   <version>1.9.5</version>
</dependency>
<dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito</artifactId>
      <version>${powermock.version}</version>
      <scope>test</scope>
</dependency>
<dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>${powermock.version}</version>
      <scope>test</scope>
</dependency>
<dependency>
          <groupId>org.powermock</groupId>
          <artifactId>powermock-api-mockito</artifactId>
          <version>${powermock.version}</version>
          <scope>test</scope>
</dependency>
<properties>
 <powermock.version>1.5.5</powermock.version>
 </properties>
</dependencies>

然后写一个很多JAVA程序员都喜欢写的重复代码,日期工具类当作测试目标。

public final class DateUtils {

 public static String format(Date date,
 String pattern) {
 SimpleDateFormat sdf = new SimpleDateFormat(pattern);
 return sdf.format(date);
 }

}

下面为DateUtils写一个测试用例:

@RunWith(PowerMockRunner.class)
@PrepareForTest(value = {DateUtils.class})
public class DateUtilsTest {

 @Test
 public void testFormat() {
 Date date = new Date();
 date.setTime(0);
 // mock static class first
 PowerMockito.mockStatic(DateUtils.class);
 // mock method by given return value.
 PowerMockito.when(DateUtils.format(date, "yyyy-MM-dd")).thenReturn("1970-01-01");
 // invoke mock method.
 String result = DateUtils.format(date, "yyyy-MM-dd");
 // validate result.
 Assert.assertEquals("1970-01-01", result);
 }
}

其中需要注意的几个地方:

  1. 静态方法需要在类声明上增加两个注解:@RunWith @PrepareForTest. 若果想Mock多个静态类,可以在将PrepareForTest的value设置为多个.
  2. 需要先调用PowerMockito.mockStatic方法来声明.
  3. 最后使用PowerMockito来when调用方法的具体返回值。

如何有效避免NullPointerException?

在我开发的每个项目里边,我都加入了一个监控模块。当有异常发生时候自动发送一个邮件给开发人员,用来方便定位问题。我发现:出现最多的异常就是NullPointerException.  于是乎我开始思考为什么会这样?

首先,什么情况下会发生NEP? 

当使用一个没有在内存空间初始化的对象时候,就会发生空指针错误。例如下列代码:

public void testNEP(){
 User user = createUser();
 String name = user.getName(); // NEP
 String id = user.id; //NEP
}

public User createUser(){
 return null;
}

项目里边遇到NEP的情况是五花八门,但并不是无规律可循。那么我们应该如何去避免NEP呢?有人说,只要在每个对象使用前都要加个if null的判断就可以了,这个方法理论上可行。但是成本太大了。实现起来较困难,并且代码也会异常丑陋。

那么问题来了,如何做才能避免NEP发生呢? 下面总结了一些点,来避免空指针问题:

不要return null.

当我们在实现一个又返回值的方法时,即使数据是不存在的。也一定不要直接返回null,因为方法返回null就意味着调用方需要check返回值。

如果返回值是集合, 可以直接用java.util.Collections的各种empty辅助方法(emptySet,emptyList,emptyMap)。这样的好处是调用方无需做特殊判断处理,不需要额外的if null来检查。

public List<User> queryUsersBySex(String sex){
 List<User> usersFromDB = userDao.query(sex);
 if (usersFromDB == null){
 return Collections.emptyList();
 }
 return usersFromDB;
}

如果返回值是普通的对象,会分为两种情况:

1. 异常代替NULL。从逻辑来看这个对象不允许为空,如果为空意味着后续的程序无法运行。这种情况可以直接声明并抛出异常,让调用者自己决定如何处理。

public User loadById(Long id)
           throws UserNotFoundException {
 User user = userDao.load(id);
 if (user == null){
   throw new UserNotFoundException();
 }
 return user;
}

2. 空对象代替NULL。假如空对象是一种特殊的对象,这种情况可以使用可以使用Null Object pattern。方式是这样,首先定义一个接口,并且实现一个特殊的Null对象,它其中也可以包含一些特殊逻辑。调用方可以通过instance of 来判断是NullUser。

null_object_implementation_-_uml_class_diagram

public interface User {
 public void sayHello();
}

public class UserImpl implements User {
 private String name;
 public void sayHello() {
 System.out.print("hello");
 }
}

public class NullUser implements User {
 public void sayHello() {
 // do nothing
 }
}

这样一来,上一个方法可以直接写成:

public User loadById(Long id) {
 User user = userDao.load(id);
 if (user == null){
   return new NullUser();
 }
 return user;
}

不要使用NULL当参数

很多”懒惰”的人将NULL当成一个特殊的方法参数,这是一种非常差的实践。无异于玩火自焚。举例,下面是一个查询列表的方法,参数用于分页处理。但是如果参数为空时候,则返回的是全部数据。

public List<Item> query(Paging paging){
 if (paging == null){
 return queryAllItems();// return all items from DB.
 }
 ....
}

这样的”挖坑”行为会给程序造成灾难性问题,谁又能猜到NULL居然也对应了这样一种特殊逻辑。同样会让调用方的代码难于理解。

警惕“失控”的数据格式

好吧,如果你和你项目里的同事都能遵守上边的潜规则,那么会极大减少NEP发送的概率。但是并不是每个人都这么做。这个时候就需要各种if null了,多数情况下我们会从很多数据源取值来构造一个对象,请格外注意那些没有约束的的来源,如:数据库、API、文件里。对于数据库表里如果字段不能为空,请增加约束来避免脏数据。而对于第三方的API调用,文件读取这些情况。以防万一,还是来个if null.

PS: 在IDEA里可以直接简写ifn 和 inn 来自动补全,好方便的哦。

慎重包装类自动装箱

请慎重使用包装类代替原始类型作为参数或者返回值,因为如果是包装类必然会有NULL的情况。请尽量优先使用原始类型当参数。

// good
public List<Page> search(int size){
 ...
 return result;

// bad
public List<Page> search(Integer size){
 if (size == null){
 return Collections.emptyList();
 }
 ...
 return result;
}

还有另外一个例子值得注意,当把包装类型NULL值赋值给原始类型时候就会发生NEP。

public void testNEP(){
 int height = height(); // NEP
 System.out.print(height)
}

public Integer height(){
 return null;
}

其他小技巧:

1 字符串比较常量在前,可以有效避免空指针判断:

public boolean isLinux(String osName){
 return "linux".equals(osName); // good
}

public boolean isLinux(String osName){
 return osName.equals("linux"); //bad
}

2 自己定义一个Assert工具类或者使用线程开源(Junit4或者spring),如:

 public static void isNull(Object object, String message) {
 if (object != null) {
 throw new IllegalArgumentException(message);
 }
 }

具体使用方法,可以直接在方法前做校验如:

public boolean isLinux(String osName){
 Assert.notNull(osName , "osName must not be null");
 return osName.equals("linux");
}

总结

  • 方法不要返回null,用其他方式替代.
  • 尽量使用原始类型.
  • 使用防御式编程,警惕无法掌控的数据类.
  • 发生异常多总结

 参考文章:

编写单元测试必须知道的原则

对于编写单元测试来说,一定要知道F.I.R.S.T原则。

Fast:执行速度快

测试用例的执行速度应该也一定是快速的,因为如果一旦Test case速度慢下来,人们频繁执行的意愿就会大大下降。测试用例就无法启到应该有的尽早发现问题作用。最终的结果是,我们的代码质量越来越糟糕。

Independent : 独立不耦合

测试用例之间不能互相依赖,也不该有执行顺序上的限制。任何一个用例都可以独立的执行,没有先后顺序的要求。如果测试用例互相依赖就会导致:一个用例失败后,依赖它的其他所有用例全部失败。导致我们很难快速定位具体的问题出在哪里。

Repeatable :可重复执行,不依赖环境

测试程序应该能够在不同环境下重复执行,而不是依赖于第三方服务、数据库、网络资源、存储等。例如:QA会搭建好一个测试环境,测试程序只能在上边运行,如果切换一个新环境就要重新搭建一个环境。因为测试程序各种依赖。这样维护一个稳定测试环境需要很大的成本,让我们无法专注到程序的质量上来。

Self-Validating : 能够自我检验

每个测试用例都应该至少有一个bool的执行状态,来表明是否是成功还是失败。很多程序员编写的测试用来没有assert语句,仅仅通过打印log的方式来检验用例执行情况,这种方式十分不可取。先不说从持续集成角度来看,但就比较正常log和错误log的耗费成本就让人头大。因此,测试用例必须包含Assert语句来验证是否通过。

Timely :提前写测试程序

现在流行的开发方式TDD(Test-driven development),写测试的时间点非常重要,应该尽可能提前。如果你在上线之后写测试的话,你会发现很难下手(因为一开始的设计问题)。从而得出一个这些代码无法测试的结论,事实上是由于压根就没有良好的测试设计。

原创文章,转载请注明出处: http://jasonli.info/archives/419.html