精益编程:Write Lean Programs

OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathers

Product Repository

First Attempt: The Worst Implement

需求1:在仓库中查找所有颜色为红色的产品

public ArrayList findAllRedProducts(ArrayList repo) {
  ArrayList result = new ArrayList();
  for (int i=0; i<repo.size(); i++) {
    Product product = (Product)repo[i];
    if (product.getColor() == Color.RED) {
       result.add(product);
    }
  }
  return result;
}
  • 指令式(Imperative)
  • 缺乏编译时类型安全性检查
  • 实现类型
  • 硬编码
  • 重复设计

Second Attempt: Using for-each

public List<Product> findAllRedProducts(List<Product> repo) {
  List<Product> result = new ArrayList<>();
  for (Product p : repo) {
    if (p.getColor() == Color.RED) {
      result.add(p);
    }
  }
  return result;
}

Third Attempt: Parameterizing

需求2:在仓库中查找所有颜色为绿色的产品

Copy-Paste是大部分程序员最容易犯的毛病,为此引入了大量的重复代码。

public List<Product> findAllGreenProducts(List<Product> repo) {
  List<Product> result = new ArrayList<>();
  for (Product p : repo) {
    if (p.getColor() == Color.GREEN) {
      result.add(p);
    }
  }
  return result;
}

为了消灭Hard Code和重复代码,得到可重用的代码,可以引入简单的参数化设计。

public List<Product> findProductsByColor(List<Product> repo, Color color) {
  List<Product> result = new ArrayList<>();
  for (Product p : repo) {
    if (p.getColor() == color) {
      result.add(p);
    }
  }
  return result;
}

Forth Attempt: Parameterizing with Every Attribute You Can Think Of

需求3:查找所有重量小于10的所有产品

大部分程序员依然会使用Copy-Paste解决这个问题,拒绝Copy-Paste的陋习,最具实效的一个办法就是把Copy-Paste的快捷键失效,当每次尝试Copy-Paste时提醒自己做更好的设计。

public List<Product> findProductsBelowWeight(List<Product> repo, int weight) {
  List<Product> result = new ArrayList<>();
  for (Product p : repo) {
    if (p.getWeight() < weight) {
      result.add(p);
    }
  }
  return result;
}

为了消除两者重复的代码,通过简单的参数化往往不能完美解决这类问题,相反会引入额外的复杂度。

public List<Product> findProducts(List<Product> repo, Color color, int weight, boolean flag) {
  List<Product> result = new ArrayList<>();
  for (Product p : repo) {
    if ((flag && p.getColor() == color) ||
       (!flag && p.getWeight() < weight)) {
      result.add(p);
    }
  }
  return result;
}

日常工作中这样的实现手法非常普遍,函数的参数列表随着需求增加不断增加,函数逻辑承担的职责越来越多,逻辑也变得越来越难以控制。

Firth Attempt: Abstracting over Criteria

为此需要抽取出隐藏的概念,使其遍历的算法与查找的标准能够独立地变化,将行为参数化

public interface ProductSpec {
  boolean satisfy(Product product);
}

此刻findProducts的算法逻辑得到封闭。

public List<Product> findProducts(List<Product> repo, ProductSpec spec) {
  List<Product> result = new ArrayList<>();
  for (Product p : repo) {
    if (spec.satisfy(p)) {
      result.add(p);
    }
  }
  return result;
}

通过可复用的Functor来封装各种变化,让变化的因素控制在最小的范围内。

public class ColorSpec implements ProductSpec {
  private Color color;

  public ColorSpec(Color color) {
    this.color = color;
  }

  @Override
  public boolean satisfy(Product product) {
    return product.getColor() == color;
  }
}
public class BelowWeightSpec implements ProductSpec {
  private int limit;

  public BelowWeightSpec(int limit) {
    this.limit = limit;
  }

  @Override
  public boolean satisfy(Product product) {
    return product.getWeight() < limit;
  }
}

用户的接口也变得简单多了,而且富有表现力。

List<Product> products = findProducts(repo, new ColorSpec(RED));

