TDD实践精益编程

苹果筛选

1. 筛选绿色的苹果

    @Test
    public void givenAppleRepoWhenFilterGreenAppleThenReturn3(){
        //given
        Apple apple1 = new Apple("green", "70");
        Apple apple2 = new Apple("red", "100");
        Apple apple3 = new Apple("red", "80");
        Apple apple4 = new Apple("green", "170");
        Apple apple5 = new Apple("green", "60");
        List<Apple> appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
        AppleFilter filter = new AppleFilter();
        //when
        List<Apple> greenApples = filter.findGreenApple(appleRepo);
        //then
        assertThat(greenApples.size(),is(3));
    }
public class AppleFilter {
    public List<Apple> findGreenApple(List<Apple> appleRepo) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple : appleRepo){
            if(apple.getColor().equals("green")){
                result.add(apple);
            }
        }
        return result;
    }
}

2.筛选红色的苹果

    @Test
    public void givenAppleRepoWhenFilterRedAppleThenReturn2(){
        //given
        Apple apple1 = new Apple("green", "70");
        Apple apple2 = new Apple("red", "100");
        Apple apple3 = new Apple("red", "80");
        Apple apple4 = new Apple("green", "170");
        Apple apple5 = new Apple("green", "60");
        List<Apple> appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
        AppleFilter filter = new AppleFilter();
        //when
        List<Apple> redApples = filter.findRedApple(appleRepo);
        //then
        assertThat(redApples.size(),is(2));
    }
    @Test
    public void givenAppleRepoWhenFilterGreenAppleThenReturn3(){
        //given
        Apple apple1 = new Apple("green", "70");
        Apple apple2 = new Apple("red", "100");
        Apple apple3 = new Apple("red", "80");
        Apple apple4 = new Apple("green", "170");
        Apple apple5 = new Apple("green", "60");
        List<Apple> appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
        AppleFilter filter = new AppleFilter();
        //when
        List<Apple> greenApples = filter.findGreenApple(appleRepo);
        //then
        assertThat(greenApples.size(),is(3));
    }
public List<Apple> findGreenApple(List<Apple> appleRepo) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple : appleRepo){
            if(apple.getColor().equals("green")){
                result.add(apple);
            }
        }
        return result;
    }
public List<Apple> findRedApple(List<Apple> appleRepo) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple : appleRepo){
            if(apple.getColor().equals("red")){
                result.add(apple);
            }
        }
        return result;
    }

**Duplicated is evil **
通过参数化,消除hard code 和重复
测试代码同样需要重构,TDD很难被普及的一个原因之一,就是没有持续的对Test进行重构

public class AppleFilterTest {
    private  List<Apple> appleRepo;
    private  AppleFilter filter;
    @Before
    public void givenAppleRepo(){
        //given
        Apple apple1 = new Apple("green", "70");
        Apple apple2 = new Apple("red", "100");
        Apple apple3 = new Apple("red", "80");
        Apple apple4 = new Apple("green", "170");
        Apple apple5 = new Apple("green", "60");
        appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
        filter = new AppleFilter();
    }
    @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = filter.findApple(appleRepo,"green");
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = filter.findApple(appleRepo,"red");
        //then
        assertThat(redApples.size(),is(2));
    }
}
public class AppleFilter {
    public List<Apple> findApple(List<Apple> appleRepo,String color) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple : appleRepo){
            if(apple.getColor().equals(color)){
                result.add(apple);
            }
        }
        return result;
    }
}

3.筛选重量大于100克的苹果

public class AppleFilterTest {
    private  List<Apple> appleRepo;
    private  AppleFilter filter;
    @Before
    public void givenAppleRepo(){
        //given
        Apple apple1 = new Apple("green", "70");
        Apple apple2 = new Apple("red", "100");
        Apple apple3 = new Apple("red", "80");
        Apple apple4 = new Apple("green", "170");
        Apple apple5 = new Apple("green", "60");
        appleRepo = asList(apple1,apple2,apple3,apple4,apple5);
        filter = new AppleFilter();
    }
    @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = filter.findApple(appleRepo,"green");
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = filter.findApple(appleRepo,"red");
        //then
        assertThat(redApples.size(),is(2));
    }
   @Test
    public void whenFilterGt100ThenReturn2(){
        List gt100 = filter.findApple(appleRepo,100);
        assertThat(gt100.size(),is(2));
    }
}
   

 public List<Apple> findApple(List<Apple> appleRepo,String color) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple : appleRepo){
            if(apple.getColor().equals(color)){
                result.add(apple);
            }
        }
        return result;
    }
