TDD實(shí)踐精益編程

蘋果篩選

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 **
通過參數(shù)化牡拇,消除hard code 和重復(fù)
測(cè)試代碼同樣需要重構(gòu)懒闷,TDD很難被普及的一個(gè)原因之一,就是沒有持續(xù)的對(duì)Test進(jìn)行重構(gòu)

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;
    }


結(jié)構(gòu)性重復(fù)魄健,只是算法不一樣而已
通過策略模式老客,將算法封裝起來,消除重復(fù)润匙,使應(yīng)用滿足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));
    }

通過組合模式,完成原子策略模式的復(fù)用

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);
    }
}

當(dāng)出現(xiàn)And語義時(shí)唉匾,一般都是違反了SRP孕讳,而且該實(shí)現(xiàn)也比較僵化谷羞,如果需求新增即是紅色亚茬,又小于100克煌贴,該實(shí)現(xiàn)無法滿足需求恐仑。
利用組合模式繼續(xù)拆分

    @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.篩選紅色或者藍(lán)色的蘋果

    @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的角度來說,目前的實(shí)現(xiàn)已經(jīng)非常不錯(cuò)了响禽,符合SRP胖笛,OCP苍柏,能夠以組合的方式實(shí)現(xiàn)更復(fù)雜的需求變化肪康。
但是從調(diào)用的客戶端角度荚恶,調(diào)用的方式并不簡(jiǎn)單,需要new大量的spec對(duì)象梅鹦,閱讀性不是很友好。

7.通過引入Specifications輔助類和匿名類來簡(jiǎn)化客戶端調(diào)換用

我們的價(jià)值觀是客戶第一冗锁,個(gè)人理解齐唆,從程序的角度來說,我們的第一客戶是調(diào)用我們程序的客戶端代碼(有可能是你自己冻河,也有可能是別人寫的代碼箍邮。調(diào)用你程序的代碼被稱為客戶端代碼,也許就是這樣的理解而被大家這么命名的吧)叨叙,所以我們有責(zé)任讓我們的客戶使用起來更簡(jiǎn)單方便锭弊。

一般接口類都對(duì)應(yīng)一個(gè)工具類,例如Collection 對(duì)應(yīng) Collections擂错,Array對(duì)應(yīng)Arrays以輔助客戶端調(diào)用味滞。基于此,我們實(shí)現(xiàn)我們自己的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之前昨凡,這基本上已經(jīng)是非常漂亮的實(shí)現(xiàn)了。我們使用輔助類抽象出了dsl語句蚁署,來靈活的并且非常有表現(xiàn)力的實(shí)現(xiàn)我們的需求便脊。
Java 8以后,接口可以有static方法和default方法光戈。來是我們的類進(jìn)一步減少哪痰。
同時(shí)引入lamda繼續(xù)簡(jiǎn)化我們的代碼,使其更具有表現(xiàn)力

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.接口靜態(tài)方法

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.接口默認(rèn)方法

and(color("red"),gtWeight(100))這樣的語義是不符合我們的人類語言方式的久妆。
color("red").and(gtWeight(100))這樣的語義更像人類語言的表達(dá)方式晌杰。

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. 泛型實(shí)現(xiàn)通用篩選

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了,同時(shí)可以過濾所有的對(duì)象類型镇饺。

12. Predicate接口

很多我們遇到的問題乎莉,前人也都遇到過并且已經(jīng)解決了。我們不需要重復(fù)發(fā)明輪子奸笤。只需要站在巨人的肩膀繼續(xù)前行惋啃。

T -> boolean : Specification實(shí)際上是這樣一種函數(shù),給定一個(gè)參數(shù)监右,返回一個(gè)boolean類型边灭。而這個(gè)函數(shù)式接口是java8中提供的函數(shù)式接口中最常用的之一: 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內(nèi)循環(huán)

目前為止我們的代碼不僅簡(jiǎn)潔健盒,而且非常靈活绒瘦。我們通過AppleFilter來迭代按Predicate進(jìn)行過濾。
Java 8的Stream提供了內(nèi)部循環(huán)扣癣,減少了臨時(shí)變量惰帽,并且提供了免費(fèi)的并行化。

    @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.結(jié)束了嗎父虑?