这是经典的OO设计,如果熟悉设计模式的读者对此已经习以为常了。设计模式是好东西,但常常被人依葫芦画瓢,死板照抄,甚至被滥用。事实上,引入或去除设计模式是一个很自然的过程。与大师们交流,问究此处为何引入设计模式,得到的答案:直觉。忘记所有设计模式吧,管它是不是模式,如果设计是简单的,它这就是模式。

至此,代码另外还有一个明显的坏味道,ColorSpecBelowWeightSpec都需要继承ProductSpec,都需要定义一个构造函数和一个私有的字段,并重写satisfy方法,这是一种典型的重复现象:重复型结构

Java缺乏闭包的支持,程序员不得不承受这样的烦恼,但此刻暂时不关心,继续前进。

Sixth Attempt: Composite Criteria

需求4:查找所有颜色为红色或者绿色,并且重量小于10的产品

按照既有的代码结构,往往易于设计出类似ColorAndBelowWeightSpec的实现。

public class ColorAndBelowWeightSpec implements ProductSpec {
  private Color color1;
  private Color color2;
  private int limit;

  public ColorAndBelowWeightSpec(Color color1, Color color2, int limit) {
    this.color1 = color1;
    this.color2 = color2;
    this.limit = limit;
  }

  @Override
  public boolean satisfy(Product p) {
    return (p.getColor() == color1 || p.getColor() == color2)
        && (p.getWeight() < limit);
  }
}

存在两个明显的坏味道:

  • 类名中包含And往往是违背单一职责的信号灯
  • ColorAndBelowWeightSpec的实现与ColorSpecBelowWeightSpec之间存在明显的重复

此刻,需要寻找更本质的抽象来表达设计,引入and/or的语义模型。

  • Composite Spec: AndSpec, OrSpec
  • Atomic Spec:ColorSpec, BeblowWeightSpec

publc class AndSpec implements ProductSpec {
  private List<ProductSpec> specs = new ArrayList<>();

  public AndSpec(ProductSpec... specs) {
    this.specs.addAll(Arrays.asList(specs));
  }

  @Override
  public boolean satisfy(Product p) {
    for (ProductSpec spec : specs) {
      if (!spec.satisfy(p))
        return false;
    }
    return true;
  }
}
publc class OrSpec implements ProductSpec {
  private List<ProductSpec> specs = new ArrayList<>();

  public OrSpec(ProductSpec... specs) {
    this.specs.addAll(Arrays.asList(specs));
  }

  @Override
  public boolean satisfy(Product p) {
    for (ProductSpec spec : specs) {
      if (spec.satisfy(p))
        return true;
    }
    return false;
  }
}

可以通过AndSpec组合ColorSpec, BelowWeightSpec来实现需求,简单漂亮,并且富有表达力。