public List<Apple> findApple(List<Apple> appleRepo,int weight) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple : appleRepo){
            if(apple.getWeight()>=weight){
                result.add(apple);
            }
        }
        return result;
    }


结构性重复,只是算法不一样而已
通过策略模式,将算法封装起来,消除重复,使应用满足SRP和OCP

public interface Specification {
    boolean satisfy(Apple apple);
}

public class ColorSpec implements Specification {
    private String color;
    public ColorSpec(String color) {
        this.color = color;
    }
    @Override
    public boolean satisfy(Apple apple) {
        return apple.getColor().equals(color);
    }
}

public class WeightGtSpec implements Specification{
    private int weight;
    public WeightGtSpec(int weight) {
        this.weight = weight;
    }
    @Override
    public boolean satisfy(Apple apple) {
        return apple.getWeight()>=weight;
    }
}
   @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = filter.findApple(appleRepo,new ColorSpec("green"));
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = filter.findApple(appleRepo,new ColorSpec("red"));
        //then
        assertThat(redApples.size(),is(2));
    }

    @Test
    public void whenFilterGt100ThenReturn2(){
        List gt100 = filter.findApple(appleRepo,new WeightGtSpec(100));
        assertThat(gt100.size(),is(2));
    }
 public List<Apple> findApple(List<Apple> appleRepo, Specification spec) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : appleRepo) {
            if (spec.satisfy(apple)) {
                result.add(apple);
            }
        }
        return result;
    }

4.筛选不是红色的苹果

    @Before
    public void givenAppleRepo(){
        //given
        Apple apple1 = new Apple("green", 70);
        Apple apple2 = new Apple("red", 100);
        Apple apple3 = new Apple("red", 80);
        Apple apple4 = new Apple("green", 170);
        Apple apple5 = new Apple("green", 60);
        Apple apple6 = new Apple("blue", 60);
        appleRepo = asList(apple1,apple2,apple3,apple4,apple5,apple6);
        filter = new AppleFilter();
    }

    @Test
    public void whenFilterNotRedThenReturn4(){
        List notRed = filter.findApple(appleRepo,new NotSpec(new ColorSpec("red")));
        assertThat(notRed.size(),is(4));
    }

通过组合模式,完成原子策略模式的复用

public class NotSpec implements Specification {
    private Specification spec;
    public NotSpec(Specification spec) {
        this.spec = spec;
    }
    @Override
    public boolean satisfy(Apple apple) {
        return !spec.satisfy(apple);
    }
}

5.筛选既是红色又大于100克的苹果

  @Test
  public void whenFilterRedAndGt100ThenReturn1(){
        List redAndGt100 = filter.findApple(appleRepo,new ColorAndWeightGtSpec(100,"red"));
        assertThat(redAndGt100.size(),is(1));
  }
public class ColorAndWeightGtSpec implements Specification {
    private int weight;
    private String color;
    public ColorAndWeightGtSpec(int weight, String color) {
        this.weight = weight;
        this.color = color;
    }
    @Override
    public boolean satisfy(Apple apple) {
        return apple.getWeight()>=weight&& apple.getColor().equals(color);
    }
}

当出现And语义时,一般都是违反了SRP,而且该实现也比较僵化,如果需求新增即是红色,又小于100克,该实现无法满足需求。
利用组合模式继续拆分

    @Test
    public void whenFilterRedAndGt100ThenReturn1(){
        List redAndGt100 = filter.findApple(appleRepo,new AndSpec(new ColorSpec("red"),new WeightGtSpec(100)));
        assertThat(redAndGt100.size(),is(1));
    }
public class AndSpec implements Specification{
    private Specification[] specs;
    public AndSpec(Specification ...specs) {
        this.specs = specs;
    }
    @Override
    public boolean satisfy(Apple apple) {
        for (Specification spec: specs)
        {
        if(!spec.satisfy(apple)){
               return false;
            }
        }
        return true;
    }
}

6.筛选红色或者蓝色的苹果

    @Test
    public void whenFilterRedOrBlueThenReturn3(){
        List redOrBlue = filter.findApple(appleRepo,new OrSpec(new ColorSpec("red"),new ColorSpec("blue")));
        assertThat(redOrBlue.size(),is(3));
    }
public class OrSpec implements Specification {
    private Specification[] specs;
    public OrSpec(Specification ...specs) {
        this.specs = specs;
    }
    @Override
    public boolean satisfy(Apple apple) {
        for (Specification spec : specs){
            if(spec.satisfy(apple)){
                return true;
            }
        }
        return false;
    }
}

