clean code 错误处理和边界

错误处理

错误处理很重要,不应当搞乱代码逻辑。

1.使用异常而非返回码

早期有些语言不支持异常,所以只能用错误标识或者返回给调用者状态码。
错误码会搞乱调用者代码,因为:调用这个函数之后需要立马检查错误,根据状态码处理相应逻辑。

遇到错误时,最好抛出一个异常,调用代码整洁,其逻辑不会被错误处理搞乱。

2.先写try-catch-finally语句

在编写可能抛出异常的代码时,先写try-Catch-Finally语句;

3.使用不可控异常(unchecked Exception)

4.给出异常发生的环境说明

应创建信息充分的错误消息,并和异常传递出去。包括失败的操作、失败的类型。

5.依调用者需要定义异常类

在定义异常类时,最重要考虑是它们如何被捕获。

将第三方API 打包 是个良好的实践手段,当你打包一个第三方 API时,会降低对它的依赖:可以较为轻松地改动其他代码库。(因为打包,会使得依赖第三方代码的点相对比较集中,方便修改)

// bad Practice
ACMEPort port = new ACMEPort(12);
try {
    port.open();
}catch(DeviceResponseException e) {
    reportPortError(e);
    logger.log("Device response exception",e);
} catch(ATM1212UnlockedException e) {
    reportPortError(e);
    logger.log("Unlock exception",e);
} catch(GMXError e) {
    reportPortError(e);
    logger.log("Device response exception",e);
} finally {
    // .....
}

以上的代码包含了一大堆重复的代码。可以通过打包调用API,让它返回通用的自定义异常类型( PortDeviceFailue),从而简化代码。

// good practice
LocalPort port = new LocalPort(12);
try {
    port.open();
} catch(PortDeviceFailure e) {
    reportError(e);
    logger.log(e.getMessage(),e);
} finally {
    // .....
}
public class LocalPort{
 private ACMEPort innerPort;

 public LocalPort(int portNumber){
     innerPort = new ACMEPort(portNumber);
 }

 public open() {
  try {
       innerPort.open();
   } catch(DeviceResponseException e) {
           // 自定义的异常类
       throw new PortDeviceFailure(e);
   } catch(ATM1212UnlockedException e) {
       throw new PortDeviceFailure(e);
   } catch(GMXError e) {
       throw new PortDeviceFailure(e);
   }
 }
}

6.定义常规流程

特例模式,创建一个类或配置一个对象,用来处理特例。
你来处理特例,客户代码不用应付异常行为,异常行为被封装到特例对象中。

7.别返回null值

只要一处没检查null值,应用程序就会失控。

如果你打算在方法中返回null值,不如抛出异常,或是返回特例对象。如果你在调用某个第三方API中可能返回null值的方法,可以考虑用新方法打包这个方法,在新方法中抛出异常或返回特例对象。

适当返回空列表,从而省略判空语句,从而避免NPE。

List<Employee> emoployees = getEmployees();
if(employees != null) {
    for(Employee e : employees) {
        totalPay += e.getPay();
    }
}

public List<Employee> getEmployees() {
    if(.. ) {
        return Collections.emptyList();
    }
}


List<Employee> employees = getEmployess();
for(Employee e : employees) {
    totalPay += e.getPay();
}

8.别传递null值

除非API要求你向他传递null值,否则尽可能避免传递null值。

因为:如果允许传递null值,需要做各种非空判断,否则会产生NPE。

大多数编程语言中,没有良好的方法对付由调用者意外传入的null值,恰当的做法就是禁止传入null值。

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}

calculator.xProjection(null, new Point(12, 13));

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        if(p1 == null || p2 == null) {
            throw new InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection");
        }
        return (p2.x - p1.x) * 1.5;
    }
}
这种需要为异常定义处理器

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        assert p1 != null : "p1 should not be null.";
        assert p2 != null : "p2 should not be null."
        return (p2.x - p1.x) * 1.5;
    }
}
传入null值,还是会得到运行时错误。

边界

边界case:
1)使用外部代码(第三方库,其他模块)与自身项目代码结合;
2)自身项目调用外来代码
需要干净利落将这些代码整合,就能保持软件边界整洁

1.使用第三方代码

第三方jar包和框架追求通用性,使用者想要集中满足特定需求的接口(方便开发),这两者的差异,致使系统边界上可能会出现问题。

举个例子,以 java.util.Map为例。Map丰富的功能,但是应用程序可能构造一个Map对象并到处传递它的引用。

假设设计初衷是:Map对象的所有接收者都不要删除映射里的东西,但是实际情况是任何使用者都能通过clear方法清空Map。

系统中无节制的传递Map对象的引用,意味着如果Map提供的接口被修改时,有非常多的地方需要跟着改动。
使用Map更整洁的方式如下:

边界上的接口(Map)是隐藏的,它能随着应用程序其他部分的极小的影响而变动。把类型转换等操作提取到Sensor类作为一个方法,这样在接口发生改变时,只需要修改这一个方法。

public class Sensors {
    private Map sensors = new HashMap();
    
    public Sensor getById(String id) {
        return (Sensor) sensors.get(id);
    }
}

总结:并不建议总以这种方式封装Map,不要将Map(或者边界上的其他接口)在系统中传递。应将其保留在类中,避免从公共API中返回边界或借口,或者将边界接口作为参数传递给公共方法。

2.使用尚不存在的代码

协同开发经常会碰到代码调用的API还没有处理好的情况,这种情况下定义好自己的接口和方法,一旦API被定义出来,可以用适配器模式进行衔接。

Adapter封装了与API的互动,也提供了一个当API发生变动时唯一需要改动的地方。

3.整洁的边界

代码中应该尽量少的了解第三方代码中的特定信息,即需要尽量去封装好第三方边界接口。

如同封装Map、适配器将我们的接口转换为第三方接口。

这两种方法,能使代码更好地与我们沟通,第三方代码改动的时候,自己的代码修改点会更少。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值