List<Product> products = findProducts(repo, new AndSpec(
  new OrSpec(new ColorSpec(RED), new ColorSpec(Greeen)), new BelowWeightSpec(10));

此时设计存在两个严重的坏味道:

  • AndSpecOrSpec存在明显的代码重复
  • 大堆的new让人眼花缭乱

Seventh Attempt: Extract Parent

先尝试消除AndSpecOrSpec存在的代码重复,OO设计的第一个直觉就是通过抽取基类。

class CombinableSpec implements ProductSpec {
  private List<ProductSpec> specs = new ArrayList<>();
  private boolean shortcut;

  protected CombinableSpec(List<ProductSpec> specs, boolean shortcut) {
    this.specs.addAll(specs);
    this.shortcut = shortcut;
  }

  @Override
  public boolean satisfy(Product p) {
    for (ProductSpec spec : specs) {
      if (spec.satisfy(p) == shortcut)
        return shortcut;
    }
    return !shortcut;
  }
}

通过参数化配置,复用CombinableSpec的实现。

publc class AndSpec extends CombinableSpec {
  public AndSpec(ProductSpec... specs) {
    super(Arrays.asList(specs), false);
  }
}
publc class OrSpec extends CombinableSpec {
  public OrSpec(ProductSpec... specs) {
    super(Arrays.asList(specs), true);
  }
}

如何评判boolean接口的使用呢?在不损伤可理解性的前提下,为了消除重复的设计是值得推荐的。boolean接口的可理解性关键依赖于调用点与函数接口之间的距离,如果在同一个文件,同一个类,并能在一个页面显示的,是完全可以接受的。

Eighth Attempt: Decorate Criteria

需求5:查找所有颜色为不是红色的产品

publc class NotSpec implements ProductSpec {
  private ProductSpec spec;

  public NotSpec(ProductSpec spec) {
    this.spec = spec;
  }

  @Override
  public boolean satisfy(Product p) {
    return !spec.satisfy(p);
  }
}

NotSpec是一种修饰了的ProductSpec,同时也使得用户的接口也变得更加人性化了。

List<Product> products = findProducts(repo, new NotSpec(new ColorSpec(RED)));

Ninth Attempt: Using Static Factory to DSL

之前遗留了一个问题,一大堆眼花缭乱的new使得代码失去了部分的可读性。

List<Product> products = findProducts(repo, new AndSpec(
  new OrSpec(new ColorSpec(RED), new ColorSpec(Greeen)), new BelowWeightSpec(10));

可以引入DSL改善程序的可读性,让代码更具表达力。

List<Product> products = findProducts(repo, and(or(color(RED), color(GREEN)), belowWeight(10)));

上述的DSL可以使用static factory的设计手段简单实现。按照惯例,可以建立类似于ProductSpecs的工具类,将这些工厂方法搬迁到工具类中去。

接口与对应工具类的对称性设计在Java社区中应用非常广泛,例如标准库中的java.util.Collection/java.util.Collections的设计。

public interface ProductSpec {
  boolean satisfy(Product p);
}
public final class ProductSpecs {
  public static ProductSpec color(final Color color) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return p.getColor() == color;
      }
    };
  }

  public static ProductSpec belowWeight(final int limit) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return p.getWeight() < limit;
      }
    };
  }

  public static ProductSpec and(ProductSpec... specs) {
    return new CombinableSpec(Arrays.asList(specs), false);
  }

  public static ProductSpec or(ProductSpec... specs) {
    return new CombinableSpec(Arrays.asList(specs), true);
  }

  public static ProductSpec not(final ProductSpec spec) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return !spec.satisfy(p);
      }
    };
  }

  private ProductSpecs() {
    throw new AssertionError("no instances");
  }
}

此外,使用匿名内部类,可以得到意外的惊喜。通过有限地引入闭包的概念,从而避免了类似Firth Attempt/Sixth Attempt的设计中引入多余的构造函数和成员变量的复杂度,从而消除了部分的结构性重复的坏味道。

当然,要让这些static factory可见,需要import static导入这些方法。

import static practical.programming.overview.ProductSpec.*;

List<Product> products = findProducts(repo, not(and(color(RED), belowWeight(10)));

Tenth Attempt: Moving Static Factory into Interface

使用Java8可以将这些工厂方法直接搬迁到ProductSpec的接口中去,这样做至少得到两个好处。

  • 可以删除ProductSpecs的工具类
  • 使的接口和静态方法(尤其静态工厂方法)关系更加紧密

Java8并没有因为comparing等静态工厂方法的增强而建立Comparators的工具类,而是直接将它们集成在Comparator的接口中,这是自Java8之后思维的一个新的转变(Comparator.comparing的实现留作作业巩固今天所学知识)。

对于本例,可以将ProductSpecs删除,将所有静态工厂方法搬迁到ProductSpec中去。

public interface ProductSpec {
  boolean satisfy(Product p);

  static ProductSpec color(Color color) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return p.getColor() == color;
      }
    };
  }

  static ProductSpec belowWeight(int limit) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return p.getWeight() < limit;
      }
    };
  }

  static ProductSpec and(ProductSpec... specs) {
    return new CombinableSpec(Arrays.asList(specs), false);
  }

  static ProductSpec or(ProductSpec... specs) {
    return new CombinableSpec(Arrays.asList(specs), true);
  }

  static ProductSpec not(ProductSpec spec) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return !spec.satisfy(p);
      }
    };
  }
}

