作者: songtianyi
错误处理是软件开发当中一个十分重要的话题,程序员一部分时间用来构思算法逻辑,另外,很大一部分时间是用来写错误处理逻辑的,你需要考虑各种奇怪的用户输入,应对不稳定的网络,各种协作对接上的失败等等。 程序是否健壮,几乎完全取决于错误处理做的足不足够细致。
我们可以按照不同的维度对错误场景进行分类,方便我们记忆,可以选取其中一个或者多个来作为自己的分类方式。
可忽略 - 不影响调用者的错误
不可忽略 - 影响调用者
io
错误处理大致分为这几个步骤
func (s *Vendor) GetSSHInitializer() SSHInitializer {
return func(c *ssh.Client, req *protocol.CliRequest) (r io.Reader, w io.WriteCloser, session *ssh.Session, err error) {
session, err = c.NewSession()
if err != nil {
return nil, nil, nil, fmt.Errorf("new ssh session failed, %s", err)
}
// get stdout and stdin channel
r, err = session.StdoutPipe()
if err != nil {
session.Close()
return nil, nil, nil, fmt.Errorf("create stdout pipe failed, %s", err)
}
if s.Echo {
modes := ssh.TerminalModes{
ssh.ECHO: 1, // enable echoing
}
if err = session.RequestPty("vt100", 0, 2000, modes); err != nil {
return nil, nil, nil, fmt.Errorf("request pty failed, %s", err)
}
}
w, err = session.StdinPipe()
if err != nil {
session.Close()
return nil, nil, nil, fmt.Errorf("create stdin pipe failed, %s", err)
}
if err = session.Shell(); err != nil {
session.Close()
return nil, nil, nil, fmt.Errorf("create shell failed, %s", err)
}
return r, w, session, nil
}
}
不同函数抛出的错误文本可能是相同的,所以需要加上上下文,用来区分。
pub enum Result<T, E> {
Ok(T),
Err(E),
}
let x: Result<i32, &str> = Ok(-3);
assert_eq!(x.is_err(), false);
let x: Result<i32, &str> = Err("Some error message");
assert_eq!(x.is_err(), true);
let version = parse_version(&[1, 2, 3, 4]);
match version {
Ok(v) => println!("working with version: {:?}", v),
Err(e) => println!("error parsing header: {:?}", e),
}
let v = version.unwrap()
if (version.is_err()) {
}
if (version.is_ok()) {
}
Object a = null;
try {
a = repository.findById(id);
}catch(ObjectNotFoundException e) {
// not exist
doSomething();
return
}
// exist
doSomething(a)
Boolean exist = repository.existById(id);
if (exist) {
doSomething();
}else {
doSomething();
}
for (String id : ids) {
try {
credentialService.deleteCredential(id);
} catch (Exception e) {
logger.debug(e.getMessage(), e);
continue;
}
}
这里存在的问题是未抛出错误,也存在事物的问题(不过在体验上影响倒不大)。如果这里抛了错,那前端会收到错误,前端认为执行失败,可以不用刷新页面,导致客户看到的和实际不一致, 然后继续选中已经被删除的部分,可能会马上遇到错误,如果客户不刷新页面,那这个功能就没法儿使用了,从这个角度讲,上述代码的处理是对的。但是,上述代码,在客户删除"成功"之后, 前端刷新页面,有部分之前选中的数据仍然存在,会给客户造成的困扰,也会影响后端定位问题的效率。
try {
repository.deleteCredentialWithIds(List<String> ids)
}catch(Exception e) {
throw e
}
FF02::3/255.255.255.255 is not ip
大概能猜到是解析的问题,但是是哪个设备配置的问题?如果有多个设备有这个地址怎么办?
功能 -> 设备 -> 对象类型 -> FF02::3/255.255.255.255 is not ip
// Go
func A() error {
return error("xxx is not ip")
}
func B() error {
if err := A(); err != nil {
return errors("call A error:", err)
}
}
func C() error {
if err := B(); err != nil {
return errors("search policy in device", device.name, "error:", err)
}
}
// serach policy in device hawei202 error: call A error: xxx is not ip
报错明确也能方便我们输出错误定位/处理的文档
private Speed getSpeed(Integer speedInt) {
if (null != speedInt) {
switch (speedInt) {
case 10: return Speed.SPEED_10Mb;
case 100: return Speed.SPEED_100Mb;
case 1000: return Speed.SPEED_1000Mb;
case 10000: return Speed.SPEED_10000Mb;
default:
// nothing to do
}
}
return null;
}
private Optional<Speed<> speed(Integer speed) {
if (null == speed) {
return Optional.empty()
}
switch (speed) {
case 10: return Speed.SPEED_10Mb;
case 100: return Speed.SPEED_100Mb;
case 1000: return Speed.SPEED_1000Mb;
case 10000: return Speed.SPEED_10000Mb;
default:
return Speed.SPEED_UNKNOWN
}
}
private Speed speed(Integer speed) {
if (null == speed) {
return Speed.SPEED_UNKNOWN
}
switch (speed) {
case 10: return Speed.SPEED_10Mb;
case 100: return Speed.SPEED_100Mb;
case 1000: return Speed.SPEED_1000Mb;
case 10000: return Speed.SPEED_10000Mb;
default:
return Speed.SPEED_UNKNOWN
}
}