目前為止我們只寫了一個(gè)實(shí)現(xiàn)了Predicate<T>接口的AndSpec接口该酗。
函數(shù)式編程中,函數(shù)第一士嚎,所以其實(shí)我們連AndSpec也沒有必要實(shí)現(xiàn)呜魄。

    @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));
    }

自此,從服務(wù)代碼的角度來說莱衩,我們一行代碼也沒有寫爵嗅,即完成了客戶的需求。

資料引用

  • 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

對(duì)應(yīng)分支

  • 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年中國(guó)行給程序員的三個(gè)建議:

    • 學(xué)習(xí)業(yè)務(wù)領(lǐng)域知識(shí)
    • 將學(xué)習(xí)重點(diǎn)放在軟件開發(fā)原則與模式上笨蚁,而不是具體技術(shù)上
    • 提升自身與其他團(tuán)隊(duì)成員之間的溝通能力
  • TDD發(fā)明人之一Kent Beck的三個(gè)建議

    • No matter the circumstance you can always improve.
    • You can always start improving with yourself.
    • You can always start improving today.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睹晒,一起剝皮案震驚了整個(gè)濱河市趟庄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌册招,老刑警劉巖岔激,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異是掰,居然都是意外死亡虑鼎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門键痛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炫彩,“玉大人,你說我怎么就攤上這事絮短〗ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵丁频,是天一觀的道長(zhǎng)杉允。 經(jīng)常有香客問我,道長(zhǎng)席里,這世上最難降的妖魔是什么叔磷? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮奖磁,結(jié)果婚禮上改基,老公的妹妹穿的比我還像新娘。我一直安慰自己咖为,他們只是感情好秕狰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躁染,像睡著了一般鸣哀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吞彤,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天我衬,我揣著相機(jī)與錄音,去河邊找鬼备畦。 笑死低飒,一個(gè)胖子當(dāng)著我的面吹牛许昨,可吹牛的內(nèi)容都是我干的懂盐。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼糕档,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼莉恼!你這毒婦竟也來了拌喉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤俐银,失蹤者是張志新(化名)和其女友劉穎尿背,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捶惜,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡田藐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吱七。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汽久。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖踊餐,靈堂內(nèi)的尸體忽然破棺而出景醇,到底是詐尸還是另有隱情,我是刑警寧澤吝岭,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布三痰,位于F島的核電站,受9級(jí)特大地震影響窜管,放射性物質(zhì)發(fā)生泄漏散劫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一微峰、第九天 我趴在偏房一處隱蔽的房頂上張望舷丹。 院中可真熱鬧,春花似錦蜓肆、人聲如沸颜凯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽症概。三九已至,卻和暖如春早芭,著一層夾襖步出監(jiān)牢的瞬間彼城,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工退个, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留募壕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓语盈,卻偏偏與公主長(zhǎng)得像舱馅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刀荒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 第一章 為什么要關(guān)心Java 8 使用Stream庫來選擇最佳低級(jí)執(zhí)行機(jī)制可以避免使用Synchronized(同...
    謝隨安閱讀 1,487評(píng)論 0 4
  • 行為參數(shù)化:幫助你處理頻繁變更的需求的一種軟件開發(fā)模式代嗤,即可以將代碼塊作為參數(shù)傳遞給另一個(gè)方法棘钞,稍后再去執(zhí)行它。此...
    夏與清風(fēng)閱讀 223評(píng)論 0 2
  • 微微一笑 發(fā)絲在指尖盤繞 陽光踩著棉云 紅著臉說不小心遲到 來往之間 故事已悄然翻篇 冰淇淋們的甜蜜 已然遺落在那...
    弦汀閱讀 216評(píng)論 1 2
  • 在猶太人種族里,當(dāng)他們的孩子剛剛懂事的時(shí)候硝逢,孩子的母親會(huì)將蜂蜜滴在書本上姨拥,讓孩子們?nèi)L一嘗書上的那一滴蜂蜜的味道…...
    才爸家庭教育閱讀 307評(píng)論 0 4
  • regular expression : RegExp用來處理字符串的規(guī)則 只能處理字符串 它是一個(gè)規(guī)則:可以驗(yàn)證...
    Leonard被注冊(cè)了閱讀 252評(píng)論 0 1