从OO Design的角度来说,目前的实现已经非常不错了,符合SRP,OCP,能够以组合的方式实现更复杂的需求变化。
但是从调用的客户端角度,调用的方式并不简单,需要new大量的spec对象,阅读性不是很友好。

7.通过引入Specifications辅助类和匿名类来简化客户端调换用

我们的价值观是客户第一,个人理解,从程序的角度来说,我们的第一客户是调用我们程序的客户端代码(有可能是你自己,也有可能是别人写的代码。调用你程序的代码被称为客户端代码,也许就是这样的理解而被大家这么命名的吧),所以我们有责任让我们的客户使用起来更简单方便。

一般接口类都对应一个工具类,例如Collection 对应 Collections,Array对应Arrays以辅助客户端调用。基于此,我们实现我们自己的Specifications辅助类。

    @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = filter.findApple(appleRepo,color("green"));
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = filter.findApple(appleRepo,color("red"));
        //then
        assertThat(redApples.size(),is(2));
    }
    @Test
    public void whenFilterGt100ThenReturn2(){
        List gt100 = filter.findApple(appleRepo,gtWeight(100));
        assertThat(gt100.size(),is(2));
    }
    @Test
    public void whenFilterNotRedThenReturn4(){
        List notRed = filter.findApple(appleRepo,not(color("red")));
        assertThat(notRed.size(),is(4));
    }
    @Test
    public void whenFilterRedAndGt100ThenReturn1(){
        List redAndGt100 = filter.findApple(appleRepo,and(color("red"),gtWeight(100)));
        assertThat(redAndGt100.size(),is(1));
    }
    @Test
    public void whenFilterRedOrBlueThenReturn3(){
        List redOrBlue = filter.findApple(appleRepo,or(color("red"),color("blue")));
        assertThat(redOrBlue.size(),is(3));
    }
public class Specifications {
    public static Specification color(String color){
        return new Specification() {
            @Override
            public boolean satisfy(Apple apple) {
                return apple.getColor().equals(color);
            }
        };
    }
    public static Specification gtWeight(int weight){
        return new Specification() {
            @Override
            public boolean satisfy(Apple apple) {
                return apple.getWeight()>=weight;
            }
        };
    }
    public static Specification or(Specification ...specs){
        return new Specification() {
            @Override
            public boolean satisfy(Apple apple) {
                for (Specification spec : specs){
                    if(spec.satisfy(apple)){
                        return true;
                    }
                }
                return false;       
            }
        };
    }
    public static Specification and(Specification ...specs){
        return new Specification() {
            @Override
            public boolean satisfy(Apple apple) {
                for (Specification spec : specs){
                    if(!spec.satisfy(apple)){
                        return false;
                    }
                }
                return true;
            }
        };
    }
    public static Specification not(Specification spec){
        return new Specification() {
            @Override
            public boolean satisfy(Apple apple) {
                return !spec.satisfy(apple);
            }
        };
    }
}

Java 8之前,这基本上已经是非常漂亮的实现了。我们使用辅助类抽象出了dsl语句,来灵活的并且非常有表现力的实现我们的需求。
Java 8以后,接口可以有static方法和default方法。来是我们的类进一步减少。
同时引入lamda继续简化我们的代码,使其更具有表现力

8.引入lamda

public class Specifications {
    public static Specification color(String color){
        return apple -> apple.getColor().equals(color);
    }
    public static Specification gtWeight(int weight){
        return apple -> apple.getWeight()>=weight;
    }
    public static Specification or(Specification ...specs){
        return apple -> {
            for (Specification spec : specs){
                if(spec.satisfy(apple)){
                    return true;
                }
            }
            return false;           
       };
    }
    public static Specification and(Specification ...specs){
        return apple -> {
            for (Specification spec : specs){
                if(!spec.satisfy(apple)){
                    return false;
                }
            }
            return true;
        };
    }
    public static Specification not(Specification spec){
        return apple -> !spec.satisfy(apple);
    }
}

9.接口静态方法

public interface Specification {
    boolean satisfy(Apple apple);
    static Specification color(String color){
        return apple -> apple.getColor().equals(color);
    }
    static Specification gtWeight(int weight){
        return apple -> apple.getWeight()>=weight;
    }
    static Specification or(Specification ...specs){
        return apple -> {
            for (Specification spec : specs){
                if(spec.satisfy(apple)){
                    return true;
                }
            }
            return false;   
        };
    }
    static Specification and(Specification ...specs){
        return apple -> {
            for (Specification spec : specs){
                if(!spec.satisfy(apple)){
                    return false;
                }
            }
            return true;
        };
    }
    static Specification not(Specification spec){
        return apple -> !spec.satisfy(apple);
    }
}

