可维护性常见的度量指标
可维护性–软件系统的易维护性或者可以修改组件以纠正故障、提高性能或其他属性,或适应变化的环境;
可扩展性–软件设计/实施考虑到未来的增长,并被视为系统扩展能力和所需工作水平的一个系统性度量实施扩展;
灵活性–软件能够根据用户需求、外部技术和社会需求轻松更改环境等;
适应性–交互式系统(自适应系统)的能力,该系统可以根据获取的有关其用户及其环境的信息,将其行为适应单个用户;
可管理性–监控和维护软件系统的效率和容易程度,以保持系统的性能、安全性和平稳运行;
可支持性–软件的保存效率部署后运行,基于包括质量文档、诊断信息和知识渊博且可用的技术人员在内的资源;
聚合度与耦合度
耦合度coupling是衡量模块之间依赖的指标,取决于模块间的接口数目,每个接口的复杂度。
内聚度cohesion是衡量函数或模块职责的相关联的程度的指标,高内聚的模块所有元素的工作目标相同。
要求高内聚、低耦合。
SOLID
SOLID是五个类的设计原则,即SRP单一责任原则、OCP开放-封闭原则、LSP替换原则、DIP依赖转置原则、ISP接口聚合原则。
SRP单一责任原则
ADT中不应该有多于1个原因让其发生变化,否则就拆分开。
一个类有一个责任,如果有多个责任,可能要引入额外的包、占据资源、导致频繁重新配置、部署等。
OCP开放-封闭原则
模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
模块自身代码是不应该被修改的,扩展模块行为的一般途径是修改模块的内部实现,如果一个模块不能被修改,那它常备认为具有固定的行为。
关键是抽象技术,改变类的行为用继承和委托机制。
LSP替换原则
子类型必须能够替换其基类型,派生类必须能够通过基类型接口使用,client无需了解二者差异。(Chapter9)
ISP接口隔离原则
只提供client必须的接口,避免接口污染、胖接口。
将胖接口分解为多个小接口,不同client使用不同的接口,只能访问所需要的端口。
DIP依赖转置原则
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
委托的时候要通过interface建立联系。
语法、正则表达式
终止节点、叶结点、终止符为语法解析树的叶子结点,无法向下扩展。
连接(以空格表示)、重复(以*表示)、选择(或,以|表示)为三个基本操作符
其他操作符如?(单字符存在或不存在),+(克林闭包),[…](穷举的或),[^…](穷举的集合补集的或) 如下
其中?+优先级最高,连接次之,|最低
语法树,对应派生过程(示例)
正则语法,简化后可以表达为一个产生式而不包含任何非终止节点。
url、markdown是正则的,但是html不是正则的。
Pattern对象是对regex正则表达式编译后的结果,Matcher对象是利用Pattern对象对输入字符串进行解析。
设计模式
adapter模式
将某个类/接口转化为client期望的其他形式,解决类之间接口不兼容问题,通过增加一个接口,将已存在的子类封装,client面向接口编程,隐藏具体子类。
例如LegacyRectangle提供的display应该接收x,y,w,h分别是左上角坐标、宽、高,但是client期望获得左上角坐标、右下角坐标。
decorator模式
每个子类实现不同的特性,需要特性的任意组合时,继承会使大量代码重复,组合爆炸。
为对象增加不同侧面的特性,对每一个特性构造子类,通过委托机制增加到对象上,以递归的方式实现。
接口定义装饰物执行的公共操作。起始对象在其基础上增加功能(装饰),将通用方法放到此对象中。
interface Stack{//最基础的stack功能
void push(Item e);
Item pop();
}
public class ArrayStack implements Stack{
public ArrayStack(){...}
public void push(Item e){...}
public Item pop(){...}
...
}
public abstract class StackDecorator implements Stack{//用于decorator的基础类
protected final Stack stack;
public StackDecorator(Stack stack){
this.stack = stack;
}
public void push(Item e){
stack.push(e);
}
public Item pop(){
return stack.pop();
}
}
public class UndoStack extends StackDecorator {
private final UndoLog log = new UndoLog();
public UndoStack(Stack stack) {
super(stack);
}
public void push(Item e) {
super.push(e);//通过基础类的委托实现
log.append(UndoLog.PUSH, e);//新增特性
}
public void undo() {//新特性
//implement decorator behaviors on stack
}
...
}
使用如下:
Stack s = new ArrayStack();
Stack t = new UndoStack(new ArrayStack());
Stack t = new SecureStack(new SynchronizedStack(new UndoStack(s)));
另一个例子如下:
public interface IceCream { //顶层接口
void AddTopping();
}
public class PlainIceCream implements IceCream{ //基础实现,无填加的冰激凌
@Override
public void AddTopping() {
System.out.println("Plain IceCream ready for some
toppings!");
}
}
/*装饰器基类*/
public abstract class ToppingDecorator implements IceCream{
protected final IceCream input;
public ToppingDecorator(IceCream i){
this.input = i;
}
public abstract void AddTopping(); //留给具体装饰器实现
}
public class CandyTopping extends ToppingDecorator{
public CandyTopping(IceCream i) {
super(i);
}
public void AddTopping() {
input.AddTopping(); //decorate others first
System.out.println("Candy Topping added!");
}
}
public class NutsTopping extends ToppingDecorator {
public NutsTopping(IceCream i) {
super(i);
}
public void AddTopping() {
input.AddTopping(); //decorate others first
System.out.println("Nuts Topping added!");
}
}
public class PeanutTopping extends ToppingDecorator {
public PeanutTopping(IceCream i) {
super(i);
}
public void AddTopping() {
input.AddTopping(); //decorate others first
System.out.println("Peanut Topping added!");
}
}
public class Client {
public static void main(String[] args) {
IceCream toppingIceCream = new NutsTopping(
new PeanutTopping(
new CandyTopping(
new PlainIceCream())));
toppingIceCream.AddTopping();
结果如下:
Decorator在运行时合成特性,继承在编译时组合功能;装饰器由多个协作对象组成,继承生成一个单一的、类型明确的对象;可以混搭多种装饰,多重继承在概念上很困难。
strategy模式
有多种不同的算法来实现同一个任务,但client根据需要动态切换算法,不是写死在代码中。
为不同实现算法构造抽象接口,利用委托机制,运行时动态传入client倾向的算法实例。
接口有不同的实现,实例包含有接口,client通过调用方法时传入实例化参数不同(接口不同的实现),调用不同的实现方法。
template模式
做事情步骤一样,具体实现不同。共性的步骤在抽象类内实现,差异化步骤在各个子类实现。模板定义了算法步骤。
使用继承和重写实现模板。
public abstract class CarBuilder {
protected abstract void BuildSkeleton();
protected abstract void InstallEngine();
protected abstract void InstallDoor();
// Template Method that specifies the general logic
public void BuildCar() { //通用逻辑
BuildSkeleton();
InstallEngine();
InstallDoor();
}
}
public class PorcheBuilder extends CarBuilder {
protected void BuildSkeleton() {
System.out.println("Building Porche Skeleton");
}
protected void InstallEngine() {
System.out.println("Installing Porche Engine");
}
protected void InstallDoor() {
System.out.println("Installing Porche Door");
}
}
public class BeetleBuilder extends CarBuilder {
protected void BuildSkeleton() {
System.out.println("Building Beetle Skeleton");
}
protected void InstallEngine() {
System.out.println("Installing Beetle Engine");
}
protected void InstallDoor() {
System.out.println("Installing Beetle Door");
}
}
public static void main(String[] args) {
CarBuilder c = new PorcheBuilder();
c.BuildCar();
c = new BeetleBuilder();
c.BuildCar();
}
结果如下:
iterator/iterable模式
客户端希望对放入容器/集合类的一组ADT对象进行遍历访问,而无需关心容器的具体类型。
Iterable接口,实现该接口的容器对象是可迭代遍历的,Iterator接口,迭代器。
public interface Iterable<T>{
...
Iterator<T> iterator();
}
public interface Iterator<E>{
boolean hasNext();
E next();
void remove();
}
public class Pair<E> implements Iterable<E> {
private final E first, second;
public Pair(E f, E s) { first = f; second = s; }
public Iterator<E> iterator() {
return new PairIterator();
}
private class PairIterator implements Iterator<E> {
private boolean seenFirst = false, seenSecond = false;
public boolean hasNext() { return !seenSecond; }
public E next() {
if (!seenFirst) { seenFirst = true; return first; }
if (!seenSecond) { seenSecond = true; return second; }
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
factory method模式
虚拟构造器,当client不知道/不确定要创建哪个具体类的示例,或者不想在client中指明具体创建的实例,使用工厂方法。
定义一个用于创建对象的接口,让该接口的子类型决定实例化哪一个类,从而使一个类的实例化延迟其子类。
静态工厂方法既可以在ADT中,也可以单独构造工厂类,其方法可有具体指定的更有意义的名称,不必在每次调用时创建新的工厂对象,可以返回原返回类型的任意子类型。
public class SystemTraceFactory{
public static Trace getTrace() {
return new SystemTrace();
}
}
public class TraceFactory {
public static Trace getTrace(String type) {
if(type.equals("file")
return new FileTrace();
else if (type.equals("system")
return new SystemTrace();
}
}
//... some code ...
Trace log1 = SystemTraceFactory.getTrace();
log1.setDebug(true);
log1.debug( "entering log" );
Trace log2 = TraceFactory.getTrace("system");
log1.setDebug(true);
log2.debug("... ");
visitor模式
对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。
本质上是将数据和对数据特定的操作分离开。
为ADT预留一个可扩展功能的接入点,外部实现功能代码在不改变ADT本身情况下通过委托接入ADT。
/* Abstract element interface (visitable) */
public interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
/* Concrete element */
public class Book implements ItemElement{
private double price;
...
int accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}
public class Fruit implements ItemElement{
private double weight;
...
int accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}
/* Abstract visitor interface */
public interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
public int visit(Book book) {
int cost=0;
if(book.getPrice() > 50){
cost = book.getPrice()-5;
}else
cost = book.getPrice();
System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost);
return cost;
}
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg()*fruit.getWeight();
System.out.println(fruit.getName() + " cost = "+cost);
return cost;
}
}
public class ShoppingCartClient {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[]{
new Book(20, "1234"),new Book(100, "5678"),
new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};
int total = calculatePrice(items);
System.out.println("Total Cost = "+total);
}
private static int calculatePrice(ItemElement[] items) {
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum=0;
for(ItemElement item : items)
sum = sum + item.accept(visitor);
return sum;
}
}
迭代器以遍历方式访问集合数据无需暴露内部表示,将遍历功能委托到外部iterator对象;
visitor在ADT上执行特定操作,不在ADT内部实现,委托到独立的visitor对象,client灵活扩展/改变visitor算法,不影响ADT。
visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。
健壮性和正确性
Robustness健壮性,系统在不正常输入或不正常外部环境下仍能够表现正常的程度。
处理未期望的行为和错误终止;即使终止执行,要准确/无歧义向用户展示全面错误信息。
Correctness正确性,程序按照spec加以执行的能力,最重要的质量指标。
正确性,不给用户错误的结果,倾向于直接报错;健壮性,尽可能保持软件运行而不是总是退出,倾向于容错。
对外的接口倾向于健壮性,对内的实现倾向于正确。
MTBF是指相邻两次故障之间的平均工作时间,对于可修复系统是导致系统不可使用的failure;出现问题仍然可运行则不作为failure。
MTTF是不可修复系统的故障前平均时间。
Throwable
Error/运行异常、其他异常
内部错误程序员无法解决,一旦发生要让程序结束。
异常由程序导致,可以捕获和处理。
异常是程序执行中的非正常事件,导致程序无法按预想的流程执行。异常将错误信息传递给上层调用者并报告案发现场信息。
异常是return外的另一种退出途径,找不到异常处理程序则系统退出。
运行时异常是由程序员的代码处理不当造成,例如类型转换,数组越界,空指针异常等;其他异常是外部原因造成的,例如文件操作。
Checked和unchecked异常
编译器可以帮助检查程序是否抛出或处理可能的异常(checked),错误和运行时异常无法由编译器检查(unchecked)。
unchecked异常不用try…catch等处理,可以捕获和处理,但是也无法解决。
checked异常必须捕获指定处理器handler,否则编译不通过。
Checked异常处理机制
throws声明本方法可能会发生某些异常;
throw主动抛出某些异常;
try…catch…finally捕获并处理某些异常。
尽量用unchecked异常处理编程错误,它们会自动在出现的地方挂起程序并打印异常信息。
在规约中可以用@throws声明可能存在的checked异常。
方法应该抛出其他函数传来的异常、自己造成的异常。
子类型重写的方法,不应该抛出比父类型抛出异常更宽泛。可以抛出更具体的异常、不抛出异常。(子类型方法返回值、异常类型-协变,子类型的更宽泛;子类型方法参数-逆变,子类型的更具体)
一旦抛出异常,则控制权不会给client,无需考虑返回错误代码。
异常发生但找不到处理器就会终止执行程序,在控制台打印出stack trace。
try{
//code
}catch(ExceptionType e){
//handler for this type
}
如果本方法不出来异常,则可以传递给调用方处理。处理不了传给上家。
父类型若没有抛出异常,则子类型必须捕获所有的checked异常。
catch中也可以抛出异常,更改异常类型,便于client获取信息、处理。
try {
access the database
}catch (SQLException e) {
throw new ServletException("database error: " + e.getMessage());
}
finally部分的代码,是否捕获异常都会被执行,常常用于对资源的恰当清理。
class Indecisive {
public static void main(String[] args) {
System.out.println(decision());
}
static boolean decision() {
try {
return true;
} finally {
return false;
}
}
}
上述例子的运行结果为false。
当异常发生会显示如下信息:(可能Enter等不会显示)
自定义异常类
继承java.lang.Exception实现自己的checked异常类。
public class FooException extends Exception {
public FooException() {
super();
}
public FooException(String message) {
super(message);
}
public FooException(String message, Throwable cause) {
super(message, cause);
}
public FooException(Throwable cause) {
super(cause);
}
}
继承java.lang.RuntimeException实现自己的unchecked异常类。
断言
在开发阶段的代码中嵌入断言,检验某些“假设”是否成立,若成立表明程序运行正常,否则表明存在错误。
断言包括boolean的表达式、不符合展示的信息。
assert condition;
assert condition:message;
使用场景:
内部不变量,例如assert x>0
表示不变量,例如checkRep()
控制流不变量,例如switch-case
方法的前置条件、后置条件
public double sqrt(double x){
assert x >= 0;
double r;
...//compute result r
assert Math.abs(r*r-x) < .0001;
return r;
}
使用断言可以在开发阶段调试程序、尽快避免错误,在具体使用时要关闭断言。
断言倾向于正确性,处理不能发生的情况;
异常倾向于健壮性,处理预料到的不正常情况。
防御式编程
对外部数据源要仔细检查,例如文件、网络数据、用户输入等。
对每个函数输入参数合法性要检查,决定如何处理非法输入。
类的public方法接收到外部数据被认为是ditry的,要处理好传递给private方法。