Eleventh Attempt: Using Null Object

需求6无条件过滤掉不过滤查找所有产品

import static practical.programming.overview.ProductSpec.*;

List<Product> products = findProducts(repo, always(false));
public interface ProductSpec {
  boolean satisfy(Product p);

  static ProductSpec always(boolean bool) {
    return new ProductSpec() {
      @Override
      public boolean satisfy(Product p) {
        return bool;
      }
    };
  }
}

至此,ProductSpec存在如下一些类型:

  • Composite Specs: and, or
  • Decorator Specs: not
  • Atomic Specs: always, color, beblowWeight

Twelfth Attempt: Using Lambda Expression

Java8可以使用Lambda表达式改善设计,增强表达力。

List<Product> products = findProducts(repo, (Product p) -> p.getColor() == RED);

通过类型推演,可以进一步省略Labmda表达式中参数的类型信息。

List<Product> products = findProducts(repo, p -> p.getColor() == RED);

当然,你可以通过提取static factory,构造DSL复用这些Lambda表达式。

@FunctionalInterface
public interface ProductSpec {
  boolean satisfy(Product p);

  static ProductSpec color(Color color) {
    return p -> p.getColor() == color;
  }

  static ProductSpec weightBelow(int limit) {
    return p -> p.getWeight() < limit;
  }
}
List<Product> products = findProducts(repo, color(RED));

其中,@FunctionalInterface注解标注了ProductSpec是一个函数式接口,其抽象方法boolean satisfy(Product p)的原型描述了lambda表达式的Function Descriptor

Thirteenth Attempt: Chaining Speciafications

遗留了一个问题: 如何替换匿名内部类,使用lambda实现 and/or/not/always的语义?

@FunctionalInterface
public interface ProductSpec {
  boolean satisfy(Product p);

  default ProductSpec negate() {
    return p -> !satisfy(p);
  }

  default ProductSpec and(ProductSpec other) {
    return (p) -> satisfy(p) && other.satisfy(p);
  }

  default ProductSpec or(ProductSpec other) {
    return (p) -> satisfy(p) || other.satisfy(p);
  }

  static ProductSpec always(boolean bool) {
    return p -> bool;
  }

  static ProductSpec color(Color color) {
    return p -> p.getColor() == color;
  }

  static ProductSpec belowWeight(int limit) {
    return p -> p.getWeight() < limit;
  }
}

这里引入了Java8一个重要的设计工具:default method,简单漂亮,并巧妙地实现DSL的设计,用户接口变得更加流畅、友好。

List<Product> products = findProducts(repo, color(RED).and(belowWeight(10)));

Java8支持default method,扩展了interface原来的语义,从而隐式地支持了组合式设计,使的OO的设计更加完善和强大。

Fourteenth attempt: Using Method Reference

需求7:查找所有伪劣的产品

List<Product> products = findProducts(repo, p -> p.fake());

可以使用Method Reference进一步改善lambda的表达力。

List<Product> products = findProducts(repo, Product::fake);

Fifteenth attempt: Abstracting over Type

泛化类型信息,让算法更具有通用性,并进一步增强代码的可复用性。

public static <T> List<T> filter(List<T> list, Predicate<T> p) {
  List<T> result = new ArrayList<>();
  for (T e : list) {
  if (p.test(e)) {
      result.add(e);
    }
  }
  return result;
}

这样的实现存在一个明显的问题:泛型参数缺乏型变的能力。通过对泛型参数实施无限定类型通配符的修饰,从而使的算法实现更加具有弹性和通用性。

public static <T> List<T> filter(List<? extends T> list, Predicate<? super T> p) {
  List<T> result = new ArrayList<>();
  for (T e : list) {
    if (p.test(e)) {
      result.add(e);
    }
  }
  return result;
}

Sixteenth: Maximize Reusability

