编写高质量代码之读书笔记3

来源:互联网 发布:大学软件工程项目 编辑:程序博客网 时间:2024/05/10 09:20
建议 107 : 使用反射增加装饰模式的普适性
     这里虽然不复杂,但是这个例子比较经典。所以记录下来
public interface Animal {
    public void doStuff();
}

public class Rat implements Animal{
    @Override
    public void doStuff() {
        System.out.println("jerry will play with tom");
    }
}

下面为Rat增加能力。
public interface Feature {
    public void load();
}

public class FlyFeature implements Feature{
    @Override
    public void load() {
        System.out.println("增加一只翅膀");
    }
}

public class DigFeature implements Feature{
    @Override
    public void load() {
        System.out.println("增加钻地能力");
    }
}

接下来是装饰类,将Feature装饰到Rat身上。
public class DecorateAnimal implements Animal {

    private Animal animal;
    private Class<? extends Feature> clz;

    public DecorateAnimal(Animal _animal,Class<? extends Feature> _clz){
        animal = _animal;
        clz = _clz;
    }

    @Override
    public void doStuff() {
        final InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object obj = null;
                if(Modifier.isPublic(method.getModifiers())){
                    obj = method.invoke(clz.newInstance(),args);
                }
                animal.doStuff();
                return obj;
            }

        };
        ClassLoader cl = getClass().getClassLoader();
        Feature proxy = (Feature) Proxy.newProxyInstance(cl,clz.getInterfaces(),handler);
        proxy.load();
    }
}

接下来是为Rat增加能力的时候了
public class AnimalTest {

    public static void main(){
        Animal Jerry = new Rat();
        Jerry = new DecorateAnimal(Jerry,FlyFeature.class);
        Jerry = new DecorateAnimal(Jerry,DigFeature.class);
        Jerry.doStuff();
    }
}
这个是一个比较经典的装饰场景,一个层级调用的方式,完成了我们想要增加的功能,代码几乎没有耦合,再增加任何功能的话,也会很容易。


建议:108 反射让模版方法模式更强大。
我们都接触过JUnit单元测试,我们只要写test开头的public,非抽象的方法,在运行的时候,就会顺序执行,
这个就是实现这个的典型案例
public abstract class AbsPopulator {

    public final void dataInitaling() throws Exception{
        Method[] method = getClass().getMethods();
        for(Method m :method){
            if(isInitDataMethod(m)){
                m.invoke(this);
            }
        }
    }

    private boolean isInitDataMethod(Method m) {
       
        return m.getName().startsWith("test")
                && Modifier.isPublic(m.getModifiers())
                && Modifier.isAbstract(m.getModifiers());
    }

}

这样只要类继承AbsPopulator,运行时执行dataInitaling()就可以了,所有的测试操作都会执行

建议 110:提倡异常封装
这个建议之前确实很少使用,感觉比较有用,所以记录下来,
      1.提高系统的友好性(封装的Exception可以指定抛出何种异常,更清晰的反映给用户,例如文件size过大,名字过长等)
      2.提高系统可维护性(可以直接定位到具体位置,不需要捋代码)
      3.解决Java异常机制自身的缺陷(java只允许一个Exception抛出,这是我们可以把每个Exception放到List中,然后通过我们的封装将Exception队列抛出,常见于判断用户名和密码等数据的校验,如果用户名和密码都有问题,提示用户,就可以避免,第一次提示用户名不对,用户改完,又提示密码不对,这样用户体验很糟糕)

建议 113:不要再Finally中出现return语句,或者修改return返回值/对象,

建议  114:在构造函数中不要抛出异常

建议 115:使用Throwable获得栈信息
public class MyLog {

    public static void v(){
        StackTraceElement[] str = new Throwable().getStackTrace();
        StackTraceElement element = str[1];
        Log.v("XPC""element=" + element.getClassName()+":"+element.getMethodName()+":"+element.getLineNumber());
    }
}
这个在我们搜集堆栈信息的时候,比较有用,
我们可以知道那个方法,那个函数那一行调用的。
com.example.activitytest.MainActivity:onResume:100

建议 116 :异常只为确实异常的事务服务
这个case只能说注意,但是作者的只为有点过于绝对,其实代码是死的,人是活的,我们完全可以在使用的时候,根据特殊的场景来特殊使用,就像之前我遇到的,android中判断一个应用是否安装,如果不存在则...,这个就可以使用这种case来处理,否则非常麻烦。