10.接口默认方法

and(color("red"),gtWeight(100))这样的语义是不符合我们的人类语言方式的。
color("red").and(gtWeight(100))这样的语义更像人类语言的表达方式。

public interface Specification {
    boolean satisfy(Apple apple);
    static Specification color(String color){
        return apple -> apple.getColor().equals(color);
    }
    static Specification gtWeight(int weight){
        return apple -> apple.getWeight()>=weight;
    }
    default Specification or(Specification spec){
        return apple -> satisfy(apple) || spec.satisfy(apple);
    }
    default Specification and(Specification spec){
        return apple -> satisfy(apple) && spec.satisfy(apple);
    }
    default Specification not(){
        return apple -> !satisfy(apple);
    }
}
    @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = filter.findApple(appleRepo,color("green"));
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = filter.findApple(appleRepo,color("red"));
        //then
        assertThat(redApples.size(),is(2));
    }
    @Test
    public void whenFilterGt100ThenReturn2(){
        List gt100 = filter.findApple(appleRepo,gtWeight(100));
        assertThat(gt100.size(),is(2));
    }
    @Test
    public void whenFilterNotRedThenReturn4(){
        List notRed = filter.findApple(appleRepo,color("red").not());
        assertThat(notRed.size(),is(4));
    }
    @Test
    public void whenFilterRedAndGt100ThenReturn1(){
        List redAndGt100 = filter.findApple(appleRepo,color("red").and(gtWeight(100)));
        assertThat(redAndGt100.size(),is(1));
    }
    @Test
    public void whenFilterRedOrBlueThenReturn3(){
        List redOrBlue = filter.findApple(appleRepo,color("red").or(color("blue")));
        assertThat(redOrBlue.size(),is(3));
    }

11. 泛型实现通用筛选

public interface Specification<T> {
    boolean satisfy(T apple);
    default Specification<T> or(Specification<T> spec) {
        return t -> satisfy(t) || spec.satisfy(t);
    }
    default Specification<T> and(Specification<T> spec) {
        return t -> satisfy(t) && spec.satisfy(t);
    }
    default Specification<T> not() {
        return t -> !satisfy(t);
    }
}

public interface AppleSpec extends Specification<Apple> {
    static Specification<Apple> color(String color){
        return apple -> apple.getColor().equals(color);
    }
    static Specification<Apple> gtWeight(int weight){
        return apple -> apple.getWeight()>=weight;
    }
}

通过泛型化我们的Specification不仅仅可以过滤Apple了,同时可以过滤所有的对象类型。

12. Predicate接口

很多我们遇到的问题,前人也都遇到过并且已经解决了。我们不需要重复发明轮子。只需要站在巨人的肩膀继续前行。

T -> boolean : Specification实际上是这样一种函数,给定一个参数,返回一个boolean类型。而这个函数式接口是java8中提供的函数式接口中最常用的之一: Predicate<T>接口。

public interface AppleSpec extends Predicate<Apple> {
    static Predicate<Apple> color(String color){
        return apple -> apple.getColor().equals(color);
    }
    static Predicate<Apple> gtWeight(int weight){
        return apple -> apple.getWeight()>=weight;
    }
}

13.Stream内循环

目前为止我们的代码不仅简洁,而且非常灵活。我们通过AppleFilter来迭代按Predicate进行过滤。
Java 8的Stream提供了内部循环,减少了临时变量,并且提供了免费的并行化。

    @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = appleRepo.stream().filter(color("green")).collect(Collectors.toList());
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = appleRepo.stream().filter(color("red")).collect(Collectors.toList());
        //then
        assertThat(redApples.size(),is(2));
    }
    @Test
    public void whenFilterGt100ThenReturn2(){
        List gt100 = appleRepo.stream().filter(gtWeight(100)).collect(Collectors.toList());
        assertThat(gt100.size(),is(2));
    }
    @Test
    public void whenFilterNotRedThenReturn4(){
        List notRed = appleRepo.stream().filter(color("red").negate()).collect(Collectors.toList());
        assertThat(notRed.size(),is(4));
    }
    @Test
    public void whenFilterRedAndGt100ThenReturn1(){
        List redAndGt100 = appleRepo.stream().filter(color("red").and(gtWeight(100))).collect(Collectors.toList());
        assertThat(redAndGt100.size(),is(1));
    }
    @Test
    public void whenFilterRedOrBlueThenReturn3(){
        List redOrBlue = appleRepo.stream().filter(color("red").or(gtWeight(100))).collect(Collectors.toList());
        assertThat(redOrBlue.size(),is(3));
    }