and, or, not, always在代数系统中具有稳定的抽象,为此需要进一步重构,以便最大化代码的可复用性。这样当需要建立诸如NumberSpec, FruitSpec时无需重复地再写一遍and, or, not, always的实现。

为此,建立更为抽象的Predicate的概念,并将通用的、抽象的negate, and, or, always搬迁到Predicate中去,使其具有更大的可复用性。

@FunctionalInterface
public interface Predicate<T> {
  boolean test(T t);

  default Predicate negate() {
    return p -> !satisfy(p);
  }

  default Predicate<T> and(Predicate<? super T> other) {
    return p -> satisfy(p) && other.satisfy(p);
  }

  default Predicate<T> or(Predicate<? super T> other) {
    return p -> satisfy(p) || other.satisfy(p);
  }

  static Predicate<T> always(boolean bool) {
    return p -> bool;
  }
}

同时,将领域内的color, belowWeight等原子放回ProductSpecs工具类中去(因为不存在ProductSpec的接口了),让领域内的lambda表达式具有更大的复用性。

public final class ProductSpecs {
  public static Predicate<Product> color(Color color) {
    return p -> p.getColor() == color;
  }

  public static Predicate<Product> belowWeight(int limit) {
    return p -> p.getWeight() < limit;
  }

  private ProductSpecs() {
    throw new AssertionError("no instances");
  }
}

至此,可复用的基础设施便从领域中剥离出来,使其具有更高度的可重用性。

Seventeenth Attempt: Using Stream API

Java8可以使用集合库的Stream复用代码。

import static java.util.stream.Collectors.toList;

repo.stream()
    .filter(p -> p.getColor() == RED && p.getPrice() < 10)
    .collect(toList());

如果要支持并发,则可以构建parallelStream

import static java.util.stream.Collectors.toList;

repo.parallelStream()
    .filter(p -> p.getColor() == RED && p.getPrice() < 10)
    .collect(toList());

集合类通过stream, parallelStream工厂方法创建Stream之后,其操作可分为2种基本类型:

  • Transformation:其返回值为Stream类型
  • Action:其返回值不是Stream类型

通过Stream的机制,实现了集合类的惰性求值,直至Action才真正地开始执行计算。Transformation从某种意义上,可以看成是StreamBuilder,直至Action启动执行。

Eighteenth attempt: Replace Java with Scala

Scala语言是一门跨越OOFP的一个混血儿,可以方便地与Java进行互操作。在Scala中,函数作为一等公民,使用Lambda是一个很自然的过程。当你熟悉了Scala,我相信你绝对会放弃Java,放弃Java8,犹如作者本人一样。

repo.filter(p => p.color == RED && p.weight < 10)

遗留了三个问题:

  • 如何复用lambda表达式?
  • 如何实现 and/or/not的语义?
  • 如何实现 always的语义?

Nineteenth Attempt: Abstracting Control Structure

引入静态工厂方法及其操作符重载的机制构造内部DSL

import ProductSpec._

repo.filter(color(RED) && belowWeight(10))
object ProductSpec {
  def color(color: Color) = ???
  def bebowWeight(limit: Int) = ???
}

如何替换实现???,并让其具有&&, ||, !的语义呢?

object ProductSpec {
  def color(color: Color) = new Predicate((p: Product) => p.color == color)
  def bebowWeight(limit: Int) = new Predicate((p: Product) => p.weight < limit)
}

Predicate一个扩展匿名函数A => Boolean的子类,其中,从面向对象的角度看,A => Boolean的类型为Function[A, Boolean]

class Predicate[A](pred: A => Boolean) extends (A => Boolean) {
  override def apply(a: A) = pred(a)

  def &&(that: A => Boolean) = new Predicate[A](x => pred(x) && that(x))
  def ||(that: A => Boolean) = new Predicate[A](x => pred(x) || that(x))
  def unary_! = new Predicate[A](x => !pred(x))
}

其中!是一个一元操作符。

Twentieth Attempt: Using Companion Object

