- 一个函数最好不要超过30行,超过就分成几个子函数
- 尽量不要有多层循环(包括双层),每层循环可以放到不同的函数中,
甚至内层循环体也可以放到一个函数中,提高程序可读性和可重用性。
foo() {
for () {
for () {
do1;
do2;
do3;
}
}
}
可以改成,
foo() {
for () {
foo1();
}
}
foo1() {
for() {
doSomething();
}
}
doSomething() {
do1;
do2;
do3;
}
- 如果一个比较heavy load(代价比较高)的方法,被调用多次,那么第一次调用的时候可考虑把结果缓存下来,以后直接用这个结果,而不需要多次调用该方法。
- 类和方法的参数,要尽可能是直接需要的参数,而不要传递大对象,只传递自己需要的对象。这样类和方法的输入才清晰,而且调用者也不必创建大对象,为哪些成员变量需要初始化而不知所措。
- 当你发现某些代码老是重复的写来写去,那么那些代码就很有可能能够抽象出来,放在公共函数里,变化的部分可以用模板方法来实现。
- 当一个类里面有一些函数根据同一个标志来进行不同的计算,那么这个类应该可以分为两个子类了。每个子类做一种计算,消除或减少if-else语句,提高可维护性。
- 最好不要用static方式初始化static变量,这会使单元测试很不方便。尽量不要写如下代码,
class MyClass {
static YourClass yourClass;
static {
yourClass = new YourClass();
yourClass.doSomething();
}
}
把YourClass抽取出来设计成singleton。
- 在构造函数中启动线程要小心(尽可能不要这么做),线程启动的时候可能某些变量没有初始化,特别是需要子类初始化的变量。
- 使用daemon的线程要小心,main线程结束后daemon马上就结束,这样导致daemon线程正在处理的数据没有完成,比如数据没有保存或者提交。
- 如果有可能通过Class.newInstance()动态创建类实例,那么类的构造函数参数,要用java类,比如int要用Integer.
- 当有两个设计方案,无法取舍的时候,多想想:
- 哪个方案对外部的影响小: 包括外部模块,外部团队等.
- 哪个方案更容易测试.
- 哪个更容易控制,陷阱更少.
- 哪个方案改动的地方更集中.有时候一个方案看起来改动的多,但主要集中在一个地方,引入的风险反而更少. 而改动少,但改动面广的方案,反而风险高.
- get, create, build等创建对象的方法,应该是幂等的,内部不应该修改传入的对象参数的属性,或者静态全局对象属性.
- 谨慎的使用内部类,否则用Class.newInstance()实例化可能会失败.
- 业务与存储解耦
class AsinRevenueDataRevenueHelper {
private static Map<String, Map<String, RevenueHBaseDaoChain>> realm2DaoMap = new ConcurrentHashMap<String, Map<String, RevenueHBaseDaoChain>>();
public static AsinRevenueDataResult getAsinRevenueData(String asin, String realm, Date date) {
...
}
// 其余每个API也都是静态的
}
这个类相当于一个Singleton, 不太容易扩展. Realm相当于一个区域或者国家, 不管为哪个国家服务都是从HBase读取数据. 当我要将一部分国家的数据库迁移到DynamoDB时,
这个类就不能用了. 我只好修改这个设计, 如下:
public interface AsinRevenueDataRevenueHelperInterface {
public AsinRevenueDataResult getAsinRevenueData(String asin, Date date);
// 其它接口
...
}
基于这个接口有两个实现.
// 从HBase获取数据接口实现类
public class AsinRevenueDataHBaseHelper implements AsinRevenueDataRevenueHelperInterface {
private String realm;
public AsinRevenueDataHBaseHelper(String realm) {
this.realm = realm;
}
@Override
public AsinRevenueDataResult getAsinRevenueData(String asin, Date date) {
...
}
// 其它override的APIs
}
// 从DynamoDB获取数据接口实现类
public class AsinRevenueDataDynamoDBHelper implements AsinRevenueDataRevenueHelperInterface {
private String realm;
public AsinRevenueDataDynamoDBHelper(String realm) {
this.realm = realm;
}
@Override
public AsinRevenueDataResult getAsinRevenueData(String asin, Date date) {
...
}
// 其它override的APIs
}
数据接口工厂类:
public class AsinRevenueDataHelperProvider {
private static Map<String, AsinRevenueDataHelperInterface> realm2AsinRevenueDataHelperMap = new HashMap<String, AsinRevenueDataHelperInterface>();
private static boolean initialized = false;
public static AsinRevenueDataHelperInterface getAsinRevenueDataHelper(String realm) {
if (!initialized) {
initialize();
}
return realm2AsinRevenueDataHelperMap.get(realm);
}
public static synchronized initialize() {
if (initialized) return;
// 对每个国家初始化数据接口类
for each realm in realms {
if (isDynamoDBEnabled(realm)) {
realm2AsinRevenueDataHelperMap.put(realm, new AsinRevenueDataDynamoDBHelper());
} else {
realm2AsinRevenueDataHelperMap.put(realm, new AsinRevenueDataHBaseHelper());
}
}
initiazlied = true;
}
}
修改完设计后,Client端只需要跟接口(AsinRevenueDataHelperInterface)以及工厂类(AsinRevenueDataHelperProvider)耦合,不需要知道具体存储介质.
- 在设计一个新系统的时候,一开始不要想得很全很大,而是先从一个最简单的最common的use case入手,画出这个use case的流程图,甚至编写伪代码,挖掘其中的概念,遵循面向对象高内聚低耦合的策略,一步一步迭代,逐渐形成概念模型。
- 原有业务逻辑是 C = getA(),当我们的业务逻辑改成 C = max(getA(), getB())时,如果getB()的内部有异常,应当抛出异常,而不是返回0,不要假设getB()出现异常,就用getA()。既然增加了B,而B出现异常的话,就要显式的抛出异常好了,当然没有B则可以fallback到A(没有B和B出现异常是两回事。)