建议 120 :不使用stop方法停止线程
stop方法停止线程的危害很明显,将会导致线程执行不完,
那么如何停止呢?1.通过标志位
                         2.通过isInterrupted 和  interrupt方法的组合
如果是线程池的话,则通过shutdown关闭就可以了

建议 121 :线程优先级只使用三个等级
         虽然线程优先级有10个等级,但是相邻登记之间,转换为各个系统的等级之后,相差很小,所以为了保证尽量接近我们想要的结果,只使用 MIN_PRIORITY(1) ,(NORM_PRIORITY)5 ,(MAX_PRIORITY)10这三个等级来设置优先级

建议 122 ;使用线程异常处理器提高系统可靠性
        这个之前很早就用过,所以我在考虑是否可以全局做一个异常处理接收器,然后各个位置都发出自定义的异常,这样就可以统一处理接收到各个异常时,我们要做什么操作了

建议 123 :虽然volatile是直接从主内存中写入和读取的,但仍然不是线程安全的,只是每次获取最新数据





建议:124  异步运算考虑使用Callable接口
当采用异步运算的时候,当我们需要监听线程运算状态的时候,可以结合  Future 和 Callable的组合来实现,
例如当需要线程执行的时候,显示一个进度条,就可以采用这种模型。

建议 125 尽量使用线程池
这里主要介绍的和学习的应该是线程池的实现原理,归根结底很简单。
第一次submit的时候,创建足够多的线程池,
然后从一个BlockingQueue里面获取任务,当没有任务,则阻塞,这样即使当没有任务的时候,也是有一些线程存在并一致处于block状态的,但是当有任务到来的时候,直接执行,省去了创建和销毁的开销。

建议 126 适时选择不同的线程池来实现
     corePoolSize : 最小线程数,线程池启动后保持线程的最小数量,但是需要注意的是线程数辆是逐渐达到
                            corePoolSize的,第一次当需要多少就会启动多少。
     maximumPoolSize:最大数量线程
     keepAliveTime:线程最大声明期,当超过corePoolSize的线程等待时间达到keepAliveTime的时候,销毁线程
     unit:时间单位
     workQueue:任务队列
     threadFactory:线程工厂,定义如何启动一个线程,名字/是否是后台线程等
     handler:拒绝任务处理器,当线程超过maximumPoolSize时,由它来处理

     newSingleThreadExecutor
     newCacheThreadExecutor
     newFixedThreadPool
     根据含义查看源码,功能很简单

建议 127 显式锁 VS 内部锁
     1.Lock 支持更细粒度的所控制,参考:ReentrantReadWriteLock();
     2.Lock是无阻塞锁,synchronized是阻塞锁,
     3.Lock可实现公平锁,synchronized只能实现非公平锁(当A线程解锁,B,C线程随机持有锁,并获取执行权,为非公平锁,Lock可以在参数中加入true,声明出公平锁)
     4.Lock是代码级的,synchronized是JVM级的

     
建议 131 :CyclicBarrier 让多线程齐步走

使用场景,多个线程同时到达起点之后,统一开始干活:
public class Worker implements Runnable{

    private CyclicBarrier cb;

    public Worker(CyclicBarrier _cb){
        cb = _cb;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            Log.v("XPC", Thread.currentThread().getName() + "-到达汇合点");
            cb.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

public class CyclicBarrierTest {

    public static void test(){
        CyclicBarrier cb = new CyclicBarrier(2new Runnable() {
            @Override
            public void run() {
                Log.v("XPC"" 隧道已经打通!");
            }
        });
        new Thread(new Worker(cb),"工人1").start();
        new Thread(new Worker(cb),"工人2").start();

    }
}

LOG:
07-21 01:12:05.034 V/XPC: 工人1-到达汇合点
07-21 01:12:05.374 V/XPC: 工人2-到达汇合点
07-21 01:12:05.374 V/XPC: 隧道已经打通!

可以看到线程1执行到await的时候,不不会执行run方法,而是扽线程2也执行了await方法的时候,才开始执行,其实这个需求使用CountDownLatch也可以实现,只是这个会逻辑更加清晰和简单

建议146 注释:
    四种有效的注释
          1.版权注释
          2.解释意图的注释
          3.警告的注释
          4.todo的注释

建议 147:让接口的职责单一:
      

合并接口



建议 149 :依赖抽象而不是实现:
依赖抽象可以将我们的代码解耦合。


到这里 151个建议粗略的看了一遍,吸收了一些东西,以后再经常回头看看吧。

0 0