always静态工厂方法,可以搬迁到Predicate的伴生对象中去。

object Predicate {
  def always[A](bool: Boolean) = new Predicate[A](_ => bool)
}

Predicate的设计既使用了OO的特性,又引入了FP的思维,Scala使其两者如此和谐、完美,简直不可思议。

Conclusion

世界是多样性的,计算机工业也不仅仅只存在一种方法论。在我的哲学观里,OO和FP之间并不矛盾,而是一个和谐的,相互补充的统一体。

除了C++语言之外,使得我最偏爱Scala,多范式,一个问题存在多种解决方案等等思维习惯,给了程序员最灵活、最自由的空间。

Review Comprator.comparing

以标准库Collections.sort,及其ComparatorJava8中的增强,及其Comparator.comparing的泛型定义复习今天所学知识。

public final class Collectins {
  private Collectins() {
  }

  public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
  }
}

使用匿名内部类是Collectins.sort最经典的使用方法之一。

Collections.sort(products, new Comparator() {
  @Override
  public int compare(Product p1, Product p2) {
    return p1.getName().compareTo(p2.getName);
  }
});

可以通过lambda表达式替代匿名内部类,简化设计。

Collections.sort(products, (Product p1, Product p2) -> p1.getName().compareTo(p2.getName));

通过类型推演,但依然得到编译器类型安全的保护。

Collections.sort(products, (p1, p2) -> p1.getName().compareTo(p2.getName));

通过Comprator.compring的静态工厂方法,改善表达力。

Collections.sort(persons, comparing(p -> p.getName()))

通过Function Reference的机制,进一步改善表达力。

Collections.sort(persons, comparing(Person::getName()))

其中,Comprator.compring的实现为:

@FunctionalInterface
public interface Comparator<T> {
  int compare(T o1, T o2);

  static <T, U extends Comparable<? super U>> Comparator<T>
    comparing(Function<? super T, ? extends U> extractor) {
      return (c1, c2) -> extractor.apply(c1).compareTo(extractor.apply(c2));
  }
}
时间: 2024-08-26 09:02:04

精益编程:Write Lean Programs的相关文章

iOS开发:八次尝试 带你走进精益编程

  开场 今天, 我们将从一个小功能开始, 先去不假思索的实现它 Product Repository: Filtering Operation Code start 有一个产品库, 我们要对它做过滤操作. 第一个需求并不复杂. 需求1:在仓库中查找所有颜色为红色的产品 First Attempt: Hard Code 我们先用最简单的方式去实现它, 硬编码 - (NSArray *)findAllRedProducts:(NSArray *)products { NSMutableArray

谁都可以精益创业

前天晚上,参加了由CSDN移动开发者俱乐部举办的线下沙龙主题的演讲活动,在程序员眼里,实践精益创业.李路 knewone知新 CTO是该活动演讲的嘉宾,另一位则是钟大伟 电台 CTO.电台之前Gauin在极客公园创新大会一文有介绍路况.因此,我们今天着重了解李路的主题演讲. 实践精益创业主题演讲-李 说实话,因为李路大哥我才去这个活动的.去年夏天有幸一起聊过,对我提了不少建议.他是个有理想.有追求.有智慧的全能产品人.骨子里我和他是一类人,所以我很敬佩他,欣赏这位大哥.感谢去年的建议,感谢今年这

精益创业-用户体验设计的新包装

[编者按]本文译自Smashing Magazine,译者@C7210.<精益创业>一书中曾为我们介绍到,小型的创业公司应先向市场推出极简的原型产品,然后在不断地试验和学习中,以最小的成本和有效的方式验证产品是否符合用户需求,并迭代优化产品,灵活调整方向. 而与之相同的精益用户体验也同样提倡把注意力从交付工作上移开.同样的理念,只是处于不同的领域而已. 精益创业(Lean Startup)的基本思路及实践方式,从某种程度上讲,其实就是用户体验设计圈子中的行家们多年来所讲述和提倡的东西.与过去不

手机彩信杂志的“精益生产”(之一)

列位看官,新年好! 从去年开始,研究手机新媒体,重点是彩信杂志.给自己定下的原则,就是少谈概念,多说操作,希望今年继续秉承这个理念,也希望能有更多的互动. 从今天开始,说说新媒体的"精益生产"--其实也正因为是新媒体,才最有条件搞精益生产.说好不说概念,可概念这事还真绕不过去,怎么也得先从"精益生产"的概念说起呀. 精益生产(Lean Production,简称LP)是美国http://www.aliyun.com/zixun/aggregation/13513.h

基于精益分析的case学习[翻译]

题外话 下面的内容是在国外的材料中看到的一个概念,叫做精益分析(Lean Analytics),想来觉得不错,内容虽然不是游戏相关的,不过比较深刻,思想也是不错的.这是其中的第一个case,简单翻译了一下,大家可以看看.关于精益分析,这个和最近这一两年提到的精益创业(Lean Startup)有很大的关系,大家如果感兴趣可以自行查阅资料.近期针对这个问题我会写一下我的体会. 前言 精益创业正在激起企业家行动起来.它聚焦于识别商业计划的最危险部分,进而快速找到降低风险的方法,是一个学习的迭代周期.

运维杂谈老王:详谈运维可视化、DevOps和运维危机

本文分为三个部分,第一部分从服务交付和服务度量两方面介绍运维可视化:第二部分介绍什么是DevOps以及它给运维带来的改变和影响:第三部分结合最新的数据资料和趋势聊一聊运维人可能面临的危机. Part 1    可视化 没有比"可视化"更好的一个词能概括运维的本质,而"可视化"又应该分成两部分:可视化的服务交付和可视化的服务度量.   一.可视化的服务交付 早期的运维是从ITIL开始的,那个时候大家都不知道运维是什么,幸好找到了一个IT服务最佳实践--ITIL.开始了

敏捷开发系列之满足不断变化的需求

软件开发方法一直处在不断发展过程中.在诸多方法中,敏捷开发以其能持续满足不断变化的用户需求正在受到越来越多人的重视,从中小项目开始进入大型开发项目,近几年来上升势头明显.为帮助读者进一步了解敏捷开发方法,本报邀请长期在国外从事软件研发工作的专业人士撰文就此进行深入探讨. 在软件工业界,敏捷开发已成为众多高效开发团队的制胜之道.在欧美软件企业中,有近半数企业已采用敏捷方法进行开发,而近几年受软件外包和外企的带动,敏捷开发在中国也出现了日渐普及的态势,如腾讯内部几乎所有的开发团队都在实施敏捷方法.敏

《UX最佳实践:提高用户体验影响力的艺术 》一2.4 项目阶段

2.4 项目阶段 项目要经历三个阶段,如图2-4所示.在第一个阶段,我们有意将开发流程与UCD流程分开进行.解决方案管理团队负责通过外部驱动研究方法定义目标解决方案,与此同时,开发团队并行开发技术平台.从一开始,ByDesign用户体验团队就同时协助解决方案管理团队和负责开发技术平台的UI架构团队.用户体验设计师与解决方案经理紧密合作,一起定义应用程序的目标设计.同时,我们也必须确保在UI架构中能完美地实现出UI模式,撰写详尽的UI规范文档,并在开发阶段协助框架开发人员.在第二阶段,我们得让已实

界面设计趋势:超越Clear设计风格的应用

文章描述:综合来看,界面设计有三大趋势值得注意,在不久的将来超越Clear设计风格的应用将会层出不穷. Clear这款待办事宜应用软件(参见相关介绍)给我们留下的最深印象就是摆脱了按钮的束缚,出色地践行了少即是多的信条.活泼的层次感.动态转换.清脆悦耳的声音提示,这一切都体现出简约的视觉语言以及扁平化的导航结构. 综合来看,界面设计有三大趋势值得注意,在不久的将来超越Clear设计风格的应用将会层出不穷: 1.移动优先,简约主义 移动设备屏幕的方寸之地迫使设计者返璞归真,必须把那些花俏的修饰和多