14.结束了吗?

目前为止我们只写了一个实现了Predicate<T>接口的AndSpec接口。
函数式编程中,函数第一,所以其实我们连AndSpec也没有必要实现。

    @Test
    public void whenFilterGreenAppleThenReturn3(){
        //when
        List<Apple> greenApples = appleRepo.stream().filter(apple -> apple.getColor().equals("green")).collect(Collectors.toList());
        //then
        assertThat(greenApples.size(),is(3));
    }
    @Test
    public void whenFilterRedAppleThenReturn2(){
        //when
        List<Apple> redApples = appleRepo.stream().filter(apple -> apple.getColor().equals("red")).collect(Collectors.toList());
        //then
        assertThat(redApples.size(),is(2));
    }
    @Test
    public void whenFilterGt100ThenReturn2(){
        List gt100 = appleRepo.stream().filter(apple -> apple.getWeight()>=100).collect(Collectors.toList());
        assertThat(gt100.size(),is(2));
    }
    @Test
    public void whenFilterNotRedThenReturn4(){
        List notRed = appleRepo.stream().filter(apple -> !apple.getColor().equals("red")).collect(Collectors.toList());
        assertThat(notRed.size(),is(4));
    }

    @Test
    public void whenFilterRedAndGt100ThenReturn1(){
        Predicate<Apple> red = apple -> apple.getColor().equals("red");
        Predicate<Apple> gt100 = apple -> apple.getWeight()>=100;
        List redAndGt100 = appleRepo.stream().filter(red.and(gt100)).collect(Collectors.toList());
        assertThat(redAndGt100.size(),is(1));
    }
    @Test
    public void whenFilterRedOrBlueThenReturn3(){
        Predicate<Apple> red = apple -> apple.getColor().equals("red");
        Predicate<Apple> gt100 = apple -> apple.getWeight()>=100;
        List redOrBlue = appleRepo.stream().filter(red.or(gt100)).collect(Collectors.toList());
        assertThat(redOrBlue.size(),is(3));
    }

自此,从服务代码的角度来说,我们一行代码也没有写,即完成了客户的需求。

资料引用

  • https://codingstyle.cn/topics/12
  • Test Driven Java Development
  • Test Driven Development with Mockito
  • Pragmatic Unit Testing in Java 8 with Junit
  • Java 8 in action

Git Repository

https://github.com/jerrywalker0435/apple-filter

对应分支

  • 1-filter-green-apple
  • 2-filter-red-apple-1
  • 2-filter-red-apple-2
  • 3-filter-with-specification
  • 3-filter-with-weight-1
  • 4-filter-not-spec
  • 5-filter-color-and-weight-1
  • 5-filter-color-and-weight-2
  • 6-red-or-blue
  • 7-specifications
  • 8-lambda
  • 9-static-method
  • 10-default-method
  • 11-generic-specification
  • 12-predicate
  • 13-stream
  • 14-no-code

后记

  • 马丁福勒2017年中国行给程序员的三个建议:

    • 学习业务领域知识
    • 将学习重点放在软件开发原则与模式上,而不是具体技术上
    • 提升自身与其他团队成员之间的沟通能力
  • TDD发明人之一Kent Beck的三个建议

    • No matter the circumstance you can always improve.
    • You can always start improving with yourself.
    • You can always start improving today.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • 第一章 为什么要关心Java 8 使用Stream库来选择最佳低级执行机制可以避免使用Synchronized(同...
    谢随安阅读 1,491评论 0 4
  • 行为参数化:帮助你处理频繁变更的需求的一种软件开发模式,即可以将代码块作为参数传递给另一个方法,稍后再去执行它。此...
    夏与清风阅读 231评论 0 2
  • 微微一笑 发丝在指尖盘绕 阳光踩着棉云 红着脸说不小心迟到 来往之间 故事已悄然翻篇 冰淇淋们的甜蜜 已然遗落在那...
    弦汀阅读 217评论 1 2
  • 在犹太人种族里,当他们的孩子刚刚懂事的时候,孩子的母亲会将蜂蜜滴在书本上,让孩子们去尝一尝书上的那一滴蜂蜜的味道…...
    才爸家庭教育阅读 308评论 0 4
  • regular expression : RegExp用来处理字符串的规则 只能处理字符串 它是一个规则:可以验证...
    Leonard被注册了阅读 